@@ -196,17 +196,41 @@ create thousands of files for every `py_test`), at the risk of having to rely on
196196a system having the necessary Python installed.
197197""" ,
198198 attrs = {
199+ "interpreter_target" : attr .label (
200+ doc = """
201+ A label to a Python interpreter executable.
202+
203+ *Mutually exclusive with `interpreter_path`.*
204+
205+ On Windows, if the path doesn't exist, various suffixes will be tried to
206+ find a usable path.
207+
208+ :::{seealso}
209+ The {obj}`interpreter_path` attribute for getting the interpreter from
210+ a path or PATH environment lookup.
211+ :::
212+ """
213+ ),
199214 "interpreter_path" : attr .string (
200215 doc = """
201216An absolute path or program name on the `PATH` env var.
202217
218+ *Mutually exclusive with `interpreter_target`.*
219+
203220Values with slashes are assumed to be the path to a program. Otherwise, it is
204221treated as something to search for on `PATH`
205222
206223Note that, when a plain program name is used, the path to the interpreter is
207224resolved at repository evalution time, not runtime of any resulting binaries.
225+
226+ If not set, defaults to `python3`.
227+
228+ :::{seealso}
229+ The {obj}`interpreter_target` attribute for getting the interpreter from
230+ a label
231+ :::
208232""" ,
209- default = "python3 " ,
233+ default = "" ,
210234 ),
211235 "on_failure" : attr .string (
212236 default = _OnFailure .SKIP ,
@@ -260,20 +284,34 @@ def _resolve_interpreter_path(rctx):
260284 returns a description of why it couldn't be resolved
261285 A path object or None. The path may not exist.
262286 """
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
287+ if rctx .attr .interpreter_path and rctx .attr .interpreter_target :
288+ fail ("interpreter_path and interpreter_target are mutually exclusive" )
289+
290+ if rctx .attr .interpreter_target :
291+ path = rctx .path (rctx .attr .interpreter_target )
292+ if path .exists :
293+ resolved_path = path
294+ describe_failure = None
295+ else :
296+ resolved_path = None
297+ describe_failure = lambda : "Target '{}' could not be resolved to a file that exists" .format (rctx .attr .interpreter_target )
298+
270299 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 ))
300+ interpreter_path = rctx .attr .interpreter_path or "python3"
301+ if "/" not in interpreter_path and "\\ " not in interpreter_path :
302+ # Provide a bit nicer integration with pyenv: recalculate the runtime if the
303+ # user changes the python version using e.g. `pyenv shell`
304+ repo_utils .getenv (rctx , "PYENV_VERSION" )
305+ result = repo_utils .which_unchecked (rctx , interpreter_path )
306+ resolved_path = result .binary
307+ describe_failure = result .describe_failure
275308 else :
276- describe_failure = None
309+ rctx .watch (interpreter_path )
310+ resolved_path = rctx .path (interpreter_path )
311+ if not resolved_path .exists :
312+ describe_failure = lambda : "Path not found: {}" .format (repr (interpreter_path ))
313+ else :
314+ describe_failure = None
277315
278316 return struct (
279317 resolved_path = resolved_path ,
0 commit comments