-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement PEP 517 Build Backend #1039
Changes from all commits
28eef1e
c4b0389
fb5df4e
2911c9f
65ac1ca
5b8aceb
f1a3e2b
95ce0cf
76888ab
7079a47
cca9ed3
5f322bd
5db94ea
ea08d17
3427921
d2270e7
ea77313
0d8ee1f
93f5fe2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import os | ||
import sys | ||
import subprocess | ||
import tokenize | ||
import shutil | ||
import tempfile | ||
|
||
from setuptools import dist | ||
from setuptools.dist import SetupRequirementsError | ||
|
||
|
||
SETUPTOOLS_IMPLEMENTATION_REVISION = 0.1 | ||
|
||
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'] | ||
dist._skip_install_eggs = True | ||
|
||
sys.argv = sys.argv[:1] + ['egg_info'] + \ | ||
config_settings["--global-option"] | ||
try: | ||
_run_setup() | ||
except SetupRequirementsError as e: | ||
requirements += e.specifiers | ||
|
||
dist._skip_install_eggs = False | ||
|
||
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) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two |
||
|
||
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): | ||
sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', metadata_directory] | ||
_run_setup() | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will start working once pypa/wheel#190 is merged but there is no need to wait for that. |
||
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] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import pytest | ||
import os | ||
|
||
# Only test the backend on Python 3 | ||
# because we don't want to require | ||
# a concurrent.futures backport for testing | ||
pytest.importorskip('concurrent.futures') | ||
|
||
from contextlib import contextmanager | ||
from importlib import import_module | ||
from tempfile import mkdtemp | ||
from concurrent.futures import ProcessPoolExecutor | ||
from .files import build_files | ||
from .textwrap import DALS | ||
from . import contexts | ||
|
||
|
||
class BuildBackend(object): | ||
"""PEP 517 Build Backend""" | ||
def __init__(self, cwd=None, env={}, backend_name='setuptools.pep517'): | ||
self.cwd = cwd | ||
self.env = env | ||
self.backend_name = backend_name | ||
self.pool = ProcessPoolExecutor() | ||
|
||
def __getattr__(self, name): | ||
"""Handles aribrary function invokations on the build backend.""" | ||
|
||
def method(*args, **kw): | ||
return self.pool.submit( | ||
BuildBackendCaller(os.path.abspath(self.cwd), self.env, | ||
self.backend_name), | ||
(name, args, kw)).result() | ||
|
||
return method | ||
|
||
|
||
class BuildBackendCaller(object): | ||
def __init__(self, cwd, env, backend_name): | ||
self.cwd = cwd | ||
self.env = env | ||
self.backend_name = backend_name | ||
|
||
def __call__(self, info): | ||
"""Handles aribrary function invokations on the build backend.""" | ||
os.chdir(self.cwd) | ||
os.environ.update(self.env) | ||
name, args, kw = info | ||
return getattr(import_module(self.backend_name), name)(*args, **kw) | ||
|
||
|
||
@contextmanager | ||
def enter_directory(dir, val=None): | ||
original_dir = os.getcwd() | ||
os.chdir(dir) | ||
yield val | ||
os.chdir(original_dir) | ||
|
||
|
||
@pytest.fixture | ||
def build_backend(): | ||
tmpdir = mkdtemp() | ||
with enter_directory(tmpdir): | ||
setup_script = DALS(""" | ||
from setuptools import setup | ||
|
||
setup( | ||
name='foo', | ||
py_modules=['hello'], | ||
setup_requires=['six'], | ||
entry_points={'console_scripts': ['hi = hello.run']}, | ||
zip_safe=False, | ||
) | ||
""") | ||
|
||
build_files({ | ||
'setup.py': setup_script, | ||
'hello.py': DALS(""" | ||
def run(): | ||
print('hello') | ||
""") | ||
}) | ||
|
||
return enter_directory(tmpdir, BuildBackend(cwd='.')) | ||
|
||
|
||
def test_get_requires_for_build_wheel(build_backend): | ||
with build_backend as b: | ||
assert list(sorted(b.get_requires_for_build_wheel())) == \ | ||
list(sorted(['six', 'setuptools', 'wheel'])) | ||
|
||
def test_build_wheel(build_backend): | ||
with build_backend as b: | ||
dist_dir = os.path.abspath('pip-wheel') | ||
os.makedirs(dist_dir) | ||
wheel_name = b.build_wheel(dist_dir) | ||
|
||
assert os.path.isfile(os.path.join(dist_dir, wheel_name)) | ||
|
||
|
||
def test_build_sdist(build_backend): | ||
with build_backend as b: | ||
dist_dir = os.path.abspath('pip-sdist') | ||
os.makedirs(dist_dir) | ||
sdist_name = b.build_sdist(dist_dir) | ||
|
||
assert os.path.isfile(os.path.join(dist_dir, sdist_name)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So again I want to emphasize that this is an internal setuptools implementation detail that should not be used outside of setuptools.