-
Notifications
You must be signed in to change notification settings - Fork 55
Support in tree hooks (pyproject.toml backend-path key) #46
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
Changes from all commits
bd8c50d
c6d3135
c87b574
0f3af65
9e4935d
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 |
---|---|---|
|
@@ -26,6 +26,14 @@ def __init__(self, traceback): | |
self.traceback = traceback | ||
|
||
|
||
class BackendInvalid(Exception): | ||
"""Will be raised if the backend is invalid.""" | ||
def __init__(self, backend_name, backend_path, message): | ||
self.backend_name = backend_name | ||
self.backend_path = backend_path | ||
self.message = message | ||
|
||
|
||
class UnsupportedOperation(Exception): | ||
"""May be raised by build_sdist if the backend indicates that it can't.""" | ||
def __init__(self, traceback): | ||
|
@@ -41,15 +49,45 @@ def default_subprocess_runner(cmd, cwd=None, extra_environ=None): | |
check_call(cmd, cwd=cwd, env=env) | ||
|
||
|
||
def norm_and_check(source_tree, requested): | ||
"""Normalise and check a backend path. | ||
|
||
Ensure that the requested backend path is specified as a relative path, | ||
and resolves to a location under the given source tree. | ||
|
||
Return an absolute version of the requested path. | ||
""" | ||
if os.path.isabs(requested): | ||
raise ValueError("paths must be relative") | ||
|
||
abs_source = os.path.abspath(source_tree) | ||
abs_requested = os.path.normpath(os.path.join(abs_source, requested)) | ||
# We have to use commonprefix for Python 2.7 compatibility. So we | ||
# normalise case to avoid problems because commonprefix is a character | ||
# based comparison :-( | ||
norm_source = os.path.normcase(abs_source) | ||
norm_requested = os.path.normcase(abs_requested) | ||
if os.path.commonprefix([norm_source, norm_requested]) != norm_source: | ||
pfmoore marked this conversation as resolved.
Show resolved
Hide resolved
|
||
raise ValueError("paths must be inside source tree") | ||
|
||
return abs_requested | ||
|
||
|
||
class Pep517HookCaller(object): | ||
"""A wrapper around a source directory to be built with a PEP 517 backend. | ||
|
||
source_dir : The path to the source directory, containing pyproject.toml. | ||
backend : The build backend spec, as per PEP 517, from pyproject.toml. | ||
backend_path : The backend path, as per PEP 517, from pyproject.toml. | ||
""" | ||
def __init__(self, source_dir, build_backend): | ||
def __init__(self, source_dir, build_backend, backend_path=None): | ||
self.source_dir = abspath(source_dir) | ||
self.build_backend = build_backend | ||
if backend_path: | ||
backend_path = [ | ||
norm_and_check(self.source_dir, p) for p in backend_path | ||
] | ||
self.backend_path = backend_path | ||
self._subprocess_runner = default_subprocess_runner | ||
|
||
# TODO: Is this over-engineered? Maybe frontends only need to | ||
|
@@ -143,25 +181,40 @@ def _call_hook(self, hook_name, kwargs): | |
# letters, digits and _, . and : characters, and will be used as a | ||
# Python identifier, so non-ASCII content is wrong on Python 2 in | ||
# any case). | ||
# For backend_path, we use sys.getfilesystemencoding. | ||
if sys.version_info[0] == 2: | ||
build_backend = self.build_backend.encode('ASCII') | ||
else: | ||
build_backend = self.build_backend | ||
extra_environ = {'PEP517_BUILD_BACKEND': build_backend} | ||
|
||
if self.backend_path: | ||
backend_path = os.pathsep.join(self.backend_path) | ||
if sys.version_info[0] == 2: | ||
backend_path = backend_path.encode(sys.getfilesystemencoding()) | ||
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 may fail if a Unix filesystem erroneously reports that it's ASCII and someone tries to use a non-ASCII path. But I don't know what the correct thing to do on Python 2 in that case is (Python 3 has |
||
extra_environ['PEP517_BACKEND_PATH'] = backend_path | ||
|
||
with tempdir() as td: | ||
compat.write_json({'kwargs': kwargs}, pjoin(td, 'input.json'), | ||
hook_input = {'kwargs': kwargs} | ||
compat.write_json(hook_input, pjoin(td, 'input.json'), | ||
indent=2) | ||
|
||
# Run the hook in a subprocess | ||
self._subprocess_runner( | ||
[sys.executable, _in_proc_script, hook_name, td], | ||
cwd=self.source_dir, | ||
extra_environ={'PEP517_BUILD_BACKEND': build_backend} | ||
extra_environ=extra_environ | ||
) | ||
|
||
data = compat.read_json(pjoin(td, 'output.json')) | ||
if data.get('unsupported'): | ||
raise UnsupportedOperation(data.get('traceback', '')) | ||
if data.get('no_backend'): | ||
raise BackendUnavailable(data.get('traceback', '')) | ||
if data.get('backend_invalid'): | ||
raise BackendInvalid( | ||
backend_name=self.build_backend, | ||
backend_path=self.backend_path, | ||
message=data.get('backend_error', '') | ||
) | ||
return data['return_val'] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
def get_requires_for_build_sdist(config_settings): | ||
return ["intree_backend_called"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[build-system] | ||
build-backend = 'intree_backend' | ||
backend-path = ['backend'] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from os.path import dirname, abspath, join as pjoin | ||
import pytoml | ||
from testpath import modified_env | ||
import pytest | ||
|
||
from pep517.wrappers import Pep517HookCaller, BackendInvalid | ||
|
||
SAMPLES_DIR = pjoin(dirname(abspath(__file__)), 'samples') | ||
BUILDSYS_PKGS = pjoin(SAMPLES_DIR, 'buildsys_pkgs') | ||
|
||
|
||
def get_hooks(pkg, backend=None, path=None): | ||
source_dir = pjoin(SAMPLES_DIR, pkg) | ||
with open(pjoin(source_dir, 'pyproject.toml')) as f: | ||
data = pytoml.load(f) | ||
if backend is None: | ||
backend = data['build-system']['build-backend'] | ||
if path is None: | ||
path = data['build-system']['backend-path'] | ||
return Pep517HookCaller(source_dir, backend, path) | ||
|
||
|
||
def test_backend_path_within_tree(): | ||
source_dir = pjoin(SAMPLES_DIR, 'pkg1') | ||
assert Pep517HookCaller(source_dir, 'dummy', ['.', 'subdir']) | ||
assert Pep517HookCaller(source_dir, 'dummy', ['../pkg1', 'subdir/..']) | ||
# TODO: Do we want to insist on ValueError, or invent another exception? | ||
with pytest.raises(Exception): | ||
assert Pep517HookCaller(source_dir, 'dummy', [source_dir]) | ||
with pytest.raises(Exception): | ||
Pep517HookCaller(source_dir, 'dummy', ['.', '..']) | ||
with pytest.raises(Exception): | ||
Pep517HookCaller(source_dir, 'dummy', ['subdir/../..']) | ||
with pytest.raises(Exception): | ||
Pep517HookCaller(source_dir, 'dummy', ['/']) | ||
|
||
|
||
def test_intree_backend(): | ||
hooks = get_hooks('pkg_intree') | ||
with modified_env({'PYTHONPATH': BUILDSYS_PKGS}): | ||
res = hooks.get_requires_for_build_sdist({}) | ||
assert res == ["intree_backend_called"] | ||
|
||
|
||
def test_intree_backend_not_in_path(): | ||
hooks = get_hooks('pkg_intree', backend='buildsys') | ||
with modified_env({'PYTHONPATH': BUILDSYS_PKGS}): | ||
with pytest.raises(BackendInvalid): | ||
hooks.get_requires_for_build_sdist({}) |
Uh oh!
There was an error while loading. Please reload this page.