diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 12301490..b00bfd03 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,7 @@ updates: directory: "/" schedule: interval: "daily" + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f4e80bc5..267abcc8 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -14,4 +14,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: hynek/build-and-inspect-python-package@v1 + - uses: hynek/build-and-inspect-python-package@v2 diff --git a/.github/workflows/reusable-change-detection.yml b/.github/workflows/reusable-change-detection.yml index fa2bfcbd..eced8417 100644 --- a/.github/workflows/reusable-change-detection.yml +++ b/.github/workflows/reusable-change-detection.yml @@ -22,7 +22,7 @@ jobs: - name: Get a list of the changed runtime-related files if: github.event_name == 'pull_request' id: changed-testable-files - uses: Ana06/get-changed-files@v2.2.0 + uses: Ana06/get-changed-files@v2.3.0 with: filter: | src/** @@ -42,7 +42,7 @@ jobs: - name: Get a list of the changed documentation-related files if: github.event_name == 'pull_request' id: changed-docs-files - uses: Ana06/get-changed-files@v2.2.0 + uses: Ana06/get-changed-files@v2.3.0 with: filter: | docs/** diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index b0216c4a..c4d39f54 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" diff --git a/.github/workflows/reusable-pytest.yml b/.github/workflows/reusable-pytest.yml index 48eda8dd..56363853 100644 --- a/.github/workflows/reusable-pytest.yml +++ b/.github/workflows/reusable-pytest.yml @@ -44,7 +44,7 @@ jobs: fetch-depth: 0 - name: Setup python for test ${{ matrix.py }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.py }} allow-prereleases: true @@ -66,7 +66,7 @@ jobs: shell: python - name: Setup python for tox - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.9 @@ -91,7 +91,7 @@ jobs: if: always() run: tox -e coverage - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 if: always() env: PYTHON: ${{ matrix.python }} diff --git a/.github/workflows/reusable-type.yml b/.github/workflows/reusable-type.yml index 76e78d7e..a3879e63 100644 --- a/.github/workflows/reusable-type.yml +++ b/.github/workflows/reusable-type.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup Python 3.9 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.9 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8edc0ca8..832a95c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ on: branches: - main schedule: - - cron: "0 8 * * *" + - cron: "0 8 * * 1" workflow_dispatch: concurrency: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 59f7c344..59e6d6f2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-ast - id: check-builtin-literals @@ -16,31 +16,27 @@ repos: - id: debug-statements - id: end-of-file-fixer - id: trailing-whitespace - - id: double-quote-string-fixer - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.14 + rev: v0.16 hooks: - id: validate-pyproject - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.7.0 - hooks: - - id: black - repo: https://github.com/asottile/blacken-docs rev: 1.16.0 hooks: - id: blacken-docs - additional_dependencies: [black==23.7.0] + additional_dependencies: [black==23.*] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.3" + rev: "v4.0.0-alpha.8" hooks: - id: prettier - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.287 + rev: v0.2.2 hooks: - id: ruff - args: [--fix, --format, grouped, --show-fixes] + args: [--fix, --show-fixes] + - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: "v2.2.5" + rev: "v2.2.6" hooks: - id: codespell args: ["-L", "sur"] diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b3210f6b..6136a6ee 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,27 @@ Changelog +++++++++ + +next release +============ + +Python 3.7 (past EoL) support will be removed. + +1.1.0 (2024-02-29) +================== + +- Use external pip if available instead of installing, speeds up environment + setup with virtualenv slightly and venv significantly. + (PR :pr:`736`) +- Stopped injecting ``wheel`` as a build dependency automatically, in the + case of missing ``pyproject.toml`` -- by :user:`webknjaz`. + (PR :pr:`716`) +- Use ``importlib_metadata`` on Python <3.10.2 for bugfixes not present in + those CPython standard libraries (not required when bootstrapping) -- by + :user:`GianlucaFicarelli`. + (PR :pr:`693`, fixes issue :issue:`692`) + + 1.0.3 (2023-09-06) ================== diff --git a/README.md b/README.md index 325a2f1e..9d0c25d8 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ [![CI test](https://github.com/pypa/build/actions/workflows/test.yml/badge.svg)](https://github.com/pypa/build/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/pypa/build/branch/main/graph/badge.svg)](https://codecov.io/gh/pypa/build) -[![Documentation Status](https://readthedocs.org/projects/pypa-build/badge/?version=latest)](https://pypa-build.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/pypa-build/badge/?version=latest)](https://build.pypa.io/en/latest/?badge=latest) [![PyPI version](https://badge.fury.io/py/build.svg)](https://pypi.org/project/build/) [![Discord](https://img.shields.io/discord/803025117553754132?label=Discord%20chat%20%23build)](https://discord.gg/pypa) A simple, correct Python build frontend. -See the [documentation](https://pypa-build.readthedocs.io/en/latest/) for more information. +See the [documentation](https://build.pypa.io) for more information. ### Installation @@ -28,7 +28,7 @@ $ python -m build This will build the package in an isolated environment, generating a source-distribution and wheel in the directory `dist/`. -See the [documentation](https://pypa-build.readthedocs.io/en/latest/) for full information. +See the [documentation](https://build.pypa.io) for full information. ### Code of Conduct diff --git a/pyproject.toml b/pyproject.toml index 9618c396..403ef5e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "build" -version = "1.0.3" +version = "1.1.0" description = "A simple, correct Python build frontend" readme = "README.md" requires-python = ">= 3.7" @@ -28,15 +28,17 @@ classifiers = [ "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] -urls.homepage = "https://github.com/pypa/build" -urls.changelog = "https://pypa-build.readthedocs.io/en/stable/changelog.html" +urls.changelog = "https://build.pypa.io/en/stable/changelog.html" +urls.homepage = "https://build.pypa.io" +urls.issues = "https://github.com/pypa/build/issues" +urls.source = "https://github.com/pypa/build" dependencies = [ "packaging >= 19.0", "pyproject_hooks", # not actually a runtime dependency, only supplied as there is not "recommended dependency" support 'colorama; os_name == "nt"', - 'importlib-metadata >= 4.6; python_version < "3.10"', # Not required for 3.8+, but fixes a stdlib bug + 'importlib-metadata >= 4.6; python_full_version < "3.10.2"', # Not required for 3.8+, but fixes a stdlib bug 'tomli >= 1.1.0; python_version < "3.11"', ] @@ -89,8 +91,7 @@ source = [ ] [tool.coverage.report] -exclude_lines = [ - '\#\s*pragma: no cover', +exclude_also = [ '^\s*raise NotImplementedError\b', "if typing.TYPE_CHECKING:", ] @@ -140,19 +141,19 @@ module = [ ] ignore_missing_imports = true -[tool.black] -line-length = 127 -skip-string-normalization = true - [tool.ruff] -line-length = 127 +src = ["src"] exclude = ["tests/packages/test-bad-syntax"] -select = [ +line-length = 127 + +[tool.ruff.format] +quote-style = "single" + +[tool.ruff.lint] +extend-select = [ "B", # flake8-bugbear "C4", # flake8-comprehensions "C9", # mccabe - "E", # pycodestyle - "F", # pyflakes "I", # isort "PGH", # pygrep-hooks "RUF", # ruff @@ -162,17 +163,26 @@ select = [ "TRY", # tryceratops "EM", # flake8-errmsg ] -src = ["src"] -[tool.ruff.mccabe] +[tool.ruff.lint.mccabe] max-complexity = 10 -[tool.ruff.isort] +[tool.ruff.lint.isort] lines-between-types = 1 lines-after-imports = 2 -known-first-party = ["build"] [tool.check-wheel-contents] ignore = [ "W005", # We _are_ build ] + +[tool.bumpversion] +current_version = "1.0.0" + +[[tool.bumpversion.files]] +filename = "pyproject.toml" +search = 'version = "{current_version}"' + +[[tool.bumpversion.files]] +filename = "src/build/__init__.py" +search = "__version__ = '{current_version}'" diff --git a/src/build/__init__.py b/src/build/__init__.py index 34e22279..3c70aa71 100644 --- a/src/build/__init__.py +++ b/src/build/__init__.py @@ -7,7 +7,7 @@ from __future__ import annotations -__version__ = '1.0.3' +__version__ = '1.1.0' import contextlib import difflib @@ -24,6 +24,7 @@ import pyproject_hooks from . import env +from ._compat import tomllib from ._exceptions import ( BuildBackendException, BuildException, @@ -34,12 +35,6 @@ from ._util import check_dependency, parse_wheel_filename -if sys.version_info >= (3, 11): - import tomllib -else: - import tomli as tomllib - - RunnerType = Callable[[Sequence[str], Optional[str], Optional[Mapping[str, str]]], None] ConfigSettingsType = Mapping[str, Union[str, Sequence[str]]] PathType = Union[str, 'os.PathLike[str]'] @@ -49,7 +44,7 @@ _DEFAULT_BACKEND = { 'build-backend': 'setuptools.build_meta:__legacy__', - 'requires': ['setuptools >= 40.8.0', 'wheel'], + 'requires': ['setuptools >= 40.8.0'], } diff --git a/src/build/__main__.py b/src/build/__main__.py index ba83a5e5..03c3dcd8 100644 --- a/src/build/__main__.py +++ b/src/build/__main__.py @@ -230,7 +230,7 @@ def build_package_via_sdist( :param isolation: Isolate the build in a separate environment :param skip_dependency_check: Do not perform the dependency check """ - from ._util import TarFile + from ._compat import tarfile if 'sdist' in distributions: msg = 'Only binary distributions are allowed but sdist was specified' @@ -243,7 +243,7 @@ def build_package_via_sdist( built: list[str] = [] if distributions: # extract sdist - with TarFile.open(sdist) as t: + with tarfile.TarFile.open(sdist) as t: t.extractall(sdist_out) try: _ProjectBuilder.log(f'Building {_natural_language_list(distributions)} from sdist') @@ -263,7 +263,7 @@ def main_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description=textwrap.indent( textwrap.dedent( - ''' + """ A simple, correct Python build frontend. By default, a source distribution (sdist) is built from {srcdir} @@ -275,7 +275,7 @@ def main_parser() -> argparse.ArgumentParser: If you do this, the default behavior will be disabled, and all artifacts will be built from {srcdir} (even if you combine -w/--wheel with -s/--sdist, the wheel will be built from {srcdir}). - ''' + """ ).strip(), ' ', ), diff --git a/src/build/_compat/__init__.py b/src/build/_compat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/build/_importlib.py b/src/build/_compat/importlib.py similarity index 59% rename from src/build/_importlib.py rename to src/build/_compat/importlib.py index f95b2a69..7487ddfd 100644 --- a/src/build/_importlib.py +++ b/src/build/_compat/importlib.py @@ -1,14 +1,20 @@ +from __future__ import annotations + import sys if sys.version_info < (3, 8): import importlib_metadata as metadata -elif sys.version_info < (3, 9, 10) or (3, 10, 0) <= sys.version_info < (3, 10, 2): +elif sys.version_info >= (3, 10, 2): + from importlib import metadata +else: try: import importlib_metadata as metadata except ModuleNotFoundError: + # helps bootstrapping when dependencies aren't installed from importlib import metadata -else: - from importlib import metadata -__all__ = ['metadata'] + +__all__ = [ + 'metadata', +] diff --git a/src/build/_compat/tarfile.py b/src/build/_compat/tarfile.py new file mode 100644 index 00000000..2984a497 --- /dev/null +++ b/src/build/_compat/tarfile.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import sys +import tarfile +import typing + + +if typing.TYPE_CHECKING: + TarFile = tarfile.TarFile + +else: + # Per https://peps.python.org/pep-0706/, the "data" filter will become + # the default in Python 3.14. The first series of releases with the filter + # had a broken filter that could not process symlinks correctly. + if ( + (3, 8, 18) <= sys.version_info < (3, 9) + or (3, 9, 18) <= sys.version_info < (3, 10) + or (3, 10, 13) <= sys.version_info < (3, 11) + or (3, 11, 5) <= sys.version_info < (3, 12) + or (3, 12) <= sys.version_info < (3, 14) + ): + + class TarFile(tarfile.TarFile): + extraction_filter = staticmethod(tarfile.data_filter) + + else: + TarFile = tarfile.TarFile + + +__all__ = [ + 'TarFile', +] diff --git a/src/build/_compat/tomllib.py b/src/build/_compat/tomllib.py new file mode 100644 index 00000000..51191efe --- /dev/null +++ b/src/build/_compat/tomllib.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +import sys + + +if sys.version_info >= (3, 11): + from tomllib import TOMLDecodeError, load, loads +else: + from tomli import TOMLDecodeError, load, loads + + +__all__ = [ + 'TOMLDecodeError', + 'load', + 'loads', +] diff --git a/src/build/_exceptions.py b/src/build/_exceptions.py index 90a75b24..3d16a769 100644 --- a/src/build/_exceptions.py +++ b/src/build/_exceptions.py @@ -20,8 +20,11 @@ def __init__( self, exception: Exception, description: str | None = None, - exc_info: tuple[type[BaseException], BaseException, types.TracebackType] - | tuple[None, None, None] = (None, None, None), + exc_info: tuple[type[BaseException], BaseException, types.TracebackType] | tuple[None, None, None] = ( + None, + None, + None, + ), ) -> None: super().__init__() self.exception = exception diff --git a/src/build/_util.py b/src/build/_util.py index 691e15a7..a57c41eb 100644 --- a/src/build/_util.py +++ b/src/build/_util.py @@ -1,9 +1,6 @@ from __future__ import annotations import re -import sys -import tarfile -import typing from collections.abc import Iterator, Set @@ -27,7 +24,7 @@ def check_dependency( """ import packaging.requirements - from ._importlib import metadata + from ._compat import importlib req = packaging.requirements.Requirement(req_string) normalised_req_string = str(req) @@ -48,8 +45,8 @@ def check_dependency( return try: - dist = metadata.distribution(req.name) - except metadata.PackageNotFoundError: + dist = importlib.metadata.distribution(req.name) + except importlib.metadata.PackageNotFoundError: # dependency is not installed in the environment. yield (*ancestral_req_strings, normalised_req_string) else: @@ -64,25 +61,3 @@ def check_dependency( def parse_wheel_filename(filename: str) -> re.Match[str] | None: return _WHEEL_FILENAME_REGEX.match(filename) - - -if typing.TYPE_CHECKING: - TarFile = tarfile.TarFile - -else: - # Per https://peps.python.org/pep-0706/, the "data" filter will become - # the default in Python 3.14. The first series of releases with the filter - # had a broken filter that could not process symlinks correctly. - if ( - (3, 8, 18) <= sys.version_info < (3, 9) - or (3, 9, 18) <= sys.version_info < (3, 10) - or (3, 10, 13) <= sys.version_info < (3, 11) - or (3, 11, 5) <= sys.version_info < (3, 12) - or (3, 12) <= sys.version_info < (3, 14) - ): - - class TarFile(tarfile.TarFile): - extraction_filter = staticmethod(tarfile.data_filter) - - else: - TarFile = tarfile.TarFile diff --git a/src/build/env.py b/src/build/env.py index ef8d983b..5cb7a3ae 100644 --- a/src/build/env.py +++ b/src/build/env.py @@ -58,6 +58,54 @@ def _should_use_virtualenv() -> bool: ) +def _minimum_pip_version() -> str: + if platform.system() == 'Darwin' and int(platform.mac_ver()[0].split('.')[0]) >= 11: + # macOS 11+ name scheme change requires 20.3. Intel macOS 11.0 can be + # told to report 10.16 for backwards compatibility; but that also fixes + # earlier versions of pip so this is only needed for 11+. + is_apple_silicon_python = platform.machine() != 'x86_64' + return '21.0.1' if is_apple_silicon_python else '20.3.0' + + # PEP-517 and manylinux1 was first implemented in 19.1 + return '19.1.0' + + +def _has_valid_pip(**distargs: object) -> bool: + """ + Given a path, see if Pip is present and return True if the version is + sufficient for build, False if it is not. ModuleNotFoundError is thrown if + pip is not present. + """ + + import packaging.version + + from ._compat import importlib + + name = 'pip' + + try: + pip_distribution = next(iter(importlib.metadata.distributions(name=name, **distargs))) + except StopIteration: + raise ModuleNotFoundError(name) from None + + current_pip_version = packaging.version.Version(pip_distribution.version) + + return current_pip_version >= packaging.version.Version(_minimum_pip_version()) + + +@functools.lru_cache(maxsize=None) +def _valid_global_pip() -> bool | None: + """ + This checks for a valid global pip. Returns None if pip is missing, False + if Pip is too old, and True if it can be used. + """ + + try: + return _has_valid_pip() + except ModuleNotFoundError: + return None + + def _subprocess(cmd: list[str]) -> None: """Invoke subprocess and output stdout and stderr if it fails.""" try: @@ -107,6 +155,12 @@ def python_executable(self) -> str: """The python executable of the isolated build environment.""" return self._python_executable + def _pip_args(self) -> list[str]: + if _valid_global_pip(): + return [sys.executable, '-Im', 'pip', '--python', self.python_executable] + else: + return [self.python_executable, '-Im', 'pip'] + def make_extra_environ(self) -> dict[str, str]: path = os.environ.get('PATH') return {'PATH': os.pathsep.join([self._scripts_dir, path]) if path is not None else self._scripts_dir} @@ -131,9 +185,7 @@ def install(self, requirements: Collection[str]) -> None: req_file.write(os.linesep.join(requirements)) try: cmd = [ - self.python_executable, - '-Im', - 'pip', + *self._pip_args(), 'install', '--use-pep517', '--no-warn-script-location', @@ -169,7 +221,11 @@ def _create_isolated_env_virtualenv(path: str) -> tuple[str, str]: """ import virtualenv - cmd = [str(path), '--no-setuptools', '--no-wheel', '--activators', ''] + if _valid_global_pip(): + cmd = [path, '--no-seed', '--activators', ''] + else: + cmd = [path, '--no-setuptools', '--no-wheel', '--activators', ''] + result = virtualenv.cli_run(cmd, setup_logging=False) executable = str(result.creator.exe) script_dir = str(result.creator.script_dir) @@ -203,42 +259,25 @@ def _create_isolated_env_venv(path: str) -> tuple[str, str]: """ import venv - import packaging.version - - if sys.version_info < (3, 8): - import importlib_metadata as metadata - else: - from importlib import metadata - symlinks = _fs_supports_symlink() try: with warnings.catch_warnings(): if sys.version_info[:3] == (3, 11, 0): warnings.filterwarnings('ignore', 'check_home argument is deprecated and ignored.', DeprecationWarning) - venv.EnvBuilder(with_pip=True, symlinks=symlinks).create(path) + venv.EnvBuilder(with_pip=not _valid_global_pip(), symlinks=symlinks).create(path) except subprocess.CalledProcessError as exc: raise FailedProcessError(exc, 'Failed to create venv. Maybe try installing virtualenv.') from None executable, script_dir, purelib = _find_executable_and_scripts(path) # Get the version of pip in the environment - pip_distribution = next(iter(metadata.distributions(name='pip', path=[purelib]))) - current_pip_version = packaging.version.Version(pip_distribution.version) - - if platform.system() == 'Darwin' and int(platform.mac_ver()[0].split('.')[0]) >= 11: - # macOS 11+ name scheme change requires 20.3. Intel macOS 11.0 can be told to report 10.16 for backwards - # compatibility; but that also fixes earlier versions of pip so this is only needed for 11+. - is_apple_silicon_python = platform.machine() != 'x86_64' - minimum_pip_version = '21.0.1' if is_apple_silicon_python else '20.3.0' - else: - # PEP-517 and manylinux1 was first implemented in 19.1 - minimum_pip_version = '19.1.0' - - if current_pip_version < packaging.version.Version(minimum_pip_version): - _subprocess([executable, '-m', 'pip', 'install', f'pip>={minimum_pip_version}']) + if not _valid_global_pip() and not _has_valid_pip(path=[purelib]): + _subprocess([executable, '-m', 'pip', 'install', f'pip>={_minimum_pip_version()}']) # Avoid the setuptools from ensurepip to break the isolation - _subprocess([executable, '-m', 'pip', 'uninstall', 'setuptools', '-y']) + if not _valid_global_pip(): + _subprocess([executable, '-m', 'pip', 'uninstall', 'setuptools', '-y']) + return executable, script_dir diff --git a/src/build/util.py b/src/build/util.py index 9f204b85..671c1c62 100644 --- a/src/build/util.py +++ b/src/build/util.py @@ -8,14 +8,14 @@ import pyproject_hooks from . import PathType, ProjectBuilder, RunnerType -from ._importlib import metadata +from ._compat import importlib from .env import DefaultIsolatedEnv -def _project_wheel_metadata(builder: ProjectBuilder) -> metadata.PackageMetadata: +def _project_wheel_metadata(builder: ProjectBuilder) -> importlib.metadata.PackageMetadata: with tempfile.TemporaryDirectory() as tmpdir: path = pathlib.Path(builder.metadata_path(tmpdir)) - return metadata.PathDistribution(path).metadata + return importlib.metadata.PathDistribution(path).metadata def project_wheel_metadata( @@ -23,7 +23,7 @@ def project_wheel_metadata( isolated: bool = True, *, runner: RunnerType = pyproject_hooks.quiet_subprocess_runner, -) -> metadata.PackageMetadata: +) -> importlib.metadata.PackageMetadata: """ Return the wheel metadata for a project. diff --git a/tests/conftest.py b/tests/conftest.py index ecc25383..8f989d68 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -65,6 +65,11 @@ def is_integration(item): return os.path.basename(item.location[0]) == 'test_integration.py' +@pytest.fixture() +def local_pip(monkeypatch): + monkeypatch.setattr(build.env, '_valid_global_pip', lambda: None) + + @pytest.fixture(scope='session', autouse=True) def ensure_syconfig_vars_created(): # the config vars are globally cached and may use get_path, make sure they are created diff --git a/tests/packages/test-invalid-requirements/pyproject.toml b/tests/packages/test-invalid-requirements/pyproject.toml index 11974a0a..ec2b9200 100644 --- a/tests/packages/test-invalid-requirements/pyproject.toml +++ b/tests/packages/test-invalid-requirements/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ['setuptools >= 42.0.0', 'wheel >= 0.36.0', 'this is invalid'] +requires = ['setuptools >= 42.0.0', 'this is invalid'] build-backend = 'setuptools.build_meta' diff --git a/tests/packages/test-metadata/backend.py b/tests/packages/test-metadata/backend.py index 727dab9d..d1b361fb 100644 --- a/tests/packages/test-metadata/backend.py +++ b/tests/packages/test-metadata/backend.py @@ -24,12 +24,12 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): distinfo.mkdir(parents=True, exist_ok=True) distinfo.joinpath('METADATA').write_text( textwrap.dedent( - f''' + f""" Metadata-Version: 2.2 Name: {metadata['project']['name']} Version: {metadata['project']['version']} Summary: {metadata['project']['description']} - ''' + """ ).strip() ) return distinfo.name diff --git a/tests/packages/test-no-prepare/pyproject.toml b/tests/packages/test-no-prepare/pyproject.toml index c6ca5f83..2885c708 100644 --- a/tests/packages/test-no-prepare/pyproject.toml +++ b/tests/packages/test-no-prepare/pyproject.toml @@ -1,4 +1,4 @@ [build-system] build-backend = 'backend_no_prepare' backend-path = ['.'] -requires = ['setuptools >= 42.0.0', 'wheel >= 0.36.0'] +requires = ['setuptools >= 42.0.0'] diff --git a/tests/packages/test-setuptools/MANIFEST.in b/tests/packages/test-setuptools/MANIFEST.in new file mode 100644 index 00000000..e69e3cfd --- /dev/null +++ b/tests/packages/test-setuptools/MANIFEST.in @@ -0,0 +1 @@ +include pyproject.toml setup.cfg diff --git a/tests/packages/test-setuptools/pyproject.toml b/tests/packages/test-setuptools/pyproject.toml index b00a27a6..7c39e0e7 100644 --- a/tests/packages/test-setuptools/pyproject.toml +++ b/tests/packages/test-setuptools/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ['setuptools >= 42.0.0', 'wheel >= 0.36.0'] +requires = ['setuptools >= 42.0.0'] build-backend = 'setuptools.build_meta' diff --git a/tests/packages/test-typo/pyproject.toml b/tests/packages/test-typo/pyproject.toml index 02d1af27..3c6a0fc5 100644 --- a/tests/packages/test-typo/pyproject.toml +++ b/tests/packages/test-typo/pyproject.toml @@ -1,3 +1,3 @@ [build_sytem] -requires = ['setuptools >= 40.8.0', 'wheel'] +requires = ['setuptools >= 40.8.0'] build-backend = 'setuptools.build_meta' diff --git a/tests/test_env.py b/tests/test_env.py index 41d1f478..379d8e57 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -26,6 +26,7 @@ def test_isolation(): @pytest.mark.isolated +@pytest.mark.usefixtures('local_pip') def test_isolated_environment_install(mocker): with build.env.DefaultIsolatedEnv() as env: mocker.patch('build.env._subprocess') @@ -117,6 +118,7 @@ def test_isolated_env_log(mocker, caplog, package_test_flit): @pytest.mark.isolated +@pytest.mark.usefixtures('local_pip') def test_default_pip_is_never_too_old(): with build.env.DefaultIsolatedEnv() as env: version = subprocess.check_output( @@ -130,6 +132,7 @@ def test_default_pip_is_never_too_old(): @pytest.mark.isolated @pytest.mark.parametrize('pip_version', ['20.2.0', '20.3.0', '21.0.0', '21.0.1']) @pytest.mark.parametrize('arch', ['x86_64', 'arm64']) +@pytest.mark.usefixtures('local_pip') def test_pip_needs_upgrade_mac_os_11(mocker, pip_version, arch): SimpleNamespace = collections.namedtuple('SimpleNamespace', 'version') @@ -137,8 +140,7 @@ def test_pip_needs_upgrade_mac_os_11(mocker, pip_version, arch): mocker.patch('platform.system', return_value='Darwin') mocker.patch('platform.machine', return_value=arch) mocker.patch('platform.mac_ver', return_value=('11.0', ('', '', ''), '')) - metadata_name = 'importlib_metadata' if sys.version_info < (3, 8) else 'importlib.metadata' - mocker.patch(metadata_name + '.distributions', return_value=(SimpleNamespace(version=pip_version),)) + mocker.patch('build._compat.importlib.metadata.distributions', return_value=(SimpleNamespace(version=pip_version),)) min_version = Version('20.3' if arch == 'x86_64' else '21.0.1') with build.env.DefaultIsolatedEnv(): diff --git a/tests/test_main.py b/tests/test_main.py index 361e8a13..2ca18e47 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -230,12 +230,12 @@ def test_build_package_via_sdist_invalid_distribution(tmp_dir, package_test_setu [], [ '* Creating venv isolated environment...', - '* Installing packages in isolated environment... (setuptools >= 42.0.0, wheel >= 0.36.0)', + '* Installing packages in isolated environment... (setuptools >= 42.0.0)', '* Getting build dependencies for sdist...', '* Building sdist...', '* Building wheel from sdist', '* Creating venv isolated environment...', - '* Installing packages in isolated environment... (setuptools >= 42.0.0, wheel >= 0.36.0)', + '* Installing packages in isolated environment... (setuptools >= 42.0.0)', '* Getting build dependencies for wheel...', '* Installing packages in isolated environment... (wheel)', '* Building wheel...', @@ -260,7 +260,7 @@ def test_build_package_via_sdist_invalid_distribution(tmp_dir, package_test_setu ['--wheel'], [ '* Creating venv isolated environment...', - '* Installing packages in isolated environment... (setuptools >= 42.0.0, wheel >= 0.36.0)', + '* Installing packages in isolated environment... (setuptools >= 42.0.0)', '* Getting build dependencies for wheel...', '* Installing packages in isolated environment... (wheel)', '* Building wheel...', @@ -324,7 +324,7 @@ def main_reload_styles(): 'ERROR ', [ '* Creating venv isolated environment...', - '* Installing packages in isolated environment... (setuptools >= 42.0.0, this is invalid, wheel >= 0.36.0)', + '* Installing packages in isolated environment... (setuptools >= 42.0.0, this is invalid)', '', 'Traceback (most recent call last):', ], @@ -334,8 +334,7 @@ def main_reload_styles(): '\33[91mERROR\33[0m ', [ '\33[1m* Creating venv isolated environment...\33[0m', - '\33[1m* Installing packages in isolated environment... ' - '(setuptools >= 42.0.0, this is invalid, wheel >= 0.36.0)\33[0m', + '\33[1m* Installing packages in isolated environment... (setuptools >= 42.0.0, this is invalid)\33[0m', '', '\33[2mTraceback (most recent call last):', ], @@ -343,6 +342,7 @@ def main_reload_styles(): ], ids=['no-color', 'color'], ) +@pytest.mark.usefixtures('local_pip') def test_output_env_subprocess_error( mocker, monkeypatch, @@ -431,7 +431,7 @@ def test_venv_fail(monkeypatch, package_test_flit, tmp_dir, capsys): assert ( stdout - == '''\ + == """\ * Creating venv isolated environment... ERROR Failed to create venv. Maybe try installing virtualenv. Command 'test args' failed with return code 1 @@ -439,6 +439,6 @@ def test_venv_fail(monkeypatch, package_test_flit, tmp_dir, capsys): stdoutput stderr: stderror -''' +""" ) assert stderr == '' diff --git a/tests/test_projectbuilder.py b/tests/test_projectbuilder.py index d4246807..cccb3852 100644 --- a/tests/test_projectbuilder.py +++ b/tests/test_projectbuilder.py @@ -13,7 +13,7 @@ import build -from build import _importlib +from build._compat import importlib as _importlib build_open_owner = 'builtins' @@ -21,7 +21,7 @@ DEFAULT_BACKEND = { 'build-backend': 'setuptools.build_meta:__legacy__', - 'requires': ['setuptools >= 40.8.0', 'wheel'], + 'requires': ['setuptools >= 40.8.0'], } @@ -383,7 +383,7 @@ def test_build_with_dep_on_console_script(tmp_path, demo_pkg_inline, capfd, mock # to validate backend invocations contain the correct path we use an inline backend that will fail, but first # provides the PATH information (and validates shutil.which is able to discover the executable - as PEP states) toml = textwrap.dedent( - ''' + """ [build-system] requires = ["demo_pkg_inline"] build-backend = "build" @@ -392,17 +392,17 @@ def test_build_with_dep_on_console_script(tmp_path, demo_pkg_inline, capfd, mock [project] description = "Factory ⸻ A code generator 🏭" authors = [{name = "Łukasz Langa"}] - ''' + """ ) code = textwrap.dedent( - ''' + """ import os import shutil import sys print("BB " + os.environ["PATH"]) exe_at = shutil.which("demo-pkg-inline") print("BB " + exe_at) - ''' + """ ) (tmp_path / 'pyproject.toml').write_text(toml, encoding='UTF-8') (tmp_path / 'build.py').write_text(code, encoding='utf-8') @@ -502,7 +502,8 @@ def test_metadata_path_no_prepare(tmp_dir, package_test_no_prepare): pathlib.Path(builder.metadata_path(tmp_dir)), ).metadata - assert metadata['name'] == 'test-no-prepare' + # Setuptools < v69.0.3 (https://github.com/pypa/setuptools/pull/4159) normalized this to dashes + assert metadata['name'].replace('-', '_') == 'test_no_prepare' assert metadata['Version'] == '1.0.0' @@ -513,7 +514,8 @@ def test_metadata_path_with_prepare(tmp_dir, package_test_setuptools): pathlib.Path(builder.metadata_path(tmp_dir)), ).metadata - assert metadata['name'] == 'test-setuptools' + # Setuptools < v69.0.3 (https://github.com/pypa/setuptools/pull/4159) normalized this to dashes + assert metadata['name'].replace('-', '_') == 'test_setuptools' assert metadata['Version'] == '1.0.0' diff --git a/tests/test_self_packaging.py b/tests/test_self_packaging.py index fb1d1243..9d515e5d 100644 --- a/tests/test_self_packaging.py +++ b/tests/test_self_packaging.py @@ -27,12 +27,14 @@ 'tests/constraints.txt', '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', } sdist_patterns = { 'docs/*.rst', 'src/build/*.py', + 'src/build/_compat/*.py', 'tests/*.py', 'tests/packages/*/*.py', 'tests/packages/*/*/*.py', @@ -45,8 +47,11 @@ wheel_files = { 'build/__init__.py', 'build/__main__.py', + 'build/_compat/__init__.py', + 'build/_compat/importlib.py', + 'build/_compat/tarfile.py', + 'build/_compat/tomllib.py', 'build/_exceptions.py', - 'build/_importlib.py', 'build/_util.py', 'build/env.py', 'build/py.typed', diff --git a/tests/test_util.py b/tests/test_util.py index 74f2cf1e..0ab81b84 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -12,8 +12,10 @@ def test_wheel_metadata(package_test_setuptools, isolated): metadata = build.util.project_wheel_metadata(package_test_setuptools, isolated) - assert metadata['name'] == 'test-setuptools' + # Setuptools < v69.0.3 (https://github.com/pypa/setuptools/pull/4159) normalized this to dashes + assert metadata['name'].replace('-', '_') == 'test_setuptools' assert metadata['version'] == '1.0.0' + assert isinstance(metadata.json, dict) @pytest.mark.network @@ -26,6 +28,7 @@ def test_wheel_metadata_isolation(package_test_flit): assert metadata['name'] == 'test_flit' assert metadata['version'] == '1.0.0' + assert isinstance(metadata.json, dict) with pytest.raises( build.BuildBackendException, @@ -39,6 +42,8 @@ def test_wheel_metadata_isolation(package_test_flit): def test_with_get_requires(package_test_metadata): metadata = build.util.project_wheel_metadata(package_test_metadata) - assert metadata['name'] == 'test-metadata' + # Setuptools < v69.0.3 (https://github.com/pypa/setuptools/pull/4159) normalized this to dashes + assert metadata['name'].replace('-', '_') == 'test_metadata' assert str(metadata['version']) == '1.0.0' assert metadata['summary'] == 'hello!' + assert isinstance(metadata.json, dict) diff --git a/tox.ini b/tox.ini index e841f12f..0956c317 100644 --- a/tox.ini +++ b/tox.ini @@ -106,3 +106,12 @@ commands = depends = path {py312, py311, py310, py39, py38, py37, pypy39, pypy38, pypy37}{, -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}