Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-build for openSUSE:Factory checked in at 2026-05-04 21:17:09 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-build (Old) and /work/SRC/openSUSE:Factory/.python-build.new.30200 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-build" Mon May 4 21:17:09 2026 rev:18 rq:1350557 version:1.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-build/python-build.changes 2026-04-18 21:31:44.962270897 +0200 +++ /work/SRC/openSUSE:Factory/.python-build.new.30200/python-build.changes 2026-05-04 21:17:16.759989261 +0200 @@ -1,0 +2,18 @@ +Sun May 3 18:04:12 UTC 2026 - Dirk Müller <[email protected]> + +- update to 1.5.0: + * Drop Python 3.9 support - by :user:`henryiii` (:issue:`1036`) + * Make --ignore-installed opt-in from the API via fresh=True - + by :user:`henryiii` (:issue:`1056`) + * Fix release pipeline generating CHANGELOG.rst entries with + inconsistent heading levels, which broke sphinx -W and pinned + Read the Docs stable at 1.4.0 - by :user:`gaborbernat`. + * Revert :pr:`1039` from build 1.4.3, no longer check + direct_url (for now) - by :user:`henryiii` (:issue:`1039`) + * Add --ignore-installed to pip install command to prevent + issues with packages already present in the isolated build + environment - by :user:`henryiii` (:issue:`1037`) + * Partial revert of :pr:`973`, keeping log messages in one + entry, multiple lines. (:issue:`1044`) + +------------------------------------------------------------------- Old: ---- build-1.4.3.tar.gz New: ---- build-1.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-build.spec ++++++ --- /var/tmp/diff_new_pack.ixrRxu/_old 2026-05-04 21:17:17.288010884 +0200 +++ /var/tmp/diff_new_pack.ixrRxu/_new 2026-05-04 21:17:17.292011048 +0200 @@ -33,7 +33,7 @@ %endif %{?sle15_python_module_pythons} Name: python-build%{psuffix} -Version: 1.4.3 +Version: 1.5.0 Release: 0 Summary: Simple PEP517 package builder License: MIT ++++++ build-1.4.3.tar.gz -> build-1.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/.github/workflows/pre-release.yml new/build-1.5.0/.github/workflows/pre-release.yml --- old/build-1.4.3/.github/workflows/pre-release.yml 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/.github/workflows/pre-release.yml 2026-04-30 05:17:22.000000000 +0200 @@ -29,7 +29,7 @@ persist-credentials: false token: ${{ secrets.GH_RELEASE_TOKEN }} - name: Install the latest version of uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true cache-dependency-glob: "pyproject.toml" @@ -45,6 +45,8 @@ git config user.email "$(echo "$user_info" | jq -r '.id')+$(echo "$user_info" | jq -r '.login')@users.noreply.github.com" - name: Generate release commit and tag run: uv tool run --with tox-uv tox r -e release -- --version "${{ inputs.bump }}" --no-push + - name: Verify docs build against the release commit + run: uv tool run --with tox-uv tox r -e docs - name: Push release commit and tag run: | git remote set-url origin "https://x-access-token:${GH_RELEASE_TOKEN}@github.com/${{ github.repository }}.git" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/.github/workflows/reusable-change-detection.yml new/build-1.5.0/.github/workflows/reusable-change-detection.yml --- old/build-1.4.3/.github/workflows/reusable-change-detection.yml 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/.github/workflows/reusable-change-detection.yml 2026-04-30 05:17:22.000000000 +0200 @@ -29,7 +29,7 @@ filter: | src/** tests/** - tox.ini + tox.toml pyproject.toml .github/workflows/test.yml .github/workflows/reusable-type.yml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/.github/workflows/reusable-pytest.yml new/build-1.5.0/.github/workflows/reusable-pytest.yml --- old/build-1.4.3/.github/workflows/reusable-pytest.yml 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/.github/workflows/reusable-pytest.yml 2026-04-30 05:17:22.000000000 +0200 @@ -15,15 +15,14 @@ - macos - windows py: - - "pypy3.9-v7.3.14" - "pypy3.10-v7.3.19" - "pypy3.11-v7.3.19" + - "3.15" - "3.14" - "3.13" - "3.12" - "3.11" - "3.10" - - "3.9" tox-target: - "tox" - "min" @@ -45,7 +44,7 @@ persist-credentials: false - name: Setup uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - name: Install tox (uv) run: uv tool install tox --with tox-uv @@ -56,33 +55,22 @@ python-version: ${{ matrix.py }} allow-prereleases: true - - name: Pick environment to run - run: | - import platform - import os - import sys - - if platform.python_implementation() == "PyPy": - base = f"pypy{sys.version_info.major}{sys.version_info.minor}" - else: - base = f"py{sys.version_info.major}{sys.version_info.minor}" - env = f"BASE={base}\n" - print(f"Picked:\n{env}for {sys.version}") - with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as file: - file.write(env) - shell: python - - name: Run test suite via tox if: matrix.tox-target == 'tox' shell: bash run: | + PYTHON_VERSION=${{ matrix.py }} + BASE=${PYTHON_VERSION%-*} tox -vv --notest -e $BASE tox -e $BASE --skip-pkg-install - name: Run minimum version test if: matrix.tox-target == 'min' shell: bash - run: tox -e $BASE-${{ matrix.tox-target }} + run: | + PYTHON_VERSION=${{ matrix.py }} + BASE=${PYTHON_VERSION%-*} + tox -e ${{ matrix.tox-target }}-$BASE - name: Run path test if: matrix.tox-target == 'tox' && matrix.py == '3.10' @@ -100,4 +88,4 @@ files: .tox/coverage.xml flags: tests env_vars: PYTHON - name: ${{ matrix.py }} - ${{ matrix.os }} + name: ${{ matrix.py }} - ${{ matrix.os }} - ${{ matrix.tox-target }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/.github/workflows/reusable-type.yml new/build-1.5.0/.github/workflows/reusable-type.yml --- old/build-1.4.3/.github/workflows/reusable-type.yml 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/.github/workflows/reusable-type.yml 2026-04-30 05:17:22.000000000 +0200 @@ -13,10 +13,10 @@ with: persist-credentials: false - - name: Setup Python 3.9 + - name: Setup Python 3.10 uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: - python-version: 3.9 + python-version: "3.10" - name: Install tox run: python -m pip install tox diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/.pre-commit-config.yaml new/build-1.5.0/.pre-commit-config.yaml --- old/build-1.4.3/.pre-commit-config.yaml 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/.pre-commit-config.yaml 2026-04-30 05:17:22.000000000 +0200 @@ -26,7 +26,7 @@ - id: blacken-docs additional_dependencies: [black==25.*] - repo: https://github.com/rbubley/mirrors-prettier - rev: "v3.8.1" + rev: "v3.8.3" hooks: - id: prettier - repo: https://github.com/LilSpazJoekp/docstrfmt @@ -37,7 +37,7 @@ additional_dependencies: ["sphinx>=9.1"] exclude: ^docs/index\.rst$ # https://github.com/LilSpazJoekp/docstrfmt/issues/176 - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.9 + rev: v0.15.12 hooks: - id: ruff-check args: [--fix, --show-fixes] @@ -53,12 +53,8 @@ - id: rst-backticks - id: rst-directive-colons - id: rst-inline-touching-normal - - repo: https://github.com/tox-dev/tox-ini-fmt - rev: "1.7.1" - hooks: - - id: tox-ini-fmt - repo: https://github.com/zizmorcore/zizmor-pre-commit - rev: v1.23.1 + rev: v1.24.1 hooks: - id: zizmor - repo: local diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/CHANGELOG.rst new/build-1.5.0/CHANGELOG.rst --- old/build-1.4.3/CHANGELOG.rst 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/CHANGELOG.rst 2026-04-30 05:17:22.000000000 +0200 @@ -1,19 +1,62 @@ -******************** +#################### + 1.5.0 (2026-04-30) +#################### + +********** + Features +********** + +- Drop Python 3.9 support - by :user:`henryiii` (:issue:`1036`) + +********** + Bugfixes +********** + +- Make ``--ignore-installed`` opt-in from the API via ``fresh=True`` - by :user:`henryiii` (:issue:`1056`) + +*************** + Miscellaneous +*************** + +- :issue:`1033` + +#################### + 1.4.4 (2026-04-22) +#################### + +********** + Bugfixes +********** + +- Fix release pipeline generating ``CHANGELOG.rst`` entries with inconsistent heading levels, which broke ``sphinx -W`` + and pinned Read the Docs ``stable`` at 1.4.0 - by :user:`gaborbernat`. (:issue:`1031`) +- Revert :pr:`1039` from build 1.4.3, no longer check direct_url (for now) - by :user:`henryiii` (:issue:`1039`) +- Add ``--ignore-installed`` to pip install command to prevent issues with packages already present in the isolated + build environment - by :user:`henryiii` (:issue:`1037`) (:issue:`1040`) +- Partial revert of :pr:`973`, keeping log messages in one entry, multiple lines. (:issue:`1044`) + +*************** + Miscellaneous +*************** + +- :issue:`1048`, :issue:`1049` + +#################### 1.4.3 (2026-04-10) -******************** +#################### -========== +********** Features -========== +********** - Add ``kind`` parameter to log messages to separate semantic and representation - by :user:`abitrolly` (:issue:`973`) -========== +********** Bugfixes -========== +********** -- Strip ``PYTHONPATH`` from the environment during isolated builds to prevent host packages from leaking into the build - - by :user:`gaborbernat` (:issue:`405`) +- Strip ``PYTHONPATH`` from the environment during isolated builds to prevent host packages from leaking into the build + - by :user:`gaborbernat` (:issue:`405`) - Pass ``--no-input`` to pip to prevent hidden credential prompts that cause hangs, and automatically set ``PIP_KEYRING_PROVIDER=subprocess`` (or ``UV_KEYRING_PROVIDER=subprocess`` for the uv installer) when the ``keyring`` CLI is on ``PATH`` -- by :user:`gaborbernat` (:issue:`409`) @@ -25,9 +68,9 @@ - Resolve thread-safety races in the build API - by :user:`gaborbernat` (:issue:`1015`) - Validate ``backend-path`` entries exist on disk with a clear error - by :user:`gaborbernat` (:issue:`1016`) -=============== +*************** Miscellaneous -=============== +*************** - :issue:`1020`, :issue:`1021` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/docs/conf.py new/build-1.5.0/docs/conf.py --- old/build-1.4.3/docs/conf.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/docs/conf.py 2026-04-30 05:17:22.000000000 +0200 @@ -16,7 +16,7 @@ if hasattr(__builtins__, 'EncodingWarning'): - warnings.filterwarnings('ignore', category=EncodingWarning, module='sphinx_copybutton') # noqa: F821 + warnings.filterwarnings('ignore', category=EncodingWarning, module='sphinx_copybutton') # -- Project information ----------------------------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/docs/development/contributing.rst new/build-1.5.0/docs/development/contributing.rst --- old/build-1.4.3/docs/development/contributing.rst 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/docs/development/contributing.rst 2026-04-30 05:17:22.000000000 +0200 @@ -41,24 +41,42 @@ Running Tests *************** -Due to its nature, build has a somewhat complex test suite with two sets of tests: unit tests and integration tests. +Due to its nature, ``build`` has a somewhat complex test suite with two sets of tests: unit tests and integration tests. Unit tests verify the actual code implementation, while integration tests run build on real world projects as a sanity -check. Integration tests take a long time to run and are not very helpful for tracking down issues, so they are disabled -by default. They can be enabled by passing either ``--run-integration`` or ``--only-integration`` arguments to pytest, -where the latter will disable unit tests and only run integration tests. Even though these tests are disabled by -default, they will be run in CI where test suite run durations are not a big issue. - -To run the test suite, use tox which automates running tests on different environments. Simply run ``tox`` in the -project directory to execute the full test suite. The project has a fairly large environment matrix, running tests for -all supported Python versions and implementations, and with the module being invoked directly from path, sdist install, -or wheel install. Additionally, there are environments for type checking and documentation building, plus extras like -checking code with minimum versions of dependencies. +check. To run tests we use ``tox``. Some example commands for this project include running type checking with ``tox -e type``, running only unit tests -against Python 3.9 with ``tox -e py39``, running both unit and integration tests with ``tox -- --run-integration``, -running only integration tests with ``tox -- --only-integration``, or running only integration tests with parallel tasks -using ``tox -- -n auto --only-integration``. You can also run unit tests against a specific Python version with wheel -installation using ``tox -e py39-wheel``. +against Python 3.14 with ``tox run -e 3.14``, running both unit and integration tests with ``tox run -- +--run-integration``, running only integration tests with ``tox run -- --only-integration``, or running only integration +tests with parallel tasks using ``tox run -- -n auto --only-integration``. + +Tests run in parallel by default, but if you pass any arguments, you need to include ``-n auto`` if you want to keep +parallel runs. + +Integration tests take a long time to run, so they are disabled by default. Passing either ``--run-integration`` or +``--only-integration`` arguments through ``tox`` to ``pytest`` will run them, where the latter will disable unit tests +and only run integration tests. CI still runs both test suites. + +.. code-block:: console + + tox -- -n auto --only-integration + +The project has a fairly large environment matrix, running tests for all supported Python versions and implementations, +and with the module being invoked directly from path, sdist install, or wheel install. To run tests only for Python +3.14: + +.. code-block:: console + + tox -e 3.14 + +and with the module being invoked directly from path, sdist install, or wheel install. + +Additionally, there are environments for type checking and documentation building, plus extras like checking code with +minimum versions of dependencies. For type checking, + +.. code-block:: console + + tox -e type Code coverage is tracked to ensure all code paths are tested. Aim for complete coverage of any new code you add. The CI system will report coverage metrics on your pull request and runs the test suite across all supported operating systems. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/docs/development/release.rst new/build-1.5.0/docs/development/release.rst --- old/build-1.4.3/docs/development/release.rst 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/docs/development/release.rst 2026-04-30 05:17:22.000000000 +0200 @@ -105,4 +105,4 @@ The automation consists of the pre-release workflow at ``.github/workflows/pre-release.yml`` for manual triggering, the CD workflow at ``.github/workflows/cd.yml`` that triggers on tag pushes, the release script at ``tasks/release.py`` for -version bumping and tag creation, and the ``[testenv:release]`` environment in ``tox.ini`` providing dependencies. +version bumping and tag creation, and the ``[env.release]`` environment in ``tox.toml`` providing dependencies. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/docs/how-to/choosing-tools.rst new/build-1.5.0/docs/how-to/choosing-tools.rst --- old/build-1.4.3/docs/how-to/choosing-tools.rst 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/docs/how-to/choosing-tools.rst 2026-04-30 05:17:22.000000000 +0200 @@ -83,7 +83,7 @@ Use `tox <https://tox.wiki/>`_ when you need to: -- Test your package across multiple Python versions (e.g., 3.9, 3.10, 3.11) +- Test your package across multiple Python versions (e.g., 3.10, 3.11, 3.12, 3.13, 3.14) - Run tests in `isolated environments <https://packaging.python.org/en/latest/glossary/#term-Virtual-Environment>`_ - Automate testing workflows - Run linters (code checkers), formatters, type checkers @@ -137,7 +137,7 @@ import nox - @nox.session(python=["3.8", "3.9", "3.10", "3.11", "3.12"]) + @nox.session(python=["3.10", "3.11", "3.12", "3.13", "3.14"]) def tests(session): session.install("pytest", "pytest-cov") session.run("pytest", "tests") @@ -232,7 +232,7 @@ runs-on: ubuntu-latest strategy: matrix: - python: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/docs/how-to/ci-cd.rst new/build-1.5.0/docs/how-to/ci-cd.rst --- old/build-1.4.3/docs/how-to/ci-cd.rst 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/docs/how-to/ci-cd.rst 2026-04-30 05:17:22.000000000 +0200 @@ -140,7 +140,7 @@ runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 @@ -292,17 +292,13 @@ paths: - dist/ - build:py38: + build:py310: extends: .build_template - image: python:3.8 + image: python:3.10 - build:py39: + build:py314: extends: .build_template - image: python:3.9 - - build:py312: - extends: .build_template - image: python:3.12 + image: python:3.14 ********** CircleCI @@ -346,11 +342,11 @@ language: python python: - - "3.8" - - "3.9" - "3.10" - "3.11" - "3.12" + - "3.13" + - "3.14" install: - pip install build @@ -375,10 +371,10 @@ strategy: matrix: - Python38: - python.version: '3.8' - Python312: - python.version: '3.12' + Python310: + python.version: '3.10' + Python314: + python.version: '3.14' steps: - task: UsePythonVersion@0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/docs/how-to/install.rst new/build-1.5.0/docs/how-to/install.rst --- old/build-1.4.3/docs/how-to/install.rst 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/docs/how-to/install.rst 2026-04-30 05:17:22.000000000 +0200 @@ -87,13 +87,11 @@ ``build`` is verified to be compatible with the following Python versions: -- 3.9 - 3.10 - 3.11 - 3.12 - 3.13 - 3.14 -- PyPy 3.9 - PyPy 3.10 - PyPy 3.11 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/docs/index.rst new/build-1.5.0/docs/index.rst --- old/build-1.4.3/docs/index.rst 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/docs/index.rst 2026-04-30 05:17:22.000000000 +0200 @@ -95,14 +95,14 @@ development/contributing development/release + Source Code <https://github.com/pypa/build/> + Issue Tracker <https://github.com/pypa/build/issues> .. toctree:: - :caption: Project + :caption: Changelog :hidden: changelog - Source Code <https://github.com/pypa/build/> - Issue Tracker <https://github.com/pypa/build/issues> .. _pip: https://github.com/pypa/pip .. _PyPI: https://pypi.org/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/docs/tutorial/getting-started.rst new/build-1.5.0/docs/tutorial/getting-started.rst --- old/build-1.4.3/docs/tutorial/getting-started.rst 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/docs/tutorial/getting-started.rst 2026-04-30 05:17:22.000000000 +0200 @@ -14,7 +14,7 @@ Prerequisites *************** -You need Python 3.9 or later installed on your system. Check your Python version: +You need Python 3.10 or later installed on your system. Check your Python version: .. code-block:: console diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/pyproject.toml new/build-1.5.0/pyproject.toml --- old/build-1.4.3/pyproject.toml 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/pyproject.toml 2026-04-30 05:17:22.000000000 +0200 @@ -6,7 +6,7 @@ name = "build" description = "A simple, correct Python build frontend" readme = "README.md" -requires-python = ">= 3.9" +requires-python = ">= 3.10" license = "MIT" authors = [ { name = "Filipe Laíns", email = "[email protected]" }, @@ -17,7 +17,7 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -49,7 +49,6 @@ "keyring", ] virtualenv = [ - "virtualenv >= 20.11; python_version < '3.10'", "virtualenv >= 20.17; python_version >= '3.10' and python_version < '3.14'", "virtualenv >= 20.31; python_version >= '3.14'", ] @@ -65,6 +64,17 @@ "uv >= 0.1.18", "virtualenv >= 20.0.35", ] +lint = [ + "prek", +] +coverage = [ + "covdefaults >= 2.3", + "coverage[toml] >= 5.1", + "diff_cover >= 3", +] +bump = [ + "bump-my-version >= 0.10", +] docs = [ "furo >= 2025.12.19", "pre-commit >= 3.0", @@ -87,11 +97,11 @@ "pytest-rerunfailures >= 9.1", "pytest-xdist >= 1.34", "wheel >= 0.36.0", - 'setuptools >= 42.0.0; python_version < "3.10"', 'setuptools >= 56.0.0; python_version == "3.10"', 'setuptools >= 56.0.0; python_version == "3.11"', 'setuptools >= 67.8.0; python_version >= "3.12"', "setuptools_scm >= 6", + "pip >= 22.3", { include-group = "extra" }, ] typing = [ @@ -120,14 +130,19 @@ ] [tool.flit.sdist] -include = ["tests/", ".gitignore", "CHANGELOG.rst", "docs/", ".dockerignore", "tox.ini"] +include = ["tests/", ".gitignore", "CHANGELOG.rst", "docs/", ".dockerignore", "tox.toml"] exclude = ["**/__pycache__", "docs/_build", "**/*.egg-info", "tests/packages/*/build", "docs/changelog/template.jinja2"] +[tool.uv] +# Our docs dependencies do not support 3.9, so remove it from the uv solve +environments = ["python_version >= '3.10'"] + [tool.coverage] run.plugins = ["covdefaults"] run.source = ["build", "tests"] -run.omit = ["tests/conftest.py", "tests/test_integration.py"] +run.omit = ["tests/conftest.py", "tests/test_integration.py",] +report.omit = ["src/build/_types.py"] run.disable_warnings = [ "module-not-measured", # Triggers in multithreaded context on build "no-sysmon", @@ -164,12 +179,13 @@ "ignore:check_home argument is deprecated and ignored:DeprecationWarning:", # PyPy 3.11 "default:Python 3.14 will, by default, filter extracted tar archives:DeprecationWarning", "default:unclosed:ResourceWarning", # Python 3.11 Windows + "ignore:os.path.commonprefix:DeprecationWarning" # https://github.com/pypa/pyproject-hooks/pull/222 ] [tool.mypy] files = ["src", "tests"] exclude = ["tests/packages"] -python_version = "3.9" +python_version = "3.10" strict = true enable_error_code = ["ignore-without-code", "truthy-bool", "redundant-expr"] warn_unreachable = true @@ -214,6 +230,7 @@ [tool.ruff.lint.flake8-tidy-imports.banned-api] "typing.TYPE_CHECKING".msg = "Use TYPE_CHECKING=False instead" +"os.path.commonprefix".msg = "Use os.path.commonpath or manually implement string manip" [tool.ruff.lint.per-file-ignores] "tasks/**.py" = ["T20", "INP", "S607"] @@ -244,8 +261,7 @@ title_format = false issue_format = ":issue:`{issue}`" template = "docs/changelog/template.jinja2" -underlines = ["*", "=", "-"] -top_underline = "#" +underlines = ["#", "*", "=", "-"] [[tool.towncrier.section]] path = "" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/src/build/__init__.py new/build-1.5.0/src/build/__init__.py --- old/build-1.4.3/src/build/__init__.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/src/build/__init__.py 2026-04-30 05:17:22.000000000 +0200 @@ -26,7 +26,7 @@ from ._util import check_dependency -__version__ = '1.4.3' +__version__ = '1.5.0' __all__ = [ 'BuildBackendException', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/src/build/__main__.py new/build-1.5.0/src/build/__main__.py --- old/build-1.4.3/src/build/__main__.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/src/build/__main__.py 2026-04-30 05:17:22.000000000 +0200 @@ -105,15 +105,20 @@ def log(message: str, *, kind: tuple[str, ...] | None = None) -> None: if _ctx.verbosity >= -1: - if kind is None: - print(fill(message, initial_indent=' '), file=sys.stderr) # noqa: T201 - elif kind[0] == 'step': - _cprint('{bold}{}{reset}', fill(message, initial_indent='* '), file=sys.stderr) - - elif kind[0] == 'subprocess': - initial_indent = '> ' if kind[1] == 'cmd' else '< ' - for line in message.splitlines(): - _cprint('{dim}{}{reset}', fill(line, initial_indent=initial_indent), file=sys.stderr) + match kind: + case ('step', *_): + (first, *rest) = message.splitlines() + _cprint('{bold}{}{reset}', fill(first, initial_indent='* '), file=sys.stderr) + for line in rest: + print(fill(line, initial_indent=' '), file=sys.stderr) # noqa: T201 + case ('subprocess', 'cmd'): + for line in message.splitlines(): + _cprint('{dim}{}{reset}', fill(line, initial_indent='> '), file=sys.stderr) + case ('subprocess', 'stdout' | 'stderr'): + for line in message.splitlines(): + _cprint('{dim}{}{reset}', fill(line, initial_indent='< '), file=sys.stderr) + case _: + print(fill(message, initial_indent=' '), file=sys.stderr) # noqa: T201 return log @@ -174,7 +179,7 @@ install = partial(install, constraints=set(map(str.strip, dependency_constraints_file))) # first install the build dependencies - install(builder.build_system_requires) + install(builder.build_system_requires, _fresh=True) # then get the extra required dependencies from the backend (which was installed in the call above :P) install(builder.get_requires_for_build(distribution, config_settings)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/src/build/_builder.py new/build-1.5.0/src/build/_builder.py --- old/build-1.4.3/src/build/_builder.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/src/build/_builder.py 2026-04-30 05:17:22.000000000 +0200 @@ -114,21 +114,24 @@ msg = '`requires` must be an array of strings' raise BuildSystemTableValidationError(msg) - if 'build-backend' not in build_system_table: - _find_typo(build_system_table, 'build-backend') - # If ``build-backend`` is missing, inject the legacy setuptools backend - # but leave ``requires`` intact to emulate pip - build_system_table['build-backend'] = _DEFAULT_BACKEND['build-backend'] - elif not isinstance(build_system_table['build-backend'], str): - msg = '`build-backend` must be a string' - raise BuildSystemTableValidationError(msg) + match build_system_table: + case {'build-backend': str()}: + pass + case {'build-backend': _}: + msg = '`build-backend` must be a string' + raise BuildSystemTableValidationError(msg) + case _: + _find_typo(build_system_table, 'build-backend') + # If ``build-backend`` is missing, inject the legacy setuptools backend + # but leave ``requires`` intact to emulate pip + build_system_table['build-backend'] = _DEFAULT_BACKEND['build-backend'] - if 'backend-path' in build_system_table and ( - not isinstance(build_system_table['backend-path'], list) - or not all(isinstance(i, str) for i in build_system_table['backend-path']) - ): - msg = '`backend-path` must be an array of strings' - raise BuildSystemTableValidationError(msg) + match build_system_table: + case {'backend-path': list() as backend_path} if all(isinstance(i, str) for i in backend_path): + pass + case {'backend-path': _}: + msg = '`backend-path` must be an array of strings' + raise BuildSystemTableValidationError(msg) unknown_props = build_system_table.keys() - {'requires', 'build-backend', 'backend-path'} if unknown_props: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/src/build/_types.py new/build-1.5.0/src/build/_types.py --- old/build-1.4.3/src/build/_types.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/src/build/_types.py 2026-04-30 05:17:22.000000000 +0200 @@ -1,21 +1,22 @@ from __future__ import annotations +import collections.abc import os import typing __all__ = ['ConfigSettings', 'Distribution', 'StrPath', 'SubprocessRunner'] -ConfigSettings = typing.Mapping[str, typing.Union[str, typing.Sequence[str]]] +ConfigSettings = collections.abc.Mapping[str, str | collections.abc.Sequence[str]] Distribution = typing.Literal['sdist', 'wheel', 'editable'] -StrPath = typing.Union[str, os.PathLike[str]] +StrPath = str | os.PathLike[str] TYPE_CHECKING = False if TYPE_CHECKING: from pyproject_hooks import SubprocessRunner else: - SubprocessRunner = typing.Callable[ - [typing.Sequence[str], typing.Optional[str], typing.Optional[typing.Mapping[str, str]]], None + SubprocessRunner = collections.abc.Callable[ + [collections.abc.Sequence[str], str | None, collections.abc.Mapping[str, str] | None], None ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/src/build/_util.py new/build-1.5.0/src/build/_util.py --- old/build-1.4.3/src/build/_util.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/src/build/_util.py 2026-04-30 05:17:22.000000000 +0200 @@ -9,8 +9,6 @@ from collections.abc import Iterator from collections.abc import Set as AbstractSet - from ._compat.importlib import metadata - _WHEEL_FILENAME_REGEX = re.compile( r'(?P<distribution>.+)-(?P<version>.+)' @@ -19,22 +17,6 @@ ) -def _url_matches_direct_url(req_url: str, dist: metadata.Distribution) -> bool: - """Check if the installed distribution's origin (PEP 610) matches the requirement URL.""" - import json - - if not (raw := dist.read_text('direct_url.json')): - return False - direct_url: dict[str, object] = json.loads(raw) - url = direct_url.get('url', '') - if isinstance(vcs_info := direct_url.get('vcs_info'), dict): - origin = f'{vcs_info.get("vcs", "")}+{url}' - if requested_revision := vcs_info.get('requested_revision'): - origin += f'@{requested_revision}' - return bool(req_url == origin) - return bool(req_url == url) - - def check_dependency( req_string: str, ancestral_req_strings: tuple[str, ...] = (), parent_extras: AbstractSet[str] = frozenset() ) -> Iterator[tuple[str, ...]]: @@ -73,10 +55,7 @@ # dependency is not installed in the environment. yield (*ancestral_req_strings, normalised_req_string) else: - if req.url and not _url_matches_direct_url(req.url, dist): - # the installed distribution's origin does not match the URL requirement (PEP 610). - yield (*ancestral_req_strings, normalised_req_string) - elif req.specifier and not req.specifier.contains(dist.version, prereleases=True): + if req.specifier and not req.specifier.contains(dist.version, prereleases=True): # the installed version is incompatible. yield (*ancestral_req_strings, normalised_req_string) elif dist.requires: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/src/build/env.py new/build-1.5.0/src/build/env.py --- old/build-1.4.3/src/build/env.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/src/build/env.py 2026-04-30 05:17:22.000000000 +0200 @@ -161,7 +161,13 @@ 'PYTHONPATH': '', } - def install(self, requirements: Collection[str], constraints: Collection[str] = []) -> None: + def install( + self, + requirements: Collection[str], + constraints: Collection[str] = [], + *, + _fresh: bool = False, # Used internally by CLI to support preset PYTHONPATH + ) -> None: """ Install packages from PEP 508 requirements in the isolated build environment. @@ -173,10 +179,11 @@ if not requirements: return - _ctx.log('Installing packages in isolated environment:', kind=('step',)) - for r in sorted(requirements): - _ctx.log(f'- {r}') - self._env_backend.install_dependencies(requirements, constraints) + _ctx.log( + 'Installing packages in isolated environment:\n' + '\n'.join(f'- {r}' for r in sorted(requirements)), + kind=('step',), + ) + self._env_backend.install_dependencies(requirements, constraints, _fresh=_fresh) class _EnvBackend(typing.Protocol): # pragma: no cover @@ -185,7 +192,13 @@ def create(self, path: str) -> None: ... - def install_dependencies(self, requirements: Collection[str], constraints: Collection[str]) -> None: ... + def install_dependencies( + self, + requirements: Collection[str], + constraints: Collection[str], + *, + _fresh: bool = False, + ) -> None: ... @property def display_name(self) -> str: ... @@ -323,7 +336,13 @@ env=_pip_env(), ) - def install_dependencies(self, requirements: Collection[str], constraints: Collection[str]) -> None: + def install_dependencies( + self, + requirements: Collection[str], + constraints: Collection[str], + *, + _fresh: bool = False, + ) -> None: with contextlib.ExitStack() as exit_stack: if self._has_valid_outer_pip: cmd = [sys.executable, '-m', 'pip', '--python', self.python_executable] @@ -333,7 +352,10 @@ if (verbosity := _ctx.verbosity) > 1: cmd += [f'-{"v" * (verbosity - 1)}'] - cmd += ['install', '--use-pep517', '--no-warn-script-location', '--no-compile', '--no-input'] + cmd += ['install'] + if _fresh: + cmd += ['--ignore-installed'] + cmd += ['--use-pep517', '--no-warn-script-location', '--no-compile', '--no-input'] # pip does not honour environment markers in command line arguments # but it does from requirement files. @@ -384,7 +406,11 @@ self.python_executable, self.scripts_dir, _ = _find_executable_and_scripts(self._env_path) def install_dependencies( # pragma: no cover -- uv tests are skipped on PyPy, covered on CPython - self, requirements: Collection[str], constraints: Collection[str] + self, + requirements: Collection[str], + constraints: Collection[str], + *, + _fresh: bool = False, ) -> None: with contextlib.ExitStack() as exit_stack: cmd = [self._uv_bin, 'pip'] @@ -392,8 +418,7 @@ if (verbosity := _ctx.verbosity) > 1: cmd += [f'-{"v" * min(2, verbosity - 1)}'] - cmd += ['install', *requirements] - cmd += ['--python', self.python_executable] + cmd += ['install', *requirements, '--python', self.python_executable] if constraints: with tempfile.NamedTemporaryFile( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/src/build/util.py new/build-1.5.0/src/build/util.py --- old/build-1.4.3/src/build/util.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/src/build/util.py 2026-04-30 05:17:22.000000000 +0200 @@ -54,7 +54,7 @@ source_dir, runner=runner, ) - env.install(builder.build_system_requires) + env.install(builder.build_system_requires, _fresh=True) env.install(builder.get_requires_for_build('wheel')) return _project_wheel_metadata(builder) else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tasks/release.py new/build-1.5.0/tasks/release.py --- old/build-1.4.3/tasks/release.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tasks/release.py 2026-04-30 05:17:22.000000000 +0200 @@ -39,12 +39,13 @@ latest_tag = repo.git.describe('--tags', '--abbrev=0') latest_tag = latest_tag.lstrip('v') parts = [int(x) for x in latest_tag.split('.')] - if version_str == 'major': - parts = [parts[0] + 1, 0, 0] - elif version_str == 'minor': - parts = [parts[0], parts[1] + 1, 0] - elif version_str in {'patch', 'auto'}: - parts[2] += 1 + match version_str: + case 'major': + parts = [parts[0] + 1, 0, 0] + case 'minor': + parts = [parts[0], parts[1] + 1, 0] + case 'patch' | 'auto': + parts[2] += 1 return Version('.'.join(str(p) for p in parts)) @@ -66,6 +67,7 @@ print('build changelog from fragments with towncrier') check_call(['towncrier', 'build', '--yes', '--version', version.public], cwd=str(ROOT_SRC_DIR)) # noqa: S603 call(['pre-commit', 'run', '--all-files'], cwd=str(ROOT_SRC_DIR)) + call(['pre-commit', 'run', '--all-files'], cwd=str(ROOT_SRC_DIR)) repo.git.add('src/build/__init__.py', 'CHANGELOG.rst', 'docs/changelog/*') check_call(['pre-commit', 'run', '--all-files', '--show-diff-on-failure'], cwd=str(ROOT_SRC_DIR)) if repo.is_dirty(index=False, working_tree=True, untracked_files=False): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tests/conftest.py new/build-1.5.0/tests/conftest.py --- old/build-1.4.3/tests/conftest.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tests/conftest.py 2026-04-30 05:17:22.000000000 +0200 @@ -14,10 +14,10 @@ import tempfile import typing -from collections.abc import Generator +from collections.abc import Callable, Generator from functools import partial, update_wrapper from pathlib import Path -from typing import Any, Callable +from typing import Any import pytest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tests/constraints.txt new/build-1.5.0/tests/constraints.txt --- old/build-1.4.3/tests/constraints.txt 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tests/constraints.txt 2026-04-30 05:17:22.000000000 +0200 @@ -1,13 +1,14 @@ importlib-metadata==4.6 packaging==24.0 +pip==22.3; python_version < "3.12" +pip==23.2; python_version >= "3.12" and python_version < "3.15" +pip==25.3; python_version >= "3.15" pyproject_hooks==1.0 -setuptools==42.0.0; python_version < "3.10" setuptools==56.0.0; python_version == "3.10" setuptools==56.0.0; python_version == "3.11" setuptools==67.8.0; python_version >= "3.12" setuptools_scm==6.3.1; python_version < "3.12" tomli==1.1.0 -virtualenv==20.11; python_version < "3.10" -virtualenv==20.17; python_version >= "3.10" and python_version < "3.14" +virtualenv==20.17; python_version < "3.14" virtualenv==20.31; python_version >= "3.14" wheel==0.36.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tests/packages/test-metadata/pyproject.toml new/build-1.5.0/tests/packages/test-metadata/pyproject.toml --- old/build-1.4.3/tests/packages/test-metadata/pyproject.toml 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tests/packages/test-metadata/pyproject.toml 2026-04-30 05:17:22.000000000 +0200 @@ -11,4 +11,3 @@ [tool.black] line-length = 127 skip-string-normalization = true -target-version = ['py39', 'py38', 'py37', 'py36'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tests/test_env.py new/build-1.5.0/tests/test_env.py --- old/build-1.4.3/tests/test_env.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tests/test_env.py 2026-04-30 05:17:22.000000000 +0200 @@ -3,6 +3,8 @@ import importlib.util import logging +import os +import pathlib import shutil import subprocess import sys @@ -48,11 +50,21 @@ @pytest.mark.isolated -def test_isolation() -> None: +def test_isolation(monkeypatch: pytest.MonkeyPatch) -> None: subprocess.check_call([sys.executable, '-c', 'import build.env']) + # Test that demonstrates the PYTHONPATH leak issue (issue #1047) + # When PYTHONPATH is set to include build, and the subprocess env + # is not properly isolated, the import will succeed instead of failing. + # Only fails on 3.15+ (due to lazy loading) + monkeypatch.setenv('PYTHONPATH', os.path.dirname(os.path.dirname(os.path.abspath(build.__file__)))) debug = 'import sys; import os; print(os.linesep.join(sys.path));' - with build.env.DefaultIsolatedEnv() as env, pytest.raises(subprocess.CalledProcessError): - subprocess.check_call([env.python_executable, '-c', f'{debug} import build.env']) + with build.env.DefaultIsolatedEnv() as env: + isolated_env = {**os.environ, **env.make_extra_environ()} + with pytest.raises(subprocess.CalledProcessError): + subprocess.check_call( + [env.python_executable, '-c', f'{debug} import build.env'], + env=isolated_env, + ) @pytest.mark.skipif(IS_PYPY, reason='PyPy3 uses get path to create and provision venv') @@ -129,8 +141,7 @@ assert [(record.levelname, record.message) for record in caplog.records] == [ ('INFO', 'Creating isolated environment: venv+pip...'), - ('INFO', 'Installing packages in isolated environment:'), - ('INFO', '- something'), + ('INFO', 'Installing packages in isolated environment:\n- something'), ] @@ -215,18 +226,20 @@ @pytest.mark.parametrize('verbosity', range(3)) @pytest.mark.parametrize('constraints', [[], ['foo']]) [email protected]('fresh', [False, True]) @pytest.mark.usefixtures('local_pip') def test_default_impl_install_cmd_well_formed( mocker: pytest_mock.MockerFixture, verbosity: int, constraints: list[str], + fresh: bool, ) -> None: mocker.patch.object(build.env._ctx, 'verbosity', verbosity) # type: ignore[attr-defined] with build.env.DefaultIsolatedEnv() as env: run_subprocess = mocker.patch('build.env.run_subprocess') - env.install(['some', 'requirements'], constraints) + env.install(['some', 'requirements'], constraints, _fresh=fresh) run_subprocess.assert_called_once_with( [ @@ -235,6 +248,7 @@ 'pip', *([f'-{"v" * (verbosity - 1)}'] if verbosity > 1 else []), 'install', + *(['--ignore-installed'] if fresh else []), '--use-pep517', '--no-warn-script-location', '--no-compile', @@ -249,19 +263,21 @@ @pytest.mark.parametrize('verbosity', range(3)) @pytest.mark.parametrize('constraints', [[], ['foo']]) [email protected]('fresh', [False, True]) @pytest.mark.skipif(IS_PYPY, reason='uv cannot find PyPy executable') @pytest.mark.skipif(MISSING_UV, reason='uv executable not found') def test_uv_impl_install_cmd_well_formed( # pragma: no cover -- uv tests are skipped on PyPy, covered on CPython mocker: pytest_mock.MockerFixture, verbosity: int, constraints: list[str], + fresh: bool, ) -> None: mocker.patch.object(build.env._ctx, 'verbosity', verbosity) # type: ignore[attr-defined] with build.env.DefaultIsolatedEnv(installer='uv') as env: run_subprocess = mocker.patch('build.env.run_subprocess') - env.install(['some', 'requirements'], constraints) + env.install(['some', 'requirements'], constraints, _fresh=fresh) run_subprocess.assert_called_once_with( [ @@ -342,12 +358,17 @@ caplog: pytest.LogCaptureFixture, mocker: pytest_mock.MockerFixture, ) -> None: + # Ensure INFO logs are captured + caplog.set_level(logging.INFO) mocker.patch.dict(sys.modules, {'uv': None}) with build.env.DefaultIsolatedEnv(installer='uv'): pass - assert any(r.message == f'Using external uv from {shutil.which("uv")}' for r in caplog.records) + # Only check that we logged using an external uv binary (do not rely on + # which() at assertion time because it can find the environment one). + # And .text is used instead of .records so a failure message is helpful. + assert 'Using external uv' in caplog.text def test_external_uv_detection_failure( @@ -573,3 +594,19 @@ (install_call,) = run_subprocess.call_args_list assert install_call.kwargs['env']['UV_KEYRING_PROVIDER'] == 'disabled' + + [email protected] +def test_pythonpath_does_not_interfere_with_outer_pip( + monkeypatch: pytest.MonkeyPatch, + tmp_path: pathlib.Path, +) -> None: + flit_core = tmp_path.joinpath('flit_core-0.0.0.dist-info/') + flit_core.mkdir() + + monkeypatch.setenv('PYTHONPATH', str(tmp_path)) + + with build.env.DefaultIsolatedEnv(installer='pip') as env: + env.install({'flit_core'}, _fresh=True) + + assert subprocess.check_call([env.python_executable, '-c', 'import flit_core']) == 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tests/test_main.py new/build-1.5.0/tests/test_main.py --- old/build-1.4.3/tests/test_main.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tests/test_main.py 2026-04-30 05:17:22.000000000 +0200 @@ -310,7 +310,7 @@ build.__main__.build_package(package_test_flit, '.', ['sdist']) - install.assert_any_call({'flit_core >=2,<4'}) + install.assert_any_call({'flit_core >=2,<4'}, _fresh=True) required_cmd.assert_called_with('sdist', None) install.assert_any_call(['dep1', 'dep2']) @@ -513,7 +513,7 @@ with pytest.raises(build.BuildBackendException, match=re.escape("Backend 'flit_core.buildapi' is not available.")): build.__main__.build_package(package_test_flit, tmp_path, ['wheel'], dependency_constraints_txt=constraints_txt_path) - install.assert_called_with({'flit_core >=2,<4'}, constraints={'flit-core==12.34', 'foo==wot'}) + install.assert_any_call({'flit_core >=2,<4'}, constraints={'flit-core==12.34', 'foo==wot'}, _fresh=True) @pytest.mark.pypy3323bug diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tests/test_projectbuilder.py new/build-1.5.0/tests/test_projectbuilder.py --- old/build-1.4.3/tests/test_projectbuilder.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tests/test_projectbuilder.py 2026-04-30 05:17:22.000000000 +0200 @@ -43,11 +43,17 @@ return self._metadata return '' - _registry: typing.ClassVar[dict[str, type[MockDistribution]]] = {} - @classmethod def from_name(cls, name: str) -> MockDistribution: - if (dist_cls := cls._registry.get(name)) is not None: + registry: dict[str, type[MockDistribution]] = { + 'extras_dep': ExtraMockDistribution, + 'requireless_dep': RequirelessMockDistribution, + 'recursive_dep': RecursiveMockDistribution, + 'prerelease_dep': PrereleaseMockDistribution, + 'circular_dep': CircularMockDistribution, + 'nested_circular_dep': NestedCircularMockDistribution, + } + if (dist_cls := registry.get(name)) is not None: return dist_cls() raise _importlib.metadata.PackageNotFoundError @@ -104,18 +110,6 @@ Requires-Dist: circular_dep""") -MockDistribution._registry.update( - { - 'extras_dep': ExtraMockDistribution, - 'requireless_dep': RequirelessMockDistribution, - 'recursive_dep': RecursiveMockDistribution, - 'prerelease_dep': PrereleaseMockDistribution, - 'circular_dep': CircularMockDistribution, - 'nested_circular_dep': NestedCircularMockDistribution, - } -) - - @pytest.mark.parametrize( ('requirement_string', 'expected'), [ @@ -154,95 +148,6 @@ assert next(build.check_dependency(requirement_string), None) == expected -class _DirectUrlMixin(MockDistribution): - _direct_url_json: str = '' - - def read_text(self, filename: str) -> str: - if filename == 'direct_url.json': - return self._direct_url_json - return super().read_text(filename) - - -class DirectUrlMockDistribution(_DirectUrlMixin): - _metadata = textwrap.dedent("""\ - Metadata-Version: 2.2 - Name: direct_url_dep - Version: 1.0.0""") - _direct_url_json = '{"url": "https://example.com/direct_url_dep-1.0.0.tar.gz", "archive_info": {}}' - - -class VcsDirectUrlMockDistribution(_DirectUrlMixin): - _metadata = textwrap.dedent("""\ - Metadata-Version: 2.2 - Name: vcs_dep - Version: 1.0.0""") - _direct_url_json = ( - '{"url": "https://github.com/example/vcs_dep.git",' - ' "vcs_info": {"vcs": "git", "requested_revision": "v1.0.0",' - ' "commit_id": "abc123"}}' - ) - - -class VcsNoRevisionMockDistribution(_DirectUrlMixin): - _metadata = textwrap.dedent("""\ - Metadata-Version: 2.2 - Name: vcs_dep - Version: 1.0.0""") - _direct_url_json = '{"url": "https://github.com/example/vcs_dep.git", "vcs_info": {"vcs": "git", "commit_id": "abc123"}}' - - -MockDistribution._registry.update( - { - 'direct_url_dep': DirectUrlMockDistribution, - 'vcs_dep': VcsDirectUrlMockDistribution, - } -) - - [email protected]( - 'requirement_string', - [ - 'extras_dep @ https://example.com/extras_dep-1.0.0.tar.gz', - 'missing_dep @ https://example.com/missing_dep-1.0.0.tar.gz', - ], -) -def test_check_dependency_url_no_direct_url_is_unmet(monkeypatch: pytest.MonkeyPatch, requirement_string: str) -> None: - monkeypatch.setattr(_importlib.metadata, 'Distribution', MockDistribution) - assert next(build.check_dependency(requirement_string), None) is not None - - -def test_check_dependency_url_matching_direct_url_is_met(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_importlib.metadata, 'Distribution', MockDistribution) - assert next(build.check_dependency('direct_url_dep @ https://example.com/direct_url_dep-1.0.0.tar.gz'), None) is None - - -def test_check_dependency_url_mismatched_direct_url_is_unmet(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_importlib.metadata, 'Distribution', MockDistribution) - assert next(build.check_dependency('direct_url_dep @ https://example.com/other-1.0.0.tar.gz'), None) is not None - - -def test_check_dependency_vcs_url_matching_direct_url_is_met(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_importlib.metadata, 'Distribution', MockDistribution) - assert next(build.check_dependency('vcs_dep @ git+https://github.com/example/[email protected]'), None) is None - - -def test_check_dependency_vcs_url_mismatched_direct_url_is_unmet(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_importlib.metadata, 'Distribution', MockDistribution) - assert next(build.check_dependency('vcs_dep @ git+https://github.com/example/[email protected]'), None) is not None - - -def test_check_dependency_vcs_url_no_revision_is_met(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_importlib.metadata, 'Distribution', MockDistribution) - monkeypatch.setitem(MockDistribution._registry, 'vcs_dep', VcsNoRevisionMockDistribution) - assert next(build.check_dependency('vcs_dep @ git+https://github.com/example/vcs_dep.git'), None) is None - - -def test_check_dependency_vcs_url_no_revision_mismatch_is_unmet(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_importlib.metadata, 'Distribution', MockDistribution) - monkeypatch.setitem(MockDistribution._registry, 'vcs_dep', VcsNoRevisionMockDistribution) - assert next(build.check_dependency('vcs_dep @ git+https://github.com/other/repo.git'), None) is not None - - def test_bad_project(package_test_no_project: str) -> None: # Passing a nonexistent project directory with pytest.raises(build.BuildException): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tests/test_self_packaging.py new/build-1.5.0/tests/test_self_packaging.py --- old/build-1.4.3/tests/test_self_packaging.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tests/test_self_packaging.py 2026-04-30 05:17:22.000000000 +0200 @@ -30,7 +30,7 @@ 'tests/packages/test-cant-build-via-sdist/some-file-that-is-needed-for-build.txt', 'tests/packages/test-no-project/empty.txt', 'tests/packages/test-setuptools/MANIFEST.in', - 'tox.ini', + 'tox.toml', } sdist_patterns = { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tests/test_util.py new/build-1.5.0/tests/test_util.py --- old/build-1.4.3/tests/test_util.py 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tests/test_util.py 2026-04-30 05:17:22.000000000 +0200 @@ -4,8 +4,10 @@ import importlib.util import re +import unittest.mock import pytest +import pytest_mock import build.util @@ -50,3 +52,25 @@ assert str(metadata['version']) == '1.0.0' assert metadata['summary'] == 'hello!' assert isinstance(metadata.json, dict) + + +def test_project_wheel_metadata_installs_build_requires_fresh(mocker: pytest_mock.MockerFixture) -> None: + env = mocker.MagicMock() + env_cm = mocker.MagicMock() + env_cm.__enter__.return_value = env + env_cm.__exit__.return_value = False + mocker.patch('build.util.DefaultIsolatedEnv', return_value=env_cm) + + builder = mocker.MagicMock() + builder.build_system_requires = {'dep1'} + builder.get_requires_for_build.return_value = {'dep2'} + mocker.patch('build.util.ProjectBuilder.from_isolated_env', return_value=builder) + metadata = unittest.mock.sentinel.metadata + mocker.patch('build.util._project_wheel_metadata', return_value=metadata) + + assert build.util.project_wheel_metadata('/tmp/project') is metadata + + assert env.install.call_args_list == [ + mocker.call({'dep1'}, _fresh=True), + mocker.call({'dep2'}), + ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tox.ini new/build-1.5.0/tox.ini --- old/build-1.4.3/tox.ini 2026-04-10 23:24:52.000000000 +0200 +++ new/build-1.5.0/tox.ini 1970-01-01 01:00:00.000000000 +0100 @@ -1,134 +0,0 @@ -[tox] -requires = - tox>=4.22 - tox-uv - virtualenv>=20.0.34 -env_list = - fix - type - docs - path - {py314, py313, py312, py311, py310, py39, pypy311, pypy310, pypy39}{, -min} -skip_missing_interpreters = true - -[testenv] -description = - run test suite with {basepython} -deps = - pip -pass_env = - LC_ALL - PIP_* - PYTEST_* - TERM -set_env = - COVERAGE_CORE = sysmon - COVERAGE_FILE = {toxworkdir}/.coverage.{envname} - PYPY3323BUG = 1 - PYTHONWARNDEFAULTENCODING = 1 - TEST_STATUS_DIR = {envtmpdir} -commands = - pytest -ra --cov --cov-config pyproject.toml \ - --cov-report=html:{envdir}/htmlcov --cov-context=test \ - --cov-report=xml:{toxworkdir}/coverage.{envname}.xml {posargs:-n auto} -dependency_groups = - test - -[testenv:fix] -description = run static analysis and style checks -base_python = python3.10 -skip_install = true -deps = - prek -pass_env = - HOMEPATH - PROGRAMDATA -commands = - prek run --all-files --show-diff-on-failure - python -c 'print("hint: run {envdir}/bin/prek install to add checks as pre-commit hook")' - -[testenv:type] -description = run type check on code base -set_env = - PYTHONWARNDEFAULTENCODING = -commands = - mypy {posargs} -dependency_groups = - mypy - -[testenv:docs] -description = build documentations -base_python = python3.14 -set_env = - PYTHONUTF8 = 1 -commands = - pre-commit run docstrfmt --all-files - proselint check docs - sphinx-build -W -n -b html docs {env:READTHEDOCS_OUTPUT:{envtmpdir}}/html - python -c 'import os; print("Documentation available under file://" + os.environ.get("READTHEDOCS_OUTPUT", "{envtmpdir}") + "/html/index.html")' -dependency_groups = - docs - -[testenv:path] -description = verify build can run from source (bootstrap) -set_env = - COVERAGE_FILE = {toxworkdir}/.coverage.{envname} - PYTHONPATH = {toxinidir}/src -commands_pre = - python -E -m pip uninstall -y build colorama - -[testenv:{py314, py313, py312, py311, py310, py39, pypy39, pypy310, pypy311}-min] -description = check minimum versions required of all dependencies -set_env = - PIP_CONSTRAINT = {toxinidir}/tests/constraints.txt - UV_CONSTRAINT = {toxinidir}/tests/constraints.txt - -[testenv:dev] -description = generate a DEV environment -package = editable -deps = - virtualenv>=20.0.34 -commands = - python -m pip list --format=columns - python -c 'import sys; print(sys.executable)' -dependency_groups = - docs - test - -[testenv:coverage] -description = combine coverage from test environments -skip_install = true -deps = - coverage[toml]>=5.1 - diff_cover>=3 -parallel_show_output = true -pass_env = - DIFF_AGAINST -set_env = -commands = - - coverage combine {toxworkdir} - coverage report --skip-covered --show-missing -i - coverage xml -o {toxworkdir}/coverage.xml -i - coverage html -d {toxworkdir}/htmlcov -i - python -m diff_cover.diff_cover_tool --compare-branch {env:DIFF_AGAINST:origin/main} {toxworkdir}/coverage.xml -depends = - path - {py314, py313, py312, py311, py310, py39, pypy311, pypy310, pypy39}{, -min} - -[testenv:bump] -description = bump versions, pass major/minor/patch -skip_install = true -deps = - bump-my-version>=0.10 -set_env = -commands = - bump-my-version bump {posargs} - -[testenv:release] -description = create a release (commit, tag, and push) -skip_install = true -change_dir = {toxinidir}/tasks -commands = - python release.py {posargs} -dependency_groups = - release diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/build-1.4.3/tox.toml new/build-1.5.0/tox.toml --- old/build-1.4.3/tox.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/build-1.5.0/tox.toml 2026-04-30 05:17:22.000000000 +0200 @@ -0,0 +1,119 @@ +requires = ["tox-uv>=1.28"] +env_list = [ + "fix", + "type", + "docs", + "path", + { product = [{ prefix = "3.", start = 10, stop = 15 }]}, + { product = [{ prefix = "pypy3.", start = 10, stop = 11 }]}, + { product = [{ prefix = "min-3.", start = 10, stop = 15 }]}, + { product = [{ prefix = "min-pypy3.", start = 10, stop = 11 }]}, +] +skip_missing_interpreters = true + +[env_run_base] +description = "run test suite with {basepython}" +uv_seed = true +pass_env = ["LC_ALL", "PIP_*", "PYTEST_*", "TERM"] +commands = [ + [ + "pytest", "-ra", "--cov", "--cov-config", "pyproject.toml", + "--cov-report=html:{envdir}/htmlcov", "--cov-context=test", + "--cov-report=xml:{toxworkdir}/coverage.{envname}.xml", + "--cov-report", "term-missing", + { replace = "posargs", default = ["-n", "auto"], extend = true }, + ], +] +dependency_groups = ["test"] + +[env_run_base.set_env] +COVERAGE_CORE = "sysmon" +COVERAGE_FILE = "{toxworkdir}/.coverage.{envname}" +PYPY3323BUG = "1" +PYTHONWARNDEFAULTENCODING = "1" +TEST_STATUS_DIR = "{envtmpdir}" + +[env_base.min] +factors = [["3.10", "3.11", "3.12", "3.13", "3.14", "3.15", "pypy3.10", "pypy3.11"]] +description = "check minimum versions required of all dependencies" +set_env = { PIP_CONSTRAINT = "{toxinidir}/tests/constraints.txt", UV_CONSTRAINT = "{toxinidir}/tests/constraints.txt" } + +[env.fix] +description = "run static analysis and style checks" +skip_install = true +pass_env = ["HOMEPATH", "PROGRAMDATA"] +commands = [ + ["prek", "run", "--all-files", "--show-diff-on-failure"], + ["python", "-c", 'print("hint: run {envdir}/bin/prek install to add checks as pre-commit hook")'], +] +dependency_groups = ["lint"] + +[env.type] +description = "run type check on code base" +set_env = { PYTHONWARNDEFAULTENCODING = "" } +commands = [["mypy", { replace = "posargs", extend = true }]] +dependency_groups = ["mypy"] + +[env.docs] +description = "build documentations" +base_python = "3.14" +set_env = { PYTHONUTF8 = "1" } +commands = [ + ["proselint", "check", "docs"], + ["sphinx-build", "-W", "-n", "-b", "html", "docs", "{env:READTHEDOCS_OUTPUT:{envtmpdir}}/html"], + ["python", "-c", 'import os; print("Documentation available under file://" + os.environ.get("READTHEDOCS_OUTPUT", "{envtmpdir}") + "/html/index.html")'], +] +dependency_groups = ["docs"] + +[env.path] +description = "verify build can run from source (bootstrap)" +set_env = { COVERAGE_FILE = "{toxworkdir}/.coverage.{envname}", PYTHONPATH = "{toxinidir}/src" } +commands_pre = [ + ["python", "-E", "-m", "pip", "uninstall", "-y", "build", "colorama"], +] + +[env.dev] +description = "generate a DEV environment" +package = "editable" +deps = ["virtualenv>=20.0.34"] +commands = [ + ["python", "-m", "pip", "list", "--format=columns"], + ["python", "-c", "import sys; print(sys.executable)"], +] +dependency_groups = ["docs", "test"] + +[env.coverage] +description = "combine coverage from test environments" +skip_install = true +parallel_show_output = true +dependency_groups = ["coverage"] +pass_env = ["DIFF_AGAINST"] +set_env = {} +commands = [ + ["-", "coverage", "combine", "{toxworkdir}"], + ["coverage", "report", "--skip-covered", "--show-missing", "-i"], + ["coverage", "xml", "-o", "{toxworkdir}/coverage.xml", "-i"], + ["coverage", "html", "-d", "{toxworkdir}/htmlcov", "-i"], + ["python", "-m", "diff_cover.diff_cover_tool", "--compare-branch", "{env:DIFF_AGAINST:origin/main}", "{toxworkdir}/coverage.xml"], +] +depends = [ + "path", + { product = [{ prefix = "3.", start = 10, stop = 15 }]}, + { product = [{ prefix = "pypy3.", start = 10, stop = 11 }]}, + { product = [{ prefix = "min-3.", start = 10, stop = 15 }]}, + { product = [{ prefix = "min-pypy3.", start = 10, stop = 11 }]}, +] + +[env.bump] +description = "bump versions, pass major/minor/patch" +skip_install = true +set_env = {} +commands = [["bump-my-version", "bump", { replace = "posargs", extend = true }]] +dependency_groups = ["bump"] + +[env.release] +description = "create a release (commit, tag, and push)" +skip_install = true +change_dir = "{toxinidir}/tasks" +commands = [["python", "release.py", { replace = "posargs", extend = true }]] +dependency_groups = ["release"]
