summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernát Gábor <bgabor8@bloomberg.net>2020-02-16 23:36:52 +0000
committerGitHub <noreply@github.com>2020-02-16 23:36:52 +0000
commit7409527035b4cf75477d2564b5f13f5c783b3178 (patch)
tree034a1142123725ca728ee1c845d7190067727fe6
parentff6368bfdbd9e9d3f4ce1c7badf860367d3c2c36 (diff)
parente1b165d558980b305b057f4d643d5280d17c3889 (diff)
downloadtox-git-7409527035b4cf75477d2564b5f13f5c783b3178.tar.gz
Merge pull request #1526 from gaborbernat/force
Allow user to inject specific Pythons to try as step 1 during discovery
-rw-r--r--docs/changelog/1526.feature.rst2
-rw-r--r--src/tox/config/__init__.py8
-rw-r--r--src/tox/interpreters/common.py25
-rw-r--r--src/tox/interpreters/unix.py18
-rw-r--r--src/tox/interpreters/windows/__init__.py14
-rw-r--r--tests/unit/interpreters/test_interpreters.py20
6 files changed, 64 insertions, 23 deletions
diff --git a/docs/changelog/1526.feature.rst b/docs/changelog/1526.feature.rst
new file mode 100644
index 00000000..eb3c9de3
--- /dev/null
+++ b/docs/changelog/1526.feature.rst
@@ -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`.
diff --git a/src/tox/config/__init__.py b/src/tox/config/__init__.py
index c159ac33..f6b34844 100644
--- a/src/tox/config/__init__.py
+++ b/src/tox/config/__init__.py
@@ -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(
diff --git a/src/tox/interpreters/common.py b/src/tox/interpreters/common.py
new file mode 100644
index 00000000..a1087fe4
--- /dev/null
+++ b/src/tox/interpreters/common.py
@@ -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
diff --git a/src/tox/interpreters/unix.py b/src/tox/interpreters/unix.py
index c8738de9..08194adf 100644
--- a/src/tox/interpreters/unix.py
+++ b/src/tox/interpreters/unix.py
@@ -2,20 +2,18 @@ from __future__ import unicode_literals
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)
diff --git a/src/tox/interpreters/windows/__init__.py b/src/tox/interpreters/windows/__init__.py
index 3933ae18..e03c342c 100644
--- a/src/tox/interpreters/windows/__init__.py
+++ b/src/tox/interpreters/windows/__init__.py
@@ -4,18 +4,16 @@ from threading import Lock
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)
@@ -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:
diff --git a/tests/unit/interpreters/test_interpreters.py b/tests/unit/interpreters/test_interpreters.py
index 31d67ac7..5d532e0b 100644
--- a/tests/unit/interpreters/test_interpreters.py
+++ b/tests/unit/interpreters/test_interpreters.py
@@ -29,10 +29,12 @@ def create_interpreters_instance():
@pytest.mark.skipif(tox.INFO.IS_PYPY, reason="testing cpython interpreter discovery")
-def test_tox_get_python_executable():
+def test_tox_get_python_executable(mocker):
class envconfig:
basepython = sys.executable
envname = "pyxx"
+ config = mocker.MagicMock()
+ config.return_value.option.return_value.discover = []
def get_exe(name):
envconfig.basepython = name
@@ -73,7 +75,7 @@ def test_tox_get_python_executable():
@pytest.mark.skipif("sys.platform == 'win32'", reason="symlink execution unreliable on Windows")
-def test_find_alias_on_path(monkeypatch, tmp_path):
+def test_find_alias_on_path(monkeypatch, tmp_path, mocker):
reporter.update_default_reporter(Verbosity.DEFAULT, Verbosity.DEBUG)
magic = tmp_path / "magic{}".format(os.path.splitext(sys.executable)[1])
os.symlink(sys.executable, str(magic))
@@ -85,6 +87,8 @@ def test_find_alias_on_path(monkeypatch, tmp_path):
class envconfig:
basepython = "magic"
envname = "pyxx"
+ config = mocker.MagicMock()
+ config.return_value.option.return_value.discover = []
detected = py.path.local.sysfind("magic")
assert detected
@@ -102,10 +106,12 @@ def test_run_and_get_interpreter_info():
class TestInterpreters:
- def test_get_executable(self, interpreters):
+ def test_get_executable(self, interpreters, mocker):
class envconfig:
basepython = sys.executable
envname = "pyxx"
+ config = mocker.MagicMock()
+ config.return_value.option.return_value.discover = []
x = interpreters.get_executable(envconfig)
assert x == sys.executable
@@ -114,10 +120,12 @@ class TestInterpreters:
assert info.executable == sys.executable
assert isinstance(info, InterpreterInfo)
- def test_get_executable_no_exist(self, interpreters):
+ def test_get_executable_no_exist(self, interpreters, mocker):
class envconfig:
basepython = "1lkj23"
envname = "pyxx"
+ config = mocker.MagicMock()
+ config.return_value.option.return_value.discover = []
assert not interpreters.get_executable(envconfig)
info = interpreters.get_info(envconfig)
@@ -154,10 +162,12 @@ class TestInterpreters:
info = interpreters.get_info(envconfig)
assert info.executable == str(magic)
- def test_get_sitepackagesdir_error(self, interpreters):
+ def test_get_sitepackagesdir_error(self, interpreters, mocker):
class envconfig:
basepython = sys.executable
envname = "123"
+ config = mocker.MagicMock()
+ config.return_value.option.return_value.discover = []
info = interpreters.get_info(envconfig)
s = interpreters.get_sitepackagesdir(info, "")