@@ -26,6 +26,14 @@ def __init__(self, traceback):
26
26
self .traceback = traceback
27
27
28
28
29
+ class BackendInvalid (Exception ):
30
+ """Will be raised if the backend is invalid."""
31
+ def __init__ (self , backend_name , backend_path , message ):
32
+ self .backend_name = backend_name
33
+ self .backend_path = backend_path
34
+ self .message = message
35
+
36
+
29
37
class UnsupportedOperation (Exception ):
30
38
"""May be raised by build_sdist if the backend indicates that it can't."""
31
39
def __init__ (self , traceback ):
@@ -41,15 +49,45 @@ def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
41
49
check_call (cmd , cwd = cwd , env = env )
42
50
43
51
52
+ def norm_and_check (source_tree , requested ):
53
+ """Normalise and check a backend path.
54
+
55
+ Ensure that the requested backend path is specified as a relative path,
56
+ and resolves to a location under the given source tree.
57
+
58
+ Return an absolute version of the requested path.
59
+ """
60
+ if os .path .isabs (requested ):
61
+ raise ValueError ("paths must be relative" )
62
+
63
+ abs_source = os .path .abspath (source_tree )
64
+ abs_requested = os .path .normpath (os .path .join (abs_source , requested ))
65
+ # We have to use commonprefix for Python 2.7 compatibility. So we
66
+ # normalise case to avoid problems because commonprefix is a character
67
+ # based comparison :-(
68
+ norm_source = os .path .normcase (abs_source )
69
+ norm_requested = os .path .normcase (abs_requested )
70
+ if os .path .commonprefix ([norm_source , norm_requested ]) != norm_source :
71
+ raise ValueError ("paths must be inside source tree" )
72
+
73
+ return abs_requested
74
+
75
+
44
76
class Pep517HookCaller (object ):
45
77
"""A wrapper around a source directory to be built with a PEP 517 backend.
46
78
47
79
source_dir : The path to the source directory, containing pyproject.toml.
48
80
backend : The build backend spec, as per PEP 517, from pyproject.toml.
81
+ backend_path : The backend path, as per PEP 517, from pyproject.toml.
49
82
"""
50
- def __init__ (self , source_dir , build_backend ):
83
+ def __init__ (self , source_dir , build_backend , backend_path = None ):
51
84
self .source_dir = abspath (source_dir )
52
85
self .build_backend = build_backend
86
+ if backend_path :
87
+ backend_path = [
88
+ norm_and_check (self .source_dir , p ) for p in backend_path
89
+ ]
90
+ self .backend_path = backend_path
53
91
self ._subprocess_runner = default_subprocess_runner
54
92
55
93
# TODO: Is this over-engineered? Maybe frontends only need to
@@ -143,25 +181,40 @@ def _call_hook(self, hook_name, kwargs):
143
181
# letters, digits and _, . and : characters, and will be used as a
144
182
# Python identifier, so non-ASCII content is wrong on Python 2 in
145
183
# any case).
184
+ # For backend_path, we use sys.getfilesystemencoding.
146
185
if sys .version_info [0 ] == 2 :
147
186
build_backend = self .build_backend .encode ('ASCII' )
148
187
else :
149
188
build_backend = self .build_backend
189
+ extra_environ = {'PEP517_BUILD_BACKEND' : build_backend }
190
+
191
+ if self .backend_path :
192
+ backend_path = os .pathsep .join (self .backend_path )
193
+ if sys .version_info [0 ] == 2 :
194
+ backend_path = backend_path .encode (sys .getfilesystemencoding ())
195
+ extra_environ ['PEP517_BACKEND_PATH' ] = backend_path
150
196
151
197
with tempdir () as td :
152
- compat .write_json ({'kwargs' : kwargs }, pjoin (td , 'input.json' ),
198
+ hook_input = {'kwargs' : kwargs }
199
+ compat .write_json (hook_input , pjoin (td , 'input.json' ),
153
200
indent = 2 )
154
201
155
202
# Run the hook in a subprocess
156
203
self ._subprocess_runner (
157
204
[sys .executable , _in_proc_script , hook_name , td ],
158
205
cwd = self .source_dir ,
159
- extra_environ = { 'PEP517_BUILD_BACKEND' : build_backend }
206
+ extra_environ = extra_environ
160
207
)
161
208
162
209
data = compat .read_json (pjoin (td , 'output.json' ))
163
210
if data .get ('unsupported' ):
164
211
raise UnsupportedOperation (data .get ('traceback' , '' ))
165
212
if data .get ('no_backend' ):
166
213
raise BackendUnavailable (data .get ('traceback' , '' ))
214
+ if data .get ('backend_invalid' ):
215
+ raise BackendInvalid (
216
+ backend_name = self .build_backend ,
217
+ backend_path = self .backend_path ,
218
+ message = data .get ('backend_error' , '' )
219
+ )
167
220
return data ['return_val' ]
0 commit comments