tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 8fa480f29a0843345e6f117c1a2be6ff4d6b1795
parent eade387da45eb7b220989935c9b587b2cf4d2141
Author: Alex Hochheiden <ahochheiden@mozilla.com>
Date:   Fri, 14 Nov 2025 14:23:32 +0000

Bug 1999798 - Add 'requirements-txt' specifier to support `requirement.txt` files in `<site>.txt` files r=ahal

Differential Revision: https://phabricator.services.mozilla.com/D272408

Diffstat:
Mpython/mach/mach/requirements.py | 24+++++++++++++++++++++++-
Mpython/mach/mach/site.py | 14++++++++++++++
Mpython/mozbuild/mozbuild/lockfiles/site_dependency_extractor.py | 19+++++++++++++++++++
3 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/python/mach/mach/requirements.py b/python/mach/mach/requirements.py @@ -28,6 +28,11 @@ class PypiOptionalSpecifier(PypiSpecifier): self.repercussion = repercussion +class RequirementsTxtSpecifier: + def __init__(self, path: str): + self.path = path + + class MachEnvRequirements: """Requirements associated with a "site dependency manifest", as defined in "python/sites/". @@ -48,6 +53,10 @@ class MachEnvRequirements: pypi-optional -- Attempt to install the package and dependencies from PyPI. Continue using the site, even if the package could not be installed. + requirements-txt -- Specifies a path to a 'requirements.txt' lockfile. + All requirements from the lockfile will be installed with hash + validation. All entries must include hashes. + packages.txt -- Denotes that the specified path is a child manifest. It will be read and processed as if its contents were concatenated into the manifest being read. @@ -65,6 +74,7 @@ class MachEnvRequirements: self.pypi_optional_requirements = [] self.vendored_requirements = [] self.vendored_fallback_requirements = [] + self.requirements_txt_files = [] def pths_as_absolute(self, topsrcdir: str): return [ @@ -189,13 +199,25 @@ def _parse_mach_env_requirements( _parse_package_specifier(raw_requirement, only_strict_requirements), ) ) + elif action == "requirements-txt": + if is_thunderbird_packages_txt: + raise Exception(THUNDERBIRD_PYPI_ERROR) + + requirements_txt_path = topsrcdir / params + if not requirements_txt_path.exists(): + raise Exception( + f"requirements.txt file not found: {requirements_txt_path}" + ) + + req_txt_specifier = RequirementsTxtSpecifier(str(requirements_txt_path)) + requirements_output.requirements_txt_files.append(req_txt_specifier) elif action == "thunderbird-packages.txt": if is_thunderbird: _parse_requirements_definition_file( topsrcdir / params, is_thunderbird_packages_txt=True ) else: - raise Exception("Unknown requirements definition action: %s" % action) + raise Exception(f"Unknown requirements definition action: {action}") def _parse_requirements_definition_file( requirements_path: Path, is_thunderbird_packages_txt diff --git a/python/mach/mach/site.py b/python/mach/mach/site.py @@ -1534,6 +1534,10 @@ def _create_venv_with_pthfile( os.environ["VIRTUAL_ENV"] = virtualenv_root if populate_with_pip: + for requirements_txt_file in requirements.requirements_txt_files: + target_venv.pip_install( + ["--requirement", requirements_txt_file.path, "--require-hashes"] + ) if requirements.pypi_requirements: requirements_list = [ str(req.requirement) for req in requirements.pypi_requirements @@ -1568,6 +1572,16 @@ def _is_venv_up_to_date( False, f'"{dep_file}" has changed since the virtualenv was created' ) + for requirements_txt_file in requirements.requirements_txt_files: + req_txt_path = requirements_txt_file.path + if ( + os.path.exists(req_txt_path) + and os.path.getmtime(req_txt_path) > metadata_mtime + ): + return SiteUpToDateResult( + False, f'"{req_txt_path}" has changed since the virtualenv was created' + ) + try: existing_metadata = MozSiteMetadata.from_path(target_venv.prefix) except MozSiteMetadataOutOfDateError as e: diff --git a/python/mozbuild/mozbuild/lockfiles/site_dependency_extractor.py b/python/mozbuild/mozbuild/lockfiles/site_dependency_extractor.py @@ -49,6 +49,7 @@ class SiteDependencyExtractor: "pypi-optional": self._handle_pypi, "vendored": self._handle_vendored, "vendored-fallback": self._handle_vendored_fallback, + "requirements-txt": self._handle_requirements_txt, } for raw in self.site_file.read_text().splitlines(): @@ -121,6 +122,24 @@ class SiteDependencyExtractor: raise DependencyParseError(f"Missing version in pypi spec: '{pkg_spec}'") self.dependencies.append(Dependency(name=name, version=version, path=None)) + def _handle_requirements_txt(self, rest: str) -> None: + requirements_txt_path = Path(self.topsrcdir) / rest + if not requirements_txt_path.exists(): + raise DependencyParseError( + f"requirements.txt file not found: {requirements_txt_path}" + ) + + with requirements_txt_path.open(encoding="utf-8") as f: + for raw_line in f: + line = raw_line.strip() + + if not line or line.startswith("#") or line.startswith("-"): + continue + + pypi_requirement_spec = line.split("\\")[0].strip().rstrip() + if pypi_requirement_spec: + self._handle_pypi(pypi_requirement_spec) + def _version_from_metadata_files(self, path: Path) -> Optional[str]: def _extract_version(file_path: Path) -> Optional[str]: try: