Skip to content

Commit

Permalink
Allow user to inject specific Pythons to try as step 1 during discovery
Browse files Browse the repository at this point in the history
At the moment it's unecessarily hard to specify a specific python
executable to use for a given spec. While mangling with the path
might work for environments for tox environments that require a python
differing the host tox python version; for environments matching the
host tox interpreter it's impossible to use any other interpreter than
what tox uses. Here we add a CLI argument (fallback to some environment
variable for CI usage) that allows the user to inject custom python
interpreters as step 1 of the discovery mechanism.

Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net>
  • Loading branch information
gaborbernat committed Feb 16, 2020
1 parent ff6368b commit 1c5c716
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 18 deletions.
2 changes: 2 additions & 0 deletions docs/changelog/x.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add ``--discover`` (fallback to ``TOX_DISCOVER`` environment variable via path separator) to inject python executables
to try as first step of a discovery - note the executable still needs to match the environment by :user:`gaborbernat`.
8 changes: 8 additions & 0 deletions src/tox/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,14 @@ def tox_addoption(parser):
help="write a json file with detailed information "
"about all commands and results involved.",
)
parser.add_argument(
"--discover",
dest="discover",
nargs="+",
metavar="PATH",
help="for python discovery first try the python executables under these paths",
default=[],
)

# We choose 1 to 4294967295 because it is the range of PYTHONHASHSEED.
parser.add_argument(
Expand Down
25 changes: 25 additions & 0 deletions src/tox/interpreters/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import os

from tox.interpreters.py_spec import CURRENT, PythonSpec
from tox.interpreters.via_path import exe_spec


def base_discover(envconfig):
base_python = envconfig.basepython
spec = PythonSpec.from_name(base_python)

# 1. check passed in discover elements
discovers = envconfig.config.option.discover
if not discovers:
discovers = os.environ.get(str("TOX_DISCOVER"), "").split(os.pathsep)
for discover in discovers:
if os.path.exists(discover):
cur_spec = exe_spec(discover, envconfig.basepython)
if cur_spec is not None and cur_spec.satisfies(spec):
return spec, cur_spec.path

# 2. check current
if spec.name is not None and CURRENT.satisfies(spec):
return spec, CURRENT.path

return spec, None
18 changes: 8 additions & 10 deletions src/tox/interpreters/unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@

import tox

from .py_spec import CURRENT, PythonSpec
from .common import base_discover
from .via_path import check_with_path


@tox.hookimpl
def tox_get_python_executable(envconfig):
base_python = envconfig.basepython
spec = PythonSpec.from_name(base_python)
# first, check current
if spec.name is not None and CURRENT.satisfies(spec):
return CURRENT.path
# second check if the literal base python
candidates = [base_python]
# third check if the un-versioned name is good
if spec.name is not None and spec.name != base_python:
spec, path = base_discover(envconfig)
if path is not None:
return path
# 3. check if the literal base python
candidates = [envconfig.basepython]
# 4. check if the un-versioned name is good
if spec.name is not None and spec.name != envconfig.basepython:
candidates.append(spec.name)
return check_with_path(candidates, spec)
14 changes: 6 additions & 8 deletions src/tox/interpreters/windows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@

import tox

from ..py_spec import CURRENT, PythonSpec
from ..common import base_discover
from ..py_spec import CURRENT
from ..via_path import check_with_path


@tox.hookimpl
def tox_get_python_executable(envconfig):
base_python = envconfig.basepython
spec = PythonSpec.from_name(base_python)
# first, check current
if spec.name is not None and CURRENT.satisfies(spec):
return CURRENT.path

spec, path = base_discover(envconfig)
if path is not None:
return path
# second check if the py.exe has it (only for non path specs)
if spec.path is None:
py_exe = locate_via_pep514(spec)
Expand All @@ -25,7 +23,7 @@ def tox_get_python_executable(envconfig):
# third check if the literal base python is on PATH
candidates = [envconfig.basepython]
# fourth check if the name is on PATH
if spec.name is not None and spec.name != base_python:
if spec.name is not None and spec.name != envconfig.basepython:
candidates.append(spec.name)
# or check known locations
if spec.major is not None and spec.minor is not None:
Expand Down

0 comments on commit 1c5c716

Please sign in to comment.