From a52f3f1a4c1087b78c29d2d6ecc0aa45f4d428b8 Mon Sep 17 00:00:00 2001 From: Ben Rowland Date: Fri, 21 Jan 2022 15:58:12 +0000 Subject: [PATCH 1/4] Support building via PyPA/build This commit adds support for using the PyPA build backend (https://pypa-build.readthedocs.io/en/latest/index.html) to build the project wheels in an isolated virtualenv as prescribed by PEP517. This is similar to the existing PEP517 support except that build will first create an sdist and then produce the wheel from that, thus validating the sdist as well as the wheel. This behaviour is controlled by the wheel_pep517 tox testenv attribute - this was a bool but is now a string. An empty string or missing attribute gives the legacy behaviour, the string "build" gives the new behaviour, and any other value gives the previous pip based PEP517 build. In essence the change is in the command run to build the wheel, switching from "pip wheel --use-pep517" to "python -Im build". Because there is an extra build artefact (the sdist), we filter the distdir for wheels only before returning the first one. Because build is not part of the standard library, it must be installed in the tox virtualenv to be used - this is accomplished by implementing the tox_testenv_install_deps() hook, if the wheel_pep517 parameter is set to "build" then the build package will be installed. Currently it is necessary to install it with the [virtualenv] option specified, build normally uses venv to create its isolated environment but that doesn't work inside tox because venvs currently cannot be created inside virtualenvs. As venv becomes more accepted (and possibly the internal backend for virtualenv) this requirement may go away. In terms of testing, I have simply added two tests (and a fixture) to match the existing PEP517 behaviour testing. build has the same requirements of Python 3.6 or above that tox-wheel has so I have not implemented any kind of checks based on different versions. Locally it is passing tests on Python 3.8 and 3.9. --- AUTHORS.rst | 1 + src/tox_wheel/plugin.py | 41 ++++++++++++++++++++++++++---- tests/test_tox_wheel.py | 55 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 8f56586..c3ac262 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -6,3 +6,4 @@ Authors * Antonio Botelho - https://github.com/botant * Thomas Grainger - https://github.com/graingert * Michael Rans - https://github.com/mcarans +* Ben Rowland - https://github.com/bennyrowland diff --git a/src/tox_wheel/plugin.py b/src/tox_wheel/plugin.py index 0371185..40dd879 100644 --- a/src/tox_wheel/plugin.py +++ b/src/tox_wheel/plugin.py @@ -1,5 +1,6 @@ from contextlib import contextmanager from functools import partial +import os.path import pluggy import py @@ -32,9 +33,12 @@ def tox_addoption(parser): ) parser.add_testenv_attribute( name="wheel_pep517", - type="bool", - default=False, - help="Build wheel using PEP 517/518" + type="string", + default="", + help=( + "Build wheel using PEP 517/518 (pass true to build with pip or " + "build to build with build)" + ), ) parser.add_testenv_attribute( name="wheel_dirty", @@ -60,6 +64,13 @@ def patch(obj, attr, value): setattr(obj, attr, original) +@hookimpl +def tox_testenv_install_deps(venv, action): + if venv.envconfig.wheel_pep517 == "build": + venv.run_install_command(["build[virtualenv]==0.7.0"], action) + return None + + @hookimpl def tox_package(session, venv): if session.config.option.wheel or venv.envconfig.wheel: @@ -153,16 +164,36 @@ def wheel_build_pep517(config, session, venv): action.setactivity("wheel-make", "cleaning up build directory ...") ensure_empty_dir(config.setupdir.join("build")) ensure_empty_dir(config.distdir) + if venv.envconfig.wheel_pep517 == "build": + commands = [ + "python", + "-Im", + "build", + "--outdir", + config.distdir, + config.setupdir, + ] + else: + commands = [ + "pip", + "wheel", + config.setupdir, + "--no-deps", + "--use-pep517", + "--wheel-dir", + config.distdir, + ] venv.test( name="wheel-make", - commands=[["pip", "wheel", config.setupdir, "--no-deps", "--use-pep517", "--wheel-dir", config.distdir]], + commands=[commands], redirect=False, ignore_outcome=False, ignore_errors=False, display_hash_seed=False, ) try: - dists = config.distdir.listdir() + # we need to filter our list of dists to include only wheels + dists = [dist for dist in config.distdir.listdir() if os.path.splitext(dist)[1] == ".whl"] except py.error.ENOENT: reporter.error( "No dist directory found. Please check pyproject.toml, e.g with:\n" diff --git a/tests/test_tox_wheel.py b/tests/test_tox_wheel.py index 7f3cd4e..dcd798f 100644 --- a/tests/test_tox_wheel.py +++ b/tests/test_tox_wheel.py @@ -54,6 +54,32 @@ def testdir_pep517(testdir): return testdir +@pytest.fixture +def testdir_pep517_build(testdir): + testdir.tmpdir.join('tox.ini').write(""" +[tox] +envlist = py-{a,b} + +[testenv] +wheel = true +wheel_pep517 = build +""") + testdir.tmpdir.join('setup.py').write(""" +from setuptools import setup + +setup(name='foobar') +""") + testdir.tmpdir.join('pyproject.toml').write(""" +[build-system] +requires = [ + "setuptools >= 35.0.2" +] +build-backend = "setuptools.build_meta" +""") + testdir.tmpdir.join('build').ensure(dir=1) + return testdir + + @pytest.fixture(params=['', '--parallel 1 --parallel-live'], ids=['sequential', 'parallel']) def options(request): return ['-e', 'py-a,py-b'] + request.param.split() @@ -99,6 +125,16 @@ def test_enabled_pep517(testdir_pep517, options): assert result.ret == 0 +def test_enabled_pep517_build(testdir_pep517_build, options): + result = testdir_pep517_build.run('tox', *options) + result.stdout.fnmatch_lines([ + 'py* wheel-make: *', + ]) + build_string = 'Successfully built foobar-0.0.0.tar.gz and foobar-0.0.0-py3-none-any.whl' + assert result.stdout.str().count(build_string) == 2 + assert result.ret == 0 + + def test_build_env_legacy(testdir_legacy, options): testdir_legacy.tmpdir.join('setup.cfg').write(""" [bdist_wheel] @@ -141,6 +177,25 @@ def test_build_env_pep517(testdir_pep517, options): assert result.ret == 0 +def test_build_env_pep517_build(testdir_pep517_build, options): + testdir_pep517_build.tmpdir.join('setup.cfg').write(""" +[bdist_wheel] +universal = 1 +""") + testdir_pep517_build.tmpdir.join('tox.ini').write(""" +wheel_build_env = build + +[testenv:build] +""", mode='a') + result = testdir_pep517_build.run('tox', *options) + result.stdout.fnmatch_lines([ + 'build wheel-make: *', + ]) + build_string = 'Successfully built foobar-0.0.0.tar.gz and foobar-0.0.0-py2.py3-none-any.whl' + assert result.stdout.str().count(build_string) == 1 + assert result.ret == 0 + + @pytest.mark.parametrize('wheel_build_env', ['', 'wheel_build_env']) def test_skip_usedevelop(testdir_legacy, options, wheel_build_env): testdir_legacy.tmpdir.join('tox.ini').write(""" From f9bdd389f6e5a13e121a07e51e4a67873d81b492 Mon Sep 17 00:00:00 2001 From: Ben Rowland Date: Thu, 4 Aug 2022 16:39:15 +0100 Subject: [PATCH 2/4] Explicitly list packages in test build Changes in setuptools automatic package discovery meant that building with the build tool was failing because it didn't like the package structure in the generated folders. Adding an empty list as the packages argument to `setup()` solves this problem. --- tests/test_tox_wheel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tox_wheel.py b/tests/test_tox_wheel.py index dcd798f..6c24df6 100644 --- a/tests/test_tox_wheel.py +++ b/tests/test_tox_wheel.py @@ -67,7 +67,7 @@ def testdir_pep517_build(testdir): testdir.tmpdir.join('setup.py').write(""" from setuptools import setup -setup(name='foobar') +setup(name='foobar', packages=[]) """) testdir.tmpdir.join('pyproject.toml').write(""" [build-system] From 6da45c5c3bc43e3875d5c8ee1963214ef3ea08a3 Mon Sep 17 00:00:00 2001 From: Ben Rowland Date: Thu, 4 Aug 2022 16:39:35 +0100 Subject: [PATCH 3/4] Updated changelog --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c166d67..b26d5a2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog ========= +Unreleased changes +------------------ + +* Added option to build wheels (and sdists) in an isolated environment using `build `_. + Contributed by Ben Rowland in `#17 `_. + 0.7.0 (2021-12-29) ------------------ From 94d401284793084a65d438fcf2fc7fcc76f40672 Mon Sep 17 00:00:00 2001 From: Ben Rowland Date: Tue, 6 Sep 2022 10:22:26 +0100 Subject: [PATCH 4/4] modify install of "build" package to use unpinned version >=0.7.0 --- src/tox_wheel/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tox_wheel/plugin.py b/src/tox_wheel/plugin.py index 40dd879..4884e60 100644 --- a/src/tox_wheel/plugin.py +++ b/src/tox_wheel/plugin.py @@ -67,7 +67,7 @@ def patch(obj, attr, value): @hookimpl def tox_testenv_install_deps(venv, action): if venv.envconfig.wheel_pep517 == "build": - venv.run_install_command(["build[virtualenv]==0.7.0"], action) + venv.run_install_command(["build[virtualenv]>=0.7.0"], action) return None