diff options
author | Bernát Gábor <bgabor8@bloomberg.net> | 2020-02-16 23:36:52 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-16 23:36:52 +0000 |
commit | 7409527035b4cf75477d2564b5f13f5c783b3178 (patch) | |
tree | 034a1142123725ca728ee1c845d7190067727fe6 | |
parent | ff6368bfdbd9e9d3f4ce1c7badf860367d3c2c36 (diff) | |
parent | e1b165d558980b305b057f4d643d5280d17c3889 (diff) | |
download | tox-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.rst | 2 | ||||
-rw-r--r-- | src/tox/config/__init__.py | 8 | ||||
-rw-r--r-- | src/tox/interpreters/common.py | 25 | ||||
-rw-r--r-- | src/tox/interpreters/unix.py | 18 | ||||
-rw-r--r-- | src/tox/interpreters/windows/__init__.py | 14 | ||||
-rw-r--r-- | tests/unit/interpreters/test_interpreters.py | 20 |
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, "") |