aboutsummaryrefslogtreecommitdiffstats
path: root/setuptools
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2017-10-12 10:21:00 +0200
committerJason R. Coombs <jaraco@jaraco.com>2017-10-12 10:21:00 +0200
commit749c3fe3232fd99d8c98d15113cce3e7f3c6a6f4 (patch)
treec0d9535404c3d50bdb17bb4eaf0896c28a420b0c /setuptools
parentba46991d7cd488d682e80bb0e69964c4ed6c3303 (diff)
parent8c385a127bdffd372f266f4fd4eb4fc134e132a3 (diff)
downloadexternal_python_setuptools-749c3fe3232fd99d8c98d15113cce3e7f3c6a6f4.tar.gz
external_python_setuptools-749c3fe3232fd99d8c98d15113cce3e7f3c6a6f4.tar.bz2
external_python_setuptools-749c3fe3232fd99d8c98d15113cce3e7f3c6a6f4.zip
Merge pull request #1143 from xoviat pep517.
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/build_meta.py148
-rw-r--r--setuptools/command/__init__.py2
-rw-r--r--setuptools/command/dist_info.py37
-rw-r--r--setuptools/tests/test_build_meta.py93
4 files changed, 279 insertions, 1 deletions
diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py
new file mode 100644
index 00000000..54f2987b
--- /dev/null
+++ b/setuptools/build_meta.py
@@ -0,0 +1,148 @@
+"""A PEP 517 interface to setuptools
+
+Previously, when a user or a command line tool (let's call it a "frontend")
+needed to make a request of setuptools to take a certain action, for
+example, generating a list of installation requirements, the frontend would
+would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line.
+
+PEP 517 defines a different method of interfacing with setuptools. Rather
+than calling "setup.py" directly, the frontend should:
+
+ 1. Set the current directory to the directory with a setup.py file
+ 2. Import this module into a safe python interpreter (one in which
+ setuptools can potentially set global variables or crash hard).
+ 3. Call one of the functions defined in PEP 517.
+
+What each function does is defined in PEP 517. However, here is a "casual"
+definition of the functions (this definition should not be relied on for
+bug reports or API stability):
+
+ - `build_wheel`: build a wheel in the folder and return the basename
+ - `get_requires_for_build_wheel`: get the `setup_requires` to build
+ - `prepare_metadata_for_build_wheel`: get the `install_requires`
+ - `build_sdist`: build an sdist in the folder and return the basename
+ - `get_requires_for_build_sdist`: get the `setup_requires` to build
+
+Again, this is not a formal definition! Just a "taste" of the module.
+"""
+
+import os
+import sys
+import tokenize
+import shutil
+import contextlib
+
+import setuptools
+import distutils
+
+
+class SetupRequirementsError(BaseException):
+ def __init__(self, specifiers):
+ self.specifiers = specifiers
+
+
+class Distribution(setuptools.dist.Distribution):
+ def fetch_build_eggs(self, specifiers):
+ raise SetupRequirementsError(specifiers)
+
+ @classmethod
+ @contextlib.contextmanager
+ def patch(cls):
+ """
+ Replace
+ distutils.dist.Distribution with this class
+ for the duration of this context.
+ """
+ orig = distutils.core.Distribution
+ distutils.core.Distribution = cls
+ try:
+ yield
+ finally:
+ distutils.core.Distribution = orig
+
+
+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
+ f = getattr(tokenize, 'open', open)(__file__)
+ code = f.read().replace('\\r\\n', '\\n')
+ f.close()
+ exec(compile(code, __file__, 'exec'))
+
+
+def _fix_config(config_settings):
+ config_settings = config_settings or {}
+ config_settings.setdefault('--global-option', [])
+ return config_settings
+
+
+def _get_build_requires(config_settings):
+ config_settings = _fix_config(config_settings)
+ requirements = ['setuptools', 'wheel']
+
+ 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_requires_for_build_wheel(config_settings=None):
+ config_settings = _fix_config(config_settings)
+ return _get_build_requires(config_settings)
+
+
+def get_requires_for_build_sdist(config_settings=None):
+ config_settings = _fix_config(config_settings)
+ return _get_build_requires(config_settings)
+
+
+def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
+ sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', metadata_directory]
+ _run_setup()
+
+ dist_infos = [f for f in os.listdir(metadata_directory)
+ if f.endswith('.dist-info')]
+
+ assert len(dist_infos) == 1
+ return dist_infos[0]
+
+
+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)
+
+ wheels = [f for f in os.listdir(wheel_directory)
+ if f.endswith('.whl')]
+
+ assert len(wheels) == 1
+ return wheels[0]
+
+
+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'] + \
+ config_settings["--global-option"]
+ _run_setup()
+ if sdist_directory != 'dist':
+ shutil.rmtree(sdist_directory)
+ shutil.copytree('dist', sdist_directory)
+
+ sdists = [f for f in os.listdir(sdist_directory)
+ if f.endswith('.tar.gz')]
+
+ assert len(sdists) == 1
+ return sdists[0]
diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py
index c96d33c2..4fe3bb56 100644
--- a/setuptools/command/__init__.py
+++ b/setuptools/command/__init__.py
@@ -2,7 +2,7 @@ __all__ = [
'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop',
'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts',
- 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib',
+ 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib', 'dist_info',
]
from distutils.command.bdist import bdist
diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py
new file mode 100644
index 00000000..c8dc659b
--- /dev/null
+++ b/setuptools/command/dist_info.py
@@ -0,0 +1,37 @@
+"""
+Create a dist_info directory
+As defined in the wheel specification
+"""
+
+import os
+import shutil
+
+from distutils.core import Command
+
+
+class dist_info(Command):
+
+ description = 'create a .dist-info directory'
+
+ user_options = [
+ ('egg-base=', 'e', "directory containing .egg-info directories"
+ " (default: top of the source tree)"),
+ ]
+
+ def initialize_options(self):
+ self.egg_base = None
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ egg_info = self.get_finalized_command('egg_info')
+ egg_info.run()
+ dist_info_dir = egg_info.egg_info[:-len('.egg-info')] + '.dist-info'
+
+ bdist_wheel = self.get_finalized_command('bdist_wheel')
+ bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir)
+
+ if self.egg_base:
+ shutil.move(dist_info_dir, os.path.join(
+ self.egg_base, dist_info_dir))
diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py
new file mode 100644
index 00000000..69a700c2
--- /dev/null
+++ b/setuptools/tests/test_build_meta.py
@@ -0,0 +1,93 @@
+import os
+
+import pytest
+
+from .files import build_files
+from .textwrap import DALS
+
+
+futures = pytest.importorskip('concurrent.futures')
+importlib = pytest.importorskip('importlib')
+
+
+class BuildBackendBase(object):
+ def __init__(self, cwd=None, env={}, backend_name='setuptools.build_meta'):
+ self.cwd = cwd
+ self.env = env
+ self.backend_name = backend_name
+
+
+class BuildBackend(BuildBackendBase):
+ """PEP 517 Build Backend"""
+ def __init__(self, *args, **kwargs):
+ super(BuildBackend, self).__init__(*args, **kwargs)
+ self.pool = futures.ProcessPoolExecutor()
+
+ def __getattr__(self, name):
+ """Handles aribrary function invocations on the build backend."""
+ def method(*args, **kw):
+ root = os.path.abspath(self.cwd)
+ caller = BuildBackendCaller(root, self.env, self.backend_name)
+ return self.pool.submit(caller, name, *args, **kw).result()
+
+ return method
+
+
+class BuildBackendCaller(BuildBackendBase):
+ 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)
+
+
+@pytest.fixture
+def build_backend(tmpdir):
+ defn = {
+ 'setup.py': DALS("""
+ __import__('setuptools').setup(
+ name='foo',
+ py_modules=['hello'],
+ setup_requires=['six'],
+ )
+ """),
+ 'hello.py': DALS("""
+ def run():
+ print('hello')
+ """),
+ }
+ build_files(defn, 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', 'setuptools', 'wheel']
+ 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'))