Commit 369b8e05 authored by Paul Ganssle's avatar Paul Ganssle Committed by GitHub

Merge pull request #1652 from pganssle/build_meta_legacy

Add build_meta_legacy backend
parents 64e60fc3 e04a41e3
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``.
......@@ -35,6 +35,13 @@ import contextlib
import setuptools
import distutils
__all__ = ['get_requires_for_build_sdist',
'get_requires_for_build_wheel',
'prepare_metadata_for_build_wheel',
'build_wheel',
'build_sdist',
'__legacy__',
'SetupRequirementsError']
class SetupRequirementsError(BaseException):
def __init__(self, specifiers):
......@@ -74,63 +81,72 @@ 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 _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 _fix_config(config_settings):
def _file_with_extension(directory, extension):
matching = (
f for f in os.listdir(directory)
if f.endswith(extension)
)
file, = matching
return file
class _BuildMetaBackend(object):
def _fix_config(self, 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)
def _get_build_requires(self, config_settings, requirements):
config_settings = self._fix_config(config_settings)
sys.argv = sys.argv[:1] + ['egg_info'] + \
config_settings["--global-option"]
try:
with Distribution.patch():
_run_setup()
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_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 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(metadata_directory, config_settings=None):
sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', _to_str(metadata_directory)]
_run_setup()
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:
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
......@@ -148,36 +164,68 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
return dist_infos[0]
def _file_with_extension(directory, extension):
matching = (
f for f in os.listdir(directory)
if f.endswith(extension)
)
file, = matching
return file
def build_wheel(wheel_directory, config_settings=None,
def build_wheel(self, wheel_directory, config_settings=None,
metadata_directory=None):
config_settings = _fix_config(config_settings)
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"]
_run_setup()
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(sdist_directory, config_settings=None):
config_settings = _fix_config(config_settings)
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]
_run_setup()
self.run_setup()
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
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
# The legacy backend
__legacy__ = _BuildMetaLegacyBackend()
......@@ -6,7 +6,6 @@ import tarfile
import pytest
from setuptools.build_meta import build_sdist
from .files import build_files
from .textwrap import DALS
from . import py2_only
......@@ -24,7 +23,6 @@ class BuildBackendBase:
self.env = env
self.backend_name = backend_name
class BuildBackend(BuildBackendBase):
"""PEP 517 Build Backend"""
......@@ -44,12 +42,24 @@ class BuildBackend(BuildBackendBase):
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 = [
......@@ -103,42 +113,43 @@ defns = [
]
@pytest.fixture(params=defns)
def build_backend(tmpdir, request):
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 BuildBackend(cwd='.')
yield self.get_build_backend()
def test_get_requires_for_build_wheel(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(build_backend):
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(build_backend):
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(build_backend):
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(build_backend):
def test_prepare_metadata_for_build_wheel(self, build_backend):
dist_dir = os.path.abspath('pip-dist-info')
os.makedirs(dist_dir)
......@@ -146,9 +157,8 @@ def test_prepare_metadata_for_build_wheel(build_backend):
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):
@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)
......@@ -156,16 +166,14 @@ def test_prepare_metadata_for_build_wheel_with_str(build_backend):
assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
def test_build_sdist_explicit_dist(build_backend):
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(build_backend):
def test_build_sdist_version_change(self, build_backend):
sdist_into_directory = os.path.abspath("out_sdist")
os.makedirs(sdist_into_directory)
......@@ -188,17 +196,17 @@ def test_build_sdist_version_change(build_backend):
assert os.path.isfile(
os.path.join(os.path.abspath("out_sdist"), sdist_name))
def test_build_sdist_setup_py_exists(tmpdir_cwd):
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 include
# ensure setup.py is included
build_files(defns[0])
targz_path = build_sdist("temp")
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(tmpdir_cwd):
def test_build_sdist_setup_py_manifest_excluded(self, tmpdir_cwd):
# Ensure that MANIFEST.in can exclude setup.py
files = {
'setup.py': DALS("""
......@@ -214,12 +222,13 @@ def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd):
}
build_files(files)
targz_path = build_sdist("temp")
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(tmpdir_cwd):
def test_build_sdist_builds_targz_even_if_zip_indicated(self, tmpdir_cwd):
files = {
'setup.py': DALS("""
__import__('setuptools').setup(
......@@ -235,4 +244,38 @@ def test_build_sdist_builds_targz_even_if_zip_indicated(tmpdir_cwd):
}
build_files(files)
build_sdist("temp")
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")
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")
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment