summaryrefslogtreecommitdiffstats
path: root/python/mozboot/mozboot/bootstrap.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /python/mozboot/mozboot/bootstrap.py
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'python/mozboot/mozboot/bootstrap.py')
-rw-r--r--python/mozboot/mozboot/bootstrap.py776
1 files changed, 776 insertions, 0 deletions
diff --git a/python/mozboot/mozboot/bootstrap.py b/python/mozboot/mozboot/bootstrap.py
new file mode 100644
index 0000000000..e57f496f29
--- /dev/null
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -0,0 +1,776 @@
+# 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 os
+import platform
+import re
+import shutil
+import stat
+import subprocess
+import sys
+import time
+from collections import OrderedDict
+from pathlib import Path
+from typing import Optional
+
+# Use distro package to retrieve linux platform information
+import distro
+from mach.site import MachSiteManager
+from mach.telemetry import initialize_telemetry_setting
+from mach.util import (
+ UserError,
+ get_state_dir,
+ to_optional_path,
+ to_optional_str,
+ win_to_msys_path,
+)
+from mozbuild.base import MozbuildObject
+from mozfile import which
+from packaging.version import Version
+
+from mozboot.archlinux import ArchlinuxBootstrapper
+from mozboot.base import MODERN_RUST_VERSION
+from mozboot.centosfedora import CentOSFedoraBootstrapper
+from mozboot.debian import DebianBootstrapper
+from mozboot.freebsd import FreeBSDBootstrapper
+from mozboot.gentoo import GentooBootstrapper
+from mozboot.mozconfig import MozconfigBuilder
+from mozboot.mozillabuild import MozillaBuildBootstrapper
+from mozboot.openbsd import OpenBSDBootstrapper
+from mozboot.opensuse import OpenSUSEBootstrapper
+from mozboot.osx import OSXBootstrapper, OSXBootstrapperLight
+from mozboot.solus import SolusBootstrapper
+from mozboot.void import VoidBootstrapper
+from mozboot.windows import WindowsBootstrapper
+
+APPLICATION_CHOICE = """
+Note on Artifact Mode:
+
+Artifact builds download prebuilt C++ components rather than building
+them locally. Artifact builds are faster!
+
+Artifact builds are recommended for people working on Firefox or
+Firefox for Android frontends, or the GeckoView Java API. They are unsuitable
+for those working on C++ code. For more information see:
+https://firefox-source-docs.mozilla.org/contributing/build/artifact_builds.html.
+
+Please choose the version of Firefox you want to build (see note above):
+%s
+Your choice: """
+
+APPLICATIONS = OrderedDict(
+ [
+ ("Firefox for Desktop Artifact Mode", "browser_artifact_mode"),
+ ("Firefox for Desktop", "browser"),
+ ("GeckoView/Firefox for Android Artifact Mode", "mobile_android_artifact_mode"),
+ ("GeckoView/Firefox for Android", "mobile_android"),
+ ("SpiderMonkey JavaScript engine", "js"),
+ ]
+)
+
+FINISHED = """
+Your system should be ready to build %s!
+"""
+
+MOZCONFIG_SUGGESTION_TEMPLATE = """
+Paste the lines between the chevrons (>>> and <<<) into
+%s:
+
+>>>
+%s
+<<<
+"""
+
+MOZCONFIG_MISMATCH_WARNING_TEMPLATE = """
+WARNING! Mismatch detected between the selected build target and the
+mozconfig file %s:
+
+Current config
+>>>
+%s
+<<<
+
+Expected config
+>>>
+%s
+<<<
+"""
+
+CONFIGURE_MERCURIAL = """
+Mozilla recommends a number of changes to Mercurial to enhance your
+experience with it.
+
+Would you like to run a configuration wizard to ensure Mercurial is
+optimally configured? (This will also ensure 'version-control-tools' is up-to-date)"""
+
+CONFIGURE_GIT = """
+Mozilla recommends using git-cinnabar to work with mozilla-central (or
+mozilla-unified).
+
+Would you like to run a few configuration steps to ensure Git is
+optimally configured?"""
+
+DEBIAN_DISTROS = (
+ "debian",
+ "ubuntu",
+ "linuxmint",
+ "elementary",
+ "neon",
+ "pop",
+ "kali",
+ "devuan",
+ "pureos",
+ "deepin",
+ "tuxedo",
+)
+
+FEDORA_DISTROS = (
+ "centos",
+ "fedora",
+ "rocky",
+ "oracle",
+)
+
+ADD_GIT_CINNABAR_PATH = """
+To add git-cinnabar to the PATH, edit your shell initialization script, which
+may be called {prefix}/.bash_profile or {prefix}/.profile, and add the following
+lines:
+
+ export PATH="{cinnabar_dir}:$PATH"
+
+Then restart your shell.
+"""
+
+
+OLD_REVISION_WARNING = """
+WARNING! You appear to be running `mach bootstrap` from an old revision.
+bootstrap is meant primarily for getting developer environments up-to-date to
+build the latest version of tree. Running bootstrap on old revisions may fail
+and is not guaranteed to bring your machine to any working state in particular.
+Proceed at your own peril.
+"""
+
+
+# Version 2.24 changes the "core.commitGraph" setting to be "True" by default.
+MINIMUM_RECOMMENDED_GIT_VERSION = Version("2.24")
+OLD_GIT_WARNING = """
+You are running an older version of git ("{old_version}").
+We recommend upgrading to at least version "{minimum_recommended_version}" to improve
+performance.
+""".strip()
+
+
+class Bootstrapper(object):
+ """Main class that performs system bootstrap."""
+
+ def __init__(
+ self,
+ choice=None,
+ no_interactive=False,
+ hg_configure=False,
+ no_system_changes=False,
+ exclude=[],
+ mach_context=None,
+ ):
+ self.instance = None
+ self.choice = choice
+ self.hg_configure = hg_configure
+ self.no_system_changes = no_system_changes
+ self.exclude = exclude
+ self.mach_context = mach_context
+ cls = None
+ args = {
+ "no_interactive": no_interactive,
+ "no_system_changes": no_system_changes,
+ }
+
+ if sys.platform.startswith("linux"):
+ # distro package provides reliable ids for popular distributions so
+ # we use those instead of the full distribution name
+ dist_id, version, codename = distro.linux_distribution(
+ full_distribution_name=False
+ )
+
+ if dist_id in FEDORA_DISTROS:
+ cls = CentOSFedoraBootstrapper
+ args["distro"] = dist_id
+ elif dist_id in DEBIAN_DISTROS:
+ cls = DebianBootstrapper
+ args["distro"] = dist_id
+ args["codename"] = codename
+ elif dist_id in ("gentoo", "funtoo"):
+ cls = GentooBootstrapper
+ elif dist_id in ("solus"):
+ cls = SolusBootstrapper
+ elif dist_id in ("arch") or Path("/etc/arch-release").exists():
+ cls = ArchlinuxBootstrapper
+ elif dist_id in ("void"):
+ cls = VoidBootstrapper
+ elif dist_id in (
+ "opensuse",
+ "opensuse-leap",
+ "opensuse-tumbleweed",
+ "suse",
+ ):
+ cls = OpenSUSEBootstrapper
+ else:
+ raise NotImplementedError(
+ "Bootstrap support for this Linux "
+ "distro not yet available: " + dist_id
+ )
+
+ args["version"] = version
+ args["dist_id"] = dist_id
+
+ elif sys.platform.startswith("darwin"):
+ # TODO Support Darwin platforms that aren't OS X.
+ osx_version = platform.mac_ver()[0]
+ if platform.machine() == "arm64" or _macos_is_running_under_rosetta():
+ cls = OSXBootstrapperLight
+ else:
+ cls = OSXBootstrapper
+ args["version"] = osx_version
+
+ elif sys.platform.startswith("openbsd"):
+ cls = OpenBSDBootstrapper
+ args["version"] = platform.uname()[2]
+
+ elif sys.platform.startswith(("dragonfly", "freebsd", "netbsd")):
+ cls = FreeBSDBootstrapper
+ args["version"] = platform.release()
+ args["flavor"] = platform.system()
+
+ elif sys.platform.startswith("win32") or sys.platform.startswith("msys"):
+ if "MOZILLABUILD" in os.environ:
+ cls = MozillaBuildBootstrapper
+ else:
+ cls = WindowsBootstrapper
+ if cls is None:
+ raise NotImplementedError(
+ "Bootstrap support is not yet available " "for your OS."
+ )
+
+ self.instance = cls(**args)
+
+ def maybe_install_private_packages_or_exit(self, application, checkout_type):
+ # Install the clang packages needed for building the style system, as
+ # well as the version of NodeJS that we currently support.
+ # Also install the clang static-analysis package by default
+ # The best place to install our packages is in the state directory
+ # we have. We should have created one above in non-interactive mode.
+ self.instance.auto_bootstrap(application, self.exclude)
+ self.instance.install_toolchain_artifact("fix-stacks")
+ self.instance.install_toolchain_artifact("minidump-stackwalk")
+ if not self.instance.artifact_mode:
+ self.instance.install_toolchain_artifact("clang-tools/clang-tidy")
+ self.instance.ensure_sccache_packages()
+ # Like 'ensure_browser_packages' or 'ensure_mobile_android_packages'
+ getattr(self.instance, "ensure_%s_packages" % application)()
+
+ def check_code_submission(self, checkout_root: Path):
+ if self.instance.no_interactive or which("moz-phab"):
+ return
+
+ # Skip moz-phab install until bug 1696357 is fixed and makes it to a moz-phab
+ # release.
+ if sys.platform.startswith("darwin") and platform.machine() == "arm64":
+ return
+
+ if not self.instance.prompt_yesno("Will you be submitting commits to Mozilla?"):
+ return
+
+ mach_binary = checkout_root / "mach"
+ subprocess.check_call((sys.executable, str(mach_binary), "install-moz-phab"))
+
+ def bootstrap(self, settings):
+ if self.choice is None:
+ applications = APPLICATIONS
+ # Like ['1. Firefox for Desktop', '2. Firefox for Android Artifact Mode', ...].
+ labels = [
+ "%s. %s" % (i, name) for i, name in enumerate(applications.keys(), 1)
+ ]
+ choices = [" {} [default]".format(labels[0])]
+ choices += [" {}".format(label) for label in labels[1:]]
+ prompt = APPLICATION_CHOICE % "\n".join(choices)
+ prompt_choice = self.instance.prompt_int(
+ prompt=prompt, low=1, high=len(applications)
+ )
+ name, application = list(applications.items())[prompt_choice - 1]
+ elif self.choice in APPLICATIONS.keys():
+ name, application = self.choice, APPLICATIONS[self.choice]
+ elif self.choice in APPLICATIONS.values():
+ name, application = next(
+ (k, v) for k, v in APPLICATIONS.items() if v == self.choice
+ )
+ else:
+ raise Exception(
+ "Please pick a valid application choice: (%s)"
+ % "/".join(APPLICATIONS.keys())
+ )
+
+ mozconfig_builder = MozconfigBuilder()
+ self.instance.application = application
+ self.instance.artifact_mode = "artifact_mode" in application
+
+ self.instance.warn_if_pythonpath_is_set()
+
+ if sys.platform.startswith("darwin") and not os.environ.get(
+ "MACH_I_DO_WANT_TO_USE_ROSETTA"
+ ):
+ # If running on arm64 mac, check whether we're running under
+ # Rosetta and advise against it.
+ if _macos_is_running_under_rosetta():
+ print(
+ "Python is being emulated under Rosetta. Please use a native "
+ "Python instead. If you still really want to go ahead, set "
+ "the MACH_I_DO_WANT_TO_USE_ROSETTA environment variable.",
+ file=sys.stderr,
+ )
+ return 1
+
+ state_dir = Path(get_state_dir())
+ self.instance.state_dir = state_dir
+
+ hg = to_optional_path(which("hg"))
+
+ # We need to enable the loading of hgrc in case extensions are
+ # required to open the repo.
+ (checkout_type, checkout_root) = current_firefox_checkout(
+ env=self.instance._hg_cleanenv(load_hgrc=True),
+ hg=hg,
+ )
+ self.instance.srcdir = checkout_root
+ self.instance.validate_environment()
+ self._validate_python_environment(checkout_root)
+
+ if self.instance.no_system_changes:
+ self.maybe_install_private_packages_or_exit(application, checkout_type)
+ self._output_mozconfig(application, mozconfig_builder)
+ sys.exit(0)
+
+ self.instance.install_system_packages()
+
+ # Like 'install_browser_packages' or 'install_mobile_android_packages'.
+ getattr(self.instance, "install_%s_packages" % application)(mozconfig_builder)
+
+ if not self.instance.artifact_mode:
+ self.instance.ensure_rust_modern()
+
+ git = to_optional_path(which("git"))
+
+ # Possibly configure Mercurial, but not if the current checkout or repo
+ # type is Git.
+ hg_installed = bool(hg)
+ if checkout_type == "hg":
+ hg_installed, hg_modern = self.instance.ensure_mercurial_modern()
+
+ if hg_installed and checkout_type == "hg":
+ if not self.instance.no_interactive:
+ configure_hg = self.instance.prompt_yesno(prompt=CONFIGURE_MERCURIAL)
+ else:
+ configure_hg = self.hg_configure
+
+ if configure_hg:
+ configure_mercurial(hg, state_dir)
+
+ # Offer to configure Git, if the current checkout or repo type is Git.
+ elif git and checkout_type == "git":
+ should_configure_git = False
+ if not self.instance.no_interactive:
+ should_configure_git = self.instance.prompt_yesno(prompt=CONFIGURE_GIT)
+ else:
+ # Assuming default configuration setting applies to all VCS.
+ should_configure_git = self.hg_configure
+
+ if should_configure_git:
+ configure_git(
+ git,
+ to_optional_path(which("git-cinnabar")),
+ state_dir,
+ checkout_root,
+ )
+
+ self.maybe_install_private_packages_or_exit(application, checkout_type)
+ self.check_code_submission(checkout_root)
+ # Wait until after moz-phab setup to check telemetry so that employees
+ # will be automatically opted-in.
+ if not self.instance.no_interactive and not settings.mach_telemetry.is_set_up:
+ initialize_telemetry_setting(settings, str(checkout_root), str(state_dir))
+
+ self._output_mozconfig(application, mozconfig_builder)
+
+ print(FINISHED % name)
+ if not (
+ which("rustc")
+ and self.instance._parse_version(Path("rustc")) >= MODERN_RUST_VERSION
+ ):
+ print(
+ "To build %s, please restart the shell (Start a new terminal window)"
+ % name
+ )
+
+ def _default_mozconfig_path(self):
+ return Path(self.mach_context.topdir) / "mozconfig"
+
+ def _read_default_mozconfig(self):
+ path = self._default_mozconfig_path()
+ with open(path, "r") as mozconfig_file:
+ return mozconfig_file.read()
+
+ def _write_default_mozconfig(self, raw_mozconfig):
+ path = self._default_mozconfig_path()
+ with open(path, "w") as mozconfig_file:
+ mozconfig_file.write(raw_mozconfig)
+ print(f'Your requested configuration has been written to "{path}".')
+
+ def _show_mozconfig_suggestion(self, raw_mozconfig):
+ suggestion = MOZCONFIG_SUGGESTION_TEMPLATE % (
+ self._default_mozconfig_path(),
+ raw_mozconfig,
+ )
+ print(suggestion, end="")
+
+ def _check_default_mozconfig_mismatch(
+ self, current_mozconfig_info, expected_application, expected_raw_mozconfig
+ ):
+ current_raw_mozconfig = self._read_default_mozconfig()
+ current_application = current_mozconfig_info["project"][0].replace("/", "_")
+ if current_mozconfig_info["artifact-builds"]:
+ current_application += "_artifact_mode"
+
+ if expected_application == current_application:
+ if expected_raw_mozconfig == current_raw_mozconfig:
+ return
+
+ # There's minor difference, show the suggestion.
+ self._show_mozconfig_suggestion(expected_raw_mozconfig)
+ return
+
+ warning = MOZCONFIG_MISMATCH_WARNING_TEMPLATE % (
+ self._default_mozconfig_path(),
+ current_raw_mozconfig,
+ expected_raw_mozconfig,
+ )
+ print(warning)
+
+ if not self.instance.prompt_yesno("Do you want to overwrite the config?"):
+ return
+
+ self._write_default_mozconfig(expected_raw_mozconfig)
+
+ def _output_mozconfig(self, application, mozconfig_builder):
+ # Like 'generate_browser_mozconfig' or 'generate_mobile_android_mozconfig'.
+ additional_mozconfig = getattr(
+ self.instance, "generate_%s_mozconfig" % application
+ )()
+ if additional_mozconfig:
+ mozconfig_builder.append(additional_mozconfig)
+ raw_mozconfig = mozconfig_builder.generate()
+
+ current_mozconfig_info = MozbuildObject.get_base_mozconfig_info(
+ self.mach_context.topdir, None, ""
+ )
+ current_mozconfig_path = current_mozconfig_info["mozconfig"]["path"]
+
+ if current_mozconfig_path:
+ # mozconfig file exists
+ if self._default_mozconfig_path().exists() and Path.samefile(
+ Path(current_mozconfig_path), self._default_mozconfig_path()
+ ):
+ # This mozconfig file may be created by bootstrap.
+ self._check_default_mozconfig_mismatch(
+ current_mozconfig_info, application, raw_mozconfig
+ )
+ elif raw_mozconfig:
+ # The mozconfig file is created by user.
+ self._show_mozconfig_suggestion(raw_mozconfig)
+ elif raw_mozconfig:
+ # No mozconfig file exists yet
+ self._write_default_mozconfig(raw_mozconfig)
+
+ def _validate_python_environment(self, topsrcdir):
+ valid = True
+ try:
+ # distutils is singled out here because some distros (namely Ubuntu)
+ # include it in a separate package outside of the main Python
+ # installation.
+ import distutils.spawn
+ import distutils.sysconfig
+
+ assert distutils.sysconfig is not None and distutils.spawn is not None
+ except ImportError as e:
+ print("ERROR: Could not import package %s" % e.name, file=sys.stderr)
+ self.instance.suggest_install_distutils()
+ valid = False
+ except AssertionError:
+ print("ERROR: distutils is not behaving as expected.", file=sys.stderr)
+ self.instance.suggest_install_distutils()
+ valid = False
+ pip3 = to_optional_path(which("pip3"))
+ if not pip3:
+ print("ERROR: Could not find pip3.", file=sys.stderr)
+ self.instance.suggest_install_pip3()
+ valid = False
+ if not valid:
+ print(
+ "ERROR: Your Python installation will not be able to run "
+ "`mach bootstrap`. `mach bootstrap` cannot maintain your "
+ "Python environment for you; fix the errors shown here, and "
+ "then re-run `mach bootstrap`.",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+ mach_site = MachSiteManager.from_environment(
+ topsrcdir,
+ lambda: os.path.normpath(get_state_dir(True, topsrcdir=topsrcdir)),
+ )
+ mach_site.attempt_populate_optional_packages()
+
+
+def update_vct(hg: Path, root_state_dir: Path):
+ """Ensure version-control-tools in the state directory is up to date."""
+ vct_dir = root_state_dir / "version-control-tools"
+
+ # Ensure the latest revision of version-control-tools is present.
+ update_mercurial_repo(
+ hg, "https://hg.mozilla.org/hgcustom/version-control-tools", vct_dir, "@"
+ )
+
+ return vct_dir
+
+
+def configure_mercurial(hg: Optional[Path], root_state_dir: Path):
+ """Run the Mercurial configuration wizard."""
+ vct_dir = update_vct(hg, root_state_dir)
+
+ hg = to_optional_str(hg)
+
+ # Run the config wizard from v-c-t.
+ args = [
+ hg,
+ "--config",
+ f"extensions.configwizard={vct_dir}/hgext/configwizard",
+ "configwizard",
+ ]
+ subprocess.call(args)
+
+
+def update_mercurial_repo(hg: Path, url, dest: Path, revision):
+ """Perform a clone/pull + update of a Mercurial repository."""
+ # Disable common extensions whose older versions may cause `hg`
+ # invocations to abort.
+ pull_args = [str(hg)]
+ if dest.exists():
+ pull_args.extend(["pull", url])
+ cwd = dest
+ else:
+ pull_args.extend(["clone", "--noupdate", url, str(dest)])
+ cwd = "/"
+
+ update_args = [str(hg), "update", "-r", revision]
+
+ print("=" * 80)
+ print(f"Ensuring {url} is up to date at {dest}")
+
+ env = os.environ.copy()
+ env.update({"HGPLAIN": "1"})
+
+ try:
+ subprocess.check_call(pull_args, cwd=str(cwd), env=env)
+ subprocess.check_call(update_args, cwd=str(dest), env=env)
+ finally:
+ print("=" * 80)
+
+
+def current_firefox_checkout(env, hg: Optional[Path] = None):
+ """Determine whether we're in a Firefox checkout.
+
+ Returns one of None, ``git``, or ``hg``.
+ """
+ HG_ROOT_REVISIONS = set(
+ [
+ # From mozilla-unified.
+ "8ba995b74e18334ab3707f27e9eb8f4e37ba3d29"
+ ]
+ )
+
+ path = Path.cwd()
+ while path:
+ hg_dir = path / ".hg"
+ git_dir = path / ".git"
+ known_file = path / "config" / "milestone.txt"
+ if hg and hg_dir.exists():
+ # Verify the hg repo is a Firefox repo by looking at rev 0.
+ try:
+ node = subprocess.check_output(
+ [str(hg), "log", "-r", "0", "--template", "{node}"],
+ cwd=str(path),
+ env=env,
+ universal_newlines=True,
+ )
+ if node in HG_ROOT_REVISIONS:
+ _warn_if_risky_revision(path)
+ return "hg", path
+ # Else the root revision is different. There could be nested
+ # repos. So keep traversing the parents.
+ except subprocess.CalledProcessError:
+ pass
+
+ # Just check for known-good files in the checkout, to prevent attempted
+ # foot-shootings. Determining a canonical git checkout of mozilla-unified
+ # is...complicated
+ elif git_dir.exists() or hg_dir.exists():
+ if known_file.exists():
+ _warn_if_risky_revision(path)
+ return ("git" if git_dir.exists() else "hg"), path
+ elif known_file.exists():
+ return "SOURCE", path
+
+ if not len(path.parents):
+ break
+ path = path.parent
+
+ raise UserError(
+ "Could not identify the root directory of your checkout! "
+ "Are you running `mach bootstrap` in an hg or git clone?"
+ )
+
+
+def update_git_tools(git: Optional[Path], root_state_dir: Path):
+ """Update git tools, hooks and extensions"""
+ # Ensure git-cinnabar is up to date.
+ cinnabar_dir = root_state_dir / "git-cinnabar"
+ cinnabar_exe = cinnabar_dir / "git-cinnabar"
+
+ if sys.platform.startswith(("win32", "msys")):
+ cinnabar_exe = cinnabar_exe.with_suffix(".exe")
+
+ # Previously, this script would do a full clone of the git-cinnabar
+ # repository. It now only downloads prebuilt binaries, so if we are
+ # updating from an old setup, remove the repository and start over.
+ if (cinnabar_dir / ".git").exists():
+ # git sets pack files read-only, which causes problems removing
+ # them on Windows. To work around that, we use an error handler
+ # on rmtree that retries to remove the file after chmod'ing it.
+ def onerror(func, path, exc):
+ if func == os.unlink:
+ os.chmod(path, stat.S_IRWXU)
+ func(path)
+ else:
+ raise
+
+ shutil.rmtree(str(cinnabar_dir), onerror=onerror)
+
+ # If we already have an executable, ask it to update itself.
+ exists = cinnabar_exe.exists()
+ if exists:
+ try:
+ subprocess.check_call([str(cinnabar_exe), "self-update"])
+ except subprocess.CalledProcessError as e:
+ print(e)
+
+ # git-cinnabar 0.6.0rc1 self-update had a bug that could leave an empty
+ # file. If that happens, install from scratch.
+ if not exists or cinnabar_exe.stat().st_size == 0:
+ from urllib.request import urlopen
+
+ import certifi
+
+ if not cinnabar_dir.exists():
+ cinnabar_dir.mkdir()
+
+ cinnabar_url = "https://github.com/glandium/git-cinnabar/"
+ download_py = cinnabar_dir / "download.py"
+ with open(download_py, "wb") as fh:
+ shutil.copyfileobj(
+ urlopen(
+ f"{cinnabar_url}/raw/master/download.py", cafile=certifi.where()
+ ),
+ fh,
+ )
+
+ try:
+ subprocess.check_call(
+ [sys.executable, str(download_py)], cwd=str(cinnabar_dir)
+ )
+ except subprocess.CalledProcessError as e:
+ print(e)
+ finally:
+ download_py.unlink()
+
+ return cinnabar_dir
+
+
+def configure_git(
+ git: Optional[Path],
+ cinnabar: Optional[Path],
+ root_state_dir: Path,
+ top_src_dir: Path,
+):
+ """Run the Git configuration steps."""
+
+ git_str = to_optional_str(git)
+
+ match = re.search(
+ r"(\d+\.\d+\.\d+)",
+ subprocess.check_output([git_str, "--version"], universal_newlines=True),
+ )
+ if not match:
+ raise Exception("Could not find git version")
+ git_version = Version(match.group(1))
+
+ if git_version < MINIMUM_RECOMMENDED_GIT_VERSION:
+ print(
+ OLD_GIT_WARNING.format(
+ old_version=git_version,
+ minimum_recommended_version=MINIMUM_RECOMMENDED_GIT_VERSION,
+ )
+ )
+
+ if git_version >= Version("2.17"):
+ # "core.untrackedCache" has a bug before 2.17
+ subprocess.check_call(
+ [git_str, "config", "core.untrackedCache", "true"], cwd=str(top_src_dir)
+ )
+
+ cinnabar_dir = str(update_git_tools(git, root_state_dir))
+
+ if not cinnabar:
+ if "MOZILLABUILD" in os.environ:
+ # Slightly modify the path on Windows to be correct
+ # for the copy/paste into the .bash_profile
+ cinnabar_dir = win_to_msys_path(cinnabar_dir)
+
+ print(
+ ADD_GIT_CINNABAR_PATH.format(
+ prefix="%USERPROFILE%", cinnabar_dir=cinnabar_dir
+ )
+ )
+ else:
+ print(ADD_GIT_CINNABAR_PATH.format(prefix="~", cinnabar_dir=cinnabar_dir))
+
+
+def _warn_if_risky_revision(path: Path):
+ # Warn the user if they're trying to bootstrap from an obviously old
+ # version of tree as reported by the version control system (a month in
+ # this case). This is an approximate calculation but is probably good
+ # enough for our purposes.
+ NUM_SECONDS_IN_MONTH = 60 * 60 * 24 * 30
+ from mozversioncontrol import get_repository_object
+
+ repo = get_repository_object(path)
+ if (time.time() - repo.get_commit_time()) >= NUM_SECONDS_IN_MONTH:
+ print(OLD_REVISION_WARNING)
+
+
+def _macos_is_running_under_rosetta():
+ proc = subprocess.run(
+ ["sysctl", "-n", "sysctl.proc_translated"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL,
+ )
+ return (
+ proc.returncode == 0 and proc.stdout.decode("ascii", "replace").strip() == "1"
+ )