firefox/python-build.patch

4559 lines
157 KiB
Diff
Raw Normal View History

2022-12-08 10:05:41 +00:00
diff --git a/python/l10n/mozxchannel/__init__.py b/python/l10n/mozxchannel/__init__.py
--- a/python/l10n/mozxchannel/__init__.py
+++ b/python/l10n/mozxchannel/__init__.py
@@ -46,25 +46,6 @@ def get_default_config(topsrcdir, string
"mobile/android/locales/l10n.toml",
],
},
- "comm-central": {
- "path": topsrcdir / "comm",
- "post-clobber": True,
- "url": "https://hg.mozilla.org/comm-central/",
- "heads": {
- # This list of repositories is ordered, starting with the
- # one with the most recent content (central) to the oldest
- # (ESR). In case two ESR versions are supported, the oldest
- # ESR goes last (e.g. esr78 goes after esr91).
- "comm": "comm-central",
- "comm-beta": "releases/comm-beta",
- "comm-esr102": "releases/comm-esr102",
- },
- "config_files": [
- "comm/calendar/locales/l10n.toml",
- "comm/mail/locales/l10n.toml",
- "comm/suite/locales/l10n.toml",
- ],
- },
},
}
diff --git a/python/mach/docs/windows-usage-outside-mozillabuild.rst b/python/mach/docs/windows-usage-outside-mozillabuild.rst
--- a/python/mach/docs/windows-usage-outside-mozillabuild.rst
+++ b/python/mach/docs/windows-usage-outside-mozillabuild.rst
@@ -117,3 +117,8 @@ Success!
At this point, you should be able to invoke Mach and manage your version control system outside
of MozillaBuild.
+
+.. tip::
+
+ `See here <https://crisal.io/words/2022/11/22/msys2-firefox-development.html>`__ for a detailed guide on
+ installing and customizing a development environment with MSYS2, zsh, and Windows Terminal.
diff --git a/python/mach/mach/site.py b/python/mach/mach/site.py
--- a/python/mach/mach/site.py
+++ b/python/mach/mach/site.py
@@ -18,10 +18,10 @@ import site
import subprocess
import sys
import sysconfig
-from pathlib import Path
import tempfile
from contextlib import contextmanager
-from typing import Optional, Callable
+from pathlib import Path
+from typing import Callable, Optional
from mach.requirements import (
MachEnvRequirements,
@@ -663,6 +663,58 @@ class CommandSiteManager:
stderr=subprocess.STDOUT,
universal_newlines=True,
)
+
+ if not check_result.returncode:
+ return
+
+ """
+ Some commands may use the "setup.py" script of first-party modules. This causes
+ a "*.egg-info" dir to be created for that module (which pip can then detect as
+ a package). Since we add all first-party module directories to the .pthfile for
+ the "mach" venv, these first-party modules are then detected by all venvs after
+ they are created. The problem is that these .egg-info directories can become
+ stale (since if the first-party module is updated it's not guaranteed that the
+ command that runs the "setup.py" was ran afterwards). This can cause
+ incompatibilities with the pip check (since the dependencies can change between
+ different versions).
+
+ These .egg-info dirs are in our VCS ignore lists (eg: ".hgignore") because they
+ are necessary to run some commands, so we don't want to always purge them, and we
+ also don't want to accidentally commit them. Given this, we can leverage our VCS
+ to find all the current first-party .egg-info dirs.
+
+ If we're in the case where 'pip check' fails, then we can try purging the
+ first-party .egg-info dirs, then run the 'pip check' again afterwards. If it's
+ still failing, then we know the .egg-info dirs weren't the problem. If that's
+ the case we can just raise the error encountered, which is the same as before.
+ """
+
+ def _delete_ignored_egg_info_dirs():
+ from pathlib import Path
+
+ from mozversioncontrol import get_repository_from_env
+
+ with get_repository_from_env() as repo:
+ ignored_file_finder = repo.get_ignored_files_finder().find(
+ "**/*.egg-info"
+ )
+
+ unique_egg_info_dirs = {
+ Path(found[0]).parent for found in ignored_file_finder
+ }
+
+ for egg_info_dir in unique_egg_info_dirs:
+ shutil.rmtree(egg_info_dir)
+
+ _delete_ignored_egg_info_dirs()
+
+ check_result = subprocess.run(
+ [self.python_path, "-m", "pip", "check"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True,
+ )
+
if check_result.returncode:
if quiet:
# If "quiet" was specified, then the "pip install" output wasn't printed
@@ -763,7 +815,7 @@ class PythonVirtualenv:
else:
self.bin_path = os.path.join(prefix, "bin")
self.python_path = os.path.join(self.bin_path, "python")
- self.prefix = prefix
+ self.prefix = os.path.realpath(prefix)
@functools.lru_cache(maxsize=None)
def resolve_sysconfig_packages_path(self, sysconfig_path):
@@ -783,16 +835,12 @@ class PythonVirtualenv:
relative_path = path.relative_to(data_path)
# Path to virtualenv's "site-packages" directory for provided sysconfig path
- return os.path.normpath(
- os.path.normcase(os.path.realpath(Path(self.prefix) / relative_path))
- )
+ return os.path.normpath(os.path.normcase(Path(self.prefix) / relative_path))
def site_packages_dirs(self):
dirs = []
if sys.platform.startswith("win"):
- dirs.append(
- os.path.normpath(os.path.normcase(os.path.realpath(self.prefix)))
- )
+ dirs.append(os.path.normpath(os.path.normcase(self.prefix)))
purelib = self.resolve_sysconfig_packages_path("purelib")
platlib = self.resolve_sysconfig_packages_path("platlib")
diff --git a/python/mozboot/bin/bootstrap.py b/python/mozboot/bin/bootstrap.py
--- a/python/mozboot/bin/bootstrap.py
+++ b/python/mozboot/bin/bootstrap.py
@@ -11,8 +11,6 @@
# Python environment (except that it's run with a sufficiently recent version of
# Python 3), so we are restricted to stdlib modules.
-from __future__ import absolute_import, print_function, unicode_literals
-
import sys
major, minor = sys.version_info[:2]
@@ -23,14 +21,13 @@ if (major < 3) or (major == 3 and minor
)
sys.exit(1)
+import ctypes
import os
import shutil
import subprocess
import tempfile
-import ctypes
-
+from optparse import OptionParser
from pathlib import Path
-from optparse import OptionParser
CLONE_MERCURIAL_PULL_FAIL = """
Failed to pull from hg.mozilla.org.
@@ -55,7 +52,7 @@ def which(name):
search_dirs = os.environ["PATH"].split(os.pathsep)
potential_names = [name]
if WINDOWS:
- potential_names.append(name + ".exe")
+ potential_names.insert(0, name + ".exe")
for path in search_dirs:
for executable_name in potential_names:
@@ -105,7 +102,7 @@ def input_clone_dest(vcs, no_interactive
return None
-def hg_clone_firefox(hg: Path, dest: Path):
+def hg_clone_firefox(hg: Path, dest: Path, head_repo, head_rev):
# We create an empty repo then modify the config before adding data.
# This is necessary to ensure storage settings are optimally
# configured.
@@ -139,16 +136,28 @@ def hg_clone_firefox(hg: Path, dest: Pat
fh.write("# This is necessary to keep performance in check\n")
fh.write("maxchainlen = 10000\n")
+ # Pulling a specific revision into an empty repository induces a lot of
+ # load on the Mercurial server, so we always pull from mozilla-unified (which,
+ # when done from an empty repository, is equivalent to a clone), and then pull
+ # the specific revision we want (if we want a specific one, otherwise we just
+ # use the "central" bookmark), at which point it will be an incremental pull,
+ # that the server can process more easily.
+ # This is the same thing that robustcheckout does on automation.
res = subprocess.call(
[str(hg), "pull", "https://hg.mozilla.org/mozilla-unified"], cwd=str(dest)
)
+ if not res and head_repo:
+ res = subprocess.call(
+ [str(hg), "pull", head_repo, "-r", head_rev], cwd=str(dest)
+ )
print("")
if res:
print(CLONE_MERCURIAL_PULL_FAIL % dest)
return None
- print('updating to "central" - the development head of Gecko and Firefox')
- res = subprocess.call([str(hg), "update", "-r", "central"], cwd=str(dest))
+ head_rev = head_rev or "central"
+ print(f'updating to "{head_rev}" - the development head of Gecko and Firefox')
+ res = subprocess.call([str(hg), "update", "-r", head_rev], cwd=str(dest))
if res:
print(
f"error updating; you will need to `cd {dest} && hg update -r central` "
@@ -157,7 +166,7 @@ def hg_clone_firefox(hg: Path, dest: Pat
return dest
-def git_clone_firefox(git: Path, dest: Path, watchman: Path):
+def git_clone_firefox(git: Path, dest: Path, watchman: Path, head_repo, head_rev):
tempdir = None
cinnabar = None
env = dict(os.environ)
@@ -196,8 +205,7 @@ def git_clone_firefox(git: Path, dest: P
[
str(git),
"clone",
- "-b",
- "bookmarks/central",
+ "--no-checkout",
"hg::https://hg.mozilla.org/mozilla-unified",
str(dest),
],
@@ -210,6 +218,19 @@ def git_clone_firefox(git: Path, dest: P
[str(git), "config", "pull.ff", "only"], cwd=str(dest), env=env
)
+ if head_repo:
+ subprocess.check_call(
+ [str(git), "cinnabar", "fetch", f"hg::{head_repo}", head_rev],
+ cwd=str(dest),
+ env=env,
+ )
+
+ subprocess.check_call(
+ [str(git), "checkout", "FETCH_HEAD" if head_rev else "bookmarks/central"],
+ cwd=str(dest),
+ env=env,
+ )
+
watchman_sample = dest / ".git/hooks/fsmonitor-watchman.sample"
# Older versions of git didn't include fsmonitor-watchman.sample.
if watchman and watchman_sample.exists():
@@ -233,12 +254,6 @@ def git_clone_firefox(git: Path, dest: P
subprocess.check_call(config_args, cwd=str(dest), env=env)
return dest
finally:
- if not cinnabar:
- print(
- "Failed to install git-cinnabar. Try performing a manual "
- "installation: https://github.com/glandium/git-cinnabar/wiki/"
- "Mozilla:-A-git-workflow-for-Gecko-development"
- )
if tempdir:
shutil.rmtree(str(tempdir))
@@ -326,11 +341,15 @@ def clone(options):
add_microsoft_defender_antivirus_exclusions(dest, no_system_changes)
print(f"Cloning Firefox {VCS_HUMAN_READABLE[vcs]} repository to {dest}")
+
+ head_repo = os.environ.get("GECKO_HEAD_REPOSITORY")
+ head_rev = os.environ.get("GECKO_HEAD_REV")
+
if vcs == "hg":
- return hg_clone_firefox(binary, dest)
+ return hg_clone_firefox(binary, dest, head_repo, head_rev)
else:
watchman = which("watchman")
- return git_clone_firefox(binary, dest, watchman)
+ return git_clone_firefox(binary, dest, watchman, head_repo, head_rev)
def bootstrap(srcdir: Path, application_choice, no_interactive, no_system_changes):
diff --git a/python/mozboot/mozboot/android.py b/python/mozboot/mozboot/android.py
--- a/python/mozboot/mozboot/android.py
+++ b/python/mozboot/mozboot/android.py
@@ -2,8 +2,6 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
import errno
import json
import os
@@ -11,15 +9,16 @@ import stat
import subprocess
import sys
import time
-import requests
+from pathlib import Path
from typing import Optional, Union
-from pathlib import Path
-from tqdm import tqdm
+
+import requests
# We need the NDK version in multiple different places, and it's inconvenient
# to pass down the NDK version to all relevant places, so we have this global
# variable.
from mozboot.bootstrap import MOZCONFIG_SUGGESTION_TEMPLATE
+from tqdm import tqdm
NDK_VERSION = "r21d"
CMDLINE_TOOLS_VERSION_STRING = "7.0"
@@ -74,7 +73,7 @@ output as packages are downloaded and in
MOBILE_ANDROID_MOZCONFIG_TEMPLATE = """
# Build GeckoView/Firefox for Android:
-ac_add_options --enable-application=mobile/android
+ac_add_options --enable-project=mobile/android
# Targeting the following architecture.
# For regular phones, no --target is needed.
@@ -90,8 +89,7 @@ ac_add_options --enable-application=mobi
MOBILE_ANDROID_ARTIFACT_MODE_MOZCONFIG_TEMPLATE = """
# Build GeckoView/Firefox for Android Artifact Mode:
-ac_add_options --enable-application=mobile/android
-ac_add_options --target=arm-linux-androideabi
+ac_add_options --enable-project=mobile/android
ac_add_options --enable-artifact-builds
{extra_lines}
@@ -162,18 +160,19 @@ def download(
download_file_path: Path,
):
with requests.Session() as session:
- request = session.head(url)
+ request = session.head(url, allow_redirects=True)
+ request.raise_for_status()
remote_file_size = int(request.headers["content-length"])
if download_file_path.is_file():
local_file_size = download_file_path.stat().st_size
if local_file_size == remote_file_size:
- print(f"{download_file_path} already downloaded. Skipping download...")
+ print(
+ f"{download_file_path.name} already downloaded. Skipping download..."
+ )
else:
- print(
- f"Partial download detected. Resuming download of {download_file_path}..."
- )
+ print(f"Partial download detected. Resuming download of {url}...")
download_internal(
download_file_path,
session,
@@ -182,7 +181,7 @@ def download(
local_file_size,
)
else:
- print(f"Downloading {download_file_path}...")
+ print(f"Downloading {url}...")
download_internal(download_file_path, session, url, remote_file_size)
diff --git a/python/mozboot/mozboot/archlinux.py b/python/mozboot/mozboot/archlinux.py
--- a/python/mozboot/mozboot/archlinux.py
+++ b/python/mozboot/mozboot/archlinux.py
@@ -2,120 +2,27 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
-import os
import sys
-import tempfile
-import subprocess
-
-from pathlib import Path
from mozboot.base import BaseBootstrapper
from mozboot.linux_common import LinuxBootstrapper
-# NOTE: This script is intended to be run with a vanilla Python install. We
-# have to rely on the standard library instead of Python 2+3 helpers like
-# the six module.
-if sys.version_info < (3,):
- input = raw_input # noqa
-
-
-AUR_URL_TEMPLATE = "https://aur.archlinux.org/cgit/aur.git/snapshot/{}.tar.gz"
-
class ArchlinuxBootstrapper(LinuxBootstrapper, BaseBootstrapper):
"""Archlinux experimental bootstrapper."""
- SYSTEM_PACKAGES = ["base-devel", "unzip", "zip"]
-
- BROWSER_PACKAGES = [
- "alsa-lib",
- "dbus-glib",
- "gtk3",
- "libevent",
- "libvpx",
- "libxt",
- "mime-types",
- "startup-notification",
- "gst-plugins-base-libs",
- "libpulse",
- "xorg-server-xvfb",
- "gst-libav",
- "gst-plugins-good",
- ]
-
- BROWSER_AUR_PACKAGES = [
- "uuid",
- ]
-
- MOBILE_ANDROID_COMMON_PACKAGES = [
- # See comment about 32 bit binaries and multilib below.
- "multilib/lib32-ncurses",
- "multilib/lib32-readline",
- "multilib/lib32-zlib",
- ]
-
def __init__(self, version, dist_id, **kwargs):
print("Using an experimental bootstrapper for Archlinux.", file=sys.stderr)
BaseBootstrapper.__init__(self, **kwargs)
- def install_system_packages(self):
- self.pacman_install(*self.SYSTEM_PACKAGES)
-
- def install_browser_packages(self, mozconfig_builder, artifact_mode=False):
- # TODO: Figure out what not to install for artifact mode
- self.aur_install(*self.BROWSER_AUR_PACKAGES)
- self.pacman_install(*self.BROWSER_PACKAGES)
-
- def install_browser_artifact_mode_packages(self, mozconfig_builder):
- self.install_browser_packages(mozconfig_builder, artifact_mode=True)
-
- def ensure_nasm_packages(self):
- # installed via install_browser_packages
- pass
-
- def install_mobile_android_packages(self, mozconfig_builder, artifact_mode=False):
- # Multi-part process:
- # 1. System packages.
- # 2. Android SDK. Android NDK only if we are not in artifact mode. Android packages.
-
- # 1. This is hard to believe, but the Android SDK binaries are 32-bit
- # and that conflicts with 64-bit Arch installations out of the box. The
- # solution is to add the multilibs repository; unfortunately, this
- # requires manual intervention.
- try:
- self.pacman_install(*self.MOBILE_ANDROID_COMMON_PACKAGES)
- except Exception as e:
- print(
- "Failed to install all packages. The Android developer "
- "toolchain requires 32 bit binaries be enabled (see "
- "https://wiki.archlinux.org/index.php/Android). You may need to "
- "manually enable the multilib repository following the instructions "
- "at https://wiki.archlinux.org/index.php/Multilib.",
- file=sys.stderr,
- )
- raise e
-
- # 2. Android pieces.
- super().install_mobile_android_packages(
- mozconfig_builder, artifact_mode=artifact_mode
- )
+ def install_packages(self, packages):
+ # watchman is not available via pacman
+ packages = [p for p in packages if p != "watchman"]
+ self.pacman_install(*packages)
def upgrade_mercurial(self, current):
self.pacman_install("mercurial")
- def pacman_is_installed(self, package):
- command = ["pacman", "-Q", package]
- return (
- subprocess.run(
- command,
- stdout=subprocess.DEVNULL,
- stderr=subprocess.DEVNULL,
- ).returncode
- == 0
- )
-
def pacman_install(self, *packages):
command = ["pacman", "-S", "--needed"]
if self.no_interactive:
@@ -124,71 +31,3 @@ class ArchlinuxBootstrapper(LinuxBootstr
command.extend(packages)
self.run_as_root(command)
-
- def run(self, command, env=None):
- subprocess.check_call(command, stdin=sys.stdin, env=env)
-
- def download(self, uri):
- command = ["curl", "-L", "-O", uri]
- self.run(command)
-
- def unpack(self, path: Path, name, ext):
- if ext == ".gz":
- compression = "-z"
- else:
- print(f"unsupported compression extension: {ext}", file=sys.stderr)
- sys.exit(1)
-
- name = path / (name + ".tar" + ext)
- command = ["tar", "-x", compression, "-f", str(name), "-C", str(path)]
- self.run(command)
-
- def makepkg(self, name):
- command = ["makepkg", "-sri"]
- if self.no_interactive:
- command.append("--noconfirm")
- makepkg_env = os.environ.copy()
- makepkg_env["PKGDEST"] = "."
- self.run(command, env=makepkg_env)
-
- def aur_install(self, *packages):
- needed = []
-
- for package in packages:
- if self.pacman_is_installed(package):
- print(
- f"warning: AUR package {package} is installed -- skipping",
- file=sys.stderr,
- )
- else:
- needed.append(package)
-
- # all required AUR packages are already installed!
- if not needed:
- return
-
- path = Path(tempfile.mkdtemp(prefix="mozboot-"))
- if not self.no_interactive:
- print(
- "WARNING! This script requires to install packages from the AUR "
- "This is potentially insecure so I recommend that you carefully "
- "read each package description and check the sources."
- f"These packages will be built in {path}: " + ", ".join(needed),
- file=sys.stderr,
- )
- choice = input("Do you want to continue? (yes/no) [no]")
- if choice != "yes":
- sys.exit(1)
-
- base_dir = Path.cwd()
- os.chdir(path)
- for name in needed:
- url = AUR_URL_TEMPLATE.format(package)
- ext = Path(url).suffix
- directory = path / name
- self.download(url)
- self.unpack(path, name, ext)
- os.chdir(directory)
- self.makepkg(name)
-
- os.chdir(base_dir)
diff --git a/python/mozboot/mozboot/base.py b/python/mozboot/mozboot/base.py
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -2,25 +2,22 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
import os
import re
import subprocess
import sys
-
from pathlib import Path
-from packaging.version import Version
+from mach.util import to_optional_path, win_to_msys_path
from mozboot import rust
from mozboot.util import (
+ MINIMUM_RUST_VERSION,
get_mach_virtualenv_binary,
- MINIMUM_RUST_VERSION,
http_download_and_save,
)
+from mozbuild.bootstrap import bootstrap_all_toolchains_for, bootstrap_toolchain
from mozfile import which
-from mozbuild.bootstrap import bootstrap_toolchain
-from mach.util import to_optional_path, win_to_msys_path
+from packaging.version import Version
NO_MERCURIAL = """
Could not find Mercurial (hg) in the current shell's path. Try starting a new
@@ -143,7 +140,7 @@ ac_add_options --enable-artifact-builds
JS_MOZCONFIG_TEMPLATE = """\
# Build only the SpiderMonkey JS test shell
-ac_add_options --enable-application=js
+ac_add_options --enable-project=js
"""
# Upgrade Mercurial older than this.
@@ -344,47 +341,12 @@ class BaseBootstrapper(object):
% __name__
)
- def ensure_stylo_packages(self):
- """
- Install any necessary packages needed for Stylo development.
- """
- raise NotImplementedError(
- "%s does not yet implement ensure_stylo_packages()" % __name__
- )
-
- def ensure_nasm_packages(self):
- """
- Install nasm.
- """
- raise NotImplementedError(
- "%s does not yet implement ensure_nasm_packages()" % __name__
- )
-
def ensure_sccache_packages(self):
"""
Install sccache.
"""
pass
- def ensure_node_packages(self):
- """
- Install any necessary packages needed to supply NodeJS"""
- raise NotImplementedError(
- "%s does not yet implement ensure_node_packages()" % __name__
- )
-
- def ensure_fix_stacks_packages(self):
- """
- Install fix-stacks.
- """
- pass
-
- def ensure_minidump_stackwalk_packages(self):
- """
- Install minidump-stackwalk.
- """
- pass
-
def install_toolchain_static_analysis(self, toolchain_job):
clang_tools_path = self.state_dir / "clang-tools"
if not clang_tools_path.exists():
@@ -428,9 +390,17 @@ class BaseBootstrapper(object):
subprocess.check_call(cmd, cwd=str(install_dir))
- def run_as_root(self, command):
+ def auto_bootstrap(self, application):
+ args = ["--with-ccache=sccache"]
+ if application.endswith("_artifact_mode"):
+ args.append("--enable-artifact-builds")
+ application = application[: -len("_artifact_mode")]
+ args.append("--enable-project={}".format(application.replace("_", "/")))
+ bootstrap_all_toolchains_for(args)
+
+ def run_as_root(self, command, may_use_sudo=True):
if os.geteuid() != 0:
- if which("sudo"):
+ if may_use_sudo and which("sudo"):
command.insert(0, "sudo")
else:
command = ["su", "root", "-c", " ".join(command)]
@@ -439,107 +409,6 @@ class BaseBootstrapper(object):
subprocess.check_call(command, stdin=sys.stdin)
- def dnf_install(self, *packages):
- if which("dnf"):
-
- def not_installed(package):
- # We could check for "Error: No matching Packages to list", but
- # checking `dnf`s exit code is sufficent.
- # Ideally we'd invoke dnf with '--cacheonly', but there's:
- # https://bugzilla.redhat.com/show_bug.cgi?id=2030255
- is_installed = subprocess.run(
- ["dnf", "list", "--installed", package],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- )
- if is_installed.returncode not in [0, 1]:
- stdout = is_installed.stdout
- raise Exception(
- f'Failed to determine whether package "{package}" is installed: "{stdout}"'
- )
- return is_installed.returncode != 0
-
- packages = list(filter(not_installed, packages))
- if len(packages) == 0:
- # avoid sudo prompt (support unattended re-bootstrapping)
- return
-
- command = ["dnf", "install"]
- else:
- command = ["yum", "install"]
-
- if self.no_interactive:
- command.append("-y")
- command.extend(packages)
-
- self.run_as_root(command)
-
- def dnf_groupinstall(self, *packages):
- if which("dnf"):
- installed = subprocess.run(
- # Ideally we'd invoke dnf with '--cacheonly', but there's:
- # https://bugzilla.redhat.com/show_bug.cgi?id=2030255
- # Ideally we'd use `--installed` instead of the undocumented
- # `installed` subcommand, but that doesn't currently work:
- # https://bugzilla.redhat.com/show_bug.cgi?id=1884616#c0
- ["dnf", "group", "list", "installed", "--hidden"],
- universal_newlines=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- )
- if installed.returncode != 0:
- raise Exception(
- f'Failed to determine currently-installed package groups: "{installed.stdout}"'
- )
- installed_packages = (pkg.strip() for pkg in installed.stdout.split("\n"))
- packages = list(filter(lambda p: p not in installed_packages, packages))
- if len(packages) == 0:
- # avoid sudo prompt (support unattended re-bootstrapping)
- return
-
- command = ["dnf", "groupinstall"]
- else:
- command = ["yum", "groupinstall"]
-
- if self.no_interactive:
- command.append("-y")
- command.extend(packages)
-
- self.run_as_root(command)
-
- def dnf_update(self, *packages):
- if which("dnf"):
- command = ["dnf", "update"]
- else:
- command = ["yum", "update"]
-
- if self.no_interactive:
- command.append("-y")
- command.extend(packages)
-
- self.run_as_root(command)
-
- def apt_install(self, *packages):
- command = ["apt-get", "install"]
- if self.no_interactive:
- command.append("-y")
- command.extend(packages)
-
- self.run_as_root(command)
-
- def apt_update(self):
- command = ["apt-get", "update"]
- if self.no_interactive:
- command.append("-y")
-
- self.run_as_root(command)
-
- def apt_add_architecture(self, arch):
- command = ["dpkg", "--add-architecture"]
- command.extend(arch)
-
- self.run_as_root(command)
-
def prompt_int(self, prompt, low, high, default=None):
"""Prompts the user with prompt and requires an integer between low and high.
@@ -757,14 +626,10 @@ class BaseBootstrapper(object):
if modern:
print("Your version of Rust (%s) is new enough." % version)
- if rustup:
- self.ensure_rust_targets(rustup, version)
- return
-
- if version:
+ elif version:
print("Your version of Rust (%s) is too old." % version)
- if rustup:
+ if rustup and not modern:
rustup_version = self._parse_version(rustup)
if not rustup_version:
print(RUSTUP_OLD)
@@ -776,10 +641,16 @@ class BaseBootstrapper(object):
if not modern:
print(RUST_UPGRADE_FAILED % (MODERN_RUST_VERSION, after))
sys.exit(1)
- else:
+ elif not rustup:
# No rustup. Download and run the installer.
print("Will try to install Rust.")
self.install_rust()
+ modern, version = self.is_rust_modern(cargo_bin)
+ rustup = to_optional_path(
+ which("rustup", extra_search_dirs=[str(cargo_bin)])
+ )
+
+ self.ensure_rust_targets(rustup, version)
def ensure_rust_targets(self, rustup: Path, rust_version):
"""Make sure appropriate cross target libraries are installed."""
diff --git a/python/mozboot/mozboot/bootstrap.py b/python/mozboot/mozboot/bootstrap.py
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -2,48 +2,46 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
-from collections import OrderedDict
-
import os
import platform
import re
import shutil
-import sys
+import stat
import subprocess
+import sys
import time
-from typing import Optional
+from collections import OrderedDict
from pathlib import Path
-from packaging.version import Version
+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,
- UserError,
to_optional_path,
to_optional_str,
win_to_msys_path,
)
-from mach.telemetry import initialize_telemetry_setting
-from mach.site import MachSiteManager
+from mozboot.archlinux import ArchlinuxBootstrapper
from mozboot.base import MODERN_RUST_VERSION
from mozboot.centosfedora import CentOSFedoraBootstrapper
-from mozboot.opensuse import OpenSUSEBootstrapper
from mozboot.debian import DebianBootstrapper
from mozboot.freebsd import FreeBSDBootstrapper
from mozboot.gentoo import GentooBootstrapper
-from mozboot.osx import OSXBootstrapper, OSXBootstrapperLight
+from mozboot.mozconfig import MozconfigBuilder
+from mozboot.mozillabuild import MozillaBuildBootstrapper
from mozboot.openbsd import OpenBSDBootstrapper
-from mozboot.archlinux import ArchlinuxBootstrapper
+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
-from mozboot.mozillabuild import MozillaBuildBootstrapper
-from mozboot.mozconfig import MozconfigBuilder
+from mozbuild.base import MozbuildObject
from mozfile import which
-from mozbuild.base import MozbuildObject
-
-# Use distro package to retrieve linux platform information
-import distro
+from packaging.version import Version
APPLICATION_CHOICE = """
Note on Artifact Mode:
@@ -123,6 +121,7 @@ DEBIAN_DISTROS = (
"devuan",
"pureos",
"deepin",
+ "tuxedo",
)
ADD_GIT_CINNABAR_PATH = """
@@ -250,13 +249,11 @@ class Bootstrapper(object):
# 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.ensure_node_packages()
- self.instance.ensure_fix_stacks_packages()
- self.instance.ensure_minidump_stackwalk_packages()
+ self.instance.auto_bootstrap(application)
+ self.instance.install_toolchain_artifact("fix-stacks")
+ self.instance.install_toolchain_artifact("minidump-stackwalk")
if not self.instance.artifact_mode:
- self.instance.ensure_stylo_packages()
self.instance.ensure_clang_static_analysis_package()
- self.instance.ensure_nasm_packages()
self.instance.ensure_sccache_packages()
# Like 'ensure_browser_packages' or 'ensure_mobile_android_packages'
getattr(self.instance, "ensure_%s_packages" % application)()
@@ -325,7 +322,6 @@ class Bootstrapper(object):
state_dir = Path(get_state_dir())
self.instance.state_dir = state_dir
- hg_installed, hg_modern = self.instance.ensure_mercurial_modern()
hg = to_optional_path(which("hg"))
# We need to enable the loading of hgrc in case extensions are
@@ -355,6 +351,10 @@ class Bootstrapper(object):
# 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)
@@ -485,8 +485,8 @@ class Bootstrapper(object):
# 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
- import distutils.spawn
assert distutils.sysconfig is not None and distutils.spawn is not None
except ImportError as e:
@@ -610,11 +610,11 @@ def current_firefox_checkout(env, hg: Op
# 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():
+ elif git_dir.exists() or hg_dir.exists():
moz_configure = path / "moz.configure"
if moz_configure.exists():
_warn_if_risky_revision(path)
- return "git", path
+ return ("git" if git_dir.exists() else "hg"), path
if not len(path.parents):
break
@@ -639,13 +639,23 @@ def update_git_tools(git: Optional[Path]
# 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():
- shutil.rmtree(str(cinnabar_dir))
+ # 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([cinnabar_exe, "self-update"])
+ subprocess.check_call([str(cinnabar_exe), "self-update"])
except subprocess.CalledProcessError as e:
print(e)
diff --git a/python/mozboot/mozboot/centosfedora.py b/python/mozboot/mozboot/centosfedora.py
--- a/python/mozboot/mozboot/centosfedora.py
+++ b/python/mozboot/mozboot/centosfedora.py
@@ -2,10 +2,11 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
+import subprocess
from mozboot.base import BaseBootstrapper
from mozboot.linux_common import LinuxBootstrapper
+from mozfile import which
class CentOSFedoraBootstrapper(LinuxBootstrapper, BaseBootstrapper):
@@ -16,79 +17,63 @@ class CentOSFedoraBootstrapper(LinuxBoot
self.version = int(version.split(".")[0])
self.dist_id = dist_id
- self.group_packages = []
-
- self.packages = ["which"]
-
- self.browser_group_packages = ["GNOME Software Development"]
-
- self.browser_packages = [
- "alsa-lib-devel",
- "dbus-glib-devel",
- "glibc-static",
- # Development group.
- "libstdc++-static",
- "libXt-devel",
- "pulseaudio-libs-devel",
- "gcc-c++",
- ]
-
- self.mobile_android_packages = []
-
+ def install_packages(self, packages):
+ if self.version >= 33 and "perl" in packages:
+ packages.append("perl-FindBin")
+ # watchman is not available on centos/rocky
if self.distro in ("centos", "rocky"):
- self.group_packages += ["Development Tools"]
-
- self.packages += ["curl-devel"]
-
- self.browser_packages += ["gtk3-devel"]
-
- if self.version == 6:
- self.group_packages += [
- "Development Libraries",
- "GNOME Software Development",
- ]
-
- else:
- self.packages += ["redhat-rpm-config"]
-
- self.browser_group_packages = ["Development Tools"]
-
- elif self.distro == "fedora":
- self.group_packages += ["C Development Tools and Libraries"]
-
- self.packages += [
- "redhat-rpm-config",
- "watchman",
- ]
- if self.version >= 33:
- self.packages.append("perl-FindBin")
-
- self.mobile_android_packages += ["ncurses-compat-libs"]
-
- self.packages += ["python3-devel"]
-
- def install_system_packages(self):
- self.dnf_groupinstall(*self.group_packages)
- self.dnf_install(*self.packages)
-
- def install_browser_packages(self, mozconfig_builder, artifact_mode=False):
- # TODO: Figure out what not to install for artifact mode
- self.dnf_groupinstall(*self.browser_group_packages)
- self.dnf_install(*self.browser_packages)
-
- def install_browser_artifact_mode_packages(self, mozconfig_builder):
- self.install_browser_packages(mozconfig_builder, artifact_mode=True)
-
- def install_mobile_android_packages(self, mozconfig_builder, artifact_mode=False):
- # Install Android specific packages.
- self.dnf_install(*self.mobile_android_packages)
-
- super().install_mobile_android_packages(
- mozconfig_builder, artifact_mode=artifact_mode
- )
+ packages = [p for p in packages if p != "watchman"]
+ self.dnf_install(*packages)
def upgrade_mercurial(self, current):
if current is None:
self.dnf_install("mercurial")
else:
self.dnf_update("mercurial")
+
+ def dnf_install(self, *packages):
+ if which("dnf"):
+
+ def not_installed(package):
+ # We could check for "Error: No matching Packages to list", but
+ # checking `dnf`s exit code is sufficent.
+ # Ideally we'd invoke dnf with '--cacheonly', but there's:
+ # https://bugzilla.redhat.com/show_bug.cgi?id=2030255
+ is_installed = subprocess.run(
+ ["dnf", "list", "--installed", package],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+ if is_installed.returncode not in [0, 1]:
+ stdout = is_installed.stdout
+ raise Exception(
+ f'Failed to determine whether package "{package}" is installed: "{stdout}"'
+ )
+ return is_installed.returncode != 0
+
+ packages = list(filter(not_installed, packages))
+ if len(packages) == 0:
+ # avoid sudo prompt (support unattended re-bootstrapping)
+ return
+
+ command = ["dnf", "install"]
+ else:
+ command = ["yum", "install"]
+
+ if self.no_interactive:
+ command.append("-y")
+ command.extend(packages)
+
+ self.run_as_root(command)
+
+ def dnf_update(self, *packages):
+ if which("dnf"):
+ command = ["dnf", "update"]
+ else:
+ command = ["yum", "update"]
+
+ if self.no_interactive:
+ command.append("-y")
+ command.extend(packages)
+
+ self.run_as_root(command)
diff --git a/python/mozboot/mozboot/debian.py b/python/mozboot/mozboot/debian.py
--- a/python/mozboot/mozboot/debian.py
+++ b/python/mozboot/mozboot/debian.py
@@ -2,48 +2,13 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
+import sys
-from mozboot.base import BaseBootstrapper, MERCURIAL_INSTALL_PROMPT
+from mozboot.base import MERCURIAL_INSTALL_PROMPT, BaseBootstrapper
from mozboot.linux_common import LinuxBootstrapper
-import sys
-
class DebianBootstrapper(LinuxBootstrapper, BaseBootstrapper):
-
- # These are common packages for all Debian-derived distros (such as
- # Ubuntu).
- COMMON_PACKAGES = [
- "build-essential",
- "libpython3-dev",
- "m4",
- "unzip",
- "uuid",
- "zip",
- ]
-
- # These are common packages for building Firefox for Desktop
- # (browser) for all Debian-derived distros (such as Ubuntu).
- BROWSER_COMMON_PACKAGES = [
- "libasound2-dev",
- "libcurl4-openssl-dev",
- "libdbus-1-dev",
- "libdbus-glib-1-dev",
- "libdrm-dev",
- "libgtk-3-dev",
- "libpulse-dev",
- "libx11-xcb-dev",
- "libxt-dev",
- "xvfb",
- ]
-
- # These are common packages for building Firefox for Android
- # (mobile/android) for all Debian-derived distros (such as Ubuntu).
- MOBILE_ANDROID_COMMON_PACKAGES = [
- "libncurses5", # For native debugging in Android Studio
- ]
-
def __init__(self, distro, version, dist_id, codename, **kwargs):
BaseBootstrapper.__init__(self, **kwargs)
@@ -52,16 +17,6 @@ class DebianBootstrapper(LinuxBootstrapp
self.dist_id = dist_id
self.codename = codename
- self.packages = list(self.COMMON_PACKAGES)
-
- try:
- version_number = int(version)
- except ValueError:
- version_number = None
-
- if (version_number and (version_number >= 11)) or version == "unstable":
- self.packages += ["watchman"]
-
def suggest_install_distutils(self):
print(
"HINT: Try installing distutils with "
@@ -75,26 +30,15 @@ class DebianBootstrapper(LinuxBootstrapp
file=sys.stderr,
)
- def install_system_packages(self):
- self.apt_install(*self.packages)
-
- def install_browser_packages(self, mozconfig_builder, artifact_mode=False):
- # TODO: Figure out what not to install for artifact mode
- self.apt_install(*self.BROWSER_COMMON_PACKAGES)
-
- def install_browser_artifact_mode_packages(self, mozconfig_builder):
- self.install_browser_packages(mozconfig_builder, artifact_mode=True)
+ def install_packages(self, packages):
+ try:
+ if int(self.version) < 11:
+ # watchman is only available starting from Debian 11.
+ packages = [p for p in packages if p != "watchman"]
+ except ValueError:
+ pass
- def install_mobile_android_packages(self, mozconfig_builder, artifact_mode=False):
- # Multi-part process:
- # 1. System packages.
- # 2. Android SDK. Android NDK only if we are not in artifact mode. Android packages.
- self.apt_install(*self.MOBILE_ANDROID_COMMON_PACKAGES)
-
- # 2. Android pieces.
- super().install_mobile_android_packages(
- mozconfig_builder, artifact_mode=artifact_mode
- )
+ self.apt_install(*packages)
def _update_package_manager(self):
self.apt_update()
@@ -122,3 +66,18 @@ class DebianBootstrapper(LinuxBootstrapp
# pip.
assert res == 1
self.run_as_root(["pip3", "install", "--upgrade", "Mercurial"])
+
+ def apt_install(self, *packages):
+ command = ["apt-get", "install"]
+ if self.no_interactive:
+ command.append("-y")
+ command.extend(packages)
+
+ self.run_as_root(command)
+
+ def apt_update(self):
+ command = ["apt-get", "update"]
+ if self.no_interactive:
+ command.append("-y")
+
+ self.run_as_root(command)
diff --git a/python/mozboot/mozboot/freebsd.py b/python/mozboot/mozboot/freebsd.py
--- a/python/mozboot/mozboot/freebsd.py
+++ b/python/mozboot/mozboot/freebsd.py
@@ -2,7 +2,6 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
import sys
from mozboot.base import BaseBootstrapper
@@ -19,11 +18,11 @@ class FreeBSDBootstrapper(BaseBootstrapp
"gmake",
"gtar",
"m4",
+ "npm",
"pkgconf",
"py%d%d-sqlite3" % sys.version_info[0:2],
"rust",
"watchman",
- "zip",
]
self.browser_packages = [
@@ -56,10 +55,11 @@ class FreeBSDBootstrapper(BaseBootstrapp
def install_browser_packages(self, mozconfig_builder, artifact_mode=False):
# TODO: Figure out what not to install for artifact mode
packages = self.browser_packages.copy()
- if sys.platform.startswith("netbsd"):
- packages.extend(["brotli", "gtk3+", "libv4l"])
- else:
- packages.extend(["gtk3", "mesa-dri", "v4l_compat"])
+ if not artifact_mode:
+ if sys.platform.startswith("netbsd"):
+ packages.extend(["brotli", "gtk3+", "libv4l", "cbindgen"])
+ else:
+ packages.extend(["gtk3", "mesa-dri", "v4l_compat", "rust-cbindgen"])
self.pkg_install(*packages)
def install_browser_artifact_mode_packages(self, mozconfig_builder):
@@ -69,19 +69,5 @@ class FreeBSDBootstrapper(BaseBootstrapp
# TODO: we don't ship clang base static analysis for this platform
pass
- def ensure_stylo_packages(self):
- # Clang / llvm already installed as browser package
- if sys.platform.startswith("netbsd"):
- self.pkg_install("cbindgen")
- else:
- self.pkg_install("rust-cbindgen")
-
- def ensure_nasm_packages(self):
- # installed via install_browser_packages
- pass
-
- def ensure_node_packages(self):
- self.pkg_install("npm")
-
def upgrade_mercurial(self, current):
self.pkg_install("mercurial")
diff --git a/python/mozboot/mozboot/gentoo.py b/python/mozboot/mozboot/gentoo.py
--- a/python/mozboot/mozboot/gentoo.py
+++ b/python/mozboot/mozboot/gentoo.py
@@ -2,8 +2,6 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
from mozboot.base import BaseBootstrapper
from mozboot.linux_common import LinuxBootstrapper
@@ -15,32 +13,13 @@ class GentooBootstrapper(LinuxBootstrapp
self.version = version
self.dist_id = dist_id
- def install_system_packages(self):
- self.ensure_system_packages()
-
- def ensure_system_packages(self):
- self.run_as_root(
- ["emerge", "--noreplace", "--quiet", "app-arch/zip", "dev-util/watchman"]
- )
-
- def install_browser_packages(self, mozconfig_builder, artifact_mode=False):
- # TODO: Figure out what not to install for artifact mode
- self.run_as_root(
- [
- "emerge",
- "--oneshot",
- "--noreplace",
- "--quiet",
- "--newuse",
- "dev-libs/dbus-glib",
- "media-sound/pulseaudio",
- "x11-libs/gtk+:3",
- "x11-libs/libXt",
- ]
- )
-
- def install_browser_artifact_mode_packages(self, mozconfig_builder):
- self.install_browser_packages(mozconfig_builder, artifact_mode=True)
+ def install_packages(self, packages):
+ DISAMBIGUATE = {
+ "tar": "app-arch/tar",
+ }
+ # watchman is available but requires messing with USEs.
+ packages = [DISAMBIGUATE.get(p, p) for p in packages if p != "watchman"]
+ self.run_as_root(["emerge", "--noreplace"] + packages)
def _update_package_manager(self):
self.run_as_root(["emerge", "--sync"])
diff --git a/python/mozboot/mozboot/linux_common.py b/python/mozboot/mozboot/linux_common.py
--- a/python/mozboot/mozboot/linux_common.py
+++ b/python/mozboot/mozboot/linux_common.py
@@ -6,8 +6,6 @@
# needed to install Stylo and Node dependencies. This class must come before
# BaseBootstrapper in the inheritance list.
-from __future__ import absolute_import, print_function, unicode_literals
-
import platform
@@ -15,68 +13,6 @@ def is_non_x86_64():
return platform.machine() != "x86_64"
-class SccacheInstall(object):
- def __init__(self, **kwargs):
- pass
-
- def ensure_sccache_packages(self):
- self.install_toolchain_artifact("sccache")
-
-
-class FixStacksInstall(object):
- def __init__(self, **kwargs):
- pass
-
- def ensure_fix_stacks_packages(self):
- self.install_toolchain_artifact("fix-stacks")
-
-
-class StyloInstall(object):
- def __init__(self, **kwargs):
- pass
-
- def ensure_stylo_packages(self):
- if is_non_x86_64():
- print(
- "Cannot install bindgen clang and cbindgen packages from taskcluster.\n"
- "Please install these packages manually."
- )
- return
-
- self.install_toolchain_artifact("clang")
- self.install_toolchain_artifact("cbindgen")
-
-
-class NasmInstall(object):
- def __init__(self, **kwargs):
- pass
-
- def ensure_nasm_packages(self):
- if is_non_x86_64():
- print(
- "Cannot install nasm from taskcluster.\n"
- "Please install this package manually."
- )
- return
-
- self.install_toolchain_artifact("nasm")
-
-
-class NodeInstall(object):
- def __init__(self, **kwargs):
- pass
-
- def ensure_node_packages(self):
- if is_non_x86_64():
- print(
- "Cannot install node package from taskcluster.\n"
- "Please install this package manually."
- )
- return
-
- self.install_toolchain_artifact("node")
-
-
class ClangStaticAnalysisInstall(object):
def __init__(self, **kwargs):
pass
@@ -94,14 +30,6 @@ class ClangStaticAnalysisInstall(object)
self.install_toolchain_static_analysis(static_analysis.LINUX_CLANG_TIDY)
-class MinidumpStackwalkInstall(object):
- def __init__(self, **kwargs):
- pass
-
- def ensure_minidump_stackwalk_packages(self):
- self.install_toolchain_artifact("minidump-stackwalk")
-
-
class MobileAndroidBootstrapper(object):
def __init__(self, **kwargs):
pass
@@ -154,13 +82,32 @@ class MobileAndroidBootstrapper(object):
class LinuxBootstrapper(
ClangStaticAnalysisInstall,
- FixStacksInstall,
- MinidumpStackwalkInstall,
MobileAndroidBootstrapper,
- NasmInstall,
- NodeInstall,
- SccacheInstall,
- StyloInstall,
):
def __init__(self, **kwargs):
pass
+
+ def ensure_sccache_packages(self):
+ pass
+
+ def install_system_packages(self):
+ self.install_packages(
+ [
+ "bash",
+ "findutils", # contains xargs
+ "gzip",
+ "libxml2", # used by bootstrapped clang
+ "m4",
+ "make",
+ "perl",
+ "tar",
+ "unzip",
+ "watchman",
+ ]
+ )
+
+ def install_browser_packages(self, mozconfig_builder, artifact_mode=False):
+ pass
+
+ def install_browser_artifact_mode_packages(self, mozconfig_builder):
+ pass
diff --git a/python/mozboot/mozboot/mach_commands.py b/python/mozboot/mozboot/mach_commands.py
--- a/python/mozboot/mozboot/mach_commands.py
+++ b/python/mozboot/mozboot/mach_commands.py
@@ -2,13 +2,11 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
import errno
import sys
+from pathlib import Path
-from pathlib import Path
-from mach.decorators import CommandArgument, Command
+from mach.decorators import Command, CommandArgument
from mozboot.bootstrap import APPLICATIONS
@@ -71,8 +69,8 @@ def vcs_setup(command_context, update_on
"""
import mozboot.bootstrap as bootstrap
import mozversioncontrol
+ from mach.util import to_optional_path
from mozfile import which
- from mach.util import to_optional_path
repo = mozversioncontrol.get_repository_object(command_context._mach_context.topdir)
tool = "hg"
diff --git a/python/mozboot/mozboot/mozconfig.py b/python/mozboot/mozboot/mozconfig.py
--- a/python/mozboot/mozboot/mozconfig.py
+++ b/python/mozboot/mozboot/mozconfig.py
@@ -2,15 +2,11 @@
# 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/.
-from __future__ import absolute_import
-
import filecmp
import os
-
from pathlib import Path
from typing import Union
-
MOZ_MYCONFIG_ERROR = """
The MOZ_MYCONFIG environment variable to define the location of mozconfigs
is deprecated. If you wish to define the mozconfig path via an environment
diff --git a/python/mozboot/mozboot/mozillabuild.py b/python/mozboot/mozboot/mozillabuild.py
--- a/python/mozboot/mozboot/mozillabuild.py
+++ b/python/mozboot/mozboot/mozillabuild.py
@@ -2,8 +2,6 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
import ctypes
import os
import platform
@@ -231,35 +229,9 @@ class MozillaBuildBootstrapper(BaseBoots
def ensure_sccache_packages(self):
from mozboot import sccache
- self.install_toolchain_artifact("sccache")
self.install_toolchain_artifact(sccache.RUSTC_DIST_TOOLCHAIN, no_unpack=True)
self.install_toolchain_artifact(sccache.CLANG_DIST_TOOLCHAIN, no_unpack=True)
- def ensure_stylo_packages(self):
- # On-device artifact builds are supported; on-device desktop builds are not.
- if is_aarch64_host():
- raise Exception(
- "You should not be performing desktop builds on an "
- "AArch64 device. If you want to do artifact builds "
- "instead, please choose the appropriate artifact build "
- "option when beginning bootstrap."
- )
-
- self.install_toolchain_artifact("clang")
- self.install_toolchain_artifact("cbindgen")
-
- def ensure_nasm_packages(self):
- self.install_toolchain_artifact("nasm")
-
- def ensure_node_packages(self):
- self.install_toolchain_artifact("node")
-
- def ensure_fix_stacks_packages(self):
- self.install_toolchain_artifact("fix-stacks")
-
- def ensure_minidump_stackwalk_packages(self):
- self.install_toolchain_artifact("minidump-stackwalk")
-
def _update_package_manager(self):
pass
diff --git a/python/mozboot/mozboot/openbsd.py b/python/mozboot/mozboot/openbsd.py
--- a/python/mozboot/mozboot/openbsd.py
+++ b/python/mozboot/mozboot/openbsd.py
@@ -2,8 +2,6 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
from mozboot.base import BaseBootstrapper
@@ -11,9 +9,17 @@ class OpenBSDBootstrapper(BaseBootstrapp
def __init__(self, version, **kwargs):
BaseBootstrapper.__init__(self, **kwargs)
- self.packages = ["gmake", "gtar", "rust", "unzip", "zip"]
+ self.packages = ["gmake", "gtar", "rust", "unzip"]
- self.browser_packages = ["llvm", "nasm", "gtk+3", "dbus-glib", "pulseaudio"]
+ self.browser_packages = [
+ "llvm",
+ "cbindgen",
+ "nasm",
+ "node",
+ "gtk+3",
+ "dbus-glib",
+ "pulseaudio",
+ ]
def install_system_packages(self):
# we use -z because there's no other way to say "any autoconf-2.13"
@@ -30,14 +36,3 @@ class OpenBSDBootstrapper(BaseBootstrapp
def ensure_clang_static_analysis_package(self):
# TODO: we don't ship clang base static analysis for this platform
pass
-
- def ensure_stylo_packages(self):
- # Clang / llvm already installed as browser package
- self.run_as_root(["pkg_add", "cbindgen"])
-
- def ensure_nasm_packages(self):
- # installed via install_browser_packages
- pass
-
- def ensure_node_packages(self):
- self.run_as_root(["pkg_add", "node"])
diff --git a/python/mozboot/mozboot/opensuse.py b/python/mozboot/mozboot/opensuse.py
--- a/python/mozboot/mozboot/opensuse.py
+++ b/python/mozboot/mozboot/opensuse.py
@@ -2,107 +2,24 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
-from mozboot.base import BaseBootstrapper, MERCURIAL_INSTALL_PROMPT
+from mozboot.base import MERCURIAL_INSTALL_PROMPT, BaseBootstrapper
from mozboot.linux_common import LinuxBootstrapper
-import distro
-import subprocess
-
class OpenSUSEBootstrapper(LinuxBootstrapper, BaseBootstrapper):
"""openSUSE experimental bootstrapper."""
- SYSTEM_PACKAGES = [
- "libcurl-devel",
- "libpulse-devel",
- "rpmconf",
- "which",
- "unzip",
- ]
-
- BROWSER_PACKAGES = [
- "alsa-devel",
- "gcc-c++",
- "gtk3-devel",
- "dbus-1-glib-devel",
- "glibc-devel-static",
- "libstdc++-devel",
- "libXt-devel",
- "libproxy-devel",
- "libuuid-devel",
- "clang-devel",
- "patterns-gnome-devel_gnome",
- ]
-
- OPTIONAL_BROWSER_PACKAGES = [
- "gconf2-devel", # https://bugzilla.mozilla.org/show_bug.cgi?id=1779931
- ]
-
- BROWSER_GROUP_PACKAGES = ["devel_C_C++", "devel_gnome"]
-
- MOBILE_ANDROID_COMMON_PACKAGES = ["java-1_8_0-openjdk"]
-
def __init__(self, version, dist_id, **kwargs):
print("Using an experimental bootstrapper for openSUSE.")
BaseBootstrapper.__init__(self, **kwargs)
- def install_system_packages(self):
- self.zypper_install(*self.SYSTEM_PACKAGES)
-
- def install_browser_packages(self, mozconfig_builder, artifact_mode=False):
- # TODO: Figure out what not to install for artifact mode
- packages_to_install = self.BROWSER_PACKAGES.copy()
-
- for package in self.OPTIONAL_BROWSER_PACKAGES:
- if self.zypper_can_install(package):
- packages_to_install.append(package)
- else:
- print(
- f"WARNING! zypper cannot find a package for '{package}' for "
- f"{distro.name(True)}. It will not be automatically installed."
- )
-
- self.zypper_install(*packages_to_install)
-
- def install_browser_group_packages(self):
- self.ensure_browser_group_packages()
-
- def install_browser_artifact_mode_packages(self, mozconfig_builder):
- self.install_browser_packages(mozconfig_builder, artifact_mode=True)
-
- def ensure_clang_static_analysis_package(self):
- from mozboot import static_analysis
-
- self.install_toolchain_static_analysis(static_analysis.LINUX_CLANG_TIDY)
-
- def ensure_browser_group_packages(self, artifact_mode=False):
- # TODO: Figure out what not to install for artifact mode
- self.zypper_patterninstall(*self.BROWSER_GROUP_PACKAGES)
-
- def install_mobile_android_packages(self, mozconfig_builder, artifact_mode=False):
- # Multi-part process:
- # 1. System packages.
- # 2. Android SDK. Android NDK only if we are not in artifact mode. Android packages.
-
- # 1. This is hard to believe, but the Android SDK binaries are 32-bit
- # and that conflicts with 64-bit Arch installations out of the box. The
- # solution is to add the multilibs repository; unfortunately, this
- # requires manual intervention.
- try:
- self.zypper_install(*self.MOBILE_ANDROID_COMMON_PACKAGES)
- except Exception as e:
- print(
- "Failed to install all packages. The Android developer "
- "toolchain requires 32 bit binaries be enabled"
- )
- raise e
-
- # 2. Android pieces.
- super().install_mobile_android_packages(
- mozconfig_builder, artifact_mode=artifact_mode
- )
+ def install_packages(self, packages):
+ ALTERNATIVE_NAMES = {
+ "libxml2": "libxml2-2",
+ }
+ # watchman is not available
+ packages = [ALTERNATIVE_NAMES.get(p, p) for p in packages if p != "watchman"]
+ self.zypper_install(*packages)
def _update_package_manager(self):
self.zypper_update()
@@ -142,14 +59,5 @@ class OpenSUSEBootstrapper(LinuxBootstra
def zypper_install(self, *packages):
self.zypper("install", *packages)
- def zypper_can_install(self, package):
- return (
- subprocess.call(["zypper", "search", package], stdout=subprocess.DEVNULL)
- == 0
- )
-
def zypper_update(self, *packages):
self.zypper("update", *packages)
-
- def zypper_patterninstall(self, *packages):
- self.zypper("install", "-t", "pattern", *packages)
diff --git a/python/mozboot/mozboot/osx.py b/python/mozboot/mozboot/osx.py
--- a/python/mozboot/mozboot/osx.py
+++ b/python/mozboot/mozboot/osx.py
@@ -2,8 +2,6 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
import platform
import subprocess
import sys
@@ -14,11 +12,10 @@ try:
except ImportError:
from urllib.request import urlopen
-from packaging.version import Version
-
+from mach.util import to_optional_path, to_optional_str
from mozboot.base import BaseBootstrapper
from mozfile import which
-from mach.util import to_optional_path, to_optional_str
+from packaging.version import Version
HOMEBREW_BOOTSTRAP = (
"https://raw.githubusercontent.com/Homebrew/install/master/install.sh"
@@ -166,21 +163,9 @@ class OSXBootstrapperLight(OSXAndroidBoo
def install_browser_artifact_mode_packages(self, mozconfig_builder):
pass
- def ensure_node_packages(self):
- pass
-
- def ensure_stylo_packages(self):
- pass
-
def ensure_clang_static_analysis_package(self):
pass
- def ensure_nasm_packages(self):
- pass
-
- def ensure_minidump_stackwalk_packages(self):
- self.install_toolchain_artifact("minidump-stackwalk")
-
class OSXBootstrapper(OSXAndroidBootstrapper, BaseBootstrapper):
def __init__(self, version, **kwargs):
@@ -299,26 +284,9 @@ class OSXBootstrapper(OSXAndroidBootstra
def ensure_sccache_packages(self):
from mozboot import sccache
- self.install_toolchain_artifact("sccache")
self.install_toolchain_artifact(sccache.RUSTC_DIST_TOOLCHAIN, no_unpack=True)
self.install_toolchain_artifact(sccache.CLANG_DIST_TOOLCHAIN, no_unpack=True)
- def ensure_fix_stacks_packages(self):
- self.install_toolchain_artifact("fix-stacks")
-
- def ensure_stylo_packages(self):
- self.install_toolchain_artifact("clang")
- self.install_toolchain_artifact("cbindgen")
-
- def ensure_nasm_packages(self):
- self.install_toolchain_artifact("nasm")
-
- def ensure_node_packages(self):
- self.install_toolchain_artifact("node")
-
- def ensure_minidump_stackwalk_packages(self):
- self.install_toolchain_artifact("minidump-stackwalk")
-
def install_homebrew(self):
print(BREW_INSTALL)
bootstrap = urlopen(url=HOMEBREW_BOOTSTRAP, timeout=20).read()
diff --git a/python/mozboot/mozboot/rust.py b/python/mozboot/mozboot/rust.py
--- a/python/mozboot/mozboot/rust.py
+++ b/python/mozboot/mozboot/rust.py
@@ -2,16 +2,11 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
import platform as platform_mod
import sys
-
# Base url for pulling the rustup installer.
-# Use the no-CNAME host for compatibilty with Python 2.7
-# which doesn't support SNI.
-RUSTUP_URL_BASE = "https://static-rust-lang-org.s3.amazonaws.com/rustup"
+RUSTUP_URL_BASE = "https://static.rust-lang.org/rustup"
# Pull this to get the lastest stable version number.
RUSTUP_MANIFEST = RUSTUP_URL_BASE + "/release-stable.toml"
@@ -123,6 +118,7 @@ def rustup_latest_version():
def http_download_and_hash(url):
import hashlib
+
import requests
h = hashlib.sha256()
diff --git a/python/mozboot/mozboot/sccache.py b/python/mozboot/mozboot/sccache.py
--- a/python/mozboot/mozboot/sccache.py
+++ b/python/mozboot/mozboot/sccache.py
@@ -2,8 +2,6 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
# sccache-dist currently expects clients to provide toolchains when
# distributing from macOS or Windows, so we download linux binaries capable
# of cross-compiling for these cases.
diff --git a/python/mozboot/mozboot/solus.py b/python/mozboot/mozboot/solus.py
--- a/python/mozboot/mozboot/solus.py
+++ b/python/mozboot/mozboot/solus.py
@@ -2,73 +2,19 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
-import sys
-import subprocess
-
from mozboot.base import BaseBootstrapper
from mozboot.linux_common import LinuxBootstrapper
-# NOTE: This script is intended to be run with a vanilla Python install. We
-# have to rely on the standard library instead of Python 2+3 helpers like
-# the six module.
-if sys.version_info < (3,):
- input = raw_input # noqa
-
class SolusBootstrapper(LinuxBootstrapper, BaseBootstrapper):
"""Solus experimental bootstrapper."""
- SYSTEM_PACKAGES = ["unzip", "zip"]
- SYSTEM_COMPONENTS = ["system.devel"]
-
- BROWSER_PACKAGES = [
- "alsa-lib",
- "dbus",
- "libgtk-3",
- "libevent",
- "libvpx",
- "libxt",
- "libstartup-notification",
- "gst-plugins-base",
- "gst-plugins-good",
- "pulseaudio",
- "xorg-server-xvfb",
- ]
-
- MOBILE_ANDROID_COMMON_PACKAGES = [
- # See comment about 32 bit binaries and multilib below.
- "ncurses-32bit",
- "readline-32bit",
- "zlib-32bit",
- ]
-
def __init__(self, version, dist_id, **kwargs):
print("Using an experimental bootstrapper for Solus.")
BaseBootstrapper.__init__(self, **kwargs)
- def install_system_packages(self):
- self.package_install(*self.SYSTEM_PACKAGES)
- self.component_install(*self.SYSTEM_COMPONENTS)
-
- def install_browser_packages(self, mozconfig_builder, artifact_mode=False):
- self.package_install(*self.BROWSER_PACKAGES)
-
- def install_browser_artifact_mode_packages(self, mozconfig_builder):
- self.install_browser_packages(mozconfig_builder, artifact_mode=True)
-
- def install_mobile_android_packages(self, mozconfig_builder, artifact_mode=False):
- try:
- self.package_install(*self.MOBILE_ANDROID_COMMON_PACKAGES)
- except Exception as e:
- print("Failed to install all packages!")
- raise e
-
- # 2. Android pieces.
- super().install_mobile_android_packages(
- mozconfig_builder, artifact_mode=artifact_mode
- )
+ def install_packages(self, packages):
+ self.package_install(*packages)
def _update_package_manager(self):
pass
@@ -84,15 +30,3 @@ class SolusBootstrapper(LinuxBootstrappe
command.extend(packages)
self.run_as_root(command)
-
- def component_install(self, *components):
- command = ["eopkg", "install", "-c"]
- if self.no_interactive:
- command.append("--yes-all")
-
- command.extend(components)
-
- self.run_as_root(command)
-
- def run(self, command, env=None):
- subprocess.check_call(command, stdin=sys.stdin, env=env)
diff --git a/python/mozboot/mozboot/static_analysis.py b/python/mozboot/mozboot/static_analysis.py
--- a/python/mozboot/mozboot/static_analysis.py
+++ b/python/mozboot/mozboot/static_analysis.py
@@ -2,8 +2,6 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
WINDOWS_CLANG_TIDY = "win64-clang-tidy"
LINUX_CLANG_TIDY = "linux64-clang-tidy"
MACOS_CLANG_TIDY = "macosx64-clang-tidy"
diff --git a/python/mozboot/mozboot/util.py b/python/mozboot/mozboot/util.py
--- a/python/mozboot/mozboot/util.py
+++ b/python/mozboot/mozboot/util.py
@@ -2,27 +2,14 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
import hashlib
import os
-import sys
-
from pathlib import Path
+from urllib.request import urlopen
from mach.site import PythonVirtualenv
from mach.util import get_state_dir
-# NOTE: This script is intended to be run with a vanilla Python install. We
-# have to rely on the standard library instead of Python 2+3 helpers like
-# the six module.
-if sys.version_info < (3,):
- from urllib2 import urlopen
-
- input = raw_input # noqa
-else:
- from urllib.request import urlopen
-
MINIMUM_RUST_VERSION = "1.63.0"
diff --git a/python/mozboot/mozboot/void.py b/python/mozboot/mozboot/void.py
--- a/python/mozboot/mozboot/void.py
+++ b/python/mozboot/mozboot/void.py
@@ -2,31 +2,11 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
-import os
-import subprocess
-import sys
-
from mozboot.base import BaseBootstrapper
from mozboot.linux_common import LinuxBootstrapper
class VoidBootstrapper(LinuxBootstrapper, BaseBootstrapper):
-
- PACKAGES = ["clang", "make", "mercurial", "watchman", "unzip", "zip"]
-
- BROWSER_PACKAGES = [
- "dbus-devel",
- "dbus-glib-devel",
- "gtk+3-devel",
- "pulseaudio",
- "pulseaudio-devel",
- "libcurl-devel",
- "libxcb-devel",
- "libXt-devel",
- ]
-
def __init__(self, version, dist_id, **kwargs):
BaseBootstrapper.__init__(self, **kwargs)
@@ -34,18 +14,10 @@ class VoidBootstrapper(LinuxBootstrapper
self.version = version
self.dist_id = dist_id
- self.packages = self.PACKAGES
- self.browser_packages = self.BROWSER_PACKAGES
-
def run_as_root(self, command):
# VoidLinux doesn't support users sudo'ing most commands by default because of the group
# configuration.
- if os.geteuid() != 0:
- command = ["su", "root", "-c", " ".join(command)]
-
- print("Executing as root:", subprocess.list2cmdline(command))
-
- subprocess.check_call(command, stdin=sys.stdin)
+ super().run_as_root(command, may_use_sudo=False)
def xbps_install(self, *packages):
command = ["xbps-install"]
@@ -62,14 +34,8 @@ class VoidBootstrapper(LinuxBootstrapper
self.run_as_root(command)
- def install_system_packages(self):
- self.xbps_install(*self.packages)
-
- def install_browser_packages(self, mozconfig_builder, artifact_mode=False):
- self.xbps_install(*self.browser_packages)
-
- def install_browser_artifact_mode_packages(self, mozconfig_builder):
- self.install_browser_packages(mozconfig_builder, artifact_mode=True)
+ def install_packages(self, packages):
+ self.xbps_install(*packages)
def _update_package_manager(self):
self.xbps_update()
diff --git a/python/mozboot/mozboot/windows.py b/python/mozboot/mozboot/windows.py
--- a/python/mozboot/mozboot/windows.py
+++ b/python/mozboot/mozboot/windows.py
@@ -2,12 +2,10 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
import ctypes
import os
+import subprocess
import sys
-import subprocess
from mozboot.base import BaseBootstrapper
from mozfile import which
@@ -50,7 +48,6 @@ class WindowsBootstrapper(BaseBootstrapp
"patchutils",
"diffutils",
"tar",
- "zip",
"unzip",
"mingw-w64-x86_64-toolchain", # TODO: Remove when Mercurial is installable from a wheel.
"mingw-w64-i686-toolchain",
@@ -106,25 +103,6 @@ class WindowsBootstrapper(BaseBootstrapp
self.install_toolchain_static_analysis(static_analysis.WINDOWS_CLANG_TIDY)
- def ensure_stylo_packages(self):
- # On-device artifact builds are supported; on-device desktop builds are not.
- if is_aarch64_host():
- raise Exception(
- "You should not be performing desktop builds on an "
- "AArch64 device. If you want to do artifact builds "
- "instead, please choose the appropriate artifact build "
- "option when beginning bootstrap."
- )
-
- self.install_toolchain_artifact("clang")
- self.install_toolchain_artifact("cbindgen")
-
- def ensure_nasm_packages(self):
- self.install_toolchain_artifact("nasm")
-
- def ensure_node_packages(self):
- self.install_toolchain_artifact("node")
-
def _update_package_manager(self):
self.pacman_update()
diff --git a/python/mozbuild/mozbuild/action/langpack_manifest.py b/python/mozbuild/mozbuild/action/langpack_manifest.py
--- a/python/mozbuild/mozbuild/action/langpack_manifest.py
+++ b/python/mozbuild/mozbuild/action/langpack_manifest.py
@@ -4,28 +4,30 @@
###
# This script generates a web manifest JSON file based on the xpi-stage
-# directory structure. It extracts the data from defines.inc files from
-# the locale directory, chrome registry entries and other information
-# necessary to produce the complete manifest file for a language pack.
+# directory structure. It extracts data necessary to produce the complete
+# manifest file for a language pack:
+# from the `langpack-manifest.ftl` file in the locale directory;
+# from chrome registry entries;
+# and from other information in the `xpi-stage` directory.
###
+
from __future__ import absolute_import, print_function, unicode_literals
import argparse
-import sys
-import os
-import json
+import datetime
import io
-import datetime
-import requests
-import mozversioncontrol
+import json
+import logging
+import os
+import sys
+
+import fluent.syntax.ast as FTL
import mozpack.path as mozpath
-from mozpack.chrome.manifest import (
- Manifest,
- ManifestLocale,
- parse_manifest,
-)
+import mozversioncontrol
+import requests
+from fluent.syntax.parser import FluentParser
from mozbuild.configure.util import Version
-from mozbuild.preprocessor import Preprocessor
+from mozpack.chrome.manifest import Manifest, ManifestLocale, parse_manifest
def write_file(path, content):
@@ -112,53 +114,89 @@ def get_timestamp_for_locale(path):
###
-# Parses multiple defines files into a single key-value pair object.
+# Parses an FTL file into a key-value pair object.
+# Does not support attributes, terms, variables, functions or selectors;
+# only messages with values consisting of text elements and literals.
#
# Args:
-# paths (str) - a comma separated list of paths to defines files
+# path (str) - a path to an FTL file
#
# Returns:
-# (dict) - a key-value dict with defines
+# (dict) - A mapping of message keys to formatted string values.
+# Empty if the file at `path` was not found.
#
# Example:
-# res = parse_defines('./toolkit/defines.inc,./browser/defines.inc')
+# res = parse_flat_ftl('./browser/langpack-metadata.ftl')
# res == {
-# 'MOZ_LANG_TITLE': 'Polski',
-# 'MOZ_LANGPACK_CREATOR': 'Aviary.pl',
-# 'MOZ_LANGPACK_CONTRIBUTORS': 'Marek Stepien, Marek Wawoczny'
+# 'langpack-title': 'Polski',
+# 'langpack-creator': 'mozilla.org',
+# 'langpack-contributors': 'Joe Solon, Suzy Solon'
# }
###
-def parse_defines(paths):
- pp = Preprocessor()
- for path in paths:
- pp.do_include(path)
+def parse_flat_ftl(path):
+ parser = FluentParser(with_spans=False)
+ try:
+ with open(path, encoding="utf-8") as file:
+ res = parser.parse(file.read())
+ except FileNotFoundError as err:
+ logging.warning(err)
+ return {}
- return pp.context
+ result = {}
+ for entry in res.body:
+ if isinstance(entry, FTL.Message) and isinstance(entry.value, FTL.Pattern):
+ flat = ""
+ for elem in entry.value.elements:
+ if isinstance(elem, FTL.TextElement):
+ flat += elem.value
+ elif isinstance(elem.expression, FTL.Literal):
+ flat += elem.expression.parse()["value"]
+ else:
+ name = type(elem.expression).__name__
+ raise Exception(f"Unsupported {name} for {entry.id.name} in {path}")
+ result[entry.id.name] = flat.strip()
+ return result
-###
-# Converts the list of contributors from the old RDF based list
-# of entries, into a comma separated list.
+##
+# Generates the title and description for the langpack.
+#
+# Uses data stored in a JSON file next to this source,
+# which is expected to have the following format:
+# Record<string, { native: string, english?: string }>
+#
+# If an English name is given and is different from the native one,
+# it will be included parenthetically in the title.
+#
+# NOTE: If you're updating the native locale names,
+# you should also update the data in
+# toolkit/components/mozintl/mozIntl.sys.mjs.
#
# Args:
-# str (str) - a string with an RDF list of contributors entries
+# app (str) - Application name
+# locale (str) - Locale identifier
#
# Returns:
-# (str) - a comma separated list of contributors
+# (str, str) - Tuple of title and description
#
-# Example:
-# s = convert_contributors('
-# <em:contributor>Marek Wawoczny</em:contributor>
-# <em:contributor>Marek Stepien</em:contributor>
-# ')
-# s == 'Marek Wawoczny, Marek Stepien'
###
-def convert_contributors(str):
- str = str.replace("<em:contributor>", "")
- tokens = str.split("</em:contributor>")
- tokens = map(lambda t: t.strip(), tokens)
- tokens = filter(lambda t: t != "", tokens)
- return ", ".join(tokens)
+def get_title_and_description(app, locale):
+ dir = os.path.dirname(__file__)
+ with open(os.path.join(dir, "langpack_localeNames.json"), encoding="utf-8") as nf:
+ names = json.load(nf)
+ if locale in names:
+ data = names[locale]
+ native = data["native"]
+ english = data["english"] if "english" in data else native
+ titleName = f"{native} ({english})" if english != native else native
+ descName = f"{native} ({locale})"
+ else:
+ titleName = locale
+ descName = locale
+
+ title = f"Language Pack: {titleName}"
+ description = f"{app} Language Pack for {descName}"
+ return title, description
###
@@ -166,26 +204,25 @@ def convert_contributors(str):
# and optionally adding the list of contributors, if provided.
#
# Args:
-# author (str) - a string with the name of the author
-# contributors (str) - RDF based list of contributors from a chrome manifest
+# ftl (dict) - a key-value mapping of locale-specific strings
#
# Returns:
# (str) - a string to be placed in the author field of the manifest.json
#
# Example:
-# s = build_author_string(
-# 'Aviary.pl',
-# '
-# <em:contributor>Marek Wawoczny</em:contributor>
-# <em:contributor>Marek Stepien</em:contributor>
-# ')
-# s == 'Aviary.pl (contributors: Marek Wawoczny, Marek Stepien)'
+# s = get_author({
+# 'langpack-creator': 'mozilla.org',
+# 'langpack-contributors': 'Joe Solon, Suzy Solon'
+# })
+# s == 'mozilla.org (contributors: Joe Solon, Suzy Solon)'
###
-def build_author_string(author, contributors):
- contrib = convert_contributors(contributors)
- if len(contrib) == 0:
+def get_author(ftl):
+ author = ftl["langpack-creator"] if "langpack-creator" in ftl else "mozilla.org"
+ contrib = ftl["langpack-contributors"] if "langpack-contributors" in ftl else ""
+ if contrib:
+ return f"{author} (contributors: {contrib})"
+ else:
return author
- return "{0} (contributors: {1})".format(author, contrib)
##
@@ -333,7 +370,7 @@ def get_version_maybe_buildid(version):
# resources are for
# app_name (str) - The name of the application the language
# resources are for
-# defines (dict) - A dictionary of defines entries
+# ftl (dict) - A dictionary of locale-specific strings
# chrome_entries (dict) - A dictionary of chrome registry entries
#
# Returns:
@@ -346,7 +383,7 @@ def get_version_maybe_buildid(version):
# '57.0.*',
# 'Firefox',
# '/var/vcs/l10n-central',
-# {'MOZ_LANG_TITLE': 'Polski'},
+# {'langpack-title': 'Polski'},
# chrome_entries
# )
# manifest == {
@@ -392,18 +429,13 @@ def create_webmanifest(
app_name,
l10n_basedir,
langpack_eid,
- defines,
+ ftl,
chrome_entries,
):
locales = list(map(lambda loc: loc.strip(), locstr.split(",")))
main_locale = locales[0]
-
- author = build_author_string(
- defines["MOZ_LANGPACK_CREATOR"],
- defines["MOZ_LANGPACK_CONTRIBUTORS"]
- if "MOZ_LANGPACK_CONTRIBUTORS" in defines
- else "",
- )
+ title, description = get_title_and_description(app_name, main_locale)
+ author = get_author(ftl)
manifest = {
"langpack_id": main_locale,
@@ -415,8 +447,8 @@ def create_webmanifest(
"strict_max_version": max_app_ver,
}
},
- "name": "{0} Language Pack".format(defines["MOZ_LANG_TITLE"]),
- "description": "Language pack for {0} for {1}".format(app_name, main_locale),
+ "name": title,
+ "description": description,
"version": get_version_maybe_buildid(version),
"languages": {},
"sources": {"browser": {"base_path": "browser/"}},
@@ -466,10 +498,8 @@ def main(args):
"--langpack-eid", help="Language pack id to use for this locale"
)
parser.add_argument(
- "--defines",
- default=[],
- nargs="+",
- help="List of defines files to load data from",
+ "--metadata",
+ help="FTL file defining langpack metadata",
)
parser.add_argument("--input", help="Langpack directory.")
@@ -480,7 +510,7 @@ def main(args):
os.path.join(args.input, "chrome.manifest"), args.input, chrome_entries
)
- defines = parse_defines(args.defines)
+ ftl = parse_flat_ftl(args.metadata)
# Mangle the app version to set min version (remove patch level)
min_app_version = args.app_version
@@ -502,7 +532,7 @@ def main(args):
args.app_name,
args.l10n_basedir,
args.langpack_eid,
- defines,
+ ftl,
chrome_entries,
)
write_file(os.path.join(args.input, "manifest.json"), res)
diff --git a/python/mozbuild/mozbuild/action/make_dmg.py b/python/mozbuild/mozbuild/action/make_dmg.py
--- a/python/mozbuild/mozbuild/action/make_dmg.py
+++ b/python/mozbuild/mozbuild/action/make_dmg.py
@@ -2,13 +2,16 @@
# 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/.
-from __future__ import absolute_import, print_function
+import argparse
+import platform
+import sys
+from pathlib import Path
+from mozbuild.bootstrap import bootstrap_toolchain
from mozbuild.repackaging.application_ini import get_application_ini_value
from mozpack import dmg
-import argparse
-import sys
+is_linux = platform.system() == "Linux"
def main(args):
@@ -41,7 +44,20 @@ def main(args):
options.inpath, "App", "CodeName", fallback="Name"
)
- dmg.create_dmg(options.inpath, options.dmgfile, volume_name, extra_files)
+ # Resolve required tools
+ dmg_tool = bootstrap_toolchain("dmg/dmg")
+ hfs_tool = bootstrap_toolchain("dmg/hfsplus")
+ mkfshfs_tool = bootstrap_toolchain("hfsplus/newfs_hfs")
+
+ dmg.create_dmg(
+ source_directory=Path(options.inpath),
+ output_dmg=Path(options.dmgfile),
+ volume_name=volume_name,
+ extra_files=extra_files,
+ dmg_tool=dmg_tool,
+ hfs_tool=hfs_tool,
+ mkfshfs_tool=mkfshfs_tool,
+ )
return 0
diff --git a/python/mozbuild/mozbuild/action/unpack_dmg.py b/python/mozbuild/mozbuild/action/unpack_dmg.py
--- a/python/mozbuild/mozbuild/action/unpack_dmg.py
+++ b/python/mozbuild/mozbuild/action/unpack_dmg.py
@@ -2,12 +2,18 @@
# 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/.
-from __future__ import absolute_import, print_function
+import argparse
+import sys
+from pathlib import Path
+from mozbuild.bootstrap import bootstrap_toolchain
from mozpack import dmg
-import argparse
-import sys
+
+def _path_or_none(input: str):
+ if not input:
+ return None
+ return Path(input)
def main(args):
@@ -26,12 +32,17 @@ def main(args):
options = parser.parse_args(args)
+ dmg_tool = bootstrap_toolchain("dmg/dmg")
+ hfs_tool = bootstrap_toolchain("dmg/hfsplus")
+
dmg.extract_dmg(
- dmgfile=options.dmgfile,
- output=options.outpath,
- dsstore=options.dsstore,
- background=options.background,
- icon=options.icon,
+ dmgfile=Path(options.dmgfile),
+ output=Path(options.outpath),
+ dmg_tool=Path(dmg_tool),
+ hfs_tool=Path(hfs_tool),
+ dsstore=_path_or_none(options.dsstore),
+ background=_path_or_none(options.background),
+ icon=_path_or_none(options.icon),
)
return 0
diff --git a/python/mozbuild/mozbuild/artifacts.py b/python/mozbuild/mozbuild/artifacts.py
--- a/python/mozbuild/mozbuild/artifacts.py
+++ b/python/mozbuild/mozbuild/artifacts.py
@@ -129,7 +129,6 @@ class ArtifactJob(object):
("bin/http3server", ("bin", "bin")),
("bin/plugins/gmp-*/*/*", ("bin/plugins", "bin")),
("bin/plugins/*", ("bin/plugins", "plugins")),
- ("bin/components/*.xpt", ("bin/components", "bin/components")),
}
# We can tell our input is a test archive by this suffix, which happens to
@@ -137,6 +136,32 @@ class ArtifactJob(object):
_test_zip_archive_suffix = ".common.tests.zip"
_test_tar_archive_suffix = ".common.tests.tar.gz"
+ # A map of extra archives to fetch and unpack. An extra archive might
+ # include optional build output to incorporate into the local artifact
+ # build. Test archives and crashreporter symbols could be extra archives
+ # but they require special handling; this mechanism is generic and intended
+ # only for the simplest cases.
+ #
+ # Each suffix key matches a candidate archive (i.e., an artifact produced by
+ # an upstream build). Each value is itself a dictionary that must contain
+ # the following keys:
+ #
+ # - `description`: a purely informational string description.
+ # - `src_prefix`: entry names in the archive with leading `src_prefix` will
+ # have the prefix stripped.
+ # - `dest_prefix`: entry names in the archive will have `dest_prefix`
+ # prepended.
+ #
+ # The entries in the archive, suitably renamed, will be extracted into `dist`.
+ _extra_archives = {
+ ".xpt_artifacts.zip": {
+ "description": "XPT Artifacts",
+ "src_prefix": "",
+ "dest_prefix": "xpt_artifacts",
+ },
+ }
+ _extra_archive_suffixes = tuple(sorted(_extra_archives.keys()))
+
def __init__(
self,
log=None,
@@ -190,6 +215,8 @@ class ArtifactJob(object):
self._symbols_archive_suffix
):
yield name
+ elif name.endswith(ArtifactJob._extra_archive_suffixes):
+ yield name
else:
self.log(
logging.DEBUG,
@@ -222,6 +249,8 @@ class ArtifactJob(object):
self._symbols_archive_suffix
):
return self.process_symbols_archive(filename, processed_filename)
+ if filename.endswith(ArtifactJob._extra_archive_suffixes):
+ return self.process_extra_archive(filename, processed_filename)
return self.process_package_artifact(filename, processed_filename)
def process_package_artifact(self, filename, processed_filename):
@@ -373,6 +402,43 @@ class ArtifactJob(object):
)
writer.add(destpath.encode("utf-8"), entry)
+ def process_extra_archive(self, filename, processed_filename):
+ for suffix, extra_archive in ArtifactJob._extra_archives.items():
+ if filename.endswith(suffix):
+ self.log(
+ logging.INFO,
+ "artifact",
+ {"filename": filename, "description": extra_archive["description"]},
+ '"{filename}" is a recognized extra archive ({description})',
+ )
+ break
+ else:
+ raise ValueError('"{}" is not a recognized extra archive!'.format(filename))
+
+ src_prefix = extra_archive["src_prefix"]
+ dest_prefix = extra_archive["dest_prefix"]
+
+ with self.get_writer(file=processed_filename, compress_level=5) as writer:
+ for filename, entry in self.iter_artifact_archive(filename):
+ if not filename.startswith(src_prefix):
+ self.log(
+ logging.DEBUG,
+ "artifact",
+ {"filename": filename, "src_prefix": src_prefix},
+ "Skipping extra archive item {filename} "
+ "that does not start with {src_prefix}",
+ )
+ continue
+ destpath = mozpath.relpath(filename, src_prefix)
+ destpath = mozpath.join(dest_prefix, destpath)
+ self.log(
+ logging.INFO,
+ "artifact",
+ {"destpath": destpath},
+ "Adding {destpath} to processed archive",
+ )
+ writer.add(destpath.encode("utf-8"), entry)
+
def iter_artifact_archive(self, filename):
if filename.endswith(".zip"):
reader = JarReader(filename)
@@ -1392,7 +1458,15 @@ https://firefox-source-docs.mozilla.org/
{"processed_filename": processed_filename},
"Writing processed {processed_filename}",
)
- self._artifact_job.process_artifact(filename, processed_filename)
+ try:
+ self._artifact_job.process_artifact(filename, processed_filename)
+ except Exception as e:
+ # Delete the partial output of failed processing.
+ try:
+ os.remove(processed_filename)
+ except FileNotFoundError:
+ pass
+ raise e
self._artifact_cache._persist_limit.register_file(processed_filename)
diff --git a/python/mozbuild/mozbuild/backend/base.py b/python/mozbuild/mozbuild/backend/base.py
--- a/python/mozbuild/mozbuild/backend/base.py
+++ b/python/mozbuild/mozbuild/backend/base.py
@@ -215,8 +215,8 @@ class BuildBackend(LoggingMixin):
invalidate the XUL cache (which includes some JS) at application
startup-time. The application checks for .purgecaches in the
application directory, which varies according to
- --enable-application. There's a further wrinkle on macOS, where
- the real application directory is part of a Cocoa bundle
+ --enable-application/--enable-project. There's a further wrinkle on
+ macOS, where the real application directory is part of a Cocoa bundle
produced from the regular application directory by the build
system. In this case, we write to both locations, since the
build system recreates the Cocoa bundle from the contents of the
diff --git a/python/mozbuild/mozbuild/backend/recursivemake.py b/python/mozbuild/mozbuild/backend/recursivemake.py
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -8,26 +8,24 @@ import io
import logging
import os
import re
-import six
-
from collections import defaultdict, namedtuple
from itertools import chain
from operator import itemgetter
-from six import StringIO
-from mozpack.manifests import InstallManifest
import mozpack.path as mozpath
-
+import six
from mozbuild import frontend
from mozbuild.frontend.context import (
AbsolutePath,
+ ObjDirPath,
Path,
RenamedSourcePath,
SourcePath,
- ObjDirPath,
)
-from .common import CommonBackend
-from .make import MakeBackend
+from mozbuild.shellutil import quote as shell_quote
+from mozpack.manifests import InstallManifest
+from six import StringIO
+
from ..frontend.data import (
BaseLibrary,
BaseProgram,
@@ -46,6 +44,7 @@ from ..frontend.data import (
HostLibrary,
HostProgram,
HostRustProgram,
+ HostSharedLibrary,
HostSimpleProgram,
HostSources,
InstallationTarget,
@@ -58,7 +57,6 @@ from ..frontend.data import (
ObjdirPreprocessedFiles,
PerSourceFlag,
Program,
- HostSharedLibrary,
RustProgram,
RustTests,
SandboxedWasmLibrary,
@@ -71,9 +69,10 @@ from ..frontend.data import (
WasmSources,
XPIDLModule,
)
-from ..util import ensureParentDir, FileAvoidWrite, OrderedDefaultDict, pairwise
from ..makeutil import Makefile
-from mozbuild.shellutil import quote as shell_quote
+from ..util import FileAvoidWrite, OrderedDefaultDict, ensureParentDir, pairwise
+from .common import CommonBackend
+from .make import MakeBackend
# To protect against accidentally adding logic to Makefiles that belong in moz.build,
# we check if moz.build-like variables are defined in Makefiles. If they are, we throw
@@ -367,7 +366,6 @@ class RecursiveMakeBackend(MakeBackend):
self._traversal = RecursiveMakeTraversal()
self._compile_graph = OrderedDefaultDict(set)
self._rust_targets = set()
- self._rust_lib_targets = set()
self._gkrust_target = None
self._pre_compile = set()
@@ -611,7 +609,6 @@ class RecursiveMakeBackend(MakeBackend):
build_target = self._build_target_for_obj(obj)
self._compile_graph[build_target]
self._rust_targets.add(build_target)
- self._rust_lib_targets.add(build_target)
if obj.is_gkrust:
self._gkrust_target = build_target
@@ -774,7 +771,6 @@ class RecursiveMakeBackend(MakeBackend):
# on other directories in the tree, so putting them first here will
# start them earlier in the build.
rust_roots = sorted(r for r in roots if r in self._rust_targets)
- rust_libs = sorted(r for r in roots if r in self._rust_lib_targets)
if category == "compile" and rust_roots:
rust_rule = root_deps_mk.create_rule(["recurse_rust"])
rust_rule.add_dependencies(rust_roots)
@@ -786,7 +782,7 @@ class RecursiveMakeBackend(MakeBackend):
# builds.
for prior_target, target in pairwise(
sorted(
- [t for t in rust_libs], key=lambda t: t != self._gkrust_target
+ [t for t in rust_roots], key=lambda t: t != self._gkrust_target
)
):
r = root_deps_mk.create_rule([target])
@@ -1201,8 +1197,9 @@ class RecursiveMakeBackend(MakeBackend):
self, obj, backend_file, target_variable, target_cargo_variable
):
backend_file.write_once("CARGO_FILE := %s\n" % obj.cargo_file)
- backend_file.write_once("CARGO_TARGET_DIR := .\n")
- backend_file.write("%s += %s\n" % (target_variable, obj.location))
+ target_dir = mozpath.normpath(backend_file.environment.topobjdir)
+ backend_file.write_once("CARGO_TARGET_DIR := %s\n" % target_dir)
+ backend_file.write("%s += $(DEPTH)/%s\n" % (target_variable, obj.location))
backend_file.write("%s += %s\n" % (target_cargo_variable, obj.name))
def _process_rust_program(self, obj, backend_file):
diff --git a/python/mozbuild/mozbuild/bootstrap.py b/python/mozbuild/mozbuild/bootstrap.py
--- a/python/mozbuild/mozbuild/bootstrap.py
+++ b/python/mozbuild/mozbuild/bootstrap.py
@@ -2,16 +2,16 @@
# 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/.
-from mozbuild.configure import ConfigureSandbox
-from pathlib import Path
import functools
import io
import logging
import os
+from pathlib import Path
+
+from mozbuild.configure import ConfigureSandbox
-@functools.lru_cache(maxsize=None)
-def _bootstrap_sandbox():
+def _raw_sandbox(extra_args=[]):
# Here, we don't want an existing mozconfig to interfere with what we
# do, neither do we want the default for --enable-bootstrap (which is not
# always on) to prevent this from doing something.
@@ -22,9 +22,17 @@ def _bootstrap_sandbox():
logger.propagate = False
sandbox = ConfigureSandbox(
{},
- argv=["configure", "--enable-bootstrap", f"MOZCONFIG={os.devnull}"],
+ argv=["configure"]
+ + extra_args
+ + ["--enable-bootstrap", f"MOZCONFIG={os.devnull}"],
logger=logger,
)
+ return sandbox
+
+
+@functools.lru_cache(maxsize=None)
+def _bootstrap_sandbox():
+ sandbox = _raw_sandbox()
moz_configure = (
Path(__file__).parent.parent.parent.parent / "build" / "moz.configure"
)
@@ -42,3 +50,12 @@ def bootstrap_toolchain(toolchain_job):
# Returns the path to the toolchain.
sandbox = _bootstrap_sandbox()
return sandbox._value_for(sandbox["bootstrap_path"](toolchain_job))
+
+
+def bootstrap_all_toolchains_for(configure_args=[]):
+ sandbox = _raw_sandbox(configure_args)
+ moz_configure = Path(__file__).parent.parent.parent.parent / "moz.configure"
+ sandbox.include_file(str(moz_configure))
+ for depend in sandbox._depends.values():
+ if depend.name == "bootstrap_path":
+ depend.result()
diff --git a/python/mozbuild/mozbuild/controller/building.py b/python/mozbuild/mozbuild/controller/building.py
--- a/python/mozbuild/mozbuild/controller/building.py
+++ b/python/mozbuild/mozbuild/controller/building.py
@@ -765,11 +765,11 @@ class StaticAnalysisFooter(Footer):
processed = monitor.num_files_processed
percent = "(%.2f%%)" % (processed * 100.0 / total)
parts = [
- ("dim", "Processing"),
+ ("bright_black", "Processing"),
("yellow", str(processed)),
- ("dim", "of"),
+ ("bright_black", "of"),
("yellow", str(total)),
- ("dim", "files"),
+ ("bright_black", "files"),
("green", percent),
]
if monitor.current_file:
diff --git a/python/mozbuild/mozbuild/frontend/gyp_reader.py b/python/mozbuild/mozbuild/frontend/gyp_reader.py
--- a/python/mozbuild/mozbuild/frontend/gyp_reader.py
+++ b/python/mozbuild/mozbuild/frontend/gyp_reader.py
@@ -4,18 +4,20 @@
from __future__ import absolute_import, print_function, unicode_literals
+import os
+import sys
+import time
+
import gyp
import gyp.msvs_emulation
+import mozpack.path as mozpath
import six
-import sys
-import os
-import time
+from mozbuild import shellutil
+from mozbuild.util import expand_variables
+from mozpack.files import FileFinder
-import mozpack.path as mozpath
-from mozpack.files import FileFinder
+from .context import VARIABLES, ObjDirPath, SourcePath, TemplateContext
from .sandbox import alphabetical_sorted
-from .context import ObjDirPath, SourcePath, TemplateContext, VARIABLES
-from mozbuild.util import expand_variables
# Define this module as gyp.generator.mozbuild so that gyp can use it
# as a generator under the name "mozbuild".
@@ -443,6 +445,12 @@ class GypProcessor(object):
"build_files": [path],
"root_targets": None,
}
+ # The NSS gyp configuration uses CC and CFLAGS to determine the
+ # floating-point ABI on arm.
+ os.environ.update(
+ CC=config.substs["CC"],
+ CFLAGS=shellutil.quote(*config.substs["CC_BASE_FLAGS"]),
+ )
if gyp_dir_attrs.no_chromium:
includes = []
diff --git a/python/mozbuild/mozbuild/generated_sources.py b/python/mozbuild/mozbuild/generated_sources.py
--- a/python/mozbuild/mozbuild/generated_sources.py
+++ b/python/mozbuild/mozbuild/generated_sources.py
@@ -8,8 +8,10 @@ import hashlib
import json
import os
+import mozpack.path as mozpath
from mozpack.files import FileFinder
-import mozpack.path as mozpath
+
+GENERATED_SOURCE_EXTS = (".rs", ".c", ".h", ".cc", ".cpp")
def sha512_digest(data):
@@ -56,7 +58,7 @@ def get_generated_sources():
base = mozpath.join(buildconfig.substs["RUST_TARGET"], rust_build_kind, "build")
finder = FileFinder(mozpath.join(buildconfig.topobjdir, base))
for p, f in finder:
- if p.endswith((".rs", ".c", ".h", ".cc", ".cpp")):
+ if p.endswith(GENERATED_SOURCE_EXTS):
yield mozpath.join(base, p), f
diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -5,6 +5,7 @@
from __future__ import absolute_import, print_function, unicode_literals
import argparse
+import errno
import itertools
import json
import logging
@@ -17,26 +18,20 @@ import subprocess
import sys
import tempfile
import time
-import errno
+from pathlib import Path
import mozbuild.settings # noqa need @SettingsProvider hook to execute
import mozpack.path as mozpath
-
-from pathlib import Path
from mach.decorators import (
+ Command,
CommandArgument,
CommandArgumentGroup,
- Command,
SettingsProvider,
SubCommand,
)
-
-from mozbuild.base import (
- BinaryNotFoundException,
- BuildEnvironmentNotFoundException,
- MachCommandConditions as conditions,
- MozbuildObject,
-)
+from mozbuild.base import BinaryNotFoundException, BuildEnvironmentNotFoundException
+from mozbuild.base import MachCommandConditions as conditions
+from mozbuild.base import MozbuildObject
from mozbuild.util import MOZBUILD_METRICS_PATH
here = os.path.abspath(os.path.dirname(__file__))
@@ -217,6 +212,114 @@ def check(
@SubCommand(
"cargo",
+ "udeps",
+ description="Run `cargo udeps` on a given crate. Defaults to gkrust.",
+ metrics_path=MOZBUILD_METRICS_PATH,
+)
+@CommandArgument(
+ "--all-crates",
+ action="store_true",
+ help="Check all of the crates in the tree.",
+)
+@CommandArgument("crates", default=None, nargs="*", help="The crate name(s) to check.")
+@CommandArgument(
+ "--jobs",
+ "-j",
+ default="0",
+ nargs="?",
+ metavar="jobs",
+ type=int,
+ help="Run the tests in parallel using multiple processes.",
+)
+@CommandArgument("-v", "--verbose", action="store_true", help="Verbose output.")
+@CommandArgument(
+ "--message-format-json",
+ action="store_true",
+ help="Emit error messages as JSON.",
+)
+@CommandArgument(
+ "--expect-unused",
+ action="store_true",
+ help="Do not return an error exit code if udeps detects unused dependencies.",
+)
+def udeps(
+ command_context,
+ all_crates=None,
+ crates=None,
+ jobs=0,
+ verbose=False,
+ message_format_json=False,
+ expect_unused=False,
+):
+ from mozbuild.controller.building import BuildDriver
+
+ command_context.log_manager.enable_all_structured_loggers()
+
+ try:
+ command_context.config_environment
+ except BuildEnvironmentNotFoundException:
+ build = command_context._spawn(BuildDriver)
+ ret = build.build(
+ command_context.metrics,
+ what=["pre-export", "export"],
+ jobs=jobs,
+ verbose=verbose,
+ mach_context=command_context._mach_context,
+ )
+ if ret != 0:
+ return ret
+ # XXX duplication with `mach vendor rust`
+ crates_and_roots = {
+ "gkrust": "toolkit/library/rust",
+ "gkrust-gtest": "toolkit/library/gtest/rust",
+ "geckodriver": "testing/geckodriver",
+ }
+
+ if all_crates:
+ crates = crates_and_roots.keys()
+ elif not crates:
+ crates = ["gkrust"]
+
+ for crate in crates:
+ root = crates_and_roots.get(crate, None)
+ if not root:
+ print(
+ "Cannot locate crate %s. Please check your spelling or "
+ "add the crate information to the list." % crate
+ )
+ return 1
+
+ udeps_targets = [
+ "force-cargo-library-udeps",
+ "force-cargo-host-library-udeps",
+ "force-cargo-program-udeps",
+ "force-cargo-host-program-udeps",
+ ]
+
+ append_env = {}
+ if message_format_json:
+ append_env["USE_CARGO_JSON_MESSAGE_FORMAT"] = "1"
+ if expect_unused:
+ append_env["CARGO_UDEPS_EXPECT_ERR"] = "1"
+
+ ret = command_context._run_make(
+ srcdir=False,
+ directory=root,
+ ensure_exit_code=0,
+ silent=not verbose,
+ print_directory=False,
+ target=udeps_targets,
+ num_jobs=jobs,
+ append_env=append_env,
+ )
+ if ret != 0:
+ return ret
+
+ return 0
+
+
+@SubCommand(
+ "cargo",
"vet",
description="Run `cargo vet`.",
)
@@ -278,6 +381,209 @@ def cargo_vet(command_context, arguments
return res if stdout else res.returncode
+@SubCommand(
+ "cargo",
+ "clippy",
+ description="Run `cargo clippy` on a given crate. Defaults to gkrust.",
+ metrics_path=MOZBUILD_METRICS_PATH,
+)
+@CommandArgument(
+ "--all-crates",
+ default=None,
+ action="store_true",
+ help="Check all of the crates in the tree.",
+)
+@CommandArgument("crates", default=None, nargs="*", help="The crate name(s) to check.")
+@CommandArgument(
+ "--jobs",
+ "-j",
+ default="0",
+ nargs="?",
+ metavar="jobs",
+ type=int,
+ help="Run the tests in parallel using multiple processes.",
+)
+@CommandArgument("-v", "--verbose", action="store_true", help="Verbose output.")
+@CommandArgument(
+ "--message-format-json",
+ action="store_true",
+ help="Emit error messages as JSON.",
+)
+def clippy(
+ command_context,
+ all_crates=None,
+ crates=None,
+ jobs=0,
+ verbose=False,
+ message_format_json=False,
+):
+ from mozbuild.controller.building import BuildDriver
+
+ command_context.log_manager.enable_all_structured_loggers()
+
+ try:
+ command_context.config_environment
+ except BuildEnvironmentNotFoundException:
+ build = command_context._spawn(BuildDriver)
+ ret = build.build(
+ command_context.metrics,
+ what=["pre-export", "export"],
+ jobs=jobs,
+ verbose=verbose,
+ mach_context=command_context._mach_context,
+ )
+ if ret != 0:
+ return ret
+ # XXX duplication with `mach vendor rust`
+ crates_and_roots = {
+ "gkrust": "toolkit/library/rust",
+ "gkrust-gtest": "toolkit/library/gtest/rust",
+ "geckodriver": "testing/geckodriver",
+ }
+
+ if all_crates:
+ crates = crates_and_roots.keys()
+ elif crates is None or crates == []:
+ crates = ["gkrust"]
+
+ final_ret = 0
+
+ for crate in crates:
+ root = crates_and_roots.get(crate, None)
+ if not root:
+ print(
+ "Cannot locate crate %s. Please check your spelling or "
+ "add the crate information to the list." % crate
+ )
+ return 1
+
+ check_targets = [
+ "force-cargo-library-clippy",
+ "force-cargo-host-library-clippy",
+ "force-cargo-program-clippy",
+ "force-cargo-host-program-clippy",
+ ]
+
+ append_env = {}
+ if message_format_json:
+ append_env["USE_CARGO_JSON_MESSAGE_FORMAT"] = "1"
+
+ ret = 2
+
+ try:
+ ret = command_context._run_make(
+ srcdir=False,
+ directory=root,
+ ensure_exit_code=0,
+ silent=not verbose,
+ print_directory=False,
+ target=check_targets,
+ num_jobs=jobs,
+ append_env=append_env,
+ )
+ except Exception as e:
+ print("%s" % e)
+ if ret != 0:
+ final_ret = ret
+
+ return final_ret
+
+
+@SubCommand(
+ "cargo",
+ "audit",
+ description="Run `cargo audit` on a given crate. Defaults to gkrust.",
+)
+@CommandArgument(
+ "--all-crates",
+ action="store_true",
+ help="Run `cargo audit` on all the crates in the tree.",
+)
+@CommandArgument(
+ "crates",
+ default=None,
+ nargs="*",
+ help="The crate name(s) to run `cargo audit` on.",
+)
+@CommandArgument(
+ "--jobs",
+ "-j",
+ default="0",
+ nargs="?",
+ metavar="jobs",
+ type=int,
+ help="Run `audit` in parallel using multiple processes.",
+)
+@CommandArgument("-v", "--verbose", action="store_true", help="Verbose output.")
+@CommandArgument(
+ "--message-format-json",
+ action="store_true",
+ help="Emit error messages as JSON.",
+)
+def audit(
+ command_context,
+ all_crates=None,
+ crates=None,
+ jobs=0,
+ verbose=False,
+ message_format_json=False,
+):
+ # XXX duplication with `mach vendor rust`
+ crates_and_roots = {
+ "gkrust": "toolkit/library/rust",
+ "gkrust-gtest": "toolkit/library/gtest/rust",
+ "geckodriver": "testing/geckodriver",
+ }
+
+ if all_crates:
+ crates = crates_and_roots.keys()
+ elif not crates:
+ crates = ["gkrust"]
+
+ final_ret = 0
+
+ for crate in crates:
+ root = crates_and_roots.get(crate, None)
+ if not root:
+ print(
+ "Cannot locate crate %s. Please check your spelling or "
+ "add the crate information to the list." % crate
+ )
+ return 1
+
+ check_targets = [
+ "force-cargo-library-audit",
+ "force-cargo-host-library-audit",
+ "force-cargo-program-audit",
+ "force-cargo-host-program-audit",
+ ]
+
+ append_env = {}
+ if message_format_json:
+ append_env["USE_CARGO_JSON_MESSAGE_FORMAT"] = "1"
+
+ ret = 2
+
+ try:
+ ret = command_context._run_make(
+ srcdir=False,
+ directory=root,
+ ensure_exit_code=0,
+ silent=not verbose,
+ print_directory=False,
+ target=check_targets
+ + ["cargo_build_flags=-f %s/Cargo.lock" % command_context.topsrcdir],
+ num_jobs=jobs,
+ append_env=append_env,
+ )
+ except Exception as e:
+ print("%s" % e)
+ if ret != 0:
+ final_ret = ret
+
+ return final_ret
+
+
@Command(
"doctor",
category="devenv",
@@ -891,8 +1197,9 @@ def gtest(
pass_thru=True,
)
+ import functools
+
from mozprocess import ProcessHandlerMixin
- import functools
def handle_line(job_id, line):
# Prepend the jobId
@@ -946,7 +1253,7 @@ def android_gtest(
setup_logging("mach-gtest", {}, {default_format: sys.stdout}, format_args)
# ensure that a device is available and test app is installed
- from mozrunner.devices.android_device import verify_android_device, get_adb_path
+ from mozrunner.devices.android_device import get_adb_path, verify_android_device
verify_android_device(
command_context, install=install, app=package, device_serial=device_serial
@@ -1046,8 +1353,8 @@ def install(command_context, **kwargs):
"""Install a package."""
if conditions.is_android(command_context):
from mozrunner.devices.android_device import (
+ InstallIntent,
verify_android_device,
- InstallIntent,
)
ret = (
@@ -1386,9 +1693,9 @@ def _run_android(
use_existing_process=False,
):
from mozrunner.devices.android_device import (
- verify_android_device,
+ InstallIntent,
_get_device,
- InstallIntent,
+ verify_android_device,
)
from six.moves import shlex_quote
@@ -1782,7 +2089,7 @@ def _run_desktop(
stacks,
show_dump_stats,
):
- from mozprofile import Profile, Preferences
+ from mozprofile import Preferences, Profile
try:
if packaged:
@@ -2106,7 +2413,34 @@ def repackage(command_context):
scriptworkers in order to bundle things up into shippable formats, such as a
.dmg on OSX or an installer exe on Windows.
"""
- print("Usage: ./mach repackage [dmg|installer|mar] [args...]")
+ print("Usage: ./mach repackage [dmg|pkg|installer|mar] [args...]")
+
+
+@SubCommand(
+ "repackage", "deb", description="Repackage a tar file into a .deb for Linux"
+)
+@CommandArgument("--input", "-i", type=str, required=True, help="Input filename")
+@CommandArgument("--output", "-o", type=str, required=True, help="Output filename")
+@CommandArgument("--arch", type=str, required=True, help="One of ['x86', 'x86_64']")
+@CommandArgument(
+ "--templates",
+ type=str,
+ required=True,
+ help="Location of the templates used to generate the debian/ directory files",
+)
+def repackage_deb(command_context, input, output, arch, templates):
+ if not os.path.exists(input):
+ print("Input file does not exist: %s" % input)
+ return 1
+
+ template_dir = os.path.join(
+ command_context.topsrcdir,
+ templates,
+ )
+
+ from mozbuild.repackaging.deb import repackage_deb
+
+ repackage_deb(input, output, template_dir, arch)
@SubCommand("repackage", "dmg", description="Repackage a tar file into a .dmg for OSX")
@@ -2117,18 +2451,24 @@ def repackage_dmg(command_context, input
print("Input file does not exist: %s" % input)
return 1
- if not os.path.exists(os.path.join(command_context.topobjdir, "config.status")):
- print(
- "config.status not found. Please run |mach configure| "
- "prior to |mach repackage|."
- )
- return 1
-
from mozbuild.repackaging.dmg import repackage_dmg
repackage_dmg(input, output)
+@SubCommand("repackage", "pkg", description="Repackage a tar file into a .pkg for OSX")
+@CommandArgument("--input", "-i", type=str, required=True, help="Input filename")
+@CommandArgument("--output", "-o", type=str, required=True, help="Output filename")
+def repackage_pkg(command_context, input, output):
+ if not os.path.exists(input):
+ print("Input file does not exist: %s" % input)
+ return 1
+
+ from mozbuild.repackaging.pkg import repackage_pkg
+
+ repackage_pkg(input, output)
+
+
@SubCommand(
"repackage", "installer", description="Repackage into a Windows installer exe"
)
diff --git a/python/mozbuild/mozbuild/repackaging/dmg.py b/python/mozbuild/mozbuild/repackaging/dmg.py
--- a/python/mozbuild/mozbuild/repackaging/dmg.py
+++ b/python/mozbuild/mozbuild/repackaging/dmg.py
@@ -2,16 +2,13 @@
# 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/.
-from __future__ import absolute_import, print_function
+import tarfile
+from pathlib import Path
-import errno
-import os
-import tempfile
-import tarfile
-import shutil
-import mozpack.path as mozpath
+import mozfile
+from mozbuild.bootstrap import bootstrap_toolchain
+from mozbuild.repackaging.application_ini import get_application_ini_value
from mozpack.dmg import create_dmg
-from mozbuild.repackaging.application_ini import get_application_ini_value
def repackage_dmg(infile, output):
@@ -19,27 +16,41 @@ def repackage_dmg(infile, output):
if not tarfile.is_tarfile(infile):
raise Exception("Input file %s is not a valid tarfile." % infile)
- tmpdir = tempfile.mkdtemp()
- try:
+ # Resolve required tools
+ dmg_tool = bootstrap_toolchain("dmg/dmg")
+ if not dmg_tool:
+ raise Exception("DMG tool not found")
+ hfs_tool = bootstrap_toolchain("dmg/hfsplus")
+ if not hfs_tool:
+ raise Exception("HFS tool not found")
+ mkfshfs_tool = bootstrap_toolchain("hfsplus/newfs_hfs")
+ if not mkfshfs_tool:
+ raise Exception("MKFSHFS tool not found")
+
+ with mozfile.TemporaryDirectory() as tmp:
+ tmpdir = Path(tmp)
with tarfile.open(infile) as tar:
tar.extractall(path=tmpdir)
# Remove the /Applications symlink. If we don't, an rsync command in
# create_dmg() will break, and create_dmg() re-creates the symlink anyway.
- try:
- os.remove(mozpath.join(tmpdir, " "))
- except OSError as e:
- if e.errno != errno.ENOENT:
- raise
+ symlink = tmpdir / " "
+ if symlink.is_file():
+ symlink.unlink()
volume_name = get_application_ini_value(
- tmpdir, "App", "CodeName", fallback="Name"
+ str(tmpdir), "App", "CodeName", fallback="Name"
)
# The extra_files argument is empty [] because they are already a part
# of the original dmg produced by the build, and they remain in the
# tarball generated by the signing task.
- create_dmg(tmpdir, output, volume_name, [])
-
- finally:
- shutil.rmtree(tmpdir)
+ create_dmg(
+ source_directory=tmpdir,
+ output_dmg=Path(output),
+ volume_name=volume_name,
+ extra_files=[],
+ dmg_tool=Path(dmg_tool),
+ hfs_tool=Path(hfs_tool),
+ mkfshfs_tool=Path(mkfshfs_tool),
+ )
diff --git a/python/mozbuild/mozbuild/test/action/test_langpack_manifest.py b/python/mozbuild/mozbuild/test/action/test_langpack_manifest.py
--- a/python/mozbuild/mozbuild/test/action/test_langpack_manifest.py
+++ b/python/mozbuild/mozbuild/test/action/test_langpack_manifest.py
@@ -5,14 +5,13 @@
from __future__ import absolute_import, print_function
-import unittest
import json
import os
-
-import mozunit
+import tempfile
+import unittest
import mozbuild.action.langpack_manifest as langpack_manifest
-from mozbuild.preprocessor import Context
+import mozunit
class TestGenerateManifest(unittest.TestCase):
@@ -20,16 +19,30 @@ class TestGenerateManifest(unittest.Test
Unit tests for langpack_manifest.py.
"""
+ def test_parse_flat_ftl(self):
+ src = """
+langpack-creator = bar {"bar"}
+langpack-contributors = { "" }
+"""
+ tmp = tempfile.NamedTemporaryFile(mode="wt", suffix=".ftl", delete=False)
+ try:
+ tmp.write(src)
+ tmp.close()
+ ftl = langpack_manifest.parse_flat_ftl(tmp.name)
+ self.assertEqual(ftl["langpack-creator"], "bar bar")
+ self.assertEqual(ftl["langpack-contributors"], "")
+ finally:
+ os.remove(tmp.name)
+
+ def test_parse_flat_ftl_missing(self):
+ ftl = langpack_manifest.parse_flat_ftl("./does-not-exist.ftl")
+ self.assertEqual(len(ftl), 0)
+
def test_manifest(self):
- ctx = Context()
- ctx["MOZ_LANG_TITLE"] = "Finnish"
- ctx["MOZ_LANGPACK_CREATOR"] = "Suomennosprojekti"
- ctx[
- "MOZ_LANGPACK_CONTRIBUTORS"
- ] = """
- <em:contributor>Joe Smith</em:contributor>
- <em:contributor>Mary White</em:contributor>
- """
+ ctx = {
+ "langpack-creator": "Suomennosprojekti",
+ "langpack-contributors": "Joe Smith, Mary White",
+ }
os.environ["MOZ_BUILD_DATE"] = "20210928100000"
manifest = langpack_manifest.create_webmanifest(
"fi",
@@ -44,16 +57,17 @@ class TestGenerateManifest(unittest.Test
)
data = json.loads(manifest)
- self.assertEqual(data["name"], "Finnish Language Pack")
+ self.assertEqual(data["name"], "Language Pack: Suomi (Finnish)")
self.assertEqual(
data["author"], "Suomennosprojekti (contributors: Joe Smith, Mary White)"
)
self.assertEqual(data["version"], "57.0.1buildid20210928.100000")
def test_manifest_without_contributors(self):
- ctx = Context()
- ctx["MOZ_LANG_TITLE"] = "Finnish"
- ctx["MOZ_LANGPACK_CREATOR"] = "Suomennosprojekti"
+ ctx = {
+ "langpack-creator": "Suomennosprojekti",
+ "langpack-contributors": "",
+ }
manifest = langpack_manifest.create_webmanifest(
"fi",
"57.0.1",
@@ -67,7 +81,7 @@ class TestGenerateManifest(unittest.Test
)
data = json.loads(manifest)
- self.assertEqual(data["name"], "Finnish Language Pack")
+ self.assertEqual(data["name"], "Language Pack: Suomi (Finnish)")
self.assertEqual(data["author"], "Suomennosprojekti")
diff --git a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -6,21 +6,18 @@ from __future__ import absolute_import,
import io
import os
-import six.moves.cPickle as pickle
-import six
import unittest
-from mozpack.manifests import InstallManifest
-from mozunit import main
-
+import mozpack.path as mozpath
+import six
+import six.moves.cPickle as pickle
from mozbuild.backend.recursivemake import RecursiveMakeBackend, RecursiveMakeTraversal
from mozbuild.backend.test_manifest import TestManifestBackend
from mozbuild.frontend.emitter import TreeMetadataEmitter
from mozbuild.frontend.reader import BuildReader
-
from mozbuild.test.backend.common import BackendTester
-
-import mozpack.path as mozpath
+from mozpack.manifests import InstallManifest
+from mozunit import main
class TestRecursiveMakeTraversal(unittest.TestCase):
@@ -1011,10 +1008,10 @@ class TestRecursiveMakeBackend(BackendTe
expected = [
"CARGO_FILE := %s/code/Cargo.toml" % env.topsrcdir,
- "CARGO_TARGET_DIR := .",
- "RUST_PROGRAMS += i686-pc-windows-msvc/release/target.exe",
+ "CARGO_TARGET_DIR := %s" % env.topobjdir,
+ "RUST_PROGRAMS += $(DEPTH)/i686-pc-windows-msvc/release/target.exe",
"RUST_CARGO_PROGRAMS += target",
- "HOST_RUST_PROGRAMS += i686-pc-windows-msvc/release/host.exe",
+ "HOST_RUST_PROGRAMS += $(DEPTH)/i686-pc-windows-msvc/release/host.exe",
"HOST_RUST_CARGO_PROGRAMS += host",
]
diff --git a/python/mozbuild/mozbuild/vendor/moz_yaml.py b/python/mozbuild/mozbuild/vendor/moz_yaml.py
--- a/python/mozbuild/mozbuild/vendor/moz_yaml.py
+++ b/python/mozbuild/mozbuild/vendor/moz_yaml.py
@@ -104,6 +104,10 @@ origin:
# optional
license-file: COPYING
+ # If there are any mozilla-specific notes you want to put
+ # about a library, they can be put here.
+ notes: Notes about the library
+
# Configuration for the automated vendoring system.
# optional
vendoring:
@@ -379,6 +383,7 @@ def _schema_1():
"origin": {
Required("name"): All(str, Length(min=1)),
Required("description"): All(str, Length(min=1)),
+ "notes": All(str, Length(min=1)),
Required("url"): FqdnUrl(),
Required("license"): Msg(License(), msg="Unsupported License"),
"license-file": All(str, Length(min=1)),
diff --git a/python/mozbuild/mozbuild/vendor/vendor_manifest.py b/python/mozbuild/mozbuild/vendor/vendor_manifest.py
--- a/python/mozbuild/mozbuild/vendor/vendor_manifest.py
+++ b/python/mozbuild/mozbuild/vendor/vendor_manifest.py
@@ -25,7 +25,7 @@ from mozbuild.vendor.rewrite_mozbuild im
MozBuildRewriteException,
)
-DEFAULT_EXCLUDE_FILES = [".git*"]
+DEFAULT_EXCLUDE_FILES = [".git*", ".git*/**"]
DEFAULT_KEEP_FILES = ["**/moz.build", "**/moz.yaml"]
DEFAULT_INCLUDE_FILES = []
diff --git a/python/mozbuild/mozbuild/vendor/vendor_rust.py b/python/mozbuild/mozbuild/vendor/vendor_rust.py
--- a/python/mozbuild/mozbuild/vendor/vendor_rust.py
+++ b/python/mozbuild/mozbuild/vendor/vendor_rust.py
@@ -196,6 +196,7 @@ class VendorRust(MozbuildObject):
f
for f in self.repository.get_changed_files("M")
if os.path.basename(f) not in ("Cargo.toml", "Cargo.lock")
+ and not f.startswith("supply-chain/")
]
if modified:
self.log(
diff --git a/python/mozbuild/mozpack/dmg.py b/python/mozbuild/mozpack/dmg.py
--- a/python/mozbuild/mozpack/dmg.py
+++ b/python/mozbuild/mozpack/dmg.py
@@ -2,28 +2,18 @@
# 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/.
-from __future__ import absolute_import, print_function, unicode_literals
-
-import buildconfig
-import errno
-import mozfile
import os
import platform
import shutil
import subprocess
+from pathlib import Path
+from typing import List
+import mozfile
from mozbuild.util import ensureParentDir
is_linux = platform.system() == "Linux"
-
-
-def mkdir(dir):
- if not os.path.isdir(dir):
- try:
- os.makedirs(dir)
- except OSError as e:
- if e.errno != errno.EEXIST:
- raise
+is_osx = platform.system() == "Darwin"
def chmod(dir):
@@ -31,48 +21,50 @@ def chmod(dir):
subprocess.check_call(["chmod", "-R", "a+rX,a-st,u+w,go-w", dir])
-def rsync(source, dest):
+def rsync(source: Path, dest: Path):
"rsync the contents of directory source into directory dest"
# Ensure a trailing slash on directories so rsync copies the *contents* of source.
- if not source.endswith("/") and os.path.isdir(source):
- source += "/"
- subprocess.check_call(["rsync", "-a", "--copy-unsafe-links", source, dest])
+ raw_source = str(source)
+ if source.is_dir():
+ raw_source = str(source) + "/"
+ subprocess.check_call(["rsync", "-a", "--copy-unsafe-links", raw_source, dest])
-def set_folder_icon(dir, tmpdir):
+def set_folder_icon(dir: Path, tmpdir: Path, hfs_tool: Path = None):
"Set HFS attributes of dir to use a custom icon"
- if not is_linux:
+ if is_linux:
+ hfs = tmpdir / "staged.hfs"
+ subprocess.check_call([hfs_tool, hfs, "attr", "/", "C"])
+ elif is_osx:
subprocess.check_call(["SetFile", "-a", "C", dir])
- else:
- hfs = os.path.join(tmpdir, "staged.hfs")
- subprocess.check_call([buildconfig.substs["HFS_TOOL"], hfs, "attr", "/", "C"])
-def generate_hfs_file(stagedir, tmpdir, volume_name):
+def generate_hfs_file(
+ stagedir: Path, tmpdir: Path, volume_name: str, mkfshfs_tool: Path
+):
"""
When cross compiling, we zero fill an hfs file, that we will turn into
a DMG. To do so we test the size of the staged dir, and add some slight
padding to that.
"""
- if is_linux:
- hfs = os.path.join(tmpdir, "staged.hfs")
- output = subprocess.check_output(["du", "-s", stagedir])
- size = int(output.split()[0]) / 1000 # Get in MB
- size = int(size * 1.02) # Bump the used size slightly larger.
- # Setup a proper file sized out with zero's
- subprocess.check_call(
- [
- "dd",
- "if=/dev/zero",
- "of={}".format(hfs),
- "bs=1M",
- "count={}".format(size),
- ]
- )
- subprocess.check_call([buildconfig.substs["MKFSHFS"], "-v", volume_name, hfs])
+ hfs = tmpdir / "staged.hfs"
+ output = subprocess.check_output(["du", "-s", stagedir])
+ size = int(output.split()[0]) / 1000 # Get in MB
+ size = int(size * 1.02) # Bump the used size slightly larger.
+ # Setup a proper file sized out with zero's
+ subprocess.check_call(
+ [
+ "dd",
+ "if=/dev/zero",
+ "of={}".format(hfs),
+ "bs=1M",
+ "count={}".format(size),
+ ]
+ )
+ subprocess.check_call([mkfshfs_tool, "-v", volume_name, hfs])
-def create_app_symlink(stagedir, tmpdir):
+def create_app_symlink(stagedir: Path, tmpdir: Path, hfs_tool: Path = None):
"""
Make a symlink to /Applications. The symlink name is a space
so we don't have to localize it. The Applications folder icon
@@ -80,18 +72,34 @@ def create_app_symlink(stagedir, tmpdir)
"""
if is_linux:
hfs = os.path.join(tmpdir, "staged.hfs")
- subprocess.check_call(
- [buildconfig.substs["HFS_TOOL"], hfs, "symlink", "/ ", "/Applications"]
- )
- else:
- os.symlink("/Applications", os.path.join(stagedir, " "))
+ subprocess.check_call([hfs_tool, hfs, "symlink", "/ ", "/Applications"])
+ elif is_osx:
+ os.symlink("/Applications", stagedir / " ")
-def create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name):
+def create_dmg_from_staged(
+ stagedir: Path,
+ output_dmg: Path,
+ tmpdir: Path,
+ volume_name: str,
+ hfs_tool: Path = None,
+ dmg_tool: Path = None,
+):
"Given a prepared directory stagedir, produce a DMG at output_dmg."
- if not is_linux:
- # Running on OS X
- hybrid = os.path.join(tmpdir, "hybrid.dmg")
+ if is_linux:
+ # The dmg tool doesn't create the destination directories, and silently
+ # returns success if the parent directory doesn't exist.
+ ensureParentDir(output_dmg)
+
+ hfs = os.path.join(tmpdir, "staged.hfs")
+ subprocess.check_call([hfs_tool, hfs, "addall", stagedir])
+ subprocess.check_call(
+ [dmg_tool, "build", hfs, output_dmg],
+ # dmg is seriously chatty
+ stdout=subprocess.DEVNULL,
+ )
+ elif is_osx:
+ hybrid = tmpdir / "hybrid.dmg"
subprocess.check_call(
[
"hdiutil",
@@ -121,37 +129,17 @@ def create_dmg_from_staged(stagedir, out
output_dmg,
]
)
- else:
- # The dmg tool doesn't create the destination directories, and silently
- # returns success if the parent directory doesn't exist.
- ensureParentDir(output_dmg)
-
- hfs = os.path.join(tmpdir, "staged.hfs")
- subprocess.check_call([buildconfig.substs["HFS_TOOL"], hfs, "addall", stagedir])
- subprocess.check_call(
- [buildconfig.substs["DMG_TOOL"], "build", hfs, output_dmg],
- # dmg is seriously chatty
- stdout=open(os.devnull, "wb"),
- )
-def check_tools(*tools):
- """
- Check that each tool named in tools exists in SUBSTS and is executable.
- """
- for tool in tools:
- path = buildconfig.substs[tool]
- if not path:
- raise Exception('Required tool "%s" not found' % tool)
- if not os.path.isfile(path):
- raise Exception('Required tool "%s" not found at path "%s"' % (tool, path))
- if not os.access(path, os.X_OK):
- raise Exception(
- 'Required tool "%s" at path "%s" is not executable' % (tool, path)
- )
-
-
-def create_dmg(source_directory, output_dmg, volume_name, extra_files):
+def create_dmg(
+ source_directory: Path,
+ output_dmg: Path,
+ volume_name: str,
+ extra_files: List[tuple],
+ dmg_tool: Path,
+ hfs_tool: Path,
+ mkfshfs_tool: Path,
+):
"""
Create a DMG disk image at the path output_dmg from source_directory.
@@ -162,73 +150,80 @@ def create_dmg(source_directory, output_
if platform.system() not in ("Darwin", "Linux"):
raise Exception("Don't know how to build a DMG on '%s'" % platform.system())
- if is_linux:
- check_tools("DMG_TOOL", "MKFSHFS", "HFS_TOOL")
- with mozfile.TemporaryDirectory() as tmpdir:
- stagedir = os.path.join(tmpdir, "stage")
- os.mkdir(stagedir)
+ with mozfile.TemporaryDirectory() as tmp:
+ tmpdir = Path(tmp)
+ stagedir = tmpdir / "stage"
+ stagedir.mkdir()
+
# Copy the app bundle over using rsync
rsync(source_directory, stagedir)
# Copy extra files
for source, target in extra_files:
- full_target = os.path.join(stagedir, target)
- mkdir(os.path.dirname(full_target))
+ full_target = stagedir / target
+ full_target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, full_target)
- generate_hfs_file(stagedir, tmpdir, volume_name)
- create_app_symlink(stagedir, tmpdir)
+ if is_linux:
+ # Not needed in osx
+ generate_hfs_file(stagedir, tmpdir, volume_name, mkfshfs_tool)
+ create_app_symlink(stagedir, tmpdir, hfs_tool)
# Set the folder attributes to use a custom icon
- set_folder_icon(stagedir, tmpdir)
+ set_folder_icon(stagedir, tmpdir, hfs_tool)
chmod(stagedir)
- create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name)
+ create_dmg_from_staged(
+ stagedir, output_dmg, tmpdir, volume_name, hfs_tool, dmg_tool
+ )
-def extract_dmg_contents(dmgfile, destdir):
- import buildconfig
-
+def extract_dmg_contents(
+ dmgfile: Path,
+ destdir: Path,
+ dmg_tool: Path = None,
+ hfs_tool: Path = None,
+):
if is_linux:
with mozfile.TemporaryDirectory() as tmpdir:
hfs_file = os.path.join(tmpdir, "firefox.hfs")
subprocess.check_call(
- [buildconfig.substs["DMG_TOOL"], "extract", dmgfile, hfs_file],
+ [dmg_tool, "extract", dmgfile, hfs_file],
# dmg is seriously chatty
- stdout=open(os.devnull, "wb"),
- )
- subprocess.check_call(
- [buildconfig.substs["HFS_TOOL"], hfs_file, "extractall", "/", destdir]
+ stdout=subprocess.DEVNULL,
)
+ subprocess.check_call([hfs_tool, hfs_file, "extractall", "/", destdir])
else:
- unpack_diskimage = os.path.join(
- buildconfig.topsrcdir, "build", "package", "mac_osx", "unpack-diskimage"
- )
- unpack_mountpoint = os.path.join(
- "/tmp", "{}-unpack".format(buildconfig.substs["MOZ_APP_NAME"])
- )
+ # TODO: find better way to resolve topsrcdir (checkout directory)
+ topsrcdir = Path(__file__).parent.parent.parent.parent.resolve()
+ unpack_diskimage = topsrcdir / "build/package/mac_osx/unpack-diskimage"
+ unpack_mountpoint = Path("/tmp/app-unpack")
subprocess.check_call([unpack_diskimage, dmgfile, unpack_mountpoint, destdir])
-def extract_dmg(dmgfile, output, dsstore=None, icon=None, background=None):
+def extract_dmg(
+ dmgfile: Path,
+ output: Path,
+ dmg_tool: Path = None,
+ hfs_tool: Path = None,
+ dsstore: Path = None,
+ icon: Path = None,
+ background: Path = None,
+):
if platform.system() not in ("Darwin", "Linux"):
raise Exception("Don't know how to extract a DMG on '%s'" % platform.system())
- if is_linux:
- check_tools("DMG_TOOL", "MKFSHFS", "HFS_TOOL")
-
- with mozfile.TemporaryDirectory() as tmpdir:
- extract_dmg_contents(dmgfile, tmpdir)
- if os.path.islink(os.path.join(tmpdir, " ")):
+ with mozfile.TemporaryDirectory() as tmp:
+ tmpdir = Path(tmp)
+ extract_dmg_contents(dmgfile, tmpdir, dmg_tool, hfs_tool)
+ applications_symlink = tmpdir / " "
+ if applications_symlink.is_symlink():
# Rsync will fail on the presence of this symlink
- os.remove(os.path.join(tmpdir, " "))
+ applications_symlink.unlink()
rsync(tmpdir, output)
if dsstore:
- mkdir(os.path.dirname(dsstore))
- rsync(os.path.join(tmpdir, ".DS_Store"), dsstore)
+ dsstore.parent.mkdir(parents=True, exist_ok=True)
+ rsync(tmpdir / ".DS_Store", dsstore)
if background:
- mkdir(os.path.dirname(background))
- rsync(
- os.path.join(tmpdir, ".background", os.path.basename(background)),
- background,
- )
+ background.parent.mkdir(parents=True, exist_ok=True)
+ rsync(tmpdir / ".background" / background.name, background)
if icon:
- mkdir(os.path.dirname(icon))
- rsync(os.path.join(tmpdir, ".VolumeIcon.icns"), icon)
+ icon.parent.mkdir(parents=True, exist_ok=True)
+ rsync(tmpdir / ".VolumeIcon.icns", icon)
diff --git a/python/mozbuild/mozpack/mozjar.py b/python/mozbuild/mozpack/mozjar.py
--- a/python/mozbuild/mozpack/mozjar.py
+++ b/python/mozbuild/mozpack/mozjar.py
@@ -287,12 +287,22 @@ class JarFileReader(object):
self.compressed = header["compression"] != JAR_STORED
self.compress = header["compression"]
+ def readable(self):
+ return True
+
def read(self, length=-1):
"""
Read some amount of uncompressed data.
"""
return self.uncompressed_data.read(length)
+ def readinto(self, b):
+ """
+ Read bytes into a pre-allocated, writable bytes-like object `b` and return
+ the number of bytes read.
+ """
+ return self.uncompressed_data.readinto(b)
+
def readlines(self):
"""
Return a list containing all the lines of data in the uncompressed
@@ -320,6 +330,10 @@ class JarFileReader(object):
self.uncompressed_data.close()
@property
+ def closed(self):
+ return self.uncompressed_data.closed
+
+ @property
def compressed_data(self):
"""
Return the raw compressed data.
diff --git a/python/mozbuild/mozpack/test/python.ini b/python/mozbuild/mozpack/test/python.ini
--- a/python/mozbuild/mozpack/test/python.ini
+++ b/python/mozbuild/mozpack/test/python.ini
@@ -14,4 +14,5 @@ subsuite = mozbuild
[test_packager_l10n.py]
[test_packager_unpack.py]
[test_path.py]
+[test_pkg.py]
[test_unify.py]
diff --git a/python/mozlint/mozlint/cli.py b/python/mozlint/mozlint/cli.py
--- a/python/mozlint/mozlint/cli.py
+++ b/python/mozlint/mozlint/cli.py
@@ -46,10 +46,13 @@ class MozlintParser(ArgumentParser):
[
["-W", "--warnings"],
{
+ "const": True,
+ "nargs": "?",
+ "choices": ["soft"],
"dest": "show_warnings",
- "default": False,
- "action": "store_true",
- "help": "Display and fail on warnings in addition to errors.",
+ "help": "Display and fail on warnings in addition to errors. "
+ "--warnings=soft can be used to report warnings but only fail "
+ "on errors.",
},
],
[
diff --git a/python/mozlint/mozlint/result.py b/python/mozlint/mozlint/result.py
--- a/python/mozlint/mozlint/result.py
+++ b/python/mozlint/mozlint/result.py
@@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from collections import defaultdict
+from itertools import chain
from json import JSONEncoder
import os
import mozpack.path as mozpath
@@ -15,7 +16,8 @@ class ResultSummary(object):
root = None
- def __init__(self, root):
+ def __init__(self, root, fail_on_warnings=True):
+ self.fail_on_warnings = fail_on_warnings
self.reset()
# Store the repository root folder to be able to build
@@ -30,9 +32,19 @@ class ResultSummary(object):
self.suppressed_warnings = defaultdict(int)
self.fixed = 0
+ def has_issues_failure(self):
+ """Returns true in case issues were detected during the lint run. Do not
+ consider warning issues in case `self.fail_on_warnings` is set to False.
+ """
+ if self.fail_on_warnings is False:
+ return any(
+ result.level != "warning" for result in chain(*self.issues.values())
+ )
+ return len(self.issues) >= 1
+
@property
def returncode(self):
- if self.issues or self.failed:
+ if self.has_issues_failure() or self.failed:
return 1
return 0
diff --git a/python/mozlint/mozlint/roller.py b/python/mozlint/mozlint/roller.py
--- a/python/mozlint/mozlint/roller.py
+++ b/python/mozlint/mozlint/roller.py
@@ -177,7 +177,11 @@ class LintRoller(object):
self._setupargs = setupargs or {}
# result state
- self.result = ResultSummary(root)
+ self.result = ResultSummary(
+ root,
+ # Prevent failing on warnings when the --warnings parameter is set to "soft"
+ fail_on_warnings=lintargs.get("show_warnings") != "soft",
+ )
self.root = root
self.exclude = exclude or []
diff --git a/python/mozlint/mozlint/types.py b/python/mozlint/mozlint/types.py
--- a/python/mozlint/mozlint/types.py
+++ b/python/mozlint/mozlint/types.py
@@ -87,40 +87,6 @@ class BaseType(object):
pass
-class FileType(BaseType):
- """Abstract base class for linter types that check each file
-
- Subclasses of this linter type will read each file and check the file contents
- """
-
- __metaclass__ = ABCMeta
-
- @abstractmethod
- def lint_single_file(payload, line, config):
- """Run linter defined by `config` against `paths` with `lintargs`.
-
- :param path: Path to the file to lint.
- :param config: Linter config the paths are being linted against.
- :param lintargs: External arguments to the linter not defined in
- the definition, but passed in by a consumer.
- :returns: An error message or None
- """
- pass
-
- def _lint(self, path, config, **lintargs):
- if os.path.isdir(path):
- return self._lint_dir(path, config, **lintargs)
-
- payload = config["payload"]
-
- errors = []
- message = self.lint_single_file(payload, path, config)
- if message:
- errors.append(result.from_config(config, message=message, path=path))
-
- return errors
-
-
class LineType(BaseType):
"""Abstract base class for linter types that check each line individually.
@@ -182,6 +148,10 @@ class ExternalType(BaseType):
return func(files, config, **lintargs)
+class ExternalFileType(ExternalType):
+ batch = False
+
+
class GlobalType(ExternalType):
"""Linter type that runs an external global linting function just once.
@@ -237,6 +207,7 @@ supported_types = {
"string": StringType(),
"regex": RegexType(),
"external": ExternalType(),
+ "external-file": ExternalFileType(),
"global": GlobalType(),
"structured_log": StructuredLogType(),
}
diff --git a/python/mozlint/test/test_roller.py b/python/mozlint/test/test_roller.py
--- a/python/mozlint/test/test_roller.py
+++ b/python/mozlint/test/test_roller.py
@@ -14,6 +14,7 @@ import pytest
from mozlint.errors import LintersNotConfigured, NoValidLinter
from mozlint.result import Issue, ResultSummary
+from mozlint.roller import LintRoller
from itertools import chain
@@ -152,26 +153,41 @@ def test_roll_warnings(lint, linters, fi
assert result.total_suppressed_warnings == 0
-def test_roll_code_review(monkeypatch, lint, linters, files):
+def test_roll_code_review(monkeypatch, linters, files):
monkeypatch.setenv("CODE_REVIEW", "1")
- lint.lintargs["show_warnings"] = False
+ lint = LintRoller(root=here, show_warnings=False)
lint.read(linters("warning"))
result = lint.roll(files)
assert len(result.issues) == 1
assert result.total_issues == 2
assert len(result.suppressed_warnings) == 0
assert result.total_suppressed_warnings == 0
+ assert result.returncode == 1
-def test_roll_code_review_warnings_disabled(monkeypatch, lint, linters, files):
+def test_roll_code_review_warnings_disabled(monkeypatch, linters, files):
monkeypatch.setenv("CODE_REVIEW", "1")
- lint.lintargs["show_warnings"] = False
+ lint = LintRoller(root=here, show_warnings=False)
lint.read(linters("warning_no_code_review"))
result = lint.roll(files)
assert len(result.issues) == 0
assert result.total_issues == 0
+ assert lint.result.fail_on_warnings is True
assert len(result.suppressed_warnings) == 1
assert result.total_suppressed_warnings == 2
+ assert result.returncode == 0
+
+
+def test_roll_code_review_warnings_soft(linters, files):
+ lint = LintRoller(root=here, show_warnings="soft")
+ lint.read(linters("warning_no_code_review"))
+ result = lint.roll(files)
+ assert len(result.issues) == 1
+ assert result.total_issues == 2
+ assert lint.result.fail_on_warnings is False
+ assert len(result.suppressed_warnings) == 0
+ assert result.total_suppressed_warnings == 0
+ assert result.returncode == 0
def fake_run_worker(config, paths, **lintargs):
diff --git a/python/mozperftest/mozperftest/test/webpagetest.py b/python/mozperftest/mozperftest/test/webpagetest.py
--- a/python/mozperftest/mozperftest/test/webpagetest.py
+++ b/python/mozperftest/mozperftest/test/webpagetest.py
@@ -29,6 +29,7 @@ ACCEPTED_CONNECTIONS = [
ACCEPTED_STATISTICS = ["average", "median", "standardDeviation"]
WPT_KEY_FILE = "WPT_key.txt"
+WPT_API_EXPIRED_MESSAGE = "API key expired"
class WPTTimeOutError(Exception):
@@ -112,6 +113,14 @@ class WPTInvalidStatisticsError(Exceptio
pass
+class WPTExpiredAPIKeyError(Exception):
+ """
+ This error is raised if we get a notification from WPT that our API key has expired
+ """
+
+ pass
+
+
class PropagatingErrorThread(Thread):
def run(self):
self.exc = None
@@ -244,6 +253,11 @@ class WebPageTest(Layer):
requested_results = requests.get(url)
results_of_request = json.loads(requested_results.text)
start = time.time()
+ if (
+ "statusText" in results_of_request.keys()
+ and results_of_request["statusText"] == WPT_API_EXPIRED_MESSAGE
+ ):
+ raise WPTExpiredAPIKeyError("The API key has expired")
while (
requested_results.status_code == 200
and time.time() - start < self.timeout_limit
diff --git a/python/mozperftest/mozperftest/tests/test_webpagetest.py b/python/mozperftest/mozperftest/tests/test_webpagetest.py
--- a/python/mozperftest/mozperftest/tests/test_webpagetest.py
+++ b/python/mozperftest/mozperftest/tests/test_webpagetest.py
@@ -13,10 +13,12 @@ from mozperftest.test.webpagetest import
WPTBrowserSelectionError,
WPTInvalidURLError,
WPTLocationSelectionError,
- WPTInvalidConnectionSelection,
- ACCEPTED_STATISTICS,
WPTInvalidStatisticsError,
WPTDataProcessingError,
+ WPTExpiredAPIKeyError,
+ WPTInvalidConnectionSelection,
+ WPT_API_EXPIRED_MESSAGE,
+ ACCEPTED_STATISTICS,
)
WPT_METRICS = [
@@ -82,7 +84,9 @@ def init_placeholder_wpt_data(fvonly=Fal
return placeholder_data
-def init_mocked_request(status_code, WPT_test_status_code=200, **kwargs):
+def init_mocked_request(
+ status_code, WPT_test_status_code=200, WPT_test_status_text="Ok", **kwargs
+):
mock_data = {
"data": {
"ec2-us-east-1": {"PendingTests": {"Queued": 3}, "Label": "California"},
@@ -92,6 +96,7 @@ def init_mocked_request(status_code, WPT
"remaining": 2000,
},
"statusCode": WPT_test_status_code,
+ "statusText": WPT_test_status_text,
}
for key, value in kwargs.items():
mock_data["data"][key] = value
@@ -245,3 +250,23 @@ def test_webpagetest_test_metric_not_fou
test = webpagetest.WebPageTest(env, mach_cmd)
with pytest.raises(WPTDataProcessingError):
test.run(metadata)
+
+
+@mock.patch("mozperftest.utils.get_tc_secret", return_value={"wpt_key": "fake_key"})
+@mock.patch(
+ "mozperftest.test.webpagetest.WebPageTest.location_queue", return_value=None
+)
+@mock.patch(
+ "requests.get",
+ return_value=init_mocked_request(
+ 200, WPT_test_status_code=400, WPT_test_status_text=WPT_API_EXPIRED_MESSAGE
+ ),
+)
+@mock.patch("mozperftest.test.webpagetest.WPT_KEY_FILE", "tests/data/WPT_fakekey.txt")
+def test_webpagetest_test_expired_api_key(*mocked):
+ mach_cmd, metadata, env = running_env(tests=[str(EXAMPLE_WPT_TEST)])
+ metadata.script["options"]["test_list"] = ["google.ca"]
+ metadata.script["options"]["test_parameters"]["wait_between_requests"] = 1
+ test = webpagetest.WebPageTest(env, mach_cmd)
+ with pytest.raises(WPTExpiredAPIKeyError):
+ test.run(metadata)
diff --git a/python/mozterm/mozterm/widgets.py b/python/mozterm/mozterm/widgets.py
--- a/python/mozterm/mozterm/widgets.py
+++ b/python/mozterm/mozterm/widgets.py
@@ -6,6 +6,8 @@ from __future__ import absolute_import,
from .terminal import Terminal
+DEFAULT = "\x1b(B\x1b[m"
+
class BaseWidget(object):
def __init__(self, terminal=None):
@@ -39,7 +41,16 @@ class Footer(BaseWidget):
for part in parts:
try:
func, part = part
- encoded = getattr(self.term, func)(part)
+ attribute = getattr(self.term, func)
+ # In Blessed, these attributes aren't always callable
+ if callable(attribute):
+ encoded = attribute(part)
+ else:
+ # If it's not callable, assume it's just the raw
+ # ANSI Escape Sequence and prepend it ourselves.
+ # Append DEFAULT to stop text that comes afterwards
+ # from inheriting the formatting we prepended.
+ encoded = attribute + part + DEFAULT
except ValueError:
encoded = part
diff --git a/python/mozterm/test/test_terminal.py b/python/mozterm/test/test_terminal.py
--- a/python/mozterm/test/test_terminal.py
+++ b/python/mozterm/test/test_terminal.py
@@ -9,32 +9,17 @@ import sys
import mozunit
import pytest
-
-from mozterm import Terminal, NullTerminal
+from mozterm import NullTerminal, Terminal
def test_terminal():
- blessings = pytest.importorskip("blessings")
+ blessed = pytest.importorskip("blessed")
term = Terminal()
- assert isinstance(term, blessings.Terminal)
+ assert isinstance(term, blessed.Terminal)
term = Terminal(disable_styling=True)
assert isinstance(term, NullTerminal)
- del sys.modules["blessings"]
- orig = sys.path[:]
- for path in orig:
- if "blessings" in path:
- sys.path.remove(path)
-
- term = Terminal()
- assert isinstance(term, NullTerminal)
-
- with pytest.raises(ImportError):
- term = Terminal(raises=True)
-
- sys.path = orig
-
def test_null_terminal():
term = NullTerminal()
diff --git a/python/mozterm/test/test_widgets.py b/python/mozterm/test/test_widgets.py
--- a/python/mozterm/test/test_widgets.py
+++ b/python/mozterm/test/test_widgets.py
@@ -4,41 +4,42 @@
from __future__ import absolute_import, unicode_literals
+import sys
from io import StringIO
import mozunit
import pytest
-
from mozterm import Terminal
from mozterm.widgets import Footer
@pytest.fixture
-def terminal(monkeypatch):
- blessings = pytest.importorskip("blessings")
+def terminal():
+ blessed = pytest.importorskip("blessed")
kind = "xterm-256color"
try:
term = Terminal(stream=StringIO(), force_styling=True, kind=kind)
- except blessings.curses.error:
+ except blessed.curses.error:
pytest.skip("terminal '{}' not found".format(kind))
- # For some reason blessings returns None for width/height though a comment
- # says that shouldn't ever happen.
- monkeypatch.setattr(term, "_height_and_width", lambda: (100, 100))
return term
+@pytest.mark.skipif(
+ not sys.platform.startswith("win"),
+ reason="Only do ANSI Escape Sequence comparisons on Windows.",
+)
def test_footer(terminal):
footer = Footer(terminal=terminal)
footer.write(
[
- ("dim", "foo"),
+ ("bright_black", "foo"),
("green", "bar"),
]
)
value = terminal.stream.getvalue()
- expected = "\x1b7\x1b[2mfoo\x1b(B\x1b[m \x1b[32mbar\x1b(B\x1b[m\x1b8"
+ expected = "\x1b7\x1b[90mfoo\x1b(B\x1b[m \x1b[32mbar\x1b(B\x1b[m\x1b8"
assert value == expected
footer.clear()
diff --git a/python/mozversioncontrol/mozversioncontrol/__init__.py b/python/mozversioncontrol/mozversioncontrol/__init__.py
--- a/python/mozversioncontrol/mozversioncontrol/__init__.py
+++ b/python/mozversioncontrol/mozversioncontrol/__init__.py
@@ -222,6 +222,16 @@ class Repository(object):
"""
@abc.abstractmethod
+ def get_ignored_files_finder(self):
+ """Obtain a mozpack.files.BaseFinder of ignored files in the working
+ directory.
+
+ The Finder will have its list of all files in the repo cached for its
+ entire lifetime, so operations on the Finder will not track with, for
+ example, changes to the repo during the Finder's lifetime.
+ """
+
+ @abc.abstractmethod
def working_directory_clean(self, untracked=False, ignored=False):
"""Determine if the working directory is free of modifications.
@@ -501,6 +511,15 @@ class HgRepository(Repository):
)
return FileListFinder(files)
+ def get_ignored_files_finder(self):
+ # Can return backslashes on Windows. Normalize to forward slashes.
+ files = list(
+ p.replace("\\", "/").split(" ")[-1]
+ for p in self._run("status", "-i").split("\n")
+ if p
+ )
+ return FileListFinder(files)
+
def working_directory_clean(self, untracked=False, ignored=False):
args = ["status", "--modified", "--added", "--removed", "--deleted"]
if untracked:
@@ -675,6 +694,16 @@ class GitRepository(Repository):
files = [p for p in self._run("ls-files", "-z").split("\0") if p]
return FileListFinder(files)
+ def get_ignored_files_finder(self):
+ files = [
+ p
+ for p in self._run(
+ "ls-files", "-i", "-o", "-z", "--exclude-standard"
+ ).split("\0")
+ if p
+ ]
+ return FileListFinder(files)
+
def working_directory_clean(self, untracked=False, ignored=False):
args = ["status", "--porcelain"]
diff --git a/python/sites/mach.txt b/python/sites/mach.txt
--- a/python/sites/mach.txt
+++ b/python/sites/mach.txt
@@ -42,10 +42,10 @@ pth:testing/mozbase/mozsystemmonitor
pth:testing/mozbase/mozscreenshot
pth:testing/mozbase/moztest
pth:testing/mozbase/mozversion
+pth:testing/mozharness
pth:testing/raptor
pth:testing/talos
pth:testing/web-platform
-vendored:testing/web-platform/tests/tools/third_party/funcsigs
vendored:testing/web-platform/tests/tools/third_party/h2
vendored:testing/web-platform/tests/tools/third_party/hpack
vendored:testing/web-platform/tests/tools/third_party/html5lib
@@ -139,5 +139,5 @@ pypi-optional:glean-sdk==51.8.2:telemetr
# Mach gracefully handles the case where `psutil` is unavailable.
# We aren't (yet) able to pin packages in automation, so we have to
# support down to the oldest locally-installed version (5.4.2).
-pypi-optional:psutil>=5.4.2,<=5.8.0:telemetry will be missing some data
-pypi-optional:zstandard>=0.11.1,<=0.17.0:zstd archives will not be possible to extract
+pypi-optional:psutil>=5.4.2,<=5.9.4:telemetry will be missing some data
+pypi-optional:zstandard>=0.11.1,<=0.19.0:zstd archives will not be possible to extract