Skip to content

Commit 206e529

Browse files
kkpatternmiss-islington
authored andcommitted
gh-99370: Calculate zip path from prefix when in a venv (GH-99371)
Before python3.11, when in a venv the zip path is calculated from prefix on POSIX platforms. In python3.11 the behavior is accidentally changed to calculating from default prefix. This change will break venv created from a non-installed python with a stdlib zip file. This commit restores the behavior back to before python3.11. (cherry picked from commit e3d4fed) Co-authored-by: Kai Zhang <kylerzhang11@gmail.com>
1 parent 3b8bcfc commit 206e529

File tree

4 files changed

+99
-2
lines changed

4 files changed

+99
-2
lines changed

Lib/test/test_getpath.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,39 @@ def test_venv_changed_name_posix(self):
383383
actual = getpath(ns, expected)
384384
self.assertEqual(expected, actual)
385385

386+
def test_venv_non_installed_zip_path_posix(self):
387+
"Test a venv created from non-installed python has correct zip path."""
388+
ns = MockPosixNamespace(
389+
argv0="/venv/bin/python",
390+
PREFIX="/usr",
391+
ENV_PATH="/venv/bin:/usr/bin",
392+
)
393+
ns.add_known_xfile("/path/to/non-installed/bin/python")
394+
ns.add_known_xfile("/venv/bin/python")
395+
ns.add_known_link("/venv/bin/python",
396+
"/path/to/non-installed/bin/python")
397+
ns.add_known_file("/path/to/non-installed/lib/python9.8/os.py")
398+
ns.add_known_dir("/path/to/non-installed/lib/python9.8/lib-dynload")
399+
ns.add_known_file("/venv/pyvenv.cfg", [
400+
r"home = /path/to/non-installed"
401+
])
402+
expected = dict(
403+
executable="/venv/bin/python",
404+
prefix="/path/to/non-installed",
405+
exec_prefix="/path/to/non-installed",
406+
base_executable="/path/to/non-installed/bin/python",
407+
base_prefix="/path/to/non-installed",
408+
base_exec_prefix="/path/to/non-installed",
409+
module_search_paths_set=1,
410+
module_search_paths=[
411+
"/path/to/non-installed/lib/python98.zip",
412+
"/path/to/non-installed/lib/python9.8",
413+
"/path/to/non-installed/lib/python9.8/lib-dynload",
414+
],
415+
)
416+
actual = getpath(ns, expected)
417+
self.assertEqual(expected, actual)
418+
386419
def test_venv_changed_name_copy_posix(self):
387420
"Test a venv --copies layout on *nix that lacks a distributed 'python'"
388421
ns = MockPosixNamespace(

Lib/test/test_venv.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,69 @@ def test_pathsep_error(self):
537537
self.assertRaises(ValueError, venv.create, bad_itempath)
538538
self.assertRaises(ValueError, venv.create, pathlib.Path(bad_itempath))
539539

540+
@unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
541+
@requireVenvCreate
542+
def test_zippath_from_non_installed_posix(self):
543+
"""
544+
Test that when create venv from non-installed python, the zip path
545+
value is as expected.
546+
"""
547+
rmtree(self.env_dir)
548+
# First try to create a non-installed python. It's not a real full
549+
# functional non-installed python, but enough for this test.
550+
non_installed_dir = os.path.realpath(tempfile.mkdtemp())
551+
try:
552+
bindir = os.path.join(non_installed_dir, self.bindir)
553+
os.mkdir(bindir)
554+
shutil.copy2(sys.executable, bindir)
555+
libdir = os.path.join(non_installed_dir, *self.lib)
556+
os.makedirs(libdir)
557+
landmark = os.path.join(libdir, "os.py")
558+
stdlib_zip = "python%d%d.zip" % sys.version_info[:2]
559+
zip_landmark = os.path.join(non_installed_dir,
560+
self.lib[0],
561+
stdlib_zip)
562+
additional_pythonpath_for_non_installed = []
563+
# Copy stdlib files to the non-installed python so venv can
564+
# correctly calculate the prefix.
565+
for eachpath in sys.path:
566+
if eachpath.endswith(".zip"):
567+
if os.path.isfile(eachpath):
568+
shutil.copyfile(
569+
eachpath,
570+
os.path.join(non_installed_dir, self.lib[0]))
571+
elif os.path.isfile(os.path.join(eachpath, "os.py")):
572+
for name in os.listdir(eachpath):
573+
if name == "site-packages":
574+
continue
575+
fn = os.path.join(eachpath, name)
576+
if os.path.isfile(fn):
577+
shutil.copy(fn, libdir)
578+
elif os.path.isdir(fn):
579+
shutil.copytree(fn, os.path.join(libdir, name))
580+
else:
581+
additional_pythonpath_for_non_installed.append(
582+
eachpath)
583+
cmd = [os.path.join(non_installed_dir, self.bindir, self.exe),
584+
"-m",
585+
"venv",
586+
"--without-pip",
587+
self.env_dir]
588+
# Our fake non-installed python is not fully functional because
589+
# it cannot find the extensions. Set PYTHONPATH so it can run the
590+
# venv module correctly.
591+
pythonpath = os.pathsep.join(
592+
additional_pythonpath_for_non_installed)
593+
subprocess.check_call(cmd, env={"PYTHONPATH": pythonpath})
594+
envpy = os.path.join(self.env_dir, self.bindir, self.exe)
595+
# Now check the venv created from the non-installed python has
596+
# correct zip path in pythonpath.
597+
cmd = [envpy, '-S', '-c', 'import sys; print(sys.path)']
598+
out, err = check_output(cmd)
599+
self.assertTrue(zip_landmark.encode() in out)
600+
finally:
601+
rmtree(non_installed_dir)
602+
540603
@requireVenvCreate
541604
class EnsurePipTest(BaseTest):
542605
"""Test venv module installation of pip."""
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix zip path for venv created from a non-installed python on POSIX
2+
platforms.

Modules/getpath.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -679,9 +679,8 @@ def search_up(prefix, *landmarks, test=isfile):
679679
else:
680680
library_dir = executable_dir
681681
pythonpath.append(joinpath(library_dir, ZIP_LANDMARK))
682-
elif build_prefix or venv_prefix:
682+
elif build_prefix:
683683
# QUIRK: POSIX uses the default prefix when in the build directory
684-
# or a venv
685684
pythonpath.append(joinpath(PREFIX, ZIP_LANDMARK))
686685
else:
687686
pythonpath.append(joinpath(prefix, ZIP_LANDMARK))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy