From 74c323d1658f554b879ef6ac2867faf77776a2ac Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 27 Jan 2019 11:45:10 -0500 Subject: [PATCH 01/11] Wrap build_meta backend in a class In order to support both the `build_meta` and `build_meta_legacy` backends, the core functionality is wrapped in a class with methods to be overridden in build_meta_legacy. The class is an implementation detail and should remain private. --- setuptools/build_meta.py | 187 ++++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 90 deletions(-) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index c883d92f14..f40549e709 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -74,81 +74,11 @@ def _to_str(s): return s -def _run_setup(setup_script='setup.py'): - # Note that we can reuse our build directory between calls - # Correctness comes first, then optimization later - __file__ = setup_script - __name__ = '__main__' - f = getattr(tokenize, 'open', open)(__file__) - code = f.read().replace('\\r\\n', '\\n') - f.close() - exec(compile(code, __file__, 'exec'), locals()) - - -def _fix_config(config_settings): - config_settings = config_settings or {} - config_settings.setdefault('--global-option', []) - return config_settings - - -def _get_build_requires(config_settings, requirements): - config_settings = _fix_config(config_settings) - - sys.argv = sys.argv[:1] + ['egg_info'] + \ - config_settings["--global-option"] - try: - with Distribution.patch(): - _run_setup() - except SetupRequirementsError as e: - requirements += e.specifiers - - return requirements - - def _get_immediate_subdirectories(a_dir): return [name for name in os.listdir(a_dir) if os.path.isdir(os.path.join(a_dir, name))] -def get_requires_for_build_wheel(config_settings=None): - config_settings = _fix_config(config_settings) - return _get_build_requires(config_settings, requirements=['wheel']) - - -def get_requires_for_build_sdist(config_settings=None): - config_settings = _fix_config(config_settings) - return _get_build_requires(config_settings, requirements=[]) - - -def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): - sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', _to_str(metadata_directory)] - _run_setup() - - dist_info_directory = metadata_directory - while True: - dist_infos = [f for f in os.listdir(dist_info_directory) - if f.endswith('.dist-info')] - - if len(dist_infos) == 0 and \ - len(_get_immediate_subdirectories(dist_info_directory)) == 1: - dist_info_directory = os.path.join( - dist_info_directory, os.listdir(dist_info_directory)[0]) - continue - - assert len(dist_infos) == 1 - break - - # PEP 517 requires that the .dist-info directory be placed in the - # metadata_directory. To comply, we MUST copy the directory to the root - if dist_info_directory != metadata_directory: - shutil.move( - os.path.join(dist_info_directory, dist_infos[0]), - metadata_directory) - shutil.rmtree(dist_info_directory, ignore_errors=True) - - return dist_infos[0] - - def _file_with_extension(directory, extension): matching = ( f for f in os.listdir(directory) @@ -158,26 +88,103 @@ def _file_with_extension(directory, extension): return file -def build_wheel(wheel_directory, config_settings=None, - metadata_directory=None): - config_settings = _fix_config(config_settings) - wheel_directory = os.path.abspath(wheel_directory) - sys.argv = sys.argv[:1] + ['bdist_wheel'] + \ - config_settings["--global-option"] - _run_setup() - if wheel_directory != 'dist': - shutil.rmtree(wheel_directory) - shutil.copytree('dist', wheel_directory) +class _BuildMetaBackend(object): - return _file_with_extension(wheel_directory, '.whl') + def _fix_config(self, config_settings): + config_settings = config_settings or {} + config_settings.setdefault('--global-option', []) + return config_settings + def _get_build_requires(self, config_settings, requirements): + config_settings = self._fix_config(config_settings) -def build_sdist(sdist_directory, config_settings=None): - config_settings = _fix_config(config_settings) - sdist_directory = os.path.abspath(sdist_directory) - sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \ - config_settings["--global-option"] + \ - ["--dist-dir", sdist_directory] - _run_setup() - - return _file_with_extension(sdist_directory, '.tar.gz') + sys.argv = sys.argv[:1] + ['egg_info'] + \ + config_settings["--global-option"] + try: + with Distribution.patch(): + self.run_setup() + except SetupRequirementsError as e: + requirements += e.specifiers + + return requirements + + def run_setup(self, setup_script='setup.py'): + # Note that we can reuse our build directory between calls + # Correctness comes first, then optimization later + __file__ = setup_script + __name__ = '__main__' + f = getattr(tokenize, 'open', open)(__file__) + code = f.read().replace('\\r\\n', '\\n') + f.close() + exec(compile(code, __file__, 'exec'), locals()) + + def get_requires_for_build_wheel(self, config_settings=None): + config_settings = self._fix_config(config_settings) + return self._get_build_requires(config_settings, requirements=['wheel']) + + def get_requires_for_build_sdist(self, config_settings=None): + config_settings = self._fix_config(config_settings) + return self._get_build_requires(config_settings, requirements=[]) + + def prepare_metadata_for_build_wheel(self, metadata_directory, + config_settings=None): + sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', + _to_str(metadata_directory)] + self.run_setup() + + dist_info_directory = metadata_directory + while True: + dist_infos = [f for f in os.listdir(dist_info_directory) + if f.endswith('.dist-info')] + + if (len(dist_infos) == 0 and + len(_get_immediate_subdirectories(dist_info_directory)) == 1): + + dist_info_directory = os.path.join( + dist_info_directory, os.listdir(dist_info_directory)[0]) + continue + + assert len(dist_infos) == 1 + break + + # PEP 517 requires that the .dist-info directory be placed in the + # metadata_directory. To comply, we MUST copy the directory to the root + if dist_info_directory != metadata_directory: + shutil.move( + os.path.join(dist_info_directory, dist_infos[0]), + metadata_directory) + shutil.rmtree(dist_info_directory, ignore_errors=True) + + return dist_infos[0] + + def build_wheel(self, wheel_directory, config_settings=None, + metadata_directory=None): + config_settings = self._fix_config(config_settings) + wheel_directory = os.path.abspath(wheel_directory) + sys.argv = sys.argv[:1] + ['bdist_wheel'] + \ + config_settings["--global-option"] + self.run_setup() + if wheel_directory != 'dist': + shutil.rmtree(wheel_directory) + shutil.copytree('dist', wheel_directory) + + return _file_with_extension(wheel_directory, '.whl') + + def build_sdist(self, sdist_directory, config_settings=None): + config_settings = self._fix_config(config_settings) + sdist_directory = os.path.abspath(sdist_directory) + sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \ + config_settings["--global-option"] + \ + ["--dist-dir", sdist_directory] + self.run_setup() + + return _file_with_extension(sdist_directory, '.tar.gz') + + +_BACKEND = _BuildMetaBackend() + +get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel +get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist +prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel +build_wheel = _BACKEND.build_wheel +build_sdist = _BACKEND.build_sdist From f40a47a776904b09747502a1f210af9fc92ec542 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 27 Jan 2019 11:46:21 -0500 Subject: [PATCH 02/11] Add __all__ to setuptools.build_meta --- setuptools/build_meta.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index f40549e709..8e31a04d45 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -35,6 +35,12 @@ import setuptools import distutils +__all__ = ['get_requires_for_build_sdist', + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'build_sdist', + 'SetupRequirementsError'] class SetupRequirementsError(BaseException): def __init__(self, specifiers): From 6d0daf14277223da4e32093c00157ae5b26d1ece Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 27 Jan 2019 12:11:21 -0500 Subject: [PATCH 03/11] Wrap build_meta tests in a reusable test class --- setuptools/tests/test_build_meta.py | 268 ++++++++++++++-------------- 1 file changed, 134 insertions(+), 134 deletions(-) diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 82b44c8944..a9865382eb 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -6,7 +6,6 @@ import pytest -from setuptools.build_meta import build_sdist from .files import build_files from .textwrap import DALS from . import py2_only @@ -103,136 +102,137 @@ def run(): ] -@pytest.fixture(params=defns) -def build_backend(tmpdir, request): - build_files(request.param, prefix=str(tmpdir)) - with tmpdir.as_cwd(): - yield BuildBackend(cwd='.') - - -def test_get_requires_for_build_wheel(build_backend): - actual = build_backend.get_requires_for_build_wheel() - expected = ['six', 'wheel'] - assert sorted(actual) == sorted(expected) - - -def test_get_requires_for_build_sdist(build_backend): - actual = build_backend.get_requires_for_build_sdist() - expected = ['six'] - assert sorted(actual) == sorted(expected) - - -def test_build_wheel(build_backend): - dist_dir = os.path.abspath('pip-wheel') - os.makedirs(dist_dir) - wheel_name = build_backend.build_wheel(dist_dir) - - assert os.path.isfile(os.path.join(dist_dir, wheel_name)) - - -def test_build_sdist(build_backend): - dist_dir = os.path.abspath('pip-sdist') - os.makedirs(dist_dir) - sdist_name = build_backend.build_sdist(dist_dir) - - assert os.path.isfile(os.path.join(dist_dir, sdist_name)) - - -def test_prepare_metadata_for_build_wheel(build_backend): - dist_dir = os.path.abspath('pip-dist-info') - os.makedirs(dist_dir) - - dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir) - - assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA')) - - -@py2_only -def test_prepare_metadata_for_build_wheel_with_str(build_backend): - dist_dir = os.path.abspath(str('pip-dist-info')) - os.makedirs(dist_dir) - - dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir) - - assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA')) - - -def test_build_sdist_explicit_dist(build_backend): - # explicitly specifying the dist folder should work - # the folder sdist_directory and the ``--dist-dir`` can be the same - dist_dir = os.path.abspath('dist') - sdist_name = build_backend.build_sdist(dist_dir) - assert os.path.isfile(os.path.join(dist_dir, sdist_name)) - - -def test_build_sdist_version_change(build_backend): - sdist_into_directory = os.path.abspath("out_sdist") - os.makedirs(sdist_into_directory) - - sdist_name = build_backend.build_sdist(sdist_into_directory) - assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name)) - - # if the setup.py changes subsequent call of the build meta - # should still succeed, given the - # sdist_directory the frontend specifies is empty - with open(os.path.abspath("setup.py"), 'rt') as file_handler: - content = file_handler.read() - with open(os.path.abspath("setup.py"), 'wt') as file_handler: - file_handler.write( - content.replace("version='0.0.0'", "version='0.0.1'")) - - shutil.rmtree(sdist_into_directory) - os.makedirs(sdist_into_directory) - - sdist_name = build_backend.build_sdist("out_sdist") - assert os.path.isfile( - os.path.join(os.path.abspath("out_sdist"), sdist_name)) - - -def test_build_sdist_setup_py_exists(tmpdir_cwd): - # If build_sdist is called from a script other than setup.py, - # ensure setup.py is include - build_files(defns[0]) - targz_path = build_sdist("temp") - with tarfile.open(os.path.join("temp", targz_path)) as tar: - assert any('setup.py' in name for name in tar.getnames()) - - -def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd): - # Ensure that MANIFEST.in can exclude setup.py - files = { - 'setup.py': DALS(""" - __import__('setuptools').setup( - name='foo', - version='0.0.0', - py_modules=['hello'] - )"""), - 'hello.py': '', - 'MANIFEST.in': DALS(""" - exclude setup.py - """) - } - - build_files(files) - targz_path = build_sdist("temp") - with tarfile.open(os.path.join("temp", targz_path)) as tar: - assert not any('setup.py' in name for name in tar.getnames()) - - -def test_build_sdist_builds_targz_even_if_zip_indicated(tmpdir_cwd): - files = { - 'setup.py': DALS(""" - __import__('setuptools').setup( - name='foo', - version='0.0.0', - py_modules=['hello'] - )"""), - 'hello.py': '', - 'setup.cfg': DALS(""" - [sdist] - formats=zip - """) - } - - build_files(files) - build_sdist("temp") +class TestBuildMetaBackend: + backend_name = 'setuptools.build_meta' + + def get_build_backend(self): + return BuildBackend(cwd='.', backend_name=self.backend_name) + + @pytest.fixture(params=defns) + def build_backend(self, tmpdir, request): + build_files(request.param, prefix=str(tmpdir)) + with tmpdir.as_cwd(): + yield self.get_build_backend() + + def test_get_requires_for_build_wheel(self, build_backend): + actual = build_backend.get_requires_for_build_wheel() + expected = ['six', 'wheel'] + assert sorted(actual) == sorted(expected) + + def test_get_requires_for_build_sdist(self, build_backend): + actual = build_backend.get_requires_for_build_sdist() + expected = ['six'] + assert sorted(actual) == sorted(expected) + + def test_build_wheel(self, build_backend): + dist_dir = os.path.abspath('pip-wheel') + os.makedirs(dist_dir) + wheel_name = build_backend.build_wheel(dist_dir) + + assert os.path.isfile(os.path.join(dist_dir, wheel_name)) + + def test_build_sdist(self, build_backend): + dist_dir = os.path.abspath('pip-sdist') + os.makedirs(dist_dir) + sdist_name = build_backend.build_sdist(dist_dir) + + assert os.path.isfile(os.path.join(dist_dir, sdist_name)) + + def test_prepare_metadata_for_build_wheel(self, build_backend): + dist_dir = os.path.abspath('pip-dist-info') + os.makedirs(dist_dir) + + dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir) + + assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA')) + + @py2_only + def test_prepare_metadata_for_build_wheel_with_str(self, build_backend): + dist_dir = os.path.abspath(str('pip-dist-info')) + os.makedirs(dist_dir) + + dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir) + + assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA')) + + def test_build_sdist_explicit_dist(self, build_backend): + # explicitly specifying the dist folder should work + # the folder sdist_directory and the ``--dist-dir`` can be the same + dist_dir = os.path.abspath('dist') + sdist_name = build_backend.build_sdist(dist_dir) + assert os.path.isfile(os.path.join(dist_dir, sdist_name)) + + def test_build_sdist_version_change(self, build_backend): + sdist_into_directory = os.path.abspath("out_sdist") + os.makedirs(sdist_into_directory) + + sdist_name = build_backend.build_sdist(sdist_into_directory) + assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name)) + + # if the setup.py changes subsequent call of the build meta + # should still succeed, given the + # sdist_directory the frontend specifies is empty + with open(os.path.abspath("setup.py"), 'rt') as file_handler: + content = file_handler.read() + with open(os.path.abspath("setup.py"), 'wt') as file_handler: + file_handler.write( + content.replace("version='0.0.0'", "version='0.0.1'")) + + shutil.rmtree(sdist_into_directory) + os.makedirs(sdist_into_directory) + + sdist_name = build_backend.build_sdist("out_sdist") + assert os.path.isfile( + os.path.join(os.path.abspath("out_sdist"), sdist_name)) + + def test_build_sdist_setup_py_exists(self, tmpdir_cwd): + # If build_sdist is called from a script other than setup.py, + # ensure setup.py is included + build_files(defns[0]) + + build_backend = self.get_build_backend() + targz_path = build_backend.build_sdist("temp") + with tarfile.open(os.path.join("temp", targz_path)) as tar: + assert any('setup.py' in name for name in tar.getnames()) + + def test_build_sdist_setup_py_manifest_excluded(self, tmpdir_cwd): + # Ensure that MANIFEST.in can exclude setup.py + files = { + 'setup.py': DALS(""" + __import__('setuptools').setup( + name='foo', + version='0.0.0', + py_modules=['hello'] + )"""), + 'hello.py': '', + 'MANIFEST.in': DALS(""" + exclude setup.py + """) + } + + build_files(files) + + build_backend = self.get_build_backend() + targz_path = build_backend.build_sdist("temp") + with tarfile.open(os.path.join("temp", targz_path)) as tar: + assert not any('setup.py' in name for name in tar.getnames()) + + def test_build_sdist_builds_targz_even_if_zip_indicated(self, tmpdir_cwd): + files = { + 'setup.py': DALS(""" + __import__('setuptools').setup( + name='foo', + version='0.0.0', + py_modules=['hello'] + )"""), + 'hello.py': '', + 'setup.cfg': DALS(""" + [sdist] + formats=zip + """) + } + + build_files(files) + + build_backend = self.get_build_backend() + build_backend.build_sdist("temp") From bd800f4c793fdcff2347a263c39c4256107b417f Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 27 Jan 2019 12:24:15 -0500 Subject: [PATCH 04/11] Add test for relative path imports in build_meta Failing test adapted from PR #1643 Co-authored-by: Tzu-ping Chung --- setuptools/tests/test_build_meta.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index a9865382eb..8222b8a2ea 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -236,3 +236,23 @@ def test_build_sdist_builds_targz_even_if_zip_indicated(self, tmpdir_cwd): build_backend = self.get_build_backend() build_backend.build_sdist("temp") + + _relative_path_import_files = { + 'setup.py': DALS(""" + __import__('setuptools').setup( + name='foo', + version=__import__('hello').__version__, + py_modules=['hello'] + )"""), + 'hello.py': '__version__ = "0.0.0"', + 'setup.cfg': DALS(""" + [sdist] + formats=zip + """) + } + + def test_build_sdist_relative_path_import(self, tmpdir_cwd): + build_files(self._relative_path_import_files) + build_backend = self.get_build_backend() + with pytest.raises(ImportError): + build_backend.build_sdist("temp") From a114112ea5e6df87a826e0fa2927cffd2472aec1 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 27 Jan 2019 12:16:14 -0500 Subject: [PATCH 05/11] Add failing test suite for build_meta_legacy This runs all build_meta tests, plus a test that it is possible to import from the directory containing `setup.py` when using the build_meta_legacy backend. --- setuptools/tests/test_build_meta.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 8222b8a2ea..5d97ebcec2 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -256,3 +256,16 @@ def test_build_sdist_relative_path_import(self, tmpdir_cwd): build_backend = self.get_build_backend() with pytest.raises(ImportError): build_backend.build_sdist("temp") + + +@pytest.mark.xfail +class TestBuildMetaLegacyBackend(TestBuildMetaBackend): + backend_name = 'setuptools.build_meta_legacy' + + # build_meta_legacy-specific tests + def test_build_sdist_relative_path_import(self, tmpdir_cwd): + # This must fail in build_meta, but must pass in build_meta_legacy + build_files(self._relative_path_import_files) + + build_backend = self.get_build_backend() + build_backend.build_sdist("temp") From fd3b06dae1a33b3131cf96ddb9d7938430b9d8b5 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 27 Jan 2019 12:31:28 -0500 Subject: [PATCH 06/11] Add build_meta_legacy backend This is part of the solution to GH #1642, it is a backwards-compatibility backend that can be used as a default PEP 517 backend for projects that use setuptools but haven't opted in to build_meta. --- setuptools/build_meta_legacy.py | 44 +++++++++++++++++++++++++++++ setuptools/tests/test_build_meta.py | 1 - 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 setuptools/build_meta_legacy.py diff --git a/setuptools/build_meta_legacy.py b/setuptools/build_meta_legacy.py new file mode 100644 index 0000000000..7eed41f111 --- /dev/null +++ b/setuptools/build_meta_legacy.py @@ -0,0 +1,44 @@ +"""Compatibility backend for setuptools + +This is a version of setuptools.build_meta that endeavors to maintain backwards +compatibility with pre-PEP 517 modes of invocation. It exists as a temporary +bridge between the old packaging mechanism and the new packaging mechanism, +and will eventually be removed. +""" + +import sys + +from setuptools.build_meta import _BuildMetaBackend +from setuptools.build_meta import SetupRequirementsError + + +__all__ = ['get_requires_for_build_sdist', + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'build_sdist', + 'SetupRequirementsError'] + + +class _BuildMetaLegacyBackend(_BuildMetaBackend): + def run_setup(self, setup_script='setup.py'): + # In order to maintain compatibility with scripts assuming that + # the setup.py script is in a directory on the PYTHONPATH, inject + # '' into sys.path. (pypa/setuptools#1642) + sys_path = list(sys.path) # Save the old path + if '' not in sys.path: + sys.path.insert(0, '') + + super(_BuildMetaLegacyBackend, + self).run_setup(setup_script=setup_script) + + sys.path = sys_path # Restore the old path + + +_BACKEND = _BuildMetaLegacyBackend() + +get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel +get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist +prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel +build_wheel = _BACKEND.build_wheel +build_sdist = _BACKEND.build_sdist diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 5d97ebcec2..b29d6f29e5 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -258,7 +258,6 @@ def test_build_sdist_relative_path_import(self, tmpdir_cwd): build_backend.build_sdist("temp") -@pytest.mark.xfail class TestBuildMetaLegacyBackend(TestBuildMetaBackend): backend_name = 'setuptools.build_meta_legacy' From 90a8701ae4088db2092a574c39c9ee56efc7eb6b Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 27 Jan 2019 12:51:05 -0500 Subject: [PATCH 07/11] Add news fragment for PR #1652 --- changelog.d/1652.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1652.change.rst diff --git a/changelog.d/1652.change.rst b/changelog.d/1652.change.rst new file mode 100644 index 0000000000..f535769942 --- /dev/null +++ b/changelog.d/1652.change.rst @@ -0,0 +1 @@ +Added the ``build_meta_legacy`` backend, a "compatibility mode" PEP 517 backend that can be used as the default when ``build-backend`` is left unspecified in ``pyproject.toml``. From 49d17725a0ad02552babbdc79c737cfb27430f4f Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 28 Jan 2019 09:22:49 -0500 Subject: [PATCH 08/11] Set sys.path in try/finally block with comment Per Nick Coghlan's suggestion on PR #1652, a try/finally block ensures that the path is restored even in the event of an error. --- setuptools/build_meta_legacy.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/setuptools/build_meta_legacy.py b/setuptools/build_meta_legacy.py index 7eed41f111..03492da9b5 100644 --- a/setuptools/build_meta_legacy.py +++ b/setuptools/build_meta_legacy.py @@ -6,6 +6,7 @@ and will eventually be removed. """ +import os import sys from setuptools.build_meta import _BuildMetaBackend @@ -25,14 +26,21 @@ def run_setup(self, setup_script='setup.py'): # In order to maintain compatibility with scripts assuming that # the setup.py script is in a directory on the PYTHONPATH, inject # '' into sys.path. (pypa/setuptools#1642) - sys_path = list(sys.path) # Save the old path - if '' not in sys.path: - sys.path.insert(0, '') - - super(_BuildMetaLegacyBackend, - self).run_setup(setup_script=setup_script) - - sys.path = sys_path # Restore the old path + sys_path = list(sys.path) # Save the original path + + try: + if '' not in sys.path: + sys.path.insert(0, '') + + super(_BuildMetaLegacyBackend, + self).run_setup(setup_script=setup_script) + finally: + # While PEP 517 frontends should be calling each hook in a fresh + # subprocess according to the standard (and thus it should not be + # strictly necessary to restore the old sys.path), we'll restore + # the original path so that the path manipulation does not persist + # within the hook after run_setup is called. + sys.path[:] = sys_path _BACKEND = _BuildMetaLegacyBackend() From db590baf81ebcb23605da54118905437412228ce Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 28 Jan 2019 09:25:03 -0500 Subject: [PATCH 09/11] Use absolute path in build_meta_legacy Using the absolute path to the directory of the setup script better mimics the semantics of a direct invocation of python setup.py. --- setuptools/build_meta_legacy.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setuptools/build_meta_legacy.py b/setuptools/build_meta_legacy.py index 03492da9b5..3d209711fe 100644 --- a/setuptools/build_meta_legacy.py +++ b/setuptools/build_meta_legacy.py @@ -28,10 +28,11 @@ def run_setup(self, setup_script='setup.py'): # '' into sys.path. (pypa/setuptools#1642) sys_path = list(sys.path) # Save the original path - try: - if '' not in sys.path: - sys.path.insert(0, '') + script_dir = os.path.dirname(os.path.abspath(setup_script)) + if script_dir not in sys.path: + sys.path.insert(0, script_dir) + try: super(_BuildMetaLegacyBackend, self).run_setup(setup_script=setup_script) finally: From 11fb3f38d23ff1e0d81e64ba3b68b3de2d2b990a Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 3 Feb 2019 12:17:46 -0500 Subject: [PATCH 10/11] Move build_meta_legacy to build_meta:legacy Rather than exposing a top-level module for the legacy backend, we will move the legacy backend into the `setuptools.build_meta` module and specify it using the module:object syntax. --- changelog.d/1652.change.rst | 2 +- setuptools/build_meta.py | 35 +++++++++++++++++++ setuptools/build_meta_legacy.py | 53 ----------------------------- setuptools/tests/test_build_meta.py | 17 +++++++-- 4 files changed, 50 insertions(+), 57 deletions(-) delete mode 100644 setuptools/build_meta_legacy.py diff --git a/changelog.d/1652.change.rst b/changelog.d/1652.change.rst index f535769942..545365ac83 100644 --- a/changelog.d/1652.change.rst +++ b/changelog.d/1652.change.rst @@ -1 +1 @@ -Added the ``build_meta_legacy`` backend, a "compatibility mode" PEP 517 backend that can be used as the default when ``build-backend`` is left unspecified in ``pyproject.toml``. +Added the ``build_meta:legacy`` backend, a "compatibility mode" PEP 517 backend that can be used as the default when ``build-backend`` is left unspecified in ``pyproject.toml``. diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index 8e31a04d45..e16f319e3c 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -40,6 +40,7 @@ 'prepare_metadata_for_build_wheel', 'build_wheel', 'build_sdist', + 'legacy', 'SetupRequirementsError'] class SetupRequirementsError(BaseException): @@ -187,6 +188,36 @@ def build_sdist(self, sdist_directory, config_settings=None): return _file_with_extension(sdist_directory, '.tar.gz') +class _BuildMetaLegacyBackend(_BuildMetaBackend): + """Compatibility backend for setuptools + + This is a version of setuptools.build_meta that endeavors to maintain backwards + compatibility with pre-PEP 517 modes of invocation. It exists as a temporary + bridge between the old packaging mechanism and the new packaging mechanism, + and will eventually be removed. + """ + def run_setup(self, setup_script='setup.py'): + # In order to maintain compatibility with scripts assuming that + # the setup.py script is in a directory on the PYTHONPATH, inject + # '' into sys.path. (pypa/setuptools#1642) + sys_path = list(sys.path) # Save the original path + + script_dir = os.path.dirname(os.path.abspath(setup_script)) + if script_dir not in sys.path: + sys.path.insert(0, script_dir) + + try: + super(_BuildMetaLegacyBackend, + self).run_setup(setup_script=setup_script) + finally: + # While PEP 517 frontends should be calling each hook in a fresh + # subprocess according to the standard (and thus it should not be + # strictly necessary to restore the old sys.path), we'll restore + # the original path so that the path manipulation does not persist + # within the hook after run_setup is called. + sys.path[:] = sys_path + +# The primary backend _BACKEND = _BuildMetaBackend() get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel @@ -194,3 +225,7 @@ def build_sdist(self, sdist_directory, config_settings=None): prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel build_wheel = _BACKEND.build_wheel build_sdist = _BACKEND.build_sdist + + +# The legacy backend +legacy = _BuildMetaLegacyBackend() diff --git a/setuptools/build_meta_legacy.py b/setuptools/build_meta_legacy.py deleted file mode 100644 index 3d209711fe..0000000000 --- a/setuptools/build_meta_legacy.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Compatibility backend for setuptools - -This is a version of setuptools.build_meta that endeavors to maintain backwards -compatibility with pre-PEP 517 modes of invocation. It exists as a temporary -bridge between the old packaging mechanism and the new packaging mechanism, -and will eventually be removed. -""" - -import os -import sys - -from setuptools.build_meta import _BuildMetaBackend -from setuptools.build_meta import SetupRequirementsError - - -__all__ = ['get_requires_for_build_sdist', - 'get_requires_for_build_wheel', - 'prepare_metadata_for_build_wheel', - 'build_wheel', - 'build_sdist', - 'SetupRequirementsError'] - - -class _BuildMetaLegacyBackend(_BuildMetaBackend): - def run_setup(self, setup_script='setup.py'): - # In order to maintain compatibility with scripts assuming that - # the setup.py script is in a directory on the PYTHONPATH, inject - # '' into sys.path. (pypa/setuptools#1642) - sys_path = list(sys.path) # Save the original path - - script_dir = os.path.dirname(os.path.abspath(setup_script)) - if script_dir not in sys.path: - sys.path.insert(0, script_dir) - - try: - super(_BuildMetaLegacyBackend, - self).run_setup(setup_script=setup_script) - finally: - # While PEP 517 frontends should be calling each hook in a fresh - # subprocess according to the standard (and thus it should not be - # strictly necessary to restore the old sys.path), we'll restore - # the original path so that the path manipulation does not persist - # within the hook after run_setup is called. - sys.path[:] = sys_path - - -_BACKEND = _BuildMetaLegacyBackend() - -get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel -get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist -prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel -build_wheel = _BACKEND.build_wheel -build_sdist = _BACKEND.build_sdist diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index b29d6f29e5..42e3098ae1 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -23,7 +23,6 @@ def __init__(self, cwd=None, env={}, backend_name='setuptools.build_meta'): self.env = env self.backend_name = backend_name - class BuildBackend(BuildBackendBase): """PEP 517 Build Backend""" @@ -43,12 +42,24 @@ def method(*args, **kw): class BuildBackendCaller(BuildBackendBase): + def __init__(self, *args, **kwargs): + super(BuildBackendCaller, self).__init__(*args, **kwargs) + + (self.backend_name, _, + self.backend_obj) = self.backend_name.partition(':') + def __call__(self, name, *args, **kw): """Handles aribrary function invocations on the build backend.""" os.chdir(self.cwd) os.environ.update(self.env) mod = importlib.import_module(self.backend_name) - return getattr(mod, name)(*args, **kw) + + if self.backend_obj: + backend = getattr(mod, self.backend_obj) + else: + backend = mod + + return getattr(backend, name)(*args, **kw) defns = [ @@ -259,7 +270,7 @@ def test_build_sdist_relative_path_import(self, tmpdir_cwd): class TestBuildMetaLegacyBackend(TestBuildMetaBackend): - backend_name = 'setuptools.build_meta_legacy' + backend_name = 'setuptools.build_meta:legacy' # build_meta_legacy-specific tests def test_build_sdist_relative_path_import(self, tmpdir_cwd): From e04a41e3129fa9945e15b16fd6d65cc212c1d946 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Tue, 5 Feb 2019 08:42:36 -0500 Subject: [PATCH 11/11] Rename build_meta:legacy to build_meta:__legacy__ --- changelog.d/1652.change.rst | 2 +- setuptools/build_meta.py | 4 ++-- setuptools/tests/test_build_meta.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog.d/1652.change.rst b/changelog.d/1652.change.rst index 545365ac83..387d212ced 100644 --- a/changelog.d/1652.change.rst +++ b/changelog.d/1652.change.rst @@ -1 +1 @@ -Added the ``build_meta:legacy`` backend, a "compatibility mode" PEP 517 backend that can be used as the default when ``build-backend`` is left unspecified in ``pyproject.toml``. +Added the ``build_meta:__legacy__`` backend, a "compatibility mode" PEP 517 backend that can be used as the default when ``build-backend`` is left unspecified in ``pyproject.toml``. diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index e16f319e3c..70b7ab230e 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -40,7 +40,7 @@ 'prepare_metadata_for_build_wheel', 'build_wheel', 'build_sdist', - 'legacy', + '__legacy__', 'SetupRequirementsError'] class SetupRequirementsError(BaseException): @@ -228,4 +228,4 @@ def run_setup(self, setup_script='setup.py'): # The legacy backend -legacy = _BuildMetaLegacyBackend() +__legacy__ = _BuildMetaLegacyBackend() diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 42e3098ae1..6236b9f42b 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -270,7 +270,7 @@ def test_build_sdist_relative_path_import(self, tmpdir_cwd): class TestBuildMetaLegacyBackend(TestBuildMetaBackend): - backend_name = 'setuptools.build_meta:legacy' + backend_name = 'setuptools.build_meta:__legacy__' # build_meta_legacy-specific tests def test_build_sdist_relative_path_import(self, tmpdir_cwd):