From c4446a8f2effbe4a5ab5b30c2f5bd993e77b3a67 Mon Sep 17 00:00:00 2001 From: Matt Clay Date: Thu, 3 Oct 2019 12:48:29 -0700 Subject: [PATCH] [stable-2.9] Fix ansible-test virtualenv real python search. ansible-test now properly searches for `pythonX.Y` instead of `python` when looking for the real python that created a `virtualenv`. (cherry picked from commit b91f452f4f7b120b82305ef95010d49c74e21182) Co-authored-by: Matt Clay --- .../ansible-test-virtualenv-python-search.yml | 2 + test/lib/ansible_test/_internal/venv.py | 70 +++++++++++++++---- 2 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 changelogs/fragments/ansible-test-virtualenv-python-search.yml diff --git a/changelogs/fragments/ansible-test-virtualenv-python-search.yml b/changelogs/fragments/ansible-test-virtualenv-python-search.yml new file mode 100644 index 00000000000..67b8bea5887 --- /dev/null +++ b/changelogs/fragments/ansible-test-virtualenv-python-search.yml @@ -0,0 +1,2 @@ +bugfixes: + - ansible-test now properly searches for ``pythonX.Y`` instead of ``python`` when looking for the real python that created a ``virtualenv`` diff --git a/test/lib/ansible_test/_internal/venv.py b/test/lib/ansible_test/_internal/venv.py index 80859f629a9..4bd4abddbde 100644 --- a/test/lib/ansible_test/_internal/venv.py +++ b/test/lib/ansible_test/_internal/venv.py @@ -4,6 +4,7 @@ __metaclass__ = type import json import os +import sys from . import types as t @@ -46,9 +47,13 @@ def create_virtual_environment(args, # type: EnvironmentConfig if python_version >= (3, 0): # use the built-in 'venv' module on Python 3.x - if run_venv(args, python, system_site_packages, pip, path): - display.info('Created Python %s virtual environment using "venv": %s' % (version, path), verbosity=1) - return True + # creating a virtual environment using 'venv' when running in a virtual environment created by 'virtualenv' results + # in a copy of the original virtual environment instead of creation of a new one + # avoid this issue by only using "real" python interpreters to invoke 'venv' + for real_python in iterate_real_pythons(args, version): + if run_venv(args, real_python, system_site_packages, pip, path): + display.info('Created Python %s virtual environment using "venv": %s' % (version, path), verbosity=1) + return True # something went wrong, most likely the package maintainer for the Python installation removed ensurepip # which will prevent creation of a virtual environment without installation of other OS packages @@ -80,6 +85,56 @@ def create_virtual_environment(args, # type: EnvironmentConfig return False +def iterate_real_pythons(args, version): # type: (EnvironmentConfig, str) -> t.Iterable[str] + """ + Iterate through available real python interpreters of the requested version. + The current interpreter will be checked and then the path will be searched. + """ + version_info = tuple(int(n) for n in version.split('.')) + current_python = None + + if version_info == sys.version_info[:len(version_info)]: + current_python = sys.executable + real_prefix = get_python_real_prefix(args, current_python) + + if real_prefix: + current_python = find_python(version, os.path.join(real_prefix, 'bin')) + + if current_python: + yield current_python + + path = os.environ.get('PATH', os.path.defpath) + + if not path: + return + + found_python = find_python(version, path) + + if not found_python: + return + + if found_python == current_python: + return + + real_prefix = get_python_real_prefix(args, found_python) + + if real_prefix: + found_python = find_python(version, os.path.join(real_prefix, 'bin')) + + if found_python: + yield found_python + + +def get_python_real_prefix(args, path): # type: (EnvironmentConfig, str) -> t.Optional[str] + """ + Return the real prefix of the specified interpreter or None if the interpreter is not a virtual environment created by 'virtualenv'. + """ + cmd = [path, os.path.join(os.path.join(ANSIBLE_TEST_DATA_ROOT, 'virtualenvcheck.py'))] + check_result = json.loads(run_command(args, cmd, capture=True, always=True)[0]) + real_prefix = check_result['real_prefix'] + return real_prefix + + def run_venv(args, # type: EnvironmentConfig run_python, # type: str system_site_packages, # type: bool @@ -87,15 +142,6 @@ def run_venv(args, # type: EnvironmentConfig path, # type: str ): # type: (...) -> bool """Create a virtual environment using the 'venv' module. Not available on Python 2.x.""" - cmd = [run_python, os.path.join(os.path.join(ANSIBLE_TEST_DATA_ROOT, 'virtualenvcheck.py'))] - check_result = json.loads(run_command(args, cmd, capture=True, always=True)[0]) - real_prefix = check_result['real_prefix'] - - if real_prefix: - # we must use the real python to create a virtual environment with venv - # attempting to use python from a virtualenv created virtual environment results in a copy of that environment instead - run_python = os.path.join(real_prefix, 'bin', 'python') - cmd = [run_python, '-m', 'venv'] if system_site_packages: