From 0a3816ebf3d241248a330d9390999b5520f840e9 Mon Sep 17 00:00:00 2001 From: HandSonic <8078023+HandSonic@users.noreply.github.com> Date: Sun, 12 May 2024 01:44:50 +0800 Subject: [PATCH] Fix #1949: zipapp virtual environment creation fails if zipapp path is symlinked (#2722) Co-authored-by: HandSonic <3> --- docs/changelog/1949.bugfix.rst | 1 + src/virtualenv/util/zipapp.py | 8 ++++++-- tests/integration/test_zipapp.py | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 docs/changelog/1949.bugfix.rst diff --git a/docs/changelog/1949.bugfix.rst b/docs/changelog/1949.bugfix.rst new file mode 100644 index 000000000..fa77dc9b9 --- /dev/null +++ b/docs/changelog/1949.bugfix.rst @@ -0,0 +1 @@ +``virtualenv.pyz`` no longer fails when zipapp path contains a symlink - by :user:`HandSonic` and :user:`petamas`. diff --git a/src/virtualenv/util/zipapp.py b/src/virtualenv/util/zipapp.py index f736e3763..958db1543 100644 --- a/src/virtualenv/util/zipapp.py +++ b/src/virtualenv/util/zipapp.py @@ -23,8 +23,12 @@ def extract(full_path, dest): def _get_path_within_zip(full_path): - full_path = os.path.abspath(str(full_path)) - sub_file = full_path[len(ROOT) + 1 :] + full_path = os.path.realpath(os.path.abspath(str(full_path))) + prefix = f"{ROOT}{os.sep}" + if not full_path.startswith(prefix): + msg = f"full_path={full_path} should start with prefix={prefix}." + raise RuntimeError(msg) + sub_file = full_path[len(prefix) :] if IS_WIN: # paths are always UNIX separators, even on Windows, though __file__ still follows platform default sub_file = sub_file.replace(os.sep, "/") diff --git a/tests/integration/test_zipapp.py b/tests/integration/test_zipapp.py index 7157a9e75..dfc6d9759 100644 --- a/tests/integration/test_zipapp.py +++ b/tests/integration/test_zipapp.py @@ -9,6 +9,7 @@ from flaky import flaky from virtualenv.discovery.py_info import PythonInfo +from virtualenv.info import fs_supports_symlink from virtualenv.run import cli_run HERE = Path(__file__).parent @@ -83,6 +84,24 @@ def _run(*args): return _run +@pytest.fixture() +def call_zipapp_symlink(zipapp, tmp_path, zipapp_test_env, temp_app_data): # noqa: ARG001 + def _run(*args): + symlinked = zipapp.parent / "symlinked_virtualenv.pyz" + symlinked.symlink_to(str(zipapp)) + cmd = [str(zipapp_test_env), str(symlinked), "-vv", str(tmp_path / "env"), *list(args)] + subprocess.check_call(cmd) + + return _run + + +@pytest.mark.skipif(not fs_supports_symlink(), reason="symlink not supported") +def test_zipapp_in_symlink(capsys, call_zipapp_symlink): + call_zipapp_symlink("--reset-app-data") + _out, err = capsys.readouterr() + assert not err + + @flaky(max_runs=2, min_passes=1) def test_zipapp_help(call_zipapp, capsys): call_zipapp("-h")