virtualenv 21.2.2__tar.gz → 21.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {virtualenv-21.2.2 → virtualenv-21.2.4}/PKG-INFO +1 -1
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/pip_install/base.py +29 -1
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/acquire.py +63 -0
- virtualenv-21.2.4/src/virtualenv/seed/wheels/embed/__init__.py +119 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/periodic_update.py +12 -2
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/zipapp.py +14 -16
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/version.py +2 -2
- virtualenv-21.2.4/tasks/upgrade_wheels.py +265 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py +51 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_acquire.py +33 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_bundle.py +64 -1
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_periodic_update.py +17 -4
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/test_util.py +28 -0
- virtualenv-21.2.2/src/virtualenv/seed/wheels/embed/__init__.py +0 -60
- virtualenv-21.2.2/tasks/upgrade_wheels.py +0 -157
- {virtualenv-21.2.2 → virtualenv-21.2.4}/.gitignore +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/LICENSE +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/README.md +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/pyproject.toml +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/__main__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/activator.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/bash/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/bash/activate.sh +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/batch/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/batch/activate.bat +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/batch/deactivate.bat +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/batch/pydoc.bat +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/cshell/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/cshell/activate.csh +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/fish/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/fish/activate.fish +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/nushell/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/nushell/activate.nu +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/powershell/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/powershell/activate.ps1 +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/python/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/python/activate_this.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/via_template.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/base.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/na.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/read_only.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/via_disk_folder.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/via_tempdir.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/cli/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/cli/parser.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/convert.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/env_var.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/ini.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/creator.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/debug.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/describe.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/pyenv_cfg.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/_virtualenv.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/api.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/builtin_way.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/cpython/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/cpython/common.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/graalpy/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/pypy/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/pypy/common.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/ref.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/rustpython/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/store.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/venv.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/builtin.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/cached_py_info.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/discover.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/py_info.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/py_spec.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/info.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/py.typed +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/report.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/activators.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/base.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/creators.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/discovery.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/seeders.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/session.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/base_embed.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/pip_invoke.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/pip_install/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/via_app_data.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/seeder.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/bundle.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/pip-25.0.1-py3-none-any.whl +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/pip-26.0.1-py3-none-any.whl +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/setuptools-75.3.4-py3-none-any.whl +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/setuptools-82.0.1-py3-none-any.whl +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/wheel-0.45.1-py3-none-any.whl +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/util.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/error.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/lock.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/path/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/path/_permission.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/path/_sync.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/path/_win.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/subprocess/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tasks/__main__zipapp.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tasks/make_zipapp.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tasks/release.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tasks/update_embedded.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/conftest.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/integration/test_cachedir_tag.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/integration/test_race_condition_simulation.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/integration/test_run_int.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/integration/test_zipapp.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/types.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/conftest.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_activation_support.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_activator.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_bash.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_batch.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_csh.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_fish.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_nushell.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_powershell.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_python_activator.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/cli/test_help_formatter.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/cli/test_parser.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/test___main__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/test_env_var.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/test_ini.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/conftest.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/console_app/demo/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/console_app/demo/__main__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/console_app/setup.cfg +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/console_app/setup.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/test_creator.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/test_interpreters.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/_test_race_condition_helper.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/conftest.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/conftest.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_embed.json +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_free_threaded.json +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_posix.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/pypy/deb_pypy37.json +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/pypy/deb_pypy38.json +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/pypy/portable_pypy38.json +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/pypy/test_pypy3.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/rustpython/rustpython_posix.json +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/rustpython/rustpython_windows.json +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/rustpython/test_rustpython.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/testing/__init__.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/testing/helpers.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/testing/path.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/testing/py_info.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/greet/greet2.c +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/greet/greet3.c +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/greet/setup.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/test_api.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/test_build_c_ext.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/test_race_condition.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/discovery/test_discovery.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/embed/test_base_embed.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/embed/test_pip_invoke.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_acquire_find_wheel.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_wheels_util.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/test_file_limit.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/test_run.py +0 -0
- {virtualenv-21.2.2 → virtualenv-21.2.4}/tox.toml +0 -0
{virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/pip_install/base.py
RENAMED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import ntpath
|
|
4
5
|
import os
|
|
6
|
+
import posixpath
|
|
5
7
|
import re
|
|
6
8
|
import zipfile
|
|
7
9
|
from abc import ABC, abstractmethod
|
|
@@ -21,6 +23,24 @@ if TYPE_CHECKING:
|
|
|
21
23
|
LOGGER = logging.getLogger(__name__)
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
def _safe_extract_zip(zip_ref: zipfile.ZipFile, target_dir: Path) -> None:
|
|
27
|
+
# Guard against zip slip: a wheel is a zip and a tampered entry name (absolute path or one containing ``..``)
|
|
28
|
+
# could escape ``target_dir``.
|
|
29
|
+
base = target_dir.resolve()
|
|
30
|
+
for info in zip_ref.infolist():
|
|
31
|
+
name = info.filename
|
|
32
|
+
if name.startswith(("/", "\\")) or ntpath.isabs(name) or posixpath.isabs(name):
|
|
33
|
+
msg = f"refusing to extract absolute path entry from wheel: {name!r}"
|
|
34
|
+
raise RuntimeError(msg)
|
|
35
|
+
candidate = (base / name).resolve()
|
|
36
|
+
try:
|
|
37
|
+
candidate.relative_to(base)
|
|
38
|
+
except ValueError as exc:
|
|
39
|
+
msg = f"refusing to extract entry escaping target directory: {name!r}"
|
|
40
|
+
raise RuntimeError(msg) from exc
|
|
41
|
+
zip_ref.extractall(str(target_dir))
|
|
42
|
+
|
|
43
|
+
|
|
24
44
|
class PipInstall(ABC):
|
|
25
45
|
def __init__(self, wheel: Path, creator: Creator, image_folder: Path) -> None:
|
|
26
46
|
self._wheel = wheel
|
|
@@ -49,11 +69,19 @@ class PipInstall(ABC):
|
|
|
49
69
|
LOGGER.debug("generated console scripts %s", " ".join(i.name for i in consoles))
|
|
50
70
|
|
|
51
71
|
def build_image(self) -> None:
|
|
72
|
+
"""Extract the seed wheel into the image directory and fix up its RECORD file.
|
|
73
|
+
|
|
74
|
+
Each archive entry is validated before extraction so a tampered wheel cannot escape the image directory via an
|
|
75
|
+
absolute path or ``..`` traversal.
|
|
76
|
+
|
|
77
|
+
:raises RuntimeError: if the wheel contains an entry that would land outside the image directory.
|
|
78
|
+
|
|
79
|
+
"""
|
|
52
80
|
# 1. first extract the wheel
|
|
53
81
|
LOGGER.debug("build install image for %s to %s", self._wheel.name, self._image_dir)
|
|
54
82
|
with zipfile.ZipFile(str(self._wheel)) as zip_ref:
|
|
55
83
|
self._shorten_path_if_needed(zip_ref)
|
|
56
|
-
zip_ref
|
|
84
|
+
_safe_extract_zip(zip_ref, self._image_dir)
|
|
57
85
|
self._extracted = True
|
|
58
86
|
# 2. now add additional files not present in the distribution
|
|
59
87
|
new_files = self._generate_new_files()
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
+
import re
|
|
6
7
|
import sys
|
|
7
8
|
from operator import eq, lt
|
|
8
9
|
from pathlib import Path
|
|
@@ -18,6 +19,34 @@ if TYPE_CHECKING:
|
|
|
18
19
|
|
|
19
20
|
LOGGER = logging.getLogger(__name__)
|
|
20
21
|
|
|
22
|
+
# PEP 503 normalized distribution name. Anything outside this character set on the way to ``pip download`` means
|
|
23
|
+
# somebody is smuggling pip options or extras, so reject it before we build the command line.
|
|
24
|
+
_DISTRIBUTION_RE = re.compile(
|
|
25
|
+
r"""
|
|
26
|
+
^
|
|
27
|
+
(?P<name>
|
|
28
|
+
[A-Za-z0-9] # must start with an alnum
|
|
29
|
+
(?:[A-Za-z0-9._-]* # inner chars: alnum plus . _ -
|
|
30
|
+
[A-Za-z0-9])? # must also end with an alnum (unless length is 1)
|
|
31
|
+
)
|
|
32
|
+
$
|
|
33
|
+
""",
|
|
34
|
+
re.VERBOSE,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Version specifier that matches what ``Version.as_version_spec`` emits: either empty, ``==<ver>`` or ``<<ver>`` where
|
|
38
|
+
# ``<ver>`` is a subset of PEP 440 public versions. Kept deliberately strict so a crafted version cannot inject pip
|
|
39
|
+
# flags.
|
|
40
|
+
_VERSION_SPEC_RE = re.compile(
|
|
41
|
+
r"""
|
|
42
|
+
^
|
|
43
|
+
(?P<operator>==|<) # only the operators Version.as_version_spec can emit
|
|
44
|
+
(?P<version>[A-Za-z0-9._+!-]+) # PEP 440 public-version character set, no whitespace
|
|
45
|
+
$
|
|
46
|
+
""",
|
|
47
|
+
re.VERBOSE,
|
|
48
|
+
)
|
|
49
|
+
|
|
21
50
|
|
|
22
51
|
def get_wheel( # noqa: PLR0913
|
|
23
52
|
distribution: str,
|
|
@@ -63,6 +92,26 @@ def download_wheel( # noqa: PLR0913
|
|
|
63
92
|
to_folder: Path,
|
|
64
93
|
env: dict[str, str],
|
|
65
94
|
) -> Wheel:
|
|
95
|
+
"""Invoke ``pip download`` in a subprocess to fetch a seed wheel.
|
|
96
|
+
|
|
97
|
+
:param distribution: PEP 503 normalized project name; rejected if it contains anything other than
|
|
98
|
+
``[A-Za-z0-9._-]``.
|
|
99
|
+
:param version_spec: optional version specifier of the form ``==<ver>`` or ``<<ver>`` as emitted by
|
|
100
|
+
:func:`Version.as_version_spec`, or ``None``/empty for the latest compatible release.
|
|
101
|
+
:param for_py_version: major.minor Python version to pass through to ``pip --python-version``.
|
|
102
|
+
:param search_dirs: additional directories to treat as a local wheel index when bootstrapping pip.
|
|
103
|
+
:param app_data: application data store used to locate the embedded pip wheel.
|
|
104
|
+
:param to_folder: directory the downloaded wheel is written into.
|
|
105
|
+
:param env: environment mapping passed through to the subprocess.
|
|
106
|
+
|
|
107
|
+
:returns: the downloaded :class:`Wheel`.
|
|
108
|
+
|
|
109
|
+
:raises ValueError: if ``distribution`` or ``version_spec`` fail the strict allow-list check.
|
|
110
|
+
:raises CalledProcessError: if ``pip download`` exits with a non-zero status.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
_check_distribution(distribution)
|
|
114
|
+
_check_version_spec(version_spec)
|
|
66
115
|
to_download = f"{distribution}{version_spec or ''}"
|
|
67
116
|
LOGGER.debug("download wheel %s %s to %s", to_download, for_py_version, to_folder)
|
|
68
117
|
cmd = [
|
|
@@ -143,6 +192,20 @@ def pip_wheel_env_run(search_dirs: list[Path], app_data: AppData, env: dict[str,
|
|
|
143
192
|
return env
|
|
144
193
|
|
|
145
194
|
|
|
195
|
+
def _check_distribution(distribution: str) -> None:
|
|
196
|
+
if not _DISTRIBUTION_RE.fullmatch(distribution):
|
|
197
|
+
msg = f"refusing to download wheel for suspicious distribution name: {distribution!r}"
|
|
198
|
+
raise ValueError(msg)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _check_version_spec(version_spec: str | None) -> None:
|
|
202
|
+
if not version_spec:
|
|
203
|
+
return
|
|
204
|
+
if not _VERSION_SPEC_RE.fullmatch(version_spec):
|
|
205
|
+
msg = f"refusing to download wheel with suspicious version spec: {version_spec!r}"
|
|
206
|
+
raise ValueError(msg)
|
|
207
|
+
|
|
208
|
+
|
|
146
209
|
__all__ = [
|
|
147
210
|
"download_wheel",
|
|
148
211
|
"get_wheel",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import zipfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from virtualenv.info import IS_ZIPAPP, ROOT
|
|
8
|
+
from virtualenv.seed.wheels.util import Wheel
|
|
9
|
+
|
|
10
|
+
BUNDLE_FOLDER = Path(__file__).absolute().parent
|
|
11
|
+
BUNDLE_SUPPORT = {
|
|
12
|
+
"3.8": {
|
|
13
|
+
"pip": "pip-25.0.1-py3-none-any.whl",
|
|
14
|
+
"setuptools": "setuptools-75.3.4-py3-none-any.whl",
|
|
15
|
+
"wheel": "wheel-0.45.1-py3-none-any.whl",
|
|
16
|
+
},
|
|
17
|
+
"3.9": {
|
|
18
|
+
"pip": "pip-26.0.1-py3-none-any.whl",
|
|
19
|
+
"setuptools": "setuptools-82.0.1-py3-none-any.whl",
|
|
20
|
+
},
|
|
21
|
+
"3.10": {
|
|
22
|
+
"pip": "pip-26.0.1-py3-none-any.whl",
|
|
23
|
+
"setuptools": "setuptools-82.0.1-py3-none-any.whl",
|
|
24
|
+
},
|
|
25
|
+
"3.11": {
|
|
26
|
+
"pip": "pip-26.0.1-py3-none-any.whl",
|
|
27
|
+
"setuptools": "setuptools-82.0.1-py3-none-any.whl",
|
|
28
|
+
},
|
|
29
|
+
"3.12": {
|
|
30
|
+
"pip": "pip-26.0.1-py3-none-any.whl",
|
|
31
|
+
"setuptools": "setuptools-82.0.1-py3-none-any.whl",
|
|
32
|
+
},
|
|
33
|
+
"3.13": {
|
|
34
|
+
"pip": "pip-26.0.1-py3-none-any.whl",
|
|
35
|
+
"setuptools": "setuptools-82.0.1-py3-none-any.whl",
|
|
36
|
+
},
|
|
37
|
+
"3.14": {
|
|
38
|
+
"pip": "pip-26.0.1-py3-none-any.whl",
|
|
39
|
+
"setuptools": "setuptools-82.0.1-py3-none-any.whl",
|
|
40
|
+
},
|
|
41
|
+
"3.15": {
|
|
42
|
+
"pip": "pip-26.0.1-py3-none-any.whl",
|
|
43
|
+
"setuptools": "setuptools-82.0.1-py3-none-any.whl",
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
MAX = "3.8"
|
|
47
|
+
|
|
48
|
+
# SHA-256 of every bundled wheel. Verified on load so a corrupted or tampered wheel on disk fails loud instead of
|
|
49
|
+
# being handed to pip. Generated together with ``BUNDLE_SUPPORT`` by ``tasks/upgrade_wheels.py``.
|
|
50
|
+
BUNDLE_SHA256 = {
|
|
51
|
+
"pip-25.0.1-py3-none-any.whl": "c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f",
|
|
52
|
+
"pip-26.0.1-py3-none-any.whl": "bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b",
|
|
53
|
+
"setuptools-75.3.4-py3-none-any.whl": "2dd50a7f42dddfa1d02a36f275dbe716f38ed250224f609d35fb60a09593d93e",
|
|
54
|
+
"setuptools-82.0.1-py3-none-any.whl": "a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb",
|
|
55
|
+
"wheel-0.45.1-py3-none-any.whl": "708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_VERIFIED_WHEELS: set[str] = set()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_embed_wheel(distribution: str, for_py_version: str) -> Wheel | None:
|
|
62
|
+
"""Return the bundled wheel that ships with virtualenv for a given distribution and Python version.
|
|
63
|
+
|
|
64
|
+
:param distribution: project name of the seed package, for example ``pip`` or ``setuptools``.
|
|
65
|
+
:param for_py_version: major.minor Python version string the environment will be created for.
|
|
66
|
+
|
|
67
|
+
:returns: a :class:`Wheel` pointing at the verified bundled file, or ``None`` when no wheel is bundled for the
|
|
68
|
+
requested combination.
|
|
69
|
+
|
|
70
|
+
:raises RuntimeError: if the bundled wheel on disk fails SHA-256 verification.
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
mapping = BUNDLE_SUPPORT.get(for_py_version, {}) or BUNDLE_SUPPORT[MAX]
|
|
74
|
+
wheel_file = mapping.get(distribution)
|
|
75
|
+
if wheel_file is None:
|
|
76
|
+
return None
|
|
77
|
+
path = BUNDLE_FOLDER / wheel_file
|
|
78
|
+
_verify_bundled_wheel(path)
|
|
79
|
+
return Wheel.from_path(path)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _verify_bundled_wheel(path: Path) -> None:
|
|
83
|
+
name = path.name
|
|
84
|
+
if name in _VERIFIED_WHEELS:
|
|
85
|
+
return
|
|
86
|
+
expected = BUNDLE_SHA256.get(name)
|
|
87
|
+
if expected is None:
|
|
88
|
+
msg = f"bundled wheel {name} has no recorded sha256 in BUNDLE_SHA256"
|
|
89
|
+
raise RuntimeError(msg)
|
|
90
|
+
actual = _hash_bundled_wheel(path)
|
|
91
|
+
if actual != expected:
|
|
92
|
+
msg = f"bundled wheel {name} sha256 mismatch: expected {expected}, got {actual}"
|
|
93
|
+
raise RuntimeError(msg)
|
|
94
|
+
_VERIFIED_WHEELS.add(name)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _hash_bundled_wheel(path: Path) -> str:
|
|
98
|
+
# ``path`` is under the package directory; when virtualenv runs from a zipapp the wheel lives inside the
|
|
99
|
+
# archive and cannot be opened as a regular file, so read the bytes straight from the zipapp entry.
|
|
100
|
+
digest = hashlib.sha256()
|
|
101
|
+
if IS_ZIPAPP:
|
|
102
|
+
entry = path.resolve().relative_to(Path(ROOT).resolve()).as_posix()
|
|
103
|
+
with zipfile.ZipFile(ROOT, "r") as archive, archive.open(entry) as stream:
|
|
104
|
+
for chunk in iter(lambda: stream.read(1 << 20), b""):
|
|
105
|
+
digest.update(chunk)
|
|
106
|
+
else:
|
|
107
|
+
with path.open("rb") as stream:
|
|
108
|
+
for chunk in iter(lambda: stream.read(1 << 20), b""):
|
|
109
|
+
digest.update(chunk)
|
|
110
|
+
return digest.hexdigest()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
__all__ = [
|
|
114
|
+
"BUNDLE_FOLDER",
|
|
115
|
+
"BUNDLE_SHA256",
|
|
116
|
+
"BUNDLE_SUPPORT",
|
|
117
|
+
"MAX",
|
|
118
|
+
"get_embed_wheel",
|
|
119
|
+
]
|
|
@@ -360,10 +360,20 @@ def release_date_for_wheel_path(dest: Path) -> datetime | None:
|
|
|
360
360
|
return None
|
|
361
361
|
|
|
362
362
|
|
|
363
|
+
#: Opt-in escape hatch to restore the pre-2026 behavior of falling back to an unverified HTTPS context when the
|
|
364
|
+
#: verified request fails. Off by default: a failed TLS handshake on the PyPI metadata lookup now aborts the update
|
|
365
|
+
#: instead of silently downgrading, because the response drives which wheel version virtualenv thinks is up to date.
|
|
366
|
+
_INSECURE_FALLBACK_ENV = "VIRTUALENV_PERIODIC_UPDATE_INSECURE"
|
|
367
|
+
|
|
368
|
+
|
|
363
369
|
def _request_context() -> Generator[ssl.SSLContext | None, None, None]:
|
|
364
370
|
yield None
|
|
365
|
-
|
|
366
|
-
|
|
371
|
+
if os.environ.get(_INSECURE_FALLBACK_ENV):
|
|
372
|
+
LOGGER.warning(
|
|
373
|
+
"falling back to unverified HTTPS for PyPI metadata because %s is set",
|
|
374
|
+
_INSECURE_FALLBACK_ENV,
|
|
375
|
+
)
|
|
376
|
+
yield ssl._create_unverified_context() # noqa: S323, SLF001
|
|
367
377
|
|
|
368
378
|
|
|
369
379
|
_PYPI_CACHE = {}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
import os
|
|
5
4
|
import zipfile
|
|
6
|
-
from
|
|
5
|
+
from pathlib import Path
|
|
7
6
|
|
|
8
|
-
from virtualenv.info import
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from pathlib import Path
|
|
7
|
+
from virtualenv.info import ROOT
|
|
12
8
|
|
|
13
9
|
LOGGER = logging.getLogger(__name__)
|
|
14
10
|
|
|
@@ -29,16 +25,18 @@ def extract(full_path: str | Path, dest: Path) -> None:
|
|
|
29
25
|
|
|
30
26
|
|
|
31
27
|
def _get_path_within_zip(full_path: str | Path) -> str:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
# Use Path.relative_to so symlinks and ``..`` segments cannot slip through a string ``startswith`` check. The zipapp
|
|
29
|
+
# root is a real file we own so ``resolve`` is safe; anything that does not resolve under ROOT is a bug or an
|
|
30
|
+
# attempt to escape the archive and we refuse it.
|
|
31
|
+
resolved = Path(full_path).resolve()
|
|
32
|
+
root = Path(ROOT).resolve()
|
|
33
|
+
try:
|
|
34
|
+
relative = resolved.relative_to(root)
|
|
35
|
+
except ValueError as exc:
|
|
36
|
+
msg = f"full_path={resolved} should be within ROOT={root}"
|
|
37
|
+
raise RuntimeError(msg) from exc
|
|
38
|
+
# Zip entries always use forward slashes regardless of platform.
|
|
39
|
+
return relative.as_posix()
|
|
42
40
|
|
|
43
41
|
|
|
44
42
|
__all__ = [
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '21.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (21, 2,
|
|
21
|
+
__version__ = version = '21.2.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (21, 2, 4)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Helper script to rebuild virtualenv_support. Downloads the wheel files using pip."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import hashlib
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from collections import OrderedDict, defaultdict
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from tempfile import TemporaryDirectory
|
|
14
|
+
from textwrap import dedent
|
|
15
|
+
from threading import Thread
|
|
16
|
+
from typing import NoReturn
|
|
17
|
+
|
|
18
|
+
STRICT = "UPGRADE_ADVISORY" not in os.environ
|
|
19
|
+
|
|
20
|
+
BUNDLED = ["pip", "setuptools", "wheel"]
|
|
21
|
+
SUPPORT = [(3, i) for i in range(8, 16)]
|
|
22
|
+
DEST = Path(__file__).resolve().parents[1] / "src" / "virtualenv" / "seed" / "wheels" / "embed"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run() -> NoReturn:
|
|
26
|
+
if "--regen" in sys.argv[1:]:
|
|
27
|
+
render_init()
|
|
28
|
+
raise SystemExit(0)
|
|
29
|
+
old_batch = {i.name for i in DEST.iterdir() if i.suffix == ".whl"}
|
|
30
|
+
with TemporaryDirectory() as temp:
|
|
31
|
+
folders = _download_all(Path(temp))
|
|
32
|
+
new_batch = {i.name: i for f in folders for i in Path(f).iterdir()}
|
|
33
|
+
new_packages = new_batch.keys() - old_batch
|
|
34
|
+
remove_packages = old_batch - new_batch.keys()
|
|
35
|
+
_sync_dest(new_packages, remove_packages, new_batch)
|
|
36
|
+
added = collect_package_versions(new_packages)
|
|
37
|
+
removed = collect_package_versions(remove_packages)
|
|
38
|
+
outcome = (1 if STRICT else 0) if (added or removed) else 0
|
|
39
|
+
print(f"Outcome {outcome} added {added} removed {removed}") # noqa: T201
|
|
40
|
+
_write_changelog(added, removed)
|
|
41
|
+
render_init(folders=folders)
|
|
42
|
+
raise SystemExit(outcome)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _download_all(temp_path: Path) -> dict[Path, str]:
|
|
46
|
+
folders: dict[Path, str] = {}
|
|
47
|
+
targets: list[Thread] = []
|
|
48
|
+
for support in SUPPORT:
|
|
49
|
+
support_ver = ".".join(str(i) for i in support)
|
|
50
|
+
into = temp_path / support_ver
|
|
51
|
+
into.mkdir()
|
|
52
|
+
folders[into] = support_ver
|
|
53
|
+
for package in BUNDLED:
|
|
54
|
+
if package == "wheel" and support >= (3, 9):
|
|
55
|
+
continue
|
|
56
|
+
thread = Thread(target=download, args=(support_ver, str(into), package))
|
|
57
|
+
targets.append(thread)
|
|
58
|
+
thread.start()
|
|
59
|
+
for thread in targets:
|
|
60
|
+
thread.join()
|
|
61
|
+
return folders
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _sync_dest(new_packages: set[str], remove_packages: set[str], new_batch: dict[str, Path]) -> None:
|
|
65
|
+
for package in remove_packages:
|
|
66
|
+
(DEST / package).unlink()
|
|
67
|
+
for package in new_packages:
|
|
68
|
+
shutil.copy2(str(new_batch[package]), DEST / package)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _write_changelog(added: dict[str, list[str]], removed: dict[str, list[str]]) -> None:
|
|
72
|
+
lines = ["Upgrade embedded wheels:", ""]
|
|
73
|
+
for key, versions in added.items():
|
|
74
|
+
text = f"* {key} to {fmt_version(versions)}"
|
|
75
|
+
if key in removed:
|
|
76
|
+
rem = ", ".join(f"``{i}``" for i in removed[key])
|
|
77
|
+
text += f" from {rem}"
|
|
78
|
+
del removed[key]
|
|
79
|
+
lines.append(text)
|
|
80
|
+
for key, versions in removed.items():
|
|
81
|
+
lines.append(f"Removed {key} of {fmt_version(versions)}")
|
|
82
|
+
lines.append("")
|
|
83
|
+
changelog = "\n".join(lines)
|
|
84
|
+
print(changelog) # noqa: T201
|
|
85
|
+
if len(lines) >= 4: # noqa: PLR2004
|
|
86
|
+
(Path(__file__).parents[1] / "docs" / "changelog" / "u.bugfix.rst").write_text(changelog, encoding="utf-8")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def render_init(folders: dict[Path, str] | None = None) -> None:
|
|
90
|
+
"""Write ``embed/__init__.py`` from the wheels currently in DEST.
|
|
91
|
+
|
|
92
|
+
When called from ``run()`` after a download round, ``folders`` maps each per-python-version temp folder to its
|
|
93
|
+
version string, which is how support for a wheel is determined. When called with ``--regen`` there are no downloaded
|
|
94
|
+
folders — the existing ``BUNDLE_SUPPORT`` from the current ``__init__.py`` is used so regeneration is deterministic.
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
if folders is None:
|
|
98
|
+
support_table = _support_table_from_existing_init()
|
|
99
|
+
else:
|
|
100
|
+
present = {i.name: i for f in folders for i in Path(f).iterdir() if i.suffix == ".whl"}
|
|
101
|
+
support_table = OrderedDict((".".join(str(j) for j in i), []) for i in SUPPORT)
|
|
102
|
+
for package in sorted(present):
|
|
103
|
+
for folder, version in sorted(folders.items()):
|
|
104
|
+
if (folder / package).exists():
|
|
105
|
+
support_table[version].append(package)
|
|
106
|
+
support_table = OrderedDict((k, OrderedDict((i.split("-")[0], i) for i in v)) for k, v in support_table.items())
|
|
107
|
+
wheel_names = sorted({wheel for mapping in support_table.values() for wheel in mapping.values()})
|
|
108
|
+
sha_table = OrderedDict((name, _sha256(DEST / name)) for name in wheel_names)
|
|
109
|
+
nl = "\n"
|
|
110
|
+
bundle = "".join(
|
|
111
|
+
f"\n {v!r}: {{{nl}{''.join(f' {p!r}: {f!r},{nl}' for p, f in line.items())} }},"
|
|
112
|
+
for v, line in support_table.items()
|
|
113
|
+
)
|
|
114
|
+
sha_block = "".join(f"\n {name!r}: {digest!r}," for name, digest in sha_table.items())
|
|
115
|
+
msg = dedent(
|
|
116
|
+
f"""
|
|
117
|
+
from __future__ import annotations
|
|
118
|
+
|
|
119
|
+
import hashlib
|
|
120
|
+
import zipfile
|
|
121
|
+
from pathlib import Path
|
|
122
|
+
|
|
123
|
+
from virtualenv.info import IS_ZIPAPP, ROOT
|
|
124
|
+
from virtualenv.seed.wheels.util import Wheel
|
|
125
|
+
|
|
126
|
+
BUNDLE_FOLDER = Path(__file__).absolute().parent
|
|
127
|
+
BUNDLE_SUPPORT = {{ {bundle} }}
|
|
128
|
+
MAX = {next(iter(support_table.keys()))!r}
|
|
129
|
+
|
|
130
|
+
# SHA-256 of every bundled wheel. Verified on load so a corrupted or tampered wheel on disk fails loud instead of
|
|
131
|
+
# being handed to pip. Generated together with ``BUNDLE_SUPPORT`` by ``tasks/upgrade_wheels.py``.
|
|
132
|
+
BUNDLE_SHA256 = {{ {sha_block} }}
|
|
133
|
+
|
|
134
|
+
_VERIFIED_WHEELS: set[str] = set()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_embed_wheel(distribution: str, for_py_version: str) -> Wheel | None:
|
|
138
|
+
\"\"\"Return the bundled wheel that ships with virtualenv for a given distribution and Python version.
|
|
139
|
+
|
|
140
|
+
:param distribution: project name of the seed package, for example ``pip`` or ``setuptools``.
|
|
141
|
+
:param for_py_version: major.minor Python version string the environment will be created for.
|
|
142
|
+
|
|
143
|
+
:returns: a :class:`Wheel` pointing at the verified bundled file, or ``None`` when no wheel is bundled for the
|
|
144
|
+
requested combination.
|
|
145
|
+
|
|
146
|
+
:raises RuntimeError: if the bundled wheel on disk fails SHA-256 verification.
|
|
147
|
+
|
|
148
|
+
\"\"\"
|
|
149
|
+
mapping = BUNDLE_SUPPORT.get(for_py_version, {{}}) or BUNDLE_SUPPORT[MAX]
|
|
150
|
+
wheel_file = mapping.get(distribution)
|
|
151
|
+
if wheel_file is None:
|
|
152
|
+
return None
|
|
153
|
+
path = BUNDLE_FOLDER / wheel_file
|
|
154
|
+
_verify_bundled_wheel(path)
|
|
155
|
+
return Wheel.from_path(path)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _verify_bundled_wheel(path: Path) -> None:
|
|
159
|
+
name = path.name
|
|
160
|
+
if name in _VERIFIED_WHEELS:
|
|
161
|
+
return
|
|
162
|
+
expected = BUNDLE_SHA256.get(name)
|
|
163
|
+
if expected is None:
|
|
164
|
+
msg = f"bundled wheel {{name}} has no recorded sha256 in BUNDLE_SHA256"
|
|
165
|
+
raise RuntimeError(msg)
|
|
166
|
+
actual = _hash_bundled_wheel(path)
|
|
167
|
+
if actual != expected:
|
|
168
|
+
msg = f"bundled wheel {{name}} sha256 mismatch: expected {{expected}}, got {{actual}}"
|
|
169
|
+
raise RuntimeError(msg)
|
|
170
|
+
_VERIFIED_WHEELS.add(name)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _hash_bundled_wheel(path: Path) -> str:
|
|
174
|
+
# ``path`` is under the package directory; when virtualenv runs from a zipapp the wheel lives inside the
|
|
175
|
+
# archive and cannot be opened as a regular file, so read the bytes straight from the zipapp entry.
|
|
176
|
+
digest = hashlib.sha256()
|
|
177
|
+
if IS_ZIPAPP:
|
|
178
|
+
entry = path.resolve().relative_to(Path(ROOT).resolve()).as_posix()
|
|
179
|
+
with zipfile.ZipFile(ROOT, "r") as archive, archive.open(entry) as stream:
|
|
180
|
+
for chunk in iter(lambda: stream.read(1 << 20), b""):
|
|
181
|
+
digest.update(chunk)
|
|
182
|
+
else:
|
|
183
|
+
with path.open("rb") as stream:
|
|
184
|
+
for chunk in iter(lambda: stream.read(1 << 20), b""):
|
|
185
|
+
digest.update(chunk)
|
|
186
|
+
return digest.hexdigest()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
__all__ = [
|
|
190
|
+
"BUNDLE_FOLDER",
|
|
191
|
+
"BUNDLE_SHA256",
|
|
192
|
+
"BUNDLE_SUPPORT",
|
|
193
|
+
"MAX",
|
|
194
|
+
"get_embed_wheel",
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
""",
|
|
198
|
+
)
|
|
199
|
+
dest_target = DEST / "__init__.py"
|
|
200
|
+
dest_target.write_text(msg, encoding="utf-8")
|
|
201
|
+
subprocess.run([sys.executable, "-m", "ruff", "check", str(dest_target), "--fix", "--unsafe-fixes"], check=False)
|
|
202
|
+
subprocess.run([sys.executable, "-m", "ruff", "format", str(dest_target), "--preview"], check=False)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _support_table_from_existing_init() -> OrderedDict[str, OrderedDict[str, str]]:
|
|
206
|
+
source = (DEST / "__init__.py").read_text(encoding="utf-8")
|
|
207
|
+
tree = ast.parse(source)
|
|
208
|
+
for node in tree.body:
|
|
209
|
+
if isinstance(node, ast.Assign) and any(
|
|
210
|
+
isinstance(t, ast.Name) and t.id == "BUNDLE_SUPPORT" for t in node.targets
|
|
211
|
+
):
|
|
212
|
+
bundle_support = ast.literal_eval(node.value)
|
|
213
|
+
return OrderedDict(
|
|
214
|
+
(version, OrderedDict(sorted(mapping.items()))) for version, mapping in bundle_support.items()
|
|
215
|
+
)
|
|
216
|
+
msg = f"BUNDLE_SUPPORT not found in {DEST / '__init__.py'}"
|
|
217
|
+
raise RuntimeError(msg)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _sha256(path: Path) -> str:
|
|
221
|
+
digest = hashlib.sha256()
|
|
222
|
+
with path.open("rb") as stream:
|
|
223
|
+
for chunk in iter(lambda: stream.read(1 << 20), b""):
|
|
224
|
+
digest.update(chunk)
|
|
225
|
+
return digest.hexdigest()
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def fmt_version(versions: list[str]) -> str:
|
|
229
|
+
return ", ".join(f"``{v}``" for v in versions)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def collect_package_versions(new_packages: set[str]) -> dict[str, list[str]]:
|
|
233
|
+
result = defaultdict(list)
|
|
234
|
+
for package in new_packages:
|
|
235
|
+
split = package.split("-")
|
|
236
|
+
if len(split) < 2: # noqa: PLR2004
|
|
237
|
+
raise ValueError(package)
|
|
238
|
+
key, version = split[0:2]
|
|
239
|
+
result[key].append(version)
|
|
240
|
+
return result
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def download(python_version: str, dest: str, package: str) -> None:
|
|
244
|
+
subprocess.call(
|
|
245
|
+
[
|
|
246
|
+
sys.executable,
|
|
247
|
+
"-W",
|
|
248
|
+
"ignore::EncodingWarning",
|
|
249
|
+
"-m",
|
|
250
|
+
"pip",
|
|
251
|
+
"--disable-pip-version-check",
|
|
252
|
+
"download",
|
|
253
|
+
"--no-cache-dir",
|
|
254
|
+
"--only-binary=:all:",
|
|
255
|
+
"--python-version",
|
|
256
|
+
python_version,
|
|
257
|
+
"-d",
|
|
258
|
+
dest,
|
|
259
|
+
package,
|
|
260
|
+
],
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
run()
|