@@ -200,13 +200,37 @@ a system having the necessary Python installed.
200
200
doc = """
201
201
An absolute path or program name on the `PATH` env var.
202
202
203
+ *Mutually exclusive with `interpreter_target`.*
204
+
203
205
Values with slashes are assumed to be the path to a program. Otherwise, it is
204
206
treated as something to search for on `PATH`
205
207
206
208
Note that, when a plain program name is used, the path to the interpreter is
207
209
resolved at repository evalution time, not runtime of any resulting binaries.
210
+
211
+ If not set, defaults to `python3`.
212
+
213
+ :::{seealso}
214
+ The {obj}`interpreter_target` attribute for getting the interpreter from
215
+ a label
216
+ :::
217
+ """ ,
218
+ default = "" ,
219
+ ),
220
+ "interpreter_target" : attr .label (
221
+ doc = """
222
+ A label to a Python interpreter executable.
223
+
224
+ *Mutually exclusive with `interpreter_path`.*
225
+
226
+ On Windows, if the path doesn't exist, various suffixes will be tried to
227
+ find a usable path.
228
+
229
+ :::{seealso}
230
+ The {obj}`interpreter_path` attribute for getting the interpreter from
231
+ a path or PATH environment lookup.
232
+ :::
208
233
""" ,
209
- default = "python3" ,
210
234
),
211
235
"on_failure" : attr .string (
212
236
default = _OnFailure .SKIP ,
@@ -247,6 +271,37 @@ def _expand_incompatible_template():
247
271
os = "@platforms//:incompatible" ,
248
272
)
249
273
274
+ def _find_python_exe_from_target (rctx ):
275
+ base_path = rctx .path (rctx .attr .interpreter_target )
276
+ if base_path .exists :
277
+ return base_path , None
278
+ attempted_paths = [base_path ]
279
+
280
+ # Try to convert a unix-y path to a Windows path. On Linux/Mac,
281
+ # the path is usually `bin/python3`. On Windows, it's simply
282
+ # `python.exe`.
283
+ basename = base_path .basename .rstrip ("3" )
284
+ path = base_path .dirname .dirname .get_child (basename )
285
+ path = rctx .path ("{}.exe" .format (path ))
286
+ if path .exists :
287
+ return path , None
288
+ attempted_paths .append (path )
289
+
290
+ # Try adding .exe to the base path
291
+ path = rctx .path ("{}.exe" .format (base_path ))
292
+ if path .exists :
293
+ return path , None
294
+ attempted_paths .append (path )
295
+
296
+ describe_failure = lambda : (
297
+ "Target '{target}' could not be resolved to a valid path. " +
298
+ "Attempted paths: {paths}"
299
+ ).format (
300
+ target = rctx .attr .interpreter_target ,
301
+ paths = "\n " .join ([str (p ) for p in attempted_paths ]),
302
+ )
303
+ return None , describe_failure
304
+
250
305
def _resolve_interpreter_path (rctx ):
251
306
"""Find the absolute path for an interpreter.
252
307
@@ -260,20 +315,27 @@ def _resolve_interpreter_path(rctx):
260
315
returns a description of why it couldn't be resolved
261
316
A path object or None. The path may not exist.
262
317
"""
263
- if "/" not in rctx .attr .interpreter_path and "\\ " not in rctx .attr .interpreter_path :
264
- # Provide a bit nicer integration with pyenv: recalculate the runtime if the
265
- # user changes the python version using e.g. `pyenv shell`
266
- repo_utils .getenv (rctx , "PYENV_VERSION" )
267
- result = repo_utils .which_unchecked (rctx , rctx .attr .interpreter_path )
268
- resolved_path = result .binary
269
- describe_failure = result .describe_failure
318
+ if rctx .attr .interpreter_path and rctx .attr .interpreter_target :
319
+ fail ("interpreter_path and interpreter_target are mutually exclusive" )
320
+
321
+ if rctx .attr .interpreter_target :
322
+ resolved_path , describe_failure = _find_python_exe_from_target (rctx )
270
323
else :
271
- rctx .watch (rctx .attr .interpreter_path )
272
- resolved_path = rctx .path (rctx .attr .interpreter_path )
273
- if not resolved_path .exists :
274
- describe_failure = lambda : "Path not found: {}" .format (repr (rctx .attr .interpreter_path ))
324
+ interpreter_path = rctx .attr .interpreter_path or "python3"
325
+ if "/" not in interpreter_path and "\\ " not in interpreter_path :
326
+ # Provide a bit nicer integration with pyenv: recalculate the runtime if the
327
+ # user changes the python version using e.g. `pyenv shell`
328
+ repo_utils .getenv (rctx , "PYENV_VERSION" )
329
+ result = repo_utils .which_unchecked (rctx , interpreter_path )
330
+ resolved_path = result .binary
331
+ describe_failure = result .describe_failure
275
332
else :
276
- describe_failure = None
333
+ rctx .watch (interpreter_path )
334
+ resolved_path = rctx .path (interpreter_path )
335
+ if not resolved_path .exists :
336
+ describe_failure = lambda : "Path not found: {}" .format (repr (interpreter_path ))
337
+ else :
338
+ describe_failure = None
277
339
278
340
return struct (
279
341
resolved_path = resolved_path ,
0 commit comments