Compare commits

...

284 commits

Author SHA1 Message Date
native-api ef4905cd81
Incorporate the pyenv-link plugin (#3538)
Some checks failed
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Failing after 1s
build / discover_build_logic_change (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-14) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15-intel) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-26) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-22.04-arm) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04-arm) (push) Has been cancelled
pyenv_tests / pyenv_tests_docker (push) Has been cancelled
build / build (push) Has been cancelled
for use in linking binary installations
2026-09-09 02:43:22 +03:00
Ivan Pozdeev 88e9ab13e8 pwsh: Fix error in shell integration if pyenv is on PATH multiple times
Fixes "The term 'pyenv pyenv' is not recognized..."
2026-09-09 02:31:49 +03:00
macayu17 798a1ad603 Incorporate the pyenv-link plugin
* Adapt test_helper for use in plugins
* Add pyenv-link tests
* Add pyenv-link tests to CI
2026-09-09 02:30:53 +03:00
macayu17 fb7f2071fb Add 'plugins/pyenv-link/' from commit '2f8cb2cf1f093e955a72babf0b65a07f84f33522'
git-subtree-dir: plugins/pyenv-link
git-subtree-mainline: ef7a9f5d14
git-subtree-split: 2f8cb2cf1f
2026-09-08 02:55:49 +05:30
Michael Newman ef7a9f5d14
docs: Add link to list of available Python distributions (#3535)
Some checks failed
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Failing after 1s
pyenv_tests / pyenv_tests (ubuntu-22.04-arm) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Has been cancelled
build / discover_build_logic_change (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15-intel) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-26) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04-arm) (push) Has been cancelled
pyenv_tests / pyenv_tests_docker (push) Has been cancelled
build / build (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-14) (push) Has been cancelled
Add a link to the python-build share directory in the intro section
so users can see which Python distributions are supported before
installing pyenv.
2026-09-06 22:14:47 +03:00
Ayush 265b0dcefa
python-build: add build prerequisite installer (#3532)
Some checks failed
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Failing after 1s
pyenv_tests / pyenv_tests (ubuntu-24.04-arm) (push) Has been cancelled
pyenv_tests / pyenv_tests_docker (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-22.04-arm) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15-intel) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-26) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-14) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15) (push) Has been cancelled
build / discover_build_logic_change (push) Has been cancelled
build / build (push) Has been cancelled
2026-09-02 17:56:35 +03:00
Michael Šimáček 2548d53706
Update graalpy URLs for 25.3.4.1 (#3533) 2026-09-02 14:47:33 +03:00
Ivan Pozdeev c293de3650 2.8.5
Some checks failed
build / discover_build_logic_change (push) Waiting to run
build / build (push) Blocked by required conditions
pyenv_tests / pyenv_tests (macos-14) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-15) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-15-intel) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-26) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-22.04-arm) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-24.04-arm) (push) Waiting to run
pyenv_tests / pyenv_tests_docker (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Failing after 1s
2026-09-01 18:05:40 +03:00
Ayush bdfc6b03f3
pyenv-binary: support relocating macOS archives (#3528) 2026-09-01 18:01:23 +03:00
pyenv-bot[bot] 8c46508bd0
Add CPython 3.15.0rc2 (#3530)
Co-authored-by: native-api <2670332+native-api@users.noreply.github.com>
2026-09-01 17:53:59 +03:00
Michael Šimáček a00e0bc475
Add GraalPy 25.3.4.1 (#3529)
Some checks failed
build / build (push) Blocked by required conditions
build / discover_build_logic_change (push) Waiting to run
pyenv_tests / pyenv_tests (macos-14) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-15) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-15-intel) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-26) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-22.04-arm) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-24.04-arm) (push) Waiting to run
pyenv_tests / pyenv_tests_docker (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Failing after 1s
2026-08-31 23:28:40 +03:00
Michael Newman 981559003f
Fix typo
Some checks failed
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Failing after 0s
pyenv_tests / pyenv_tests (macos-15) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15-intel) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-26) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-22.04-arm) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04-arm) (push) Has been cancelled
pyenv_tests / pyenv_tests_docker (push) Has been cancelled
build / discover_build_logic_change (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-14) (push) Has been cancelled
build / build (push) Has been cancelled
2026-08-29 15:01:30 +03:00
Michael Newman 4733cca31b
Fix typo
Some checks failed
build / discover_build_logic_change (push) Waiting to run
build / build (push) Blocked by required conditions
pyenv_tests / pyenv_tests (ubuntu-22.04-arm) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-24.04-arm) (push) Waiting to run
pyenv_tests / pyenv_tests_docker (push) Waiting to run
pyenv_tests / pyenv_tests (macos-14) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-15) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-15-intel) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-26) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Failing after 1s
2026-08-28 15:36:28 +03:00
native-api ef2fb22235
Merge pull request #3522 from sujeito-operator/ci-test-everywhere-on-build-logic-change
CI: test on all runners when the build logic changes
2026-08-26 16:05:18 +03:00
sujeito-operator 97900a4aa4 CI: test on all runners when the build logic changes
Co-Authored-By: Ivan Pozdeev <vano@mail.mipt.ru>
2026-08-26 10:09:25 +03:00
sujeito-operator ecf9a7f9e6 CI: collapse macos_build and ubuntu_build into a single job
Co-Authored-By: Ivan Pozdeev <vano@mail.mipt.ru>
2026-08-26 09:40:01 +03:00
native-api 495550afc8
Merge pull request #3525 from native-api/test_improvements
Test improvements
2026-08-26 06:00:53 +03:00
Ivan Pozdeev 57f1df294b tests: add file names to TAP output
by using a customized formatter
2026-08-26 05:51:24 +03:00
Ivan Pozdeev 14830b18bf tests: skip permission tests if permission bits are being overridden
e.g. if running as root in a docker container
2026-08-26 05:50:04 +03:00
Ivan Pozdeev 032e7af407 tests: consolidate shims tests 2026-08-26 05:49:17 +03:00
native-api b0cff001ff
tests: Fix "cd: null directory" in OpenSUSE (#3524)
A workaround because tests don't use the Pyenv launcher and setting PYENV_DIR to $PWD at every run invocation is too cumbersome.
2026-08-25 22:43:59 +03:00
Michael Newman 234a87e400
Fix typo 2026-08-17 01:51:28 +03:00
Ayush 50fbc8c6cc
pyenv-binary: record direct system dependencies in Linux/FreeBSD (#3520) 2026-08-15 22:47:27 +03:00
Ivan Pozdeev 0f16606e4f 2.8.4 2026-08-13 15:08:51 +03:00
native-api 7c2226386b
Add anaconda3-2026.07-1, miniconda3 26.5.3-2 2026-08-13 14:59:25 +03:00
Ivan Pozdeev 3a98f8c1c3 add_miniforge: better logging, skip 2 faulty releases 2026-08-13 14:16:14 +03:00
native-api f99638cb82
Merge pull request #3518 from pyenv/auto_add_version/3.10.21_3.11.16_3.12.14
Add CPython 3.10.21, 3.11.16, 3.12.14
2026-08-13 14:03:45 +03:00
Ivan Pozdeev 3b9604c093 Add missing 3.10.x patches 2026-08-13 13:10:43 +03:00
native-api 920052eb0b Add CPython 3.10.21, 3.11.16, 3.12.14 2026-08-13 08:53:14 +00:00
native-api cb20e375d3
Merge pull request #3517 from native-api/ci_ubuntu_arm
CI: add ubuntu ARM images
2026-08-13 01:51:46 +03:00
Ivan Pozdeev f11926a274 Run Docker tests separately 2026-08-13 01:44:17 +03:00
Ivan Pozdeev 0fc5dfe630 refactor 2026-08-13 01:44:17 +03:00
Ivan Pozdeev 2bcc7d8fc4 Add ubuntu ARM images 2026-08-13 01:44:11 +03:00
Ivan Pozdeev 2bd4f6c4da - rbenv compatibility note
We've not been syncing with Rbenv for years
2026-08-12 15:26:35 +03:00
Ivan Pozdeev 8771e721c1 Add past CVE-2026-68939 fix to CHANGELOG
Now that Github has issued a CVE number 3 weeks late
2026-08-12 15:19:48 +03:00
native-api d92b6e1bbe
Merge pull request #3516 from macayu17/fix/2334-package-feedback
pyenv-binary-package: check patchelf early and add --verbose
2026-08-11 05:25:39 +03:00
Ivan Pozdeev 9962f8cb39 pyenv-binary-package: refactor tests
* make them more human-readable
* don't double-test external code outside of integration tests
2026-08-11 05:17:49 +03:00
Ivan Pozdeev f9ffb73a7e test_helper: support here-document to assert_success/failure; + assert_output_glob 2026-08-11 05:17:42 +03:00
Ivan Pozdeev bd7c969e7f test_helper: show diff with whitespace highlights
to be able to see tiny and whitespace differences
2026-08-11 04:22:34 +03:00
macayu17 4a73bd17e2 pyenv-binary-package: add --verbose
Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-08-11 04:22:33 +03:00
macayu17 2c1f405180 pyenv-binary: check patchelf before installing archive 2026-08-11 04:19:39 +03:00
native-api e234812f46
Merge pull request #3515 from fudianchn/fix/version-file-relative-loop-3513
Stop infinite loop in pyenv version-file for relative paths
2026-08-10 13:08:04 +03:00
Ivan Pozdeev 1a591b3963 Avoid extra iteration for argument with a trailing slash 2026-08-10 12:56:57 +03:00
Ivan Pozdeev 48e70e1fe9 Only test for UNC paths every iteration if they are special 2026-08-10 12:55:19 +03:00
付典 195b9fe940 version-file: Fix infinite loop for relative paths
find_local_version_file reduces its target with ${root%/*}, but that leaves a slash-less relative argument such as "." unchanged, leading to an infinite loop

Signed-off-by: 付典 <fudianchn@gmail.com>
2026-08-10 12:49:53 +03:00
native-api b7e7adf4ec
Merge pull request #3511 from macayu17/fix/2334-freebsd-ldconfig
pyenv-binary: check FreeBSD system libraries
2026-08-10 03:29:33 +03:00
macayu17 a6ef1e70ba Resolve version prefixes in package names 2026-08-10 03:27:47 +03:00
macayu17 5a31fea21b pyenv-binary: support checking FreeBSD system libraries
* Handle both spaced and unspaced FreeBSD `ldconfig -r` output
2026-08-10 03:27:46 +03:00
native-api 17be308600
Merge pull request #3514 from native-api/add_macos_26_intel
CI: Add macos_26_intel
2026-08-08 00:58:10 +03:00
Ivan Pozdeev 9d6cf587b7 Run the discover job on slim 2026-08-07 23:17:31 +03:00
Ivan Pozdeev 1cc9e770ae Fix regexes 2026-08-07 23:17:31 +03:00
Ivan Pozdeev 0e2e214f56 Fix ubuntu_build_tar_gz 2026-08-07 23:17:31 +03:00
Ivan Pozdeev 340348d98d - code irrelevant to CPython in CPython-specific scenarios 2026-08-07 23:17:30 +03:00
Ivan Pozdeev 2d9a224e6c Add macos-26-intel to CI
* Fix macos_build_bundled_dependencies in macos-26-intel
  It has a different set of preinstalled OpenSSL formulae
2026-08-07 23:16:23 +03:00
Ivan Pozdeev da172db42d 2.8.3 2026-08-05 22:08:09 +03:00
pyenv-bot[bot] b0a64af8cc
Add CPython 3.13.15, 3.14.7 (#3512) 2026-08-05 22:06:09 +03:00
Ivan Pozdeev 9e4d1ca95a 2.8.2 2026-08-04 23:10:49 +03:00
Ayush b9e165271e
pyenv-binary: generate platform-specific package names (#3504) 2026-08-04 22:53:34 +03:00
John Sirois 3088203e33
Add CPython 3.15.0rc1 (#3510) 2026-08-04 22:48:04 +03:00
Anupam Mediratta 8f70d418bb
add_cpython: refactor extra Requests logic (#3507)
---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-08-04 13:12:42 +03:00
dependabot[bot] bdf8d254fe
Bump actions/setup-python from 6 to 7 in the github-actions group (#3505)
Bumps the github-actions group with 1 update: [actions/setup-python](https://github.com/actions/setup-python).


Updates `actions/setup-python` from 6 to 7
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-01 16:38:32 +03:00
Michael Šimáček f5915fc866
Add graalpy3.12-25.2.4 (#3502) 2026-07-29 05:53:27 +03:00
Michael Newman 84e0f3a887
Fix typos 2026-07-29 05:49:46 +03:00
Ayush 7126870320
pyenv-binary: add the package subcommand; add pyenv install --list --bare (#3498)
Co-authored-by: native-api <vano@mail.mipt.ru>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-07-28 14:31:20 +03:00
Michael Newman 26dd5d9dda
Fix typo 2026-07-27 21:57:58 +03:00
Michael Newman bac39a0ef8
Fix typo 2026-07-27 08:30:33 +03:00
Ivan Pozdeev bec9a562a5 2.8.1 2026-07-18 22:02:09 +03:00
pyenv-bot[bot] ae9e6594d6
Add CPython 3.15.0b4 (#3495)
Co-authored-by: native-api <2670332+native-api@users.noreply.github.com>
2026-07-18 21:59:23 +03:00
native-api 16c7cc6de4
Merge pull request #3488 from macayu17/feat/2334-pyenv-binary-installer
pyenv-binary: add the `generate-installer` subcommand
2026-07-16 10:51:36 +03:00
Ivan Pozdeev 017ad4a573 Add pyenv-binary tests to CI
* README: fix tests instructions
* test/README: -duplicating and unnecessary stuff
* Makefile: Build Docker images and replace bats link conditionally
* Makefile: fix indent irregularity
2026-07-16 10:39:55 +03:00
macayu17 6188e9efa9 pyenv-binary: add the generate-installer subcommand
* Relocation only implemented for Linux (requires patchelf + ldconfig -p)
* Shell-completing file name is not possible with current Pyenv completion logic:
  `pyenv-complete` does not pass info about the word being completed
* Fix `save`-produced tarball to be compatible with Python-Build fetch_tarball()
* Reuse test_helper from core

--

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-07-16 10:39:50 +03:00
Ivan Pozdeev 286a167cf0 gitignore Vim swap files 2026-07-16 10:22:39 +03:00
Ivan Pozdeev 135adbb192 2.8.0 2026-07-13 21:01:27 +03:00
native-api b3a3e702f1
version-name: skip redundant checks (#3492)
Some checks failed
macos_build / macos_build (3.10) (push) Has been cancelled
macos_build / macos_build (3.11) (push) Has been cancelled
macos_build / macos_build (3.12) (push) Has been cancelled
macos_build / macos_build (3.13) (push) Has been cancelled
macos_build / macos_build (3.14) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-14) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15-intel) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-26) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.10) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.11) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.12) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.13) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.14) (push) Has been cancelled
2026-07-11 08:24:01 +03:00
native-api 958e3aca95
Add miniconda3 26.5.3-1 (#3491) 2026-07-11 08:23:42 +03:00
Ivan Pozdeev 74c6efe2f4 Adapt tests involving current directory for Docker
In Docker CI tests, the default current dir is read-only
2026-07-11 08:02:53 +03:00
native-api 95df7dbc7b
Merge commit from fork 2026-07-11 07:22:46 +03:00
max e4c462dc70
Fix typos in docs, scripts, and tests (#3490)
Some checks failed
macos_build / macos_build (3.10) (push) Has been cancelled
macos_build / macos_build (3.11) (push) Has been cancelled
macos_build / macos_build (3.12) (push) Has been cancelled
macos_build / macos_build (3.13) (push) Has been cancelled
macos_build / macos_build (3.14) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-14) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15-intel) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-26) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.10) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.11) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.12) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.13) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.14) (push) Has been cancelled
2026-07-08 18:03:00 +03:00
OrbisAI Security 790bedd821
CI: fix: package_managers.dependabot.dependabot-missing-cooldown.dependabot-missing-cooldown security vulnerability (#3489)
Some checks failed
macos_build / macos_build (3.10) (push) Has been cancelled
macos_build / macos_build (3.11) (push) Has been cancelled
macos_build / macos_build (3.12) (push) Has been cancelled
macos_build / macos_build (3.13) (push) Has been cancelled
macos_build / macos_build (3.14) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-14) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-15-intel) (push) Has been cancelled
pyenv_tests / pyenv_tests (macos-26) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Has been cancelled
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.10) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.11) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.12) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.13) (push) Has been cancelled
ubuntu_build / ubuntu_build (3.14) (push) Has been cancelled
Automated security fix generated by OrbisAI Security
2026-07-05 13:40:15 +03:00
Ayush 19f4c7cf20
Add an experimental pyenv-binary plugin with a save command (#3487)
Some checks are pending
macos_build / macos_build (3.10) (push) Waiting to run
macos_build / macos_build (3.11) (push) Waiting to run
macos_build / macos_build (3.12) (push) Waiting to run
macos_build / macos_build (3.13) (push) Waiting to run
macos_build / macos_build (3.14) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-14) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-15) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-15-intel) (push) Waiting to run
pyenv_tests / pyenv_tests (macos-26) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-22.04) (push) Waiting to run
pyenv_tests / pyenv_tests (ubuntu-24.04) (push) Waiting to run
ubuntu_build / ubuntu_build (3.10) (push) Waiting to run
ubuntu_build / ubuntu_build (3.13) (push) Waiting to run
ubuntu_build / ubuntu_build (3.14) (push) Waiting to run
ubuntu_build / ubuntu_build (3.11) (push) Waiting to run
ubuntu_build / ubuntu_build (3.12) (push) Waiting to run
* pyenv-binary: add experimental plugin skeleton

Adds a new plugin, decoupled from `pyenv install`, for packaging and installing
relocatable Python binaries. This commit is just the command dispatcher; the
individual subcommands follow.

`pyenv binary <command>` dispatches to the matching script under the plugin's
libexec, so subcommands stay out of the top-level `pyenv commands` list.

Groundwork for the binary distribution support discussed in #2334.

* pyenv-binary: add the `save` subcommand

`pyenv binary save <version> [<output-dir>]` packs an installed version into a
relocatable .tar.gz with relative paths and writes a metadata file recording the
build platform, distro, libc version and the external system libraries the
build links against (via `ldd` on Linux, `otool -L` on macOS).

* pyenv-binary: reject invalid version names and drop readlink -f in save

A version is a single directory name under versions/, so refuse names with a
slash or a dot-dot component before building the prefix path.

Also emit the python paths directly instead of `readlink -f`, which BSD
readlink on macOS does not support; ldd/otool follow the symlinks anyway, so
the resolved paths were never needed.

* pyenv-binary: test the `save` subcommand

Cover argument validation, the version-name guard, and packaging an
installed version into an archive with matching metadata.

* pyenv-binary: test dependency parsing and tighten the version guard

Add tests that feed realistic ldd and otool listings through save and
check only the libraries resolving outside the prefix end up in the
metadata, since that filtering is the fiddliest part of the command.

Narrow the version guard to reject `.' and `..' rather than any name
containing a dot-dot, now that a slash is already refused, and note why
the find still matches *.so.* even though CPython does not produce them.
2026-07-03 09:08:54 +03:00
dependabot[bot] 46cc3e552b
Bump actions/checkout from 6 to 7 in the github-actions group (#3486)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 10:27:40 +03:00
Michael Šimáček 8f68aab443
Add graalpy-3.12-25.1.3 (#3485)
* Add graalpy-3.12-25.1.3

* Adjust GraalPy version detection with the new naming scheme

---------

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-06-30 21:25:03 +03:00
Ivan Pozdeev a27d2723d6 install: reflect the new alias syntax in reference 2026-06-30 04:17:58 +03:00
Ayush 4c78a0dd50
pyenv-install: install a version under a custom name with <version>:<alias> (#3484) 2026-06-30 04:12:16 +03:00
Ayush 91aacc5dbf
Add script to generate sponsors section for release notes (#3478)
* OpenCollectrive API is blocked by a Cloudflare challenge page in some locations so allow to specify the OpenCollective API data manually

---------

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-06-28 20:06:38 +03:00
Yo Takezawa 3974647f0f
rehash: fix race condition in landlock writability check (#3483) 2026-06-26 21:04:33 +03:00
native-api ebd788d08c
Add miniforge3-26.3.2-2, 26.3.2-3 (#3481) 2026-06-25 09:52:54 +03:00
native-api 4b77d83d9d
CI: add_version enhancements (#3480)
* Default 30 seems enough for our needs
* comments on critical knowledge
* Cleaner handling of t thunk patches
* Fix an error when adding a release for which there's also a prerelease
* Workaround excepthook not working in Github Actions
* requirements.txt workaround is only needed for old Python
2026-06-25 09:48:57 +03:00
Ivan Pozdeev 933d0aaf1d 2.7.3 2026-06-24 05:14:13 +03:00
pyenv-bot[bot] 09dce106a4
Add CPython 3.15.0b3 (#3479)
Co-authored-by: native-api <2670332+native-api@users.noreply.github.com>
2026-06-24 04:27:01 +03:00
Ivan Pozdeev b52a8e3f52 python-build scripts: Switch to jc release
They finally made a release with our fix
2026-06-19 08:36:53 +03:00
Ayush f122a9d764
CI: add_version enhancements (#3475)
- Prevent OpenSSL from bumping across major versions
- Skip adding versions that already have pending PRs
- Copy/move patches when adding new releases in the same series
2026-06-14 21:46:48 +03:00
native-api 3323ba3ed9
README: clarify shell setup instructions using the new --install option 2026-06-12 15:34:14 +03:00
Ivan Pozdeev b43c9e5d42 2.7.2 2026-06-10 23:16:50 +03:00
pyenv-bot[bot] 532b659ca2
Add CPython 3.13.14 (#3473)
---------

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-06-10 23:12:21 +03:00
pyenv-bot[bot] 7565df6eab
Add CPython 3.14.6 (#3472)
---------

Co-authored-by: native-api <2670332+native-api@users.noreply.github.com>
Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-06-10 23:11:56 +03:00
Ivan Pozdeev e55613a267 CI: Don't trigger push checks on a pull request 2026-06-10 22:03:22 +03:00
Ivan Pozdeev 6c0c5cfa96 Fix verify_* calls in 3.6.x
Fixes #3470
2026-06-10 21:56:41 +03:00
Anup Das cf6f3c3200
fix(rehash): prevent terminal hang caused by stale or sandbox-blocked lock file (#3469)
* reduce stale lock TTL to 2 min, check for stale lock before acquiring and each time -- to hold up new shell sessions for as little as possible

---------

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-06-04 22:36:11 +03:00
Ivan Pozdeev 45180928d3
2.7.1 2026-06-03 09:09:54 +03:00
native-api c12fd3ae6b
realpath.c: fix obsolete syntax warning (#3468) 2026-06-03 08:42:53 +03:00
Ayush 455d1a31db
init: add --install for shell setup (#3454) 2026-06-03 01:13:07 +03:00
pyenv-bot[bot] efc77132f7
Add CPython 3.15.0b2 (#3467)
Co-authored-by: native-api <2670332+native-api@users.noreply.github.com>
2026-06-03 00:55:40 +03:00
native-api c425c9ec3a
Update URLs for PyPy nightly; Remove pypy3.5 and pypy3.7 nightly (#3466)
pypy3.5-c-jit-latest and pypy3.7-c-jit-latest are no longer available
2026-06-02 13:16:51 +03:00
native-api 95ddd7d479
Support 3.9 EOL Pip URL, consolidate tests (#3465) 2026-06-02 10:29:59 +03:00
Ivan Pozdeev 29057a6c69
2.6.32 2026-05-31 06:20:35 +03:00
native-api 23eb9a9ee3
Merge pull request #3463 from native-api/3.14_openssl_4_r2
3.14.0-5: Support building against OpenSSL 4
2026-05-31 05:59:43 +03:00
Ivan Pozdeev bfc595b0a0
CI: trigger rebuilds for <v>t, too, if a patch for <v> is changed 2026-05-30 22:00:44 +03:00
Ivan Pozdeev 034e15b810
3.14.0-5: Support building against OpenSSL 4 2026-05-30 22:00:43 +03:00
native-api 4c6d4c9be7
Add missing CPython 3.14.2t (#3464) 2026-05-30 17:58:36 +03:00
native-api 549a644688
Fix linking against a keg_only Homebrew OpenSSL when a a non-keg_only one is also installed (#3462) 2026-05-30 16:53:57 +03:00
native-api 7f1139ebe6
Add micropython 1.22.0 to 1.28.0; add downstream patches to fix compilation errors (#3460) 2026-05-30 16:36:04 +03:00
Ivan Pozdeev 874b27c749
CI: move no-response and add-version to ubuntu-slim
Supposedly lightweight tasks, we'll see
2026-05-30 16:28:31 +03:00
native-api 08a015100e
python_build: Make verify_python verify `pythonX.Y' suffix (#3459)
* Fix erroneous use in tests that didn't show until now
2026-05-30 12:47:16 +03:00
native-api 8decb512b4
Merge pull request #3458 from native-api/with_openssl_rpath
3.11.0+: Use the `--with-openssl-rpath' Configure option when possible
2026-05-30 05:46:13 +03:00
Ivan Pozdeev 84414c0121
3.11.5: Add missing patch 2026-05-30 05:38:46 +03:00
Ivan Pozdeev 9ea5bd3005
3.11.0+: Use the `--with-openssl-rpath' Configure option
The options is available since 3.10.0+ (https://github.com/python/cpython/pull/24820)
but has no effect until 3.11.0 (cpython b9e9292d75fdea621e05e39b8629e6935d282d0d)
and broken in MacOS until 3.12.2 (cpython cc13eabc7ce08accf49656e258ba500f74a1dae8),
we add downstream patches to fix that except for 3.11-dev where we disabled it
2026-05-30 05:38:32 +03:00
native-api 0a12a9cce2
Add GraalPy 25.0.3 (#3452)
* Add graalpy-25.0.3

* CI: modified_scripts_build : fix GraalPy version detection

---------

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-05-22 09:55:55 +03:00
Ivan Pozdeev e52da606ff
CI: modified_scripts_build : fix GraalPy version detection 2026-05-22 09:48:52 +03:00
Michael Simacek 4f47143108 Add graalpy-25.0.3 2026-05-22 08:36:54 +02:00
native-api 70f096a95c
rehash: detect and remove a stale lockfile (#3450)
fixes stalling for 60s and failing if some past fault
has resulting in a stale lockfile being present

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-05-17 09:38:23 +03:00
native-api 06fd1ce788
2.7, 3.4: force C99 standard; 2.7.14-18: force OpenSSL 1 formula (#3448)
GCC 15+ uses C23 by default
(https://www.phoronix.com/news/GCC-15-Default-C23)

fixes "error: static declaration of ‘sinpi’ follows non-static declaration"
fixes "error: cannot use keyword ‘false’ as enumeration constant"

* use OpenSSL 1.0 and 1.1 only from formulae
2026-05-14 06:41:47 +03:00
native-api 0864daa5ea
miniconda 26, CI: switch check to 3.13 (#3447) 2026-05-12 01:32:56 +03:00
native-api 522164fbd5
miniforge3 26.1, 26.3, add_miniforge: exclude .pkg installers (#3446)
According to Miniforge README, they are only meant for interactive installation
2026-05-12 01:06:24 +03:00
native-api 8c59c44902
Add miniconda3 26.3.2-2, miniforge3 26.3.2-0, 26.3.2-1 (#3445) 2026-05-11 23:47:34 +03:00
Ivan Pozdeev c639152ac7
2.6.31 2026-05-11 01:39:26 +03:00
pyenv-bot[bot] 331ff66e2d
Add CPython 3.14.5 (#3444)
* Keep bundled OpenSSL 3
There's no support for OpenSSL 4 yet

---------

Co-authored-by: native-api <2670332+native-api@users.noreply.github.com>
Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-05-11 01:36:03 +03:00
native-api f6a5b409e9
CI: add_cpython: Support prereleases for non-initial CPython releases (#3443)
* CI: add_version: correctly handle exceptions

exit code 1 is used as a signal that no new version is found -- so have to use a different exit code for unhandled exceptions

* CI: add_version: support prereleases for non-initial releases

They unexpectedly made a prerelease 3.14.5rc1

* clarify confusing error message
2026-05-08 20:20:32 +03:00
Ivan Pozdeev 3399b2e2e5
clarify confusing error message 2026-05-08 20:10:35 +03:00
Ivan Pozdeev 5bb6b27d61
CI: add_version: support prereleases for non-initial releases
They unexpectedly made a prerelease 3.14.5rc1
2026-05-08 20:10:35 +03:00
Ivan Pozdeev 97573e156e
CI: add_version: correctly handle exceptions
exit code 1 is used as a signal that no new version is found -- so have to use a different exit code for unhandled exceptions
2026-05-08 17:48:39 +03:00
Ivan Pozdeev 40dba8256f
2.6.30 2026-05-08 17:37:31 +03:00
Ned Batchelder e30c23eac2
Add CPython 3.16-dev, switch 3.15-dev to maintenance branch (#3442)
3.15.0b1 released means we need 3.16 support
2026-05-08 17:10:22 +03:00
Ivan Pozdeev a7cc776274
2.6.29 2026-05-08 03:54:43 +03:00
native-api e08497a2b7
Add CPython 3.14.5rc1 (#3441) 2026-05-08 03:53:13 +03:00
John Sirois 709f42cc08
Add CPython 3.15.0b1 (#3440) 2026-05-08 03:22:11 +03:00
Ivan Pozdeev 966819a10f
Merge branch 'master' of https://github.com/pyenv/pyenv 2026-05-06 18:19:08 +03:00
Ivan Pozdeev a736e6af27
CI: add_version: move off the start of the hour
Due to load peak at 0 minutes, it may fail to get a runner.
Github tries to allocate a runner for ~15 minutes.
2026-05-06 18:18:58 +03:00
orbisai0security 6bdb85d8b7
CI: add-version: Add timeouts to CPython release metadata fetches (#3439)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-06 06:35:23 +03:00
Ivan Pozdeev 36f37e96ea
CI: no-response: fix 2026-05-06 01:26:56 +03:00
Ivan Pozdeev 3f62ee82b5
CI: add_version: Fix commit message 2026-05-06 01:25:17 +03:00
Ivan Pozdeev 27b4b4dc84
CI: no-response: half the frequency
getting "no runner found" errors
2026-05-06 01:24:27 +03:00
native-api 0b0335a378
Add PyPy v7.3.22 (#3438)
* Add PyPy v7.3.22

The release blog entry is here:
https://pypy.org/posts/2026/04/pypy-v7322-release.html

* CI: Don't require openssl@1.1

Missing in macos-26 Homebrew, causing an error,
no longer required for any supported Pythons

---------

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-04-30 10:43:25 +03:00
Ivan Pozdeev b8a6e9c33c
CI: Don't require openssl@1.1
Missing in macos-26 Homebrew, causing an error,
no longer required for any supported Pythons
2026-04-30 07:01:27 +03:00
John Sirois 3966e507ba
Add PyPy v7.3.22
The release blog entry is here:
https://pypy.org/posts/2026/04/pypy-v7322-release.html
2026-04-29 19:39:23 -07:00
Ivan Pozdeev 485090e713
2.6.28 2026-04-29 08:26:10 +03:00
native-api 6481d1451d
pyenv-latest: fast path for when there is an exact match (#3437)
Avoids O(N^2) complexity when `pyenv-latest` is called in a loop
for existing entries
2026-04-24 00:34:40 +03:00
Ivan Pozdeev dfb1a96c3e
CI: use macos-26
the Github runner is out of beta
2026-04-17 22:53:42 +03:00
Ivan Pozdeev ae1bb9c061
CI: add_python: 3.9 is EOL 2026-04-17 22:30:54 +03:00
Ivan Pozdeev 46e4e6ab07
-dead code
[no ci]
2026-04-15 15:18:16 +03:00
Ivan Pozdeev 914ff80062
2.6.27 2026-04-08 18:26:46 +03:00
native-api 840f3ff7fd
Add miniforge3 26.1.1-2, 26.1.1-3 (#3433) 2026-04-08 18:24:54 +03:00
pyenv-bot[bot] bfec0e43f5
Add CPython 3.13.13, 3.14.4, 3.15.0a8 (#3432)
Co-authored-by: native-api <2670332+native-api@users.noreply.github.com>
2026-04-08 18:08:59 +03:00
exurd ce491008e7
python-build: advise user with no c compiler (#3294)
this checks if "no acceptable C compiler found" is in the last 10 lines of the log, and asks if the user has correctly installed the build dependencies.
2026-04-01 21:32:48 +03:00
dependabot[bot] d26308df49
Bump actions/create-github-app-token in the github-actions group (#3428)
Bumps the github-actions group with 1 update: [actions/create-github-app-token](https://github.com/actions/create-github-app-token).


Updates `actions/create-github-app-token` from 2 to 3
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 09:31:31 +03:00
Ivan Pozdeev 8397a19c64
- outdated info
[no ci]
2026-03-24 09:05:13 +03:00
Ivan Pozdeev 0d71a264c2
Fix factual errors; highlight pyenv install 3
Closes #3427
[no ci]
2026-03-23 18:12:11 +03:00
Ivan Pozdeev a6521a0d35
Fix link
[no ci]
2026-03-23 17:05:57 +03:00
native-api 044d816d35
which: update pyenv help global tip (#3424) 2026-03-14 11:23:19 +03:00
Jake Lodwick 6a246fad63
commands: fast path for --sh, replace sort|uniq with sort -u (#3423) 2026-03-13 13:56:23 +03:00
Ivan Pozdeev 174c61526f
2.6.26 2026-03-11 01:37:45 +03:00
native-api 397312d0c3
Merge pull request #3422 from native-api/miniconda3-26.1.1-1
Add miniconda3-26.1.1-1
2026-03-11 01:35:04 +03:00
Ivan Pozdeev 5bd084d816
Add miniconda3-26.1.1-1 2026-03-11 01:18:41 +03:00
Ivan Pozdeev 974e820478
Replace patterns with f-strings for logging
fix KeyError: out_dir
2026-03-11 01:17:34 +03:00
pyenv-bot[bot] a47dae9131
Add CPython 3.15.0a7 (#3421) 2026-03-11 01:11:17 +03:00
Ivan Pozdeev d5cd462a35
Revert "Add miniconda3-26.1.1-1 (#3420)"
This reverts commit 1534421e56.

The scripts actually contain the previous version, 25.11.1-1
2026-03-10 08:23:42 +03:00
binbjz 1534421e56
Add miniconda3-26.1.1-1 (#3420)
Co-authored-by: binbjz <binbjz@gmail.com>
2026-03-10 08:21:06 +03:00
Jake Lodwick e985ffab82
rehash: simplify source.bash pipeline (#3419)
Same cleanup applied to conda.bash in #3151.
2026-03-10 08:15:46 +03:00
native-api 8037f22665
rehash: streamline executables discovery; fix regression (#3418)
Separate the corner-cutting code for `rehash' into a dedicated branch.

Saves some more time (about 100ms for the large fixture)
2026-03-07 13:09:14 +03:00
Tekin Tian d5fa1e225e
Fix MacPorts OpenSSL formula detection (#3417)
Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-03-07 11:44:57 +03:00
Jake Lodwick 23bcdebe18
versions: fast path for --bare --skip-aliases (#3411)
Skip sort, native extension probe, and per-symlink realpath
when called with --bare --skip-aliases (the rehash case).
Use readlink to distinguish internal aliases (relative target)
from external installs (absolute target).

In MacOS, speeds up rehash by 5.2 ms (7%) in empty environment,
55.9 ms (33%) in a regular environment (3 ver, 12 venvs, 320 bins),
93.7 ms (15%) in a large environment (6 ver, 60 venvs, 1466 bins)
2026-03-04 10:02:44 +03:00
Ivan Pozdeev aa2e8b8260
2.6.25 2026-03-03 20:06:38 +03:00
pyenv-bot[bot] c9d3829169
Add CPython 3.12.13 (#3414) 2026-03-03 20:04:59 +03:00
native-api 05d3a598c4
Add miniforge3-26.1.0-0 (#3413) 2026-03-03 09:07:15 +03:00
Ivan Pozdeev c89b6b46dc
2.6.24 2026-03-03 08:17:57 +03:00
pyenv-bot[bot] c4ed80826f
Add CPython 3.10.20, 3.11.15
Co-authored-by: native-api <2670332+native-api@users.noreply.github.com>
2026-03-03 08:15:01 +03:00
Jake Lodwick 47871b2dc3
rehash: drop redundant sort -u from make_shims call (#3410)
register_shim() already deduplicates via associative array (bash 4+) or
by checking if a shim already exists (bash 3.2). The sort -u pipe in the make_shims
call is thus redundant -- the dedup happens downstream regardless. Shim
creation is order-independent and idempotent, so sorting has no semantic
effect either.

Saves one subprocess fork during every rehash invocation.
2026-03-02 00:36:24 +03:00
binbjz 43e83b52a2
Add Anaconda3-2025.12-2 (#3408)
Co-authored-by: binbjz <binbjz@gmail.com>
2026-02-21 00:32:12 +03:00
Ivan Pozdeev 9279111a60
Revert accidentally left test code from "CI: add_version: use Github App token to create PR"
This partially reverts commit 47b6fce29d.
2026-02-19 07:41:21 +03:00
native-api 3100654510
Merge pull request #3406 from pyenv/use_github_app_token
CI: add_version: use Github App token to create PR
2026-02-19 06:05:04 +03:00
Ivan Pozdeev 06bac52676
add_cpython: fix erroneously deleting old thunks
Looks like a leftover from older logic, before
we `git mv`'d thunks on prerelease upgrade
2026-02-19 04:56:42 +03:00
Ivan Pozdeev 47b6fce29d
CI: add_version: use Github App token to create PR
The only way atm that allows to both trigger CI
and get PR notifications, as per
https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens

Created helper app https://github.com/apps/pyenv-bot/
2026-02-19 04:56:41 +03:00
Ivan Pozdeev 81befc1592
2.6.23 2026-02-17 02:38:47 +03:00
native-api c44f03c058
Add CPython 3.15.0a6 (#3403)
Co-authored-by: native-api <2670332+native-api@users.noreply.github.com>
2026-02-17 02:36:26 +03:00
Ivan Pozdeev b2a43bbcdb
CI: add_verison: Don't run "set PR properties" unnecessarily 2026-02-04 13:58:08 +03:00
Ivan Pozdeev 2f10394d33
CI: add_version: fix Actions not triggering for the pull request 2026-02-04 04:15:00 +03:00
Ivan Pozdeev 57ec277d6a
2.6.22 2026-02-04 03:52:39 +03:00
github-actions[bot] 63edb91375
Add CPython 3.13.12 (#3401) 2026-02-04 03:50:15 +03:00
Ivan Pozdeev 6d8c0e4021
2.6.21 2026-02-03 23:46:41 +03:00
github-actions[bot] fbc7067e3f
Add CPython 3.14.3 (#3400) 2026-02-03 23:44:14 +03:00
Ivan Pozdeev 82f4de5593
CI: reflect GraalPy 25.0.2 dropping MacOS x64 support 2026-01-20 18:52:39 +03:00
Michael Šimáček 968cd11daa
Add GraalPy 25.0.2 (#3395) 2026-01-20 18:12:44 +03:00
Ivan Pozdeev 5447f1f859
CI: add_script: make more frequent
practice shows a long enough delay that contributors step in
2026-01-15 16:09:57 +03:00
Ivan Pozdeev 3441980e83
2.6.20 2026-01-15 07:05:06 +03:00
Ned Batchelder bd2f32dd7d
Add CPython 3.15.0a5 (#3393) 2026-01-15 07:00:46 +03:00
native-api 94faa168cb
CI: adjust CPython PR generation logic (#3392)
* For files retrieved through the GNU mirror load balancer, specify the load balancer URL
* Explicitly `git mv` files on prerelease upgrade in case Git doesn't detect it
* Add a trailing newline to a thunk
2026-01-14 07:03:58 +03:00
Ivan Pozdeev 92c9ab5273
2.6.19 2026-01-14 04:11:02 +03:00
Ned Batchelder 37bf92af84
Add CPython 3.15.0a4 (#3390) 2026-01-14 04:00:41 +03:00
Ivan Pozdeev 05ba9388a7
Fix schedule 2026-01-11 23:45:25 +03:00
Ivan Pozdeev c62be31e8c
2.6.18 2026-01-10 06:16:21 +03:00
Ivan Pozdeev c8129ceee6
fix schedule syntax 2026-01-09 12:04:52 +03:00
native-api d9182d6edc
CI: automatically check for CPython releases (#3388)
* .gitignore local venv
* Add alt GNU mirror support with <table>
* Fix UnboundLocalError when no micro 0 releases
* Cutoff for existing vetsions as well
* add missing dependency more_itertools
* workaround fake_useragent 2.0.0 falsely declaring 3.8 compatibility
2026-01-09 09:52:09 +03:00
native-api 4cf95be5ee
Merge pull request #3387 from native-api/anaconda-2025.12-1
Add anaconda-2025.12-1
2026-01-08 14:09:29 +03:00
Ivan Pozdeev 8c50260ba2
Anaconda 2025.12 dropped MacOS x64 support 2026-01-08 14:00:56 +03:00
Ivan Pozdeev f614e5caa5
Fix hang in Ubuntu build for Anaconda-2025.12 2026-01-08 14:00:55 +03:00
native-api 3c539aa0bc
Merge pull request #3386 from native-api/miniconda-25-accept-tos
Auto-accept TOS for miniconda3-25+
2026-01-08 13:07:40 +03:00
Ivan Pozdeev 935e1a68cd
Add anaconda3-2025.12-1 2026-01-08 11:22:55 +03:00
Ivan Pozdeev 98a7a6809c
Fix verify_py prefix in anaconda3-2025.06 2026-01-08 11:20:20 +03:00
Ivan Pozdeev 45ef1677c4
Auto-accept TOS in miniconda3-25+
Required for installation since this version
2026-01-08 11:20:20 +03:00
binbjz c35f26af3e
CI: exclude unsupported MacOS-x64 for recent Miniconda versions (#3385)
* split macOS Intel build and filter unsupported Miniconda

* Generate exclusions instead

---------

Co-authored-by: binbjz <binbjz@gmail.com>
Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2026-01-08 09:08:08 +03:00
binbjz 967ed2c367
Add miniconda3-25.11.1-1 (#3382)
Co-authored-by: binbjz <binbjz@gmail.com>
2026-01-08 00:27:49 +03:00
Ivan Pozdeev 1d8240a641 set executable bit for add_cpython.py 2026-01-07 22:53:57 +03:00
Ivan Pozdeev e5a6b49e02
scripts: remove unused 2026-01-07 07:32:00 +03:00
Ivan Pozdeev 4a3aaf4327
scripts: relax version requirements
Latest version are okay.
Freezing them causes Dependabot alerts
about vulnerabilities in old versions
2026-01-03 13:32:53 +03:00
native-api 2c27f44612
Merge pull request #3358 from native-api/cpython_generate
CI: add a script to autogenerate CPython build scripts
2025-12-27 08:20:00 +03:00
Ivan Pozdeev ff94d498db
(refactor) 2025-12-27 08:18:41 +03:00
Ivan Pozdeev 99faf7f217
Add a script to generate CPython installation scripts
[no ci]
2025-12-27 08:18:41 +03:00
native-api bf37e78693
Merge pull request #3379 from native-api/make_j_argument
Recognize an argument to -j in MAKEFLAGS when building extensions in 3.10-3.11 and latest 3.5-3.9
2025-12-27 06:23:19 +03:00
Ivan Pozdeev 956f78df42
Recognize an argument to -j in MAKEFLAGS when building extensions in 3.10-3.11 and latest 3.5-3.9
Upstream issue fixed in 3.12 (https://github.com/python/cpython/issues/87800)
2025-12-27 02:56:23 +03:00
Ivan Pozdeev 56bc05d5d6
python-build: Use no-tests only for OpenSSL 1.1.1+
Introduced in 1.1.1-pre1
2025-12-27 02:56:15 +03:00
Ivan Pozdeev 8ef33165b8
CI: fix CPython-only checks 2025-12-27 02:56:14 +03:00
native-api 48743aa8f3
README: Document how to change Python version in a shell spawned from a Python-based program (e.g. Jupyter) (#3380) 2025-12-26 17:19:10 +03:00
rockandska 2c38423a98
pyenv launcher: introduce and export _PYENV_INSTALL_PREFIX (#3334)
provides a reliable way for Pyenv code to locate other parts of Pyenv code that are not under `PYENV_ROOT`

* tests: Work with Git entirely in a test repo

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2025-12-24 10:00:09 +03:00
native-api aa6c9b9694
init: don't silence automatic rehash; rehash: report final error but not provisional ones (#3377) 2025-12-21 07:21:30 +03:00
native-api a944da76c0
rehash: support BSD mktemp (#3376)
macOS 12 `mktemp' doesn't support `-p`
2025-12-21 06:38:46 +03:00
Florian Blanchet aeea3ac825
Add PowerShell support (#2749)
Co-authored-by: Ivan Pozdeev <ivan_pozdeev@mail.ru>
Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2025-12-21 05:33:19 +03:00
Ivan Pozdeev 7998159daa
tests: fix integration tests for Bash and Fish 2025-12-21 02:11:02 +03:00
native-api 5855d9dfca
Merge pull request #3372 from native-api/ci_update_cleanup
CI: Update and cleanup workflow scripts
2025-12-20 19:37:04 +03:00
native-api 22993a239e
Merge pull request #3375 from native-api/fix_inf_loop_shim_different_location
Fix an infinite loop if a shim is symlinked to and called from a different location
2025-12-20 11:24:41 +03:00
Ivan Pozdeev 6113214f65
tests: refactor: migrate to newly-added create_stub() 2025-12-20 11:05:27 +03:00
Ivan Pozdeev 7418835908
Prevent infinite loop if a shim is linked and called from a nonstandard location with "system" active 2025-12-20 11:05:27 +03:00
Ivan Pozdeev b259813357
tests: refactor: rename and consolidate create_executable() functions 2025-12-20 08:34:20 +03:00
Ivan Pozdeev 22b192913e
2.6.17 2025-12-16 23:30:42 +03:00
Edgar Ramírez Mondragón b50afe69a8
Add CPython 3.15.0a3 (#3374)
Signed-off-by: Edgar Ramírez-Mondragón <edgarrm358@gmail.com>
2025-12-16 23:28:46 +03:00
native-api 91931a4dd9
miniconda3-latest: auto accept TOS (#3373)
Since 14.07.2025, Anaconda requires accepting the new TOS upon installation
that limit eligibility for free use
2025-12-14 22:48:46 +03:00
Ivan Pozdeev dabaa901e9
CI: - Ubuntu 18.04 workaround 2025-12-13 18:07:37 +03:00
Ivan Pozdeev 4b33cb68b6
CI: - Micropython-specific code from CPython-only branches 2025-12-13 18:04:15 +03:00
Ivan Pozdeev 9b85239875
CI: - commented-out code 2025-12-13 17:52:53 +03:00
Ivan Pozdeev f19fe0f2be
CI: tests: + macos-15 2025-12-13 17:51:51 +03:00
Ivan Pozdeev 293798582a
CI: build: - EOL 3.9, + 3.13 2025-12-13 17:51:26 +03:00
Ivan Pozdeev 4b36ddf5c6
Merge branch 'master' of https://github.com/pyenv/pyenv 2025-12-13 17:26:59 +03:00
Ivan Pozdeev 4e7626721a
Delete the old issue template
[no ci]
2025-12-13 17:22:12 +03:00
native-api 3b25462407
Merge pull request #3371 from 13steinj/new-miniforge-miniconda-versions
Adds latest miniforge releases 25.9.1-0, 25.11.0-1
2025-12-09 22:25:34 +03:00
Jonathan Stein 70294ef6f1 Add miniforge3 25.9.1-0, 25.11.0-1
Adds latest miniforge releases 25.9.1-0, 25.11.0-1.

Skips 25.11.0-0 due to a regression reported in the release notes of
miniforge3-25.11.0-1.

Ref: https://github.com/conda-forge/miniforge/releases/tag/25.11.0-1
2025-12-09 12:27:18 -06:00
Jonathan Stein bdb0508220 Update requirements for the add_miniconda script
At least under Python 3.14, this quickly fails without lxml[html_clean]
installed explicitly.
2025-12-09 12:18:23 -06:00
Ivan Pozdeev e805257c1f
2.6.16 2025-12-06 00:58:03 +03:00
César Román 6cbce8616e
Add CPython 3.13.11, 3.14.2 (#3370) 2025-12-06 00:56:17 +03:00
Ivan Pozdeev a6ea5b3179 Fix rehash 2025-12-05 20:57:07 +03:00
native-api 0f7b8fa8ee
sh-rehash: Fix erroneously executing rather than printing a command (#3369) 2025-12-05 20:42:25 +03:00
binbjz 59204a6c89
Add Miniconda3-25.9.1-3 (#3368)
Co-authored-by: binbjz <binbjz@gmail.com>
2025-12-05 17:26:32 +03:00
native-api 6f0e2d3458
rehash: Fix hanging for 60 seconds when shims directory is not writable due to Linux Landlock (#3367) 2025-12-05 16:19:17 +03:00
Ivan Pozdeev 61d869f67e
2.6.15 2025-12-03 03:23:17 +03:00
John Sirois a63e48cf75
Add CPython 3.13.10. (#3364) 2025-12-03 03:21:46 +03:00
Ivan Pozdeev d1ae7fd65d
2.6.14 2025-12-02 21:00:00 +03:00
Ned Batchelder b230a338df
Add CPython 3.14.1 (#3363) 2025-12-02 20:52:30 +03:00
native-api 94e4bf0ae8
Fix "ld: symbol(s) not found" for some C system calls in MacOS if XCode is for a newer major MacOS version (#3362)
Citing https://www.postgrespro.ru/list/id/E1kfzRR-0001EH-Ph@gemulon.postgresql.org
fixing the same problem:
We previously put the -isysroot switch only into CPPFLAGS, theorizing
that it was only needed to find the right copies of include files.
However, it seems that we also need to use it while linking programs,
to find the right stub ".tbd" files for libraries.  We got away
without that up to now, but apparently that was mostly luck.  It may
also be that failures are only observed when the Xcode version is
noticeably out of sync with the host macOS version; the case that's
prompting action right now is that builds fail when using latest Xcode
(12.2) on macOS Catalina, even though it's fine on Big Sur.

Specifically, the problem arises for `preadv' and `pwritev' (new in MacOS 11 Big Sur) when building with XCode 12.4 (supporting Big Sur) in MacOS 10.15 Catalina.
2025-12-01 22:01:15 +03:00
dependabot[bot] a30c26ddf5
Bump actions/checkout from 5 to 6 in the github-actions group (#3360)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 15:59:31 +03:00
Ivan Pozdeev fdde91269b
2.6.13 2025-11-19 14:30:40 +03:00
Ned Batchelder fa741b861a
Add CPython 3.15.0a2 (#3359) 2025-11-19 14:28:31 +03:00
binbjz 2afd1733a4
Add Miniconda3-25.9.1-1 (#3357) 2025-11-08 14:35:44 +03:00
Edgar Ramírez Mondragón 032022dace
Replace macos-13 with macos-15-intel (#3356)
> The macOS 13 Ventura based runner images will begin deprecation on September 22nd and will be fully unsupported by December 4th for GitHub and ADO.

https://github.com/actions/runner-images/issues/13046

Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
2025-11-05 22:10:29 +03:00
Ivan Pozdeev c6ea700cdf 2.6.12 2025-10-31 23:27:18 +03:00
Ned Batchelder 97fd8f436d
Add CPython 3.9.25 (#3355) 2025-10-31 23:24:35 +03:00
Michael Šimáček 1b7d2f08fd
Add graalpy-25.0.1 (#3350) 2025-10-22 02:28:06 +03:00
Ivan Pozdeev 74fea7edc6 Fix leftover commented-out test code 2025-10-15 15:00:43 +03:00
Ivan Pozdeev 94071a9374 2.6.11 2025-10-15 14:40:44 +03:00
Edgar Ramírez Mondragón 7661437864
Add CPython 3.15.0a1 (#3347) 2025-10-15 14:38:53 +03:00
native-api ea3a9fe895
Merge pull request #3346 from nedbat/nedbat/3.13.9 2025-10-15 14:08:11 +03:00
Ivan Pozdeev c1c8646d75 Fix Readline 8.3 URLs
https://www.gnu.org/server/mirror.html advises
to use http://ftpmirror.gnu.org/
for automatic mirror selection
2025-10-15 13:10:13 +03:00
Ned Batchelder fc28287273 Add CPython 3.13.9 2025-10-14 16:18:13 -04:00
native-api 8d3bba2f92
Support building against Homebrew Tcl/Tk 9 (#3343)
Use tclConfig.sh/tkConfig.sh for future-proofing
3.14.0 doesn't detect Tcl/Tk with external libtommath, have to do it ourselves
2025-10-11 00:19:06 +03:00
Ivan Pozdeev a419b003b4 2.6.10 2025-10-10 04:14:38 +03:00
Ned Batchelder 1c05252dfc
Add CPython 3.12.12, 3.11.14, 3.10.19, 3.9.24 (#3344) 2025-10-10 04:12:44 +03:00
Ivan Pozdeev 8087e811cd 2.6.9 2025-10-07 22:33:22 +03:00
John Sirois faddc0d3fe
Add CPython 3.13.8 (#3341) 2025-10-07 22:28:30 +03:00
James ce9d396ccc
Add CPython 3.14.0 (#3339)
---------

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
2025-10-07 22:26:47 +03:00
native-api ba968fda2d
CI: Fix output variable for CPython-specific workflows (#3340) 2025-10-07 21:31:45 +03:00
tuzi3040 8e34326347
CI: modified-scripts-build: fix CPython-specific jobs never firing (#3338) 2025-10-07 21:00:59 +03:00
Mészáros Máté Róbert 2f8cb2cf1f
Handle osx specific ln option name (#1)
On OSX `ln` does not have `--symbolic` only `-s`.

* bin/pyenv-link-version

---------

Co-authored-by: yfprojects <62463991+real-yfprojects@users.noreply.github.com>
2023-07-07 14:18:02 +00:00
real-yfprojects b8d8dee191
Fix output
* bin/pyenv-link-version
2023-06-06 16:59:25 +02:00
real-yfprojects ba9099392a
Fix completions
* bin/pyenv-link
2023-06-06 16:56:16 +02:00
real-yfprojects d428be59ec
Write README.
* README.md
2023-06-06 16:45:22 +02:00
real-yfprojects dffc750fa2
Initial implementation
* bin/pyenv-link

* bin/pyenv-link-version
2023-06-06 16:22:52 +02:00
yfprojects f46b669f1d
Initial commit 2023-06-06 11:09:59 +00:00
423 changed files with 90648 additions and 1139 deletions

View file

@ -1,26 +0,0 @@
Too many issues will kill our team's development velocity, drastically.
Make sure you have checked all steps below.
### Prerequisite
* [ ] Make sure your problem is not listed in [the common build problems](https://github.com/pyenv/pyenv/wiki/Common-build-problems).
* [ ] Make sure no duplicated issue has already been reported in [the pyenv issues](https://github.com/pyenv/pyenv/issues). You should look for closed issues, too.
* [ ] Make sure you are not asking us to help solving your specific issue.
* GitHub issues is opened mainly for development purposes. If you want to ask someone to help solving your problem, go to some community site like [Gitter](https://gitter.im/yyuu/pyenv), [StackOverflow](https://stackoverflow.com/questions/tagged/pyenv), etc.
* [ ] Make sure your problem is not derived from packaging (e.g. [Homebrew](https://brew.sh)).
* Please refer to the package documentation for the installation issues, etc.
* [ ] Make sure your problem is not derived from plugins.
* This repository is maintaining `pyenv` and the default `python-build` plugin only. Please refrain from reporting issues of other plugins here.
### Description
- [ ] Platform information (e.g. Ubuntu Linux 16.04):
- [ ] OS architecture (e.g. amd64):
- [ ] pyenv version:
- [ ] Python version:
- [ ] C Compiler information (e.g. gcc 7.3):
- [ ] Please attach the debug trace of the failing command as a gist:
* Run `env PYENV_DEBUG=1 <faulty command> 2>&1 | tee trace.log` and attach `trace.log`. E.g. if you have a problem with installing Python, run `env PYENV_DEBUG=1 pyenv install -v <version> 2>&1 | tee trace.log` (note the `-v` option to `pyenv install`).
- [ ] If you have a problem with installing Python, please also attach `config.log` from the build directory
* The build directory is reported after the "BUILD FAILED" message and is usually under `/tmp`.
- [ ] If the build succeeds but the problem is still with the build process (e.g. the resulting Python is missing a feature), please attach
* the debug trace from reinstalling the faulty version with `env PYENV_DEBUG=1 pyenv install -f -k -v <version> 2>&1 | tee trace.log`
* `config.log` from the build directory. When using `pyenv install` with `-k` as per above, the build directory will be under `$PYENV_ROOT/sources`.

View file

@ -3,9 +3,6 @@ Make sure you have checked all steps below.
### Prerequisite ### Prerequisite
* [ ] Please consider implementing the feature as a hook script or plugin as a first step. * [ ] Please consider implementing the feature as a hook script or plugin as a first step.
* pyenv has some powerful support for plugins and hook scripts. Please refer to [Authoring plugins](https://github.com/pyenv/pyenv/wiki/Authoring-plugins) for details and try to implement it as a plugin if possible. * pyenv has some powerful support for plugins and hook scripts. Please refer to [Authoring plugins](https://github.com/pyenv/pyenv/wiki/Authoring-plugins) for details and try to implement it as a plugin if possible.
* [ ] Please consider contributing the patch upstream to [rbenv](https://github.com/rbenv/rbenv), since we have borrowed most of the code from that project.
* We occasionally import the changes from rbenv. In general, you can expect changes made in rbenv will be imported to pyenv too, eventually.
* Generally speaking, we prefer not to make changes in the core in order to keep compatibility with rbenv.
* [ ] My PR addresses the following pyenv issue (if any) * [ ] My PR addresses the following pyenv issue (if any)
- Closes https://github.com/pyenv/pyenv/issues/XXXX - Closes https://github.com/pyenv/pyenv/issues/XXXX

44
.github/actions/build-python/action.yml vendored Normal file
View file

@ -0,0 +1,44 @@
name: Build a Python version
description: >
Install python-build's system dependencies for the current runner's OS, build
and check the requested version.
inputs:
python-version:
description: A version as accepted by `pyenv install`.
required: true
runs:
using: composite
steps:
- shell: bash
run: |
#envvars
export PYENV_ROOT="$GITHUB_WORKSPACE"
echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
# The two OSes differ only in this step.
- if: runner.os == 'macOS'
shell: bash
run: |
#prerequisites
brew install openssl readline sqlite3 xz tcl-tk@8 libb2 zstd
- if: runner.os == 'Linux'
shell: bash
run: |
#prerequisites
pyenv install-prerequisites
- shell: bash
run: |
#build
pyenv --debug install ${{ inputs.python-version }} && rc=$? || rc=$?
if [[ $rc -ne 0 ]]; then echo config.log:; cat "${TMPDIR:-/tmp}"/python-build*/*/config.log; false; fi
pyenv global ${{ inputs.python-version }}
pyenv rehash
- shell: bash
run: |
#print version
python --version
python -m pip --version
- shell: python # Prove that actual Python == expected Python
env:
EXPECTED_PYTHON: ${{ inputs.python-version }}
run: import os, sys ; assert sys.version.startswith(os.getenv("EXPECTED_PYTHON"))

View file

@ -4,6 +4,8 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "monthly" interval: "monthly"
cooldown:
default-days: 7
groups: groups:
github-actions: github-actions:
patterns: patterns:

View file

@ -0,0 +1,364 @@
#!/usr/bin/env python3
"""Generate a "Sponsors since <date>" section for release notes.
Queries GitHub Sponsors and OpenCollective for new sponsors since the latest
pyenv release or one month ago, whichever is longer. The output is Markdown
suitable for GitHub Releases.
Requirements:
* Python 3.8+
* The ``gh`` CLI authenticated with the ``read:user`` scope.
* Network access to https://opencollective.com.
"""
import argparse
import calendar
import datetime
import json
import pathlib
import subprocess
import sys
import typing
import urllib.error
import urllib.request
GITHUB_ORG = "pyenv"
OPENCOLLECTIVE_MEMBERS_URL = "https://opencollective.com/pyenv/members.json"
class SponsorDataError(RuntimeError):
"""Raised when a sponsors data source cannot be queried or parsed."""
def parse_date(value: str) -> datetime.date:
return datetime.datetime.fromisoformat(value).date()
def one_month_ago(today: typing.Optional[datetime.date] = None) -> datetime.date:
today = today or datetime.date.today()
year = today.year
month = today.month - 1
if month == 0:
year -= 1
month = 12
try:
return datetime.date(year, month, today.day)
except ValueError:
last_day = calendar.monthrange(year, month)[1]
return datetime.date(year, month, last_day)
def latest_release_date() -> datetime.date:
"""Return the publish date of the latest GitHub release."""
latest_release_error = None
try:
result = subprocess.run(
["gh", "api", f"repos/{GITHUB_ORG}/{GITHUB_ORG}/releases/latest"],
capture_output=True,
text=True,
check=True,
)
data = json.loads(result.stdout)
published = data["published_at"].replace("Z", "+00:00")
return datetime.datetime.fromisoformat(published).date()
except (
subprocess.CalledProcessError,
OSError,
json.JSONDecodeError,
KeyError,
) as exc:
latest_release_error = exc
try:
tag = subprocess.run(
["git", "describe", "--tags", "--abbrev=0"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
date_str = subprocess.run(
["git", "log", "-1", "--format=%cI", tag],
capture_output=True,
text=True,
check=True,
).stdout.strip()
return datetime.datetime.fromisoformat(date_str).date()
except (subprocess.CalledProcessError, OSError) as exc:
detail = ""
if latest_release_error is not None:
detail = f" GitHub release lookup failed first: {latest_release_error}."
raise SponsorDataError(
"Could not determine the latest release date. "
"Pass --since explicitly or run from a clone with release tags."
f"{detail}"
) from exc
def compute_since_date(explicit_since: typing.Optional[datetime.date]) -> datetime.date:
if explicit_since is not None:
return explicit_since
return min(latest_release_date(), one_month_ago())
def github_sponsors(since: datetime.date) -> typing.List[typing.Dict]:
"""Return GitHub Sponsors created on or after *since*."""
query = """
query($org: String!, $after: String) {
organization(login: $org) {
sponsorshipsAsMaintainer(
first: 100,
after: $after,
activeOnly: false,
orderBy: {field: CREATED_AT, direction: DESC}
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
createdAt
sponsorEntity {
... on User { login, name }
... on Organization { login, name }
}
}
}
}
}
"""
since_dt = datetime.datetime.combine(
since, datetime.time.min, tzinfo=datetime.timezone.utc
)
sponsors = []
cursor = None
while True:
command = [
"gh",
"api",
"graphql",
"-F",
f"org={GITHUB_ORG}",
"-f",
f"query={query}",
]
if cursor is not None:
command.extend(["-F", f"after={cursor}"])
result = subprocess.run(
command,
capture_output=True,
text=True,
)
if result.returncode != 0:
detail = result.stderr.strip() or result.stdout.strip() or "no output"
raise SponsorDataError(
"GitHub Sponsors query failed. "
"Check that `gh auth status` shows access to the pyenv org "
"and that the token has the scopes required to read sponsorships. "
f"`gh api graphql` exited {result.returncode}: {detail}"
)
try:
data = json.loads(result.stdout)
except json.JSONDecodeError as exc:
raise SponsorDataError(
"GitHub Sponsors query returned invalid JSON."
) from exc
if "errors" in data:
raise SponsorDataError(
f"GitHub Sponsors query returned errors: {data['errors']}"
)
try:
sponsorships = data["data"]["organization"]["sponsorshipsAsMaintainer"]
except (TypeError, KeyError) as exc:
raise SponsorDataError(
"GitHub Sponsors query returned an unexpected response shape."
) from exc
try:
nodes = sponsorships["nodes"]
page_info = sponsorships["pageInfo"]
except (TypeError, KeyError) as exc:
raise SponsorDataError(
"GitHub Sponsors query returned incomplete pagination data."
) from exc
for node in nodes:
try:
created = datetime.datetime.fromisoformat(
node["createdAt"].replace("Z", "+00:00")
)
entity = node["sponsorEntity"]
login = entity["login"]
except (AttributeError, KeyError, TypeError, ValueError) as exc:
raise SponsorDataError(
"GitHub Sponsors query returned an unexpected sponsor record."
) from exc
if created < since_dt:
return sponsors
sponsors.append({
"login": login,
"name": entity.get("name") or login,
})
try:
has_next_page = page_info["hasNextPage"]
cursor = page_info["endCursor"]
except (TypeError, KeyError) as exc:
raise SponsorDataError(
"GitHub Sponsors query returned incomplete pagination data."
) from exc
if not has_next_page:
return sponsors
def opencollective_sponsors(since: datetime.date, data: typing.Union[str, None]) -> typing.List[typing.Dict]:
"""Return OpenCollective backers active on or after *since*."""
if data is None:
req = urllib.request.Request(
f"{OPENCOLLECTIVE_MEMBERS_URL}?limit=1000",
headers={"User-Agent": f"{GITHUB_ORG}/release-notes-sponsors"},
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
data = resp.read()
except urllib.error.HTTPError as exc:
raise SponsorDataError(
f"OpenCollective sponsors query failed for {OPENCOLLECTIVE_MEMBERS_URL}: "
f"HTTP {exc.code} {exc.reason}"
) from exc
except urllib.error.URLError as exc:
raise SponsorDataError(
f"OpenCollective sponsors query failed for {OPENCOLLECTIVE_MEMBERS_URL}: "
f"{exc.reason}"
) from exc
try:
members = json.loads(data)
except json.JSONDecodeError as exc:
raise SponsorDataError(
"OpenCollective sponsors query returned invalid JSON."
) from exc
since_dt = datetime.datetime.combine(since, datetime.time.min)
sponsors = []
for member in members:
if member.get("role") != "BACKER":
continue
try:
last_transaction_at = datetime.datetime.strptime(member["lastTransactionAt"], "%Y-%m-%d %H:%M")
profile = member["profile"].rstrip("/")
except (KeyError, TypeError, ValueError) as exc:
raise SponsorDataError(
"OpenCollective sponsors query returned an unexpected member record."
) from exc
if last_transaction_at < since_dt:
continue
slug = profile.split("/")[-1]
if slug == "github-sponsors":
# Listed separately under GitHub Sponsors.
continue
sponsors.append({
"name": member.get("name") or slug,
"profile": profile,
})
return sponsors
def render(
since: datetime.date,
gh_sponsors: typing.List[typing.Dict],
oc_sponsors: typing.List[typing.Dict],
) -> str:
lines = [f"## Sponsors since {since.isoformat()}", ""]
if gh_sponsors:
lines.append("### GitHub Sponsors")
for sponsor in gh_sponsors:
lines.append(
markdown_link(
sponsor["name"],
f"https://github.com/{sponsor['login']}",
)
)
lines.append("")
if oc_sponsors:
lines.append("### Open Collective")
for sponsor in oc_sponsors:
lines.append(markdown_link(sponsor["name"], sponsor["profile"]))
lines.append("")
if not gh_sponsors and not oc_sponsors:
lines.append("*No new sponsors in this period.*")
lines.append("")
return "\n".join(lines)
def markdown_link(text: str, url: str) -> str:
escaped_text = escape_markdown_text(text)
escaped_url = url.replace(")", "%29")
return f"- [{escaped_text}]({escaped_url})"
def escape_markdown_text(text: str) -> str:
escaped = text.replace("\\", "\\\\")
for char in r"`*_{}[]()#+-.!|>":
escaped = escaped.replace(char, f"\\{char}")
return escaped
def main() -> int:
parser = argparse.ArgumentParser(
description="Generate a sponsors section for GitHub release notes."
)
parser.add_argument(
"--since",
type=parse_date,
metavar="YYYY-MM-DD",
help="Include sponsors created on or after this date.",
)
parser.add_argument(
"--output",
metavar="FILE",
help="Write the section to FILE instead of stdout.",
)
parser.add_argument(
"--skip-opencollective",
action="store_true",
help="Skip OpenCollective backers if the members endpoint is unavailable.",
)
parser.add_argument(
"--opencollective-data",
metavar="FILE",
help="Take OpenCollective API reply from FILE.",
)
args = parser.parse_args()
try:
since = compute_since_date(args.since)
oc_sponsors = []
if not args.skip_opencollective:
manual_data = (
pathlib.Path(args.opencollective_data).read_text()) \
if args.opencollective_data \
else None
oc_sponsors = opencollective_sponsors(since, manual_data)
section = render(since, github_sponsors(since), oc_sponsors)
except SponsorDataError as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(section)
else:
print(section, end="")
return 0
if __name__ == "__main__":
sys.exit(main())

58
.github/workflows/add_version.yml vendored Normal file
View file

@ -0,0 +1,58 @@
name: Add versions
on:
workflow_dispatch: {}
schedule:
# Every N hours
- cron: '25 */4 * * *'
permissions:
contents: write
pull-requests: write
jobs:
add_cpython:
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: 3
cache: 'pip'
cache-dependency-path: plugins/python-build/scripts/requirements.txt
- run: pip install -r plugins/python-build/scripts/requirements.txt
- name: check for a release
run: |
python plugins/python-build/scripts/add_cpython.py --verbose >added_versions.lst && rc=$? || rc=$?
#0 means new version found, 1 not found, 2 another error
[[ $rc -gt 1 ]] && false
echo "rc=$rc" >> $GITHUB_ENV
- name: set PR properties
if: env.rc == 0
shell: python
run: |
import os
import sys
versions=[l.rstrip() for l in open("added_versions.lst")]
with open(os.environ['GITHUB_ENV'],'a') as f:
f.write(f'branch_name=auto_add_version/{"_".join(versions)}\n')
f.write(f'pr_name=Add CPython {", ".join(versions)}\n')
os.remove("added_versions.lst")
# https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens
- name: Generate Github token
if: env.rc == 0
uses: actions/create-github-app-token@v3
id: generate-token
with:
app-id: ${{ vars.PYENV_BOT_APP_ID }}
private-key: ${{ secrets.PYENV_BOT_PRIVATE_KEY }}
- name: Create Pull Request
uses: peter-evans/create-pull-request@v8
if: env.rc == 0
with:
branch: ${{ env.branch_name }}
title: ${{ env.pr_name }}
commit-message: ${{ env.pr_name }}
token: ${{ steps.generate-token.outputs.token }}

62
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,62 @@
name: build
on:
push:
branches: [master]
pull_request: {}
permissions:
contents: read
jobs:
discover_build_logic_change:
if: github.event_name == 'pull_request'
runs-on: ubuntu-slim
env:
FULL_OS_MATRIX: '[
"macos-14",
"macos-15",
"macos-15-intel",
"macos-26",
"macos-26-intel",
"ubuntu-22.04",
"ubuntu-22.04-arm",
"ubuntu-24.04",
"ubuntu-24.04-arm"
]'
outputs:
os: ${{ steps.detect.outputs.os }}
steps:
- uses: actions/checkout@v7
- run: git fetch origin "$GITHUB_BASE_REF"
- id: detect
shell: bash
run: |
if [[ -n $(git diff --name-only "origin/$GITHUB_BASE_REF" -- \
plugins/python-build/bin/python-build \
.github/actions/build-python/action.yml \
.github/workflows/build.yml) ]]
then
printf "%s\n" "os<<!" "$FULL_OS_MATRIX" "!" >> "$GITHUB_OUTPUT"
fi
build:
needs: discover_build_logic_change
# Run when the detection job succeeded *or was skipped* -- but not failed
if: ${{ !cancelled() && !failure() }}
strategy:
fail-fast: false
matrix:
os: ${{ fromJson(needs.discover_build_logic_change.outputs.os || '["macos-latest","ubuntu-latest"]') }}
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/build-python
with:
python-version: ${{ matrix.python-version }}

View file

@ -1,43 +0,0 @@
name: macos_build
on: [pull_request, push]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
macos_build:
strategy:
fail-fast: false
matrix:
python-version:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
runs-on: macos-latest
steps:
- uses: actions/checkout@v5
# Normally, we would use the superbly maintained...
# - uses: actions/setup-python@v2
# with:
# python-version: ${{ matrix.python-version }}
# ... but in the repo, we want to test pyenv builds on macOS
- run: |
brew install openssl readline sqlite3 xz tcl-tk@8 libb2 zstd
# https://github.com/pyenv/pyenv#installation
- run: pwd
- env:
PYENV_ROOT: /Users/runner/work/pyenv/pyenv
run: |
echo $PYENV_ROOT
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
bin/pyenv --debug install ${{ matrix.python-version }}
bin/pyenv global ${{ matrix.python-version }}
bin/pyenv rehash
- run: python --version
- run: python -m pip --version
- shell: python # Prove that actual Python == expected Python
env:
EXPECTED_PYTHON: ${{ matrix.python-version }}
run: import os, sys ; assert sys.version.startswith(os.getenv("EXPECTED_PYTHON"))

View file

@ -2,33 +2,87 @@ name: modified_scripts
on: [pull_request] on: [pull_request]
jobs: jobs:
discover_modified_scripts: discover_modified_scripts:
runs-on: ubuntu-latest runs-on: ubuntu-slim
outputs: outputs:
versions: ${{steps.modified-versions.outputs.versions}} versions: ${{steps.modified-versions.outputs.versions}}
versions_cpython_only: ${{steps.modified-versions.outputs.versions_cpython_only}}
versions_macos_build_exclude: ${{steps.modified-versions.outputs.versions_macos_build_exclude}}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- run: git fetch origin "$GITHUB_BASE_REF" - run: git fetch origin "$GITHUB_BASE_REF"
- shell: bash - shell: bash
run: > run: >
versions=$(git diff "origin/$GITHUB_BASE_REF" --name-only -z versions=$(git diff "origin/$GITHUB_BASE_REF" --name-only -z
| perl -ne 'BEGIN {$\="\n";$/="\0";} chomp; | perl -ne 'BEGIN {$\="\n";$/="\0";} chomp;
if (/^plugins\/python-build\/share\/python-build\/(?:([^\/]+)|patches\/([^\/]+)\/.*)$/ and -e $& ) if (/^(plugins\/python-build\/share\/python-build\/)(?:([^\/]+)|patches\/([^\/]+)\/.*)$/ and -e $& )
{ print $1.$2; }' \ {
print $2.$3;
if ( -e $1.$2.$3.t ) { print $2.$3.t; }
}' \
| sort -u); | sort -u);
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64); EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64);
echo "versions<<$EOF" >> $GITHUB_ENV; echo "versions<<$EOF" >> $GITHUB_ENV;
echo "$versions" >> $GITHUB_ENV; echo "$versions" >> $GITHUB_ENV;
echo "$EOF" >> $GITHUB_ENV; echo "$EOF" >> $GITHUB_ENV;
versions_cpython_only=$(grep -Ee '^[[:digit:]]' <<<"$version") versions_cpython_only=$(grep -Ee '^[[:digit:]]' <<<"$versions")
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64); EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64);
echo "versions_cpython_only<<$EOF" >> $GITHUB_ENV; echo "versions_cpython_only<<$EOF" >> $GITHUB_ENV;
echo $versions_cpython_only >> $GITHUB_ENV; echo "$versions_cpython_only" >> $GITHUB_ENV;
echo "$EOF" >> $GITHUB_ENV; echo "$EOF" >> $GITHUB_ENV;
- run: pip install packaging
- name: Build exclusions JSON
shell: python
run: |
import os
import json
import random
import re
import subprocess
import packaging.version
result=[]
def exclude_macos_intel(result, python_version):
result.append({'os':'macos-15-intel','python-version':python_version})
result.append({'os':'macos-26-intel','python-version':python_version})
for line in os.environ['versions'].splitlines():
if m:=re.match(r'miniconda3-\d+\.\d+-(\d+\.\d+\.\d+)', line):
version = packaging.version.Version(m.group(1))
# Miniconda dropped MacOS x64 support
if version >= packaging.version.Version('25.9.1'):
exclude_macos_intel(result, line)
if m:=re.match(r'anaconda3-(\d+\.\d+)', line):
version = packaging.version.Version(m.group(1))
# Anaconda dropped MacOS x64 support
if version >= packaging.version.Version('2025.12'):
exclude_macos_intel(result, line)
if m:=re.match(r'graalpy[^-]*-(community-)?(\d+\.\d+\.\d+)', line):
version = packaging.version.Version(m.group(2))
# GraalPy dropped MacOS x64 support
if version >= packaging.version.Version('25.0.2'):
exclude_macos_intel(result, line)
EOF = str(random.getrandbits(15*8))
with open(os.environ['GITHUB_ENV'],'w') as f:
f.write(f"versions_macos_build_exclude_json<<{EOF}\n")
json.dump(result, f)
f.write(f'\n{EOF}\n')
- id: modified-versions - id: modified-versions
run: | run: |
echo "versions=`echo "${{ env.versions }}" | jq -R . | jq -sc .`" >> $GITHUB_OUTPUT echo "versions=`echo "${{ env.versions }}" | jq -R . | jq -sc .`" >> $GITHUB_OUTPUT
echo "versions_cpython_only=`echo "${{ env.versions_cpython_only }}" | jq -R . | jq -sc .`" >> $GITHUB_OUTPUT echo "versions_cpython_only=`echo "${{ env.versions_cpython_only }}" | jq -R . | jq -sc .`" >> $GITHUB_OUTPUT
echo 'versions_macos_build_exclude=${{ env.versions_macos_build_exclude_json }}' >> $GITHUB_OUTPUT
macos_build: macos_build:
needs: discover_modified_scripts needs: discover_modified_scripts
if: needs.discover_modified_scripts.outputs.versions != '[""]' if: needs.discover_modified_scripts.outputs.versions != '[""]'
@ -36,10 +90,16 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions)}} python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions)}}
os: ["macos-13", "macos-14", "macos-15"] os:
- macos-14
- macos-15
- macos-15-intel
- macos-26
- macos-26-intel
exclude: ${{fromJson(needs.discover_modified_scripts.outputs.versions_macos_build_exclude)}}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- run: | - run: |
#envvars #envvars
export PYENV_ROOT="$GITHUB_WORKSPACE" export PYENV_ROOT="$GITHUB_WORKSPACE"
@ -47,7 +107,7 @@ jobs:
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
- run: | - run: |
#prerequisites #prerequisites
brew install openssl openssl@1.1 readline sqlite3 xz zlib brew install openssl readline sqlite3 xz tcl-tk@8 libb2 zstd
if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then
export PYENV_BOOTSTRAP_VERSION=pypy2.7-7 export PYENV_BOOTSTRAP_VERSION=pypy2.7-7
echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV
@ -90,6 +150,7 @@ jobs:
pyenv global system pyenv global system
rm -f "$(pyenv root)"/shims/* rm -f "$(pyenv root)"/shims/*
macos_build_bundled_dependencies: macos_build_bundled_dependencies:
needs: discover_modified_scripts needs: discover_modified_scripts
if: needs.discover_modified_scripts.outputs.versions_cpython_only != '[""]' if: needs.discover_modified_scripts.outputs.versions_cpython_only != '[""]'
@ -97,10 +158,15 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions_cpython_only)}} python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions_cpython_only)}}
os: ["macos-13", "macos-14", "macos-15"] os:
- macos-14
- macos-15
- macos-15-intel
- macos-26
- macos-26-intel
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- run: | - run: |
#envvars #envvars
export PYENV_ROOT="$GITHUB_WORKSPACE" export PYENV_ROOT="$GITHUB_WORKSPACE"
@ -108,49 +174,29 @@ jobs:
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
- run: | - run: |
#prerequisites #prerequisites
brew install sqlite3 xz zlib brew install sqlite3 xz tcl-tk@8 libb2 zstd
"$GITHUB_WORKSPACE/.github/workflows/scripts/brew-uninstall-cascade.sh" openssl@3 openssl@1.1 readline "$GITHUB_WORKSPACE/.github/workflows/scripts/brew-uninstall-cascade.sh" $(brew list | grep -E '^openssl(@|$)') readline
if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then
export PYENV_BOOTSTRAP_VERSION=pypy2.7-7
echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV
pyenv install $PYENV_BOOTSTRAP_VERSION
fi
- run: | - run: |
#build #build
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$? pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
if [[ $rc -ne 0 ]]; then echo config.log:; cat $TMPDIR/python-build*/*/config.log; false; fi if [[ $rc -ne 0 ]]; then echo config.log:; cat $TMPDIR/python-build*/*/config.log; false; fi
pyenv global ${{ matrix.python-version }} pyenv global ${{ matrix.python-version }}
# Micropython doesn't support --version
- run: | - run: |
#print version #print version
if [[ "${{ matrix.python-version }}" == "micropython-"* ]]; then python --version
python -c 'import sys; print(sys.version)' python -m pip --version
else
python --version
python -m pip --version
fi
# Micropython doesn't support sys.executable, os.path, older versions even os
- env: - env:
EXPECTED_PYTHON: ${{ matrix.python-version }} EXPECTED_PYTHON: ${{ matrix.python-version }}
run: | run: |
#check #check
if [[ "${{ matrix.python-version }}" == "micropython-"* ]]; then python -c '
[[ $(pyenv which python) == "${{ env.PYENV_ROOT }}/versions/${{ matrix.python-version }}/bin/python" ]] || exit 1 import os, sys, os.path
python -c 'import sys; assert sys.implementation.name == "micropython"' correct_dir = os.path.join(
else os.environ["PYENV_ROOT"],
python -c 'if True: "versions",
import os, sys, os.path os.environ["EXPECTED_PYTHON"],
correct_dir = os.path.join( "bin")
os.environ["PYENV_ROOT"], assert os.path.dirname(sys.executable) == correct_dir'
"versions",
os.environ["EXPECTED_PYTHON"],
"bin")
assert os.path.dirname(sys.executable) == correct_dir'
fi
# bundled executables in some Anaconda releases cause the post-run step to hang in MacOS
- run: |
pyenv global system
rm -f "$(pyenv root)"/shims/*
ubuntu_build: ubuntu_build:
needs: discover_modified_scripts needs: discover_modified_scripts
@ -159,10 +205,14 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions)}} python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions)}}
os: ["ubuntu-22.04", "ubuntu-24.04"] os:
- ubuntu-22.04
- ubuntu-22.04-arm
- ubuntu-24.04
- ubuntu-24.04-arm
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- run: | - run: |
#envvars #envvars
export PYENV_ROOT="$GITHUB_WORKSPACE" export PYENV_ROOT="$GITHUB_WORKSPACE"
@ -170,10 +220,7 @@ jobs:
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
- run: | - run: |
#prerequisites #prerequisites
sudo apt-get update -q; sudo apt-get install -yq make build-essential \ pyenv install-prerequisites
libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev
if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then
export PYENV_BOOTSTRAP_VERSION=pypy2.7-7 export PYENV_BOOTSTRAP_VERSION=pypy2.7-7
echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV
@ -211,6 +258,10 @@ jobs:
"bin") "bin")
assert os.path.dirname(sys.executable) == correct_dir' assert os.path.dirname(sys.executable) == correct_dir'
fi fi
# bundled executables in some Anaconda releases cause the post-run step to break
- run: |
pyenv global system
rm -f "$(pyenv root)"/shims/*
ubuntu_build_tar_gz: ubuntu_build_tar_gz:
needs: discover_modified_scripts needs: discover_modified_scripts
if: needs.discover_modified_scripts.outputs.versions_cpython_only != '[""]' if: needs.discover_modified_scripts.outputs.versions_cpython_only != '[""]'
@ -218,56 +269,36 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions_cpython_only)}} python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions_cpython_only)}}
os: ["ubuntu-latest"] runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- run: | - run: |
#envvars #envvars
export PYENV_ROOT="$GITHUB_WORKSPACE" export PYENV_ROOT="$GITHUB_WORKSPACE"
echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
echo "_PYTHON_BUILD_FORCE_SKIP_XZ=1" >> $GITHUB_PATH echo "_PYTHON_BUILD_FORCE_SKIP_XZ=1" >> $GITHUB_ENV
- run: | - run: |
#prerequisites #prerequisites
sudo apt-get update -q; sudo apt-get install -yq make build-essential \ pyenv install-prerequisites
libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev
if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then
export PYENV_BOOTSTRAP_VERSION=pypy2.7-7
echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV
pyenv install $PYENV_BOOTSTRAP_VERSION
fi
- run: | - run: |
#build #build
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$? pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
if [[ $rc -ne 0 ]]; then echo config.log:; cat $TMPDIR/python-build*/*/config.log; false; fi if [[ $rc -ne 0 ]]; then echo config.log:; cat $TMPDIR/python-build*/*/config.log; false; fi
pyenv global ${{ matrix.python-version }} pyenv global ${{ matrix.python-version }}
# Micropython doesn't support --version
- run: | - run: |
#print version #print version
if [[ "${{ matrix.python-version }}" == "micropython-"* ]]; then python --version
python -c 'import sys; print(sys.version)' python -m pip --version
else
python --version
python -m pip --version
fi
# Micropython doesn't support sys.executable, os.path, older versions even os
- env: - env:
EXPECTED_PYTHON: ${{ matrix.python-version }} EXPECTED_PYTHON: ${{ matrix.python-version }}
run: | run: |
#check #check
if [[ "${{ matrix.python-version }}" == "micropython-"* ]]; then python -c '
[[ $(pyenv which python) == "${{ env.PYENV_ROOT }}/versions/${{ matrix.python-version }}/bin/python" ]] || exit 1 import os, sys, os.path
python -c 'import sys; assert sys.implementation.name == "micropython"' correct_dir = os.path.join(
else os.environ["PYENV_ROOT"],
python -c 'if True: "versions",
import os, sys, os.path os.environ["EXPECTED_PYTHON"],
correct_dir = os.path.join( "bin")
os.environ["PYENV_ROOT"], assert os.path.dirname(sys.executable) == correct_dir'
"versions",
os.environ["EXPECTED_PYTHON"],
"bin")
assert os.path.dirname(sys.executable) == correct_dir'
fi

View file

@ -6,8 +6,8 @@ on:
issue_comment: issue_comment:
types: [created] types: [created]
schedule: schedule:
# Schedule for ten minutes after the hour, every hour # Schedule for ten minutes after the hour, every 2 hours
- cron: '10 * * * *' - cron: '10 */2 * * *'
permissions: {} permissions: {}
jobs: jobs:
@ -15,7 +15,7 @@ jobs:
permissions: permissions:
issues: write # to update issues (lee-dohm/no-response) issues: write # to update issues (lee-dohm/no-response)
runs-on: ubuntu-latest runs-on: ubuntu-slim
steps: steps:
- uses: lee-dohm/no-response@v0.5.0 - uses: lee-dohm/no-response@v0.5.0
with: with:

View file

@ -1,5 +1,8 @@
name: pyenv_tests name: pyenv_tests
on: [pull_request, push] on:
push:
branches: [master]
pull_request: {}
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read # to fetch code (actions/checkout)
@ -10,45 +13,33 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
- ubuntu-24.04
- ubuntu-22.04 - ubuntu-22.04
- ubuntu-22.04-arm
- ubuntu-24.04
- ubuntu-24.04-arm
- macos-14 - macos-14
- macos-13 - macos-15
- macos-15-intel
- macos-26
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
# Normally, we would use the superbly maintained... - name: Install prerequisites
# - uses: actions/setup-python@v2 run: |
# with:
# python-version: ${{ matrix.python-version }}
# ... but in the repo, we want to test pyenv builds on Ubuntu
# - run: |
# sudo apt-get install -y build-essential libssl-dev zlib1g-dev libbz2-dev \
# libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
# xz-utils tk-dev libffi-dev liblzma-dev python-openssl git
# https://github.com/pyenv/pyenv#installation
- run: |
if test "$RUNNER_OS" == "macOS"; then if test "$RUNNER_OS" == "macOS"; then
brew install coreutils fish brew install coreutils fish
elif [[ $(lsb_release -sr | awk -F. '{print $1}') -ge 20 ]]; then
# Ubuntu 18 has fish 2 which lacks many features that facilitate testing
sudo apt install fish -yq
fi fi
- run: pwd - name: Run tests
- env:
PYENV_ROOT: /home/runner/work/pyenv/pyenv
run: |
echo $PYENV_ROOT
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
- name: Run test on the host
run: | run: |
make test make test
- name: Run test with docker
if: ${{ ! contains(matrix.os, 'macos') }}
run: |
make test-docker
- env: - env:
PYENV_NATIVE_EXT: 1 PYENV_NATIVE_EXT: 1
run: | run: |
(cd src; ./configure; make) (cd src; ./configure; make)
bats/bin/bats test/{pyenv,hooks,versions}.bats bats/bin/bats test/{pyenv,hooks,versions}.bats
pyenv_tests_docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: |
make test-docker

View file

@ -1,45 +0,0 @@
name: ubuntu_build
on: [pull_request, push]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
ubuntu_build:
strategy:
fail-fast: false
matrix:
python-version:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
# Normally, we would use the superbly maintained...
# - uses: actions/setup-python@v2
# with:
# python-version: ${{ matrix.python-version }}
# ... but in the repo, we want to test pyenv builds on Ubuntu
- run: |
sudo apt-get update -q; sudo apt install -yq make build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev curl \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
# https://github.com/pyenv/pyenv#installation
- run: pwd
- env:
PYENV_ROOT: /home/runner/work/pyenv/pyenv
run: |
echo $PYENV_ROOT
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
bin/pyenv --debug install ${{ matrix.python-version }}
bin/pyenv global ${{ matrix.python-version }}
bin/pyenv rehash
- run: python --version
- run: python -m pip --version
- shell: python # Prove that actual Python == expected Python
env:
EXPECTED_PYTHON: ${{ matrix.python-version }}
run: import os, sys ; assert sys.version.startswith(os.getenv("EXPECTED_PYTHON"))

1
.gitignore vendored
View file

@ -11,3 +11,4 @@
/default-packages /default-packages
.idea .idea
*.un~ *.un~
*.swp

View file

@ -1,5 +1,200 @@
# Version History # Version History
## Release v2.8.5
* pyenv-binary: record direct system dependencies in Linux/FreeBSD by @macayu17 in https://github.com/pyenv/pyenv/pull/3520
* tests: Fix "cd: null directory" in OpenSUSE by @native-api in https://github.com/pyenv/pyenv/pull/3524
* Test improvements by @native-api in https://github.com/pyenv/pyenv/pull/3525
* CI: test on all runners when the build logic changes by @sujeito-operator in https://github.com/pyenv/pyenv/pull/3522
* Add GraalPy 25.3.4.1 by @msimacek in https://github.com/pyenv/pyenv/pull/3529
* Add CPython 3.15.0rc2 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3530
* pyenv-binary: support relocating macOS archives by @macayu17 in https://github.com/pyenv/pyenv/pull/3528
## Release v2.8.4
* CI: Add macos_26_intel by @native-api in https://github.com/pyenv/pyenv/pull/3514
* pyenv-binary: check FreeBSD system libraries by @macayu17 in https://github.com/pyenv/pyenv/pull/3511
* version-file: Fix infinite loop for a relative path argument by @fudianchn in https://github.com/pyenv/pyenv/pull/3515
* pyenv-binary: check patchelf early and add --verbose by @macayu17 in https://github.com/pyenv/pyenv/pull/3516
* CI: add ubuntu ARM images by @native-api in https://github.com/pyenv/pyenv/pull/3517
* Add CPython 3.10.21, 3.11.16, 3.12.14 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3518
* Add anaconda3-2026.07-1, miniconda3 26.5.3-2 by @native-api in https://github.com/pyenv/pyenv/pull/3519
## Release v2.8.3
* Add CPython 3.13.15, 3.14.7 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3512
## Release v2.8.2
* pyenv-binary: add the `package` subcommand; add `pyenv install --list --bare` by @macayu17 in https://github.com/pyenv/pyenv/pull/3498
* Add graalpy3.12-25.2.4 by @msimacek in https://github.com/pyenv/pyenv/pull/3502
* Bump actions/setup-python from 6 to 7 in the github-actions group by @dependabot[bot] in https://github.com/pyenv/pyenv/pull/3505
* add_cpython: refactor extra Requests logic by @anupamme in https://github.com/pyenv/pyenv/pull/3507
* Add CPython 3.15.0rc1 by @jsirois in https://github.com/pyenv/pyenv/pull/3510
* pyenv-binary: generate platform-specific package names by @macayu17 in https://github.com/pyenv/pyenv/pull/3504
## Release v2.8.1
* pyenv-binary: add the `generate-installer` subcommand by @macayu17 in https://github.com/pyenv/pyenv/pull/3488
* Add CPython 3.15.0b4 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3495
## Release v2.8.0
* CVE-2026-68939: Fix handling of glob characters in .python-version
* CI: add_version enhancements by @native-api in https://github.com/pyenv/pyenv/pull/3480
* Add miniforge3-26.3.2-2, 26.3.2-3 by @native-api in https://github.com/pyenv/pyenv/pull/3481
* rehash: fix race condition in landlock writability check by @Sheile in https://github.com/pyenv/pyenv/pull/3483
* Add script to generate sponsors section for release notes by @macayu17 in https://github.com/pyenv/pyenv/pull/3478
* install: Add an ability to install a version under an alias by @macayu17 in https://github.com/pyenv/pyenv/pull/3484
* Add graalpy-3.12-25.1.3 by @msimacek in https://github.com/pyenv/pyenv/pull/3485
* Bump actions/checkout from 6 to 7 in the github-actions group by @dependabot[bot] in https://github.com/pyenv/pyenv/pull/3486
* Add an experimental pyenv-binary plugin with a save command by @macayu17 in https://github.com/pyenv/pyenv/pull/3487
* CI: dependabot: set cooldown by @orbisai0security in https://github.com/pyenv/pyenv/pull/3489
* Fix typos in docs, scripts, and tests by @maxtaran2010 in https://github.com/pyenv/pyenv/pull/3490
* Add miniconda3 26.5.3-1 by @native-api in https://github.com/pyenv/pyenv/pull/3491
* version-name: skip redundant checks by @native-api in https://github.com/pyenv/pyenv/pull/3492
## Release v2.7.3
* CI: add_version enhancements by @macayu17 in https://github.com/pyenv/pyenv/pull/3475
* Add CPython 3.15.0b3 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3479
## Release v2.7.2
* fix(rehash): prevent terminal hang caused by stale or sandbox-blocked lock file by @anupddas in https://github.com/pyenv/pyenv/pull/3469
* 3.6.x: Fix verify_* calls by @native-api in https://github.com/pyenv/pyenv/commit/6c0c5cfa9619e4a7a90102d8bee6c771c4739836
* Add CPython 3.14.6 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3472
* Add CPython 3.13.14 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3473
## Release v2.7.1
* Support 3.9 EOL Pip URL, consolidate tests by @native-api in https://github.com/pyenv/pyenv/pull/3465
* Update URLs for PyPy nightly; Remove pypy3.5 and pypy3.7 nightly by @native-api in https://github.com/pyenv/pyenv/pull/3466
* Add CPython 3.15.0b2 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3467
* init: add --install for shell setup by @macayu17 in https://github.com/pyenv/pyenv/pull/3454
* realpath.c: fix obsolete syntax warning by @native-api in https://github.com/pyenv/pyenv/pull/3468
## Release v2.6.32
* Add miniconda3 26.3.2-2, miniforge3 26.3.2-0, 26.3.2-1 by @native-api in https://github.com/pyenv/pyenv/pull/3445
* miniforge3 26.1, 26.3, add_miniforge: exclude .pkg installers by @native-api in https://github.com/pyenv/pyenv/pull/3446
* miniforge 26, CI: switch check to 3.13 by @native-api in https://github.com/pyenv/pyenv/pull/3447
* 2.7, 3.4: force C99 standard; 2.7.14-18: force OpenSSL 1 formula by @native-api in https://github.com/pyenv/pyenv/pull/3448
* rehash: detect and remove a stale lockfile by @native-api in https://github.com/pyenv/pyenv/pull/3450
* Add GraalPy 25.0.3 by @msimacek in https://github.com/pyenv/pyenv/pull/3452
* 3.11.0+: Use the `--with-openssl-rpath' Configure option when possible by @native-api in https://github.com/pyenv/pyenv/pull/3458
* python_build: Make `verify_python` verify `pythonX.Y' suffix by @native-api in https://github.com/pyenv/pyenv/pull/3459
* Add micropython 1.22.0 to 1.28.0; add downstream patches to fix compilation errors by @native-api in https://github.com/pyenv/pyenv/pull/3460
* Fix linking against a keg_only Homebrew OpenSSL when a a non-keg_only one is also installed by @native-api in https://github.com/pyenv/pyenv/pull/3462
* Add missing CPython 3.14.2t by @native-api in https://github.com/pyenv/pyenv/pull/3464
* 3.14.0-5: Support building against OpenSSL 4 by @native-api in https://github.com/pyenv/pyenv/pull/3463
## Release v2.6.31
* CI: add_cpython: Support prereleases for non-initial CPython releases by @native-api in https://github.com/pyenv/pyenv/pull/3443
* Add CPython 3.14.5 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3444
## Release v2.6.30
* Add CPython 3.16-dev, switch 3.15-dev to maintenance branch by @nedbat in https://github.com/pyenv/pyenv/pull/3442
## Release v2.6.29
* Add PyPy v7.3.22 by @jsirois in https://github.com/pyenv/pyenv/pull/3438
* CI: Add timeouts to CPython release metadata fetches by @orbisai0security in https://github.com/pyenv/pyenv/pull/3439
* Add CPython 3.15.0b1 by @jsirois in https://github.com/pyenv/pyenv/pull/3440
* Add CPython 3.14.5rc1 by @native-api in https://github.com/pyenv/pyenv/pull/3441
## Release v2.6.28
* pyenv-latest: fast path for when there is an exact match by @native-api in https://github.com/pyenv/pyenv/pull/3437
## Release v2.6.27
* commands: fast path for --sh, replace sort|uniq with sort -u by @jakelodwick in https://github.com/pyenv/pyenv/pull/3423
* which: update the `pyenv help global` tip by @native-api in https://github.com/pyenv/pyenv/pull/3424
* Bump actions/create-github-app-token from 2 to 3 in the github-actions group by @dependabot[bot] in https://github.com/pyenv/pyenv/pull/3428
* python-build: advise user with no c compiler by @exurd in https://github.com/pyenv/pyenv/pull/3294
* Add CPython 3.13.13, 3.14.4, 3.15.0a8 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3432
* Add miniforge3 26.1.1-2, 26.1.1-3 by @native-api in https://github.com/pyenv/pyenv/pull/3433
## Release v2.6.26
* versions: fast path for --bare --skip-aliases by @jakelodwick in https://github.com/pyenv/pyenv/pull/3411
* Fix MacPorts OpenSSL formula detection by @tekintian in https://github.com/pyenv/pyenv/pull/3417
* rehash: streamline executables discovery; fix regression by @native-api in https://github.com/pyenv/pyenv/pull/3418
* rehash: simplify source.bash pipeline by @jakelodwick in https://github.com/pyenv/pyenv/pull/3419
* Add CPython 3.15.0a7 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3421
## Release v2.6.25
* Add miniforge3-26.1.0-0 by @native-api in https://github.com/pyenv/pyenv/pull/3413
* Add CPython 3.12.13 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3414
## Release v2.6.24
* CI: add_version: use Github App token to create PR by @native-api in https://github.com/pyenv/pyenv/pull/3406
* Add Anaconda3-2025.12-2 by @binbjz in https://github.com/pyenv/pyenv/pull/3408
* rehash: Drop redundant sort -u from make_shims call by @jakelodwick in https://github.com/pyenv/pyenv/pull/3410
* Add CPython 3.10.20, 3.11.15 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3412
## Release v2.6.23
* Add CPython 3.15.0a6 by @native-api in https://github.com/pyenv/pyenv/pull/3403
## Release v2.6.22
* Add CPython 3.13.12 by @github-actions[bot] in https://github.com/pyenv/pyenv/pull/3401
## Release v2.6.21
* Add GraalPy 25.0.2 by @msimacek in https://github.com/pyenv/pyenv/pull/3395
* Add CPython 3.14.3 by @github-actions[bot] in https://github.com/pyenv/pyenv/pull/3400
## Release v2.6.20
* CI: adjust CPython PR generation logic by @native-api in https://github.com/pyenv/pyenv/pull/3392
* Add CPython 3.15.0a5 by @nedbat in https://github.com/pyenv/pyenv/pull/3393
## Release v2.6.19
* Add CPython 3.15.0a4 by @nedbat in https://github.com/pyenv/pyenv/pull/3390
## Release v2.6.18
* Fix an infinite loop if a shim is symlinked to and called from a different location with "system" version active by @native-api in https://github.com/pyenv/pyenv/pull/3375
* CI: Update and cleanup workflow scripts by @native-api in https://github.com/pyenv/pyenv/pull/3372
* Add PowerShell support by @fblanchetNaN in https://github.com/pyenv/pyenv/pull/2749
* rehash: support older BSD `mktemp` by @native-api in https://github.com/pyenv/pyenv/pull/3376
* rehash: report final lock acquisition error while silencing provisional ones; init: don't silence automatic rehash by @native-api in https://github.com/pyenv/pyenv/pull/3377
* pyenv launcher: introduce and export _PYENV_INSTALL_PREFIX by @rockandska in https://github.com/pyenv/pyenv/pull/3334
* README: Document how to change Python version in a shell spawned from a Python-based program (e.g. Jupyter) by @native-api in https://github.com/pyenv/pyenv/pull/3380
* Recognize an argument to -j in MAKEFLAGS when building extensions in 3.10-3.11 and latest 3.5-3.9 by @native-api in https://github.com/pyenv/pyenv/pull/3379
* CI: add a script to autogenerate CPython build scripts by @native-api in https://github.com/pyenv/pyenv/pull/3358
* Add miniconda3-25.11.1-1 by @binbjz in https://github.com/pyenv/pyenv/pull/3382
* CI: exclude unsupported MacOS-x64 for recent Miniconda versions by @binbjz in https://github.com/pyenv/pyenv/pull/3385
* Auto-accept TOS for miniconda3-25+ by @native-api in https://github.com/pyenv/pyenv/pull/3386
* Add anaconda-2025.12-1 by @native-api in https://github.com/pyenv/pyenv/pull/3387
* CI: automatically check for CPython releases and generate PRs by @native-api in https://github.com/pyenv/pyenv/pull/3388
## Release v2.6.17
* Add miniforge 25.9.1-0, 25.11.0-1 by @13steinj in https://github.com/pyenv/pyenv/pull/3371
* miniconda3-latest: auto accept TOS by @native-api in https://github.com/pyenv/pyenv/pull/3373
* Add CPython 3.15.0a3 by @edgarrmondragon in https://github.com/pyenv/pyenv/pull/3374
## Release v2.6.16
* rehash: Fix hanging for 60 seconds when shims directory is not writable due to Linux Landlock by @native-api in https://github.com/pyenv/pyenv/pull/3367
* Add Miniconda3-25.9.1-3 by @binbjz in https://github.com/pyenv/pyenv/pull/3368
* sh-rehash: Fix erroneously executing rather than printing a command by @native-api in https://github.com/pyenv/pyenv/pull/3369
* Add CPython 3.13.11, 3.14.2 by @cesarcoatl in https://github.com/pyenv/pyenv/pull/3370
## Release v2.6.15
* Add CPython 3.13.10 by @jsirois in https://github.com/pyenv/pyenv/pull/3364
## Release v2.6.14
* CI: Bump actions/checkout from 5 to 6 in the github-actions group by @dependabot[bot] in https://github.com/pyenv/pyenv/pull/3360
* Fix "ld: symbol(s) not found" for C system calls in some MacOS versions if XCode is for a newer major MacOS version by @native-api in https://github.com/pyenv/pyenv/pull/3362
* Add CPython 3.14.1 by @nedbat in https://github.com/pyenv/pyenv/pull/3363
## Release v2.6.13
* CI: Replace macos-13 with macos-15-intel by @edgarrmondragon in https://github.com/pyenv/pyenv/pull/3356
* Add Miniconda3-25.9.1-1 by @binbjz in https://github.com/pyenv/pyenv/pull/3357
* Add CPython 3.15.0a2 by @nedbat in https://github.com/pyenv/pyenv/pull/3359
## Release v2.6.12
* Add graalpy-25.0.1 by @msimacek in https://github.com/pyenv/pyenv/pull/3350
* Add CPython 3.9.25 by @nedbat in https://github.com/pyenv/pyenv/pull/3355
## Release v2.6.11
* Support building against Homebrew Tcl/Tk 9 by @native-api in https://github.com/pyenv/pyenv/pull/3343
* Add CPython 3.13.9 by @nedbat in https://github.com/pyenv/pyenv/pull/3346
* Add CPython 3.15.0a1 by @edgarrmondragon in https://github.com/pyenv/pyenv/pull/3347
## Release v2.6.10
* Add CPython 3.12.12, 3.11.14, 3.10.19, 3.9.24 by @nedbat in https://github.com/pyenv/pyenv/pull/3344
## Release v2.6.9
* CI: modified-scripts-build: fix CPython-specific jobs never firing by @tuzi3040 in https://github.com/pyenv/pyenv/pull/3338
* CI: modified-scripts-build: fix output variable for CPython-specific workflows by @native-api in https://github.com/pyenv/pyenv/pull/3340
* Add CPython 3.14.0 by @SnoopJ in https://github.com/pyenv/pyenv/pull/3339
* Add CPython 3.13.8 by @jsirois in https://github.com/pyenv/pyenv/pull/3341
## Release v2.6.8 ## Release v2.6.8
* Use Zlib from XCode SDK in a way compatible with XCode 16 and CLT by @JarheadStever in https://github.com/pyenv/pyenv/pull/3301 * Use Zlib from XCode SDK in a way compatible with XCode 16 and CLT by @JarheadStever in https://github.com/pyenv/pyenv/pull/3301
* Add miniforge 25.3.0-1, 25.3.0-2, 25.3.0-3, 25.3.1-0, skip miniforge 25.3.0-0 by @native-api in https://github.com/pyenv/pyenv/pull/3306 * Add miniforge 25.3.0-1, 25.3.0-2, 25.3.0-3, 25.3.1-0, skip miniforge 25.3.0-0 by @native-api in https://github.com/pyenv/pyenv/pull/3306
@ -222,7 +417,7 @@
* Add CPython 3.13.0a4 by @saaketp in https://github.com/pyenv/pyenv/pull/2903 * Add CPython 3.13.0a4 by @saaketp in https://github.com/pyenv/pyenv/pull/2903
* Handle the case where `pyenv-commands --sh` returns nothing by @aphedges in https://github.com/pyenv/pyenv/pull/2908 * Handle the case where `pyenv-commands --sh` returns nothing by @aphedges in https://github.com/pyenv/pyenv/pull/2908
* Document default build configuration customizations by @native-api in https://github.com/pyenv/pyenv/pull/2911 * Document default build configuration customizations by @native-api in https://github.com/pyenv/pyenv/pull/2911
* Use Homebrew in Linux if Pyenv is installled with Homebrew by @native-api in https://github.com/pyenv/pyenv/pull/2906 * Use Homebrew in Linux if Pyenv is installed with Homebrew by @native-api in https://github.com/pyenv/pyenv/pull/2906
* Add miniforge and mambaforge 22.11.1-3, 22.11.1-4, 23.1.0-0 to 23.11.0-0 by @aphedges in https://github.com/pyenv/pyenv/pull/2909 * Add miniforge and mambaforge 22.11.1-3, 22.11.1-4, 23.1.0-0 to 23.11.0-0 by @aphedges in https://github.com/pyenv/pyenv/pull/2909
* Add miniconda3-24.1.2 by @binbjz in https://github.com/pyenv/pyenv/pull/2915 * Add miniconda3-24.1.2 by @binbjz in https://github.com/pyenv/pyenv/pull/2915
* Minor grammar fix in libffi backport patch in 2.5.x by @cuinix in https://github.com/pyenv/pyenv/pull/2922 * Minor grammar fix in libffi backport patch in 2.5.x by @cuinix in https://github.com/pyenv/pyenv/pull/2922
@ -1358,7 +1553,7 @@
* pyenv: Prefer gawk over awk if both are available. * pyenv: Prefer gawk over awk if both are available.
* python-build: Add new PyPy release; pypy-2.3, pypy-2.3-src (#162) * python-build: Add new PyPy release; pypy-2.3, pypy-2.3-src (#162)
* python-build: Add new Anaconda release; anaconda-1.9.2 (#155) * python-build: Add new Anaconda release; anaconda-1.9.2 (#155)
* python-build: Add new Miniconda releases; miniconda-3.3.0, minoconda-3.4.2, miniconda3-3.3.0, miniconda3-3.4.2 * python-build: Add new Miniconda releases; miniconda-3.3.0, miniconda-3.4.2, miniconda3-3.3.0, miniconda3-3.4.2
* python-build: Add new Stackless releases; stackless-2.7.3, stackless-2.7.4, stackless-2.7.5, stackless-2.7.6, stackless-3.2.5, stackless-3.3.5 (#164) * python-build: Add new Stackless releases; stackless-2.7.3, stackless-2.7.4, stackless-2.7.5, stackless-2.7.6, stackless-3.2.5, stackless-3.3.5 (#164)
* python-build: Add IronPython versions (setuptools and pip will work); ironpython-2.7.4, ironpython-dev * python-build: Add IronPython versions (setuptools and pip will work); ironpython-2.7.4, ironpython-dev
* python-build: Add new Jython beta release; jython-2.7-beta2 * python-build: Add new Jython beta release; jython-2.7-beta2

View file

@ -40,9 +40,8 @@ Lists all available pyenv commands.
Sets a local application-specific Python version by writing the version Sets a local application-specific Python version by writing the version
name to a `.python-version` file in the current directory. This version name to a `.python-version` file in the current directory. This version
overrides the global version, and can be overridden itself by setting overrides the [global version](#pyenv-global), and can be overridden
the `PYENV_VERSION` environment variable or with the `pyenv shell` itself with the [`pyenv shell`](#pyenv-shell) command.
command.
$ pyenv local 2.7.6 $ pyenv local 2.7.6
@ -98,9 +97,8 @@ This is mainly useful in special cases like provisioning scripts.
## `pyenv global` ## `pyenv global`
Sets the global version of Python to be used in all shells by writing Sets the global version of Python to be used in all shells by writing
the version name to the `~/.pyenv/version` file. This version can be the version name to the `$PYENV_ROOT/version` file. This version can be
overridden by an application-specific `.python-version` file, or by overridden with [`pyenv local`](#pyenv-local) or [`pyenv shell`](#pyenv-shell).
setting the `PYENV_VERSION` environment variable.
$ pyenv global 2.7.6 $ pyenv global 2.7.6
@ -204,8 +202,8 @@ or, if you prefer 3.3.3 over 2.7.6,
Install a Python version (using [`python-build`](https://github.com/pyenv/pyenv/tree/master/plugins/python-build)). Install a Python version (using [`python-build`](https://github.com/pyenv/pyenv/tree/master/plugins/python-build)).
Usage: pyenv install [-f] [-kvp] <version> Usage: pyenv install [-f] [-kvp] <version>[:<alias>]
pyenv install [-f] [-kvp] <definition-file> pyenv install [-f] [-kvp] <definition-file>[:<alias>]
pyenv install -l|--list pyenv install -l|--list
-l/--list List all available versions -l/--list List all available versions
@ -236,6 +234,7 @@ Then install the desired versions:
You can also install the latest version of Python in a specific version line by supplying a prefix instead of a complete name: You can also install the latest version of Python in a specific version line by supplying a prefix instead of a complete name:
$ pyenv install 3.10 $ pyenv install 3.10
$ pyenv install 3
See the [`pyenv latest` documentation](#pyenv-latest) for details on prefix resolution. See the [`pyenv latest` documentation](#pyenv-latest) for details on prefix resolution.
@ -243,9 +242,6 @@ An older option is to use the `:latest` syntax. For example, to install the late
pyenv install 3.8:latest pyenv install 3.8:latest
To install the latest major release for Python 3 try:
pyenv install 3:latest
## `pyenv uninstall` ## `pyenv uninstall`
@ -352,10 +348,9 @@ locations of the currently selected versions.
Displays the latest installed or known version with the given prefix Displays the latest installed or known version with the given prefix
Usage: pyenv latest [-k|--known] [-q|--quiet] <prefix> Usage: pyenv latest [-k|--known] <prefix>
-k/--known Select from all known versions instead of installed -k/--known Select from all known versions instead of installed
-q/--quiet Do not print an error message on resolution failure
Only full prefixes are searched: in the actual name, the given prefix must be followed by a dot or a dash. Only full prefixes are searched: in the actual name, the given prefix must be followed by a dot or a dash.
@ -398,11 +393,15 @@ List existing pyenv shims.
Configure the shell environment for pyenv Configure the shell environment for pyenv
Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [<shell>])" Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [<shell>])"
pyenv init --install [<shell>]
pyenv init --detect-shell [<shell>]
- Initialize shims directory, print PYENV_SHELL variable, completions path - Initialize shims directory, print PYENV_SHELL variable, completions path
and shell function and shell function
--path Print shims path --path Print shims path
--install Configure detected shell startup files
--no-push-path Do not push shim to the start of PATH if they're already there --no-push-path Do not push shim to the start of PATH if they're already there
--detect-shell Print shell startup files detected for the current shell
--no-rehash Add no rehash command to output --no-rehash Add no rehash command to output
## `pyenv completions` ## `pyenv completions`

View file

@ -7,6 +7,14 @@ Release checklist:
* Start [drafting a new release on GitHub](https://github.com/pyenv/pyenv/releases) to generate a summary of changes. * Start [drafting a new release on GitHub](https://github.com/pyenv/pyenv/releases) to generate a summary of changes.
Type the would-be tag name in the "Choose a tag" field and press "Generate release notes" Type the would-be tag name in the "Choose a tag" field and press "Generate release notes"
* The summary may need editing. E.g. rephrase entries, delete/merge entries that are too minor or irrelevant to the users (e.g. typo fixes, CI) * The summary may need editing. E.g. rephrase entries, delete/merge entries that are too minor or irrelevant to the users (e.g. typo fixes, CI)
* Add a sponsors section to the release notes by running:
```bash
.github/scripts/generate_release_notes_sponsors.py
```
Paste the output at the end of the release notes.
* This lists new GitHub Sponsors and OpenCollective backers since the last release or within the last month, whichever is longer.
* The GitHub Sponsors query requires the `gh` CLI with the `read:user` scope.
* If OpenCollective is unavailable, pass `--skip-opencollective` and add those backers manually.
* Update `CHANGELOG.md` with the new version number and the edited summary (only the changes section) * Update `CHANGELOG.md` with the new version number and the edited summary (only the changes section)
* Push the version number in `libexec/pyenv---version` and `plugins/python-build/bin/python-build` * Push the version number in `libexec/pyenv---version` and `plugins/python-build/bin/python-build`
* Minor version is pushed if there are significant functional changes (not e.g. bugfixes/formula adaptations/supporting niche use cases). * Minor version is pushed if there are significant functional changes (not e.g. bugfixes/formula adaptations/supporting niche use cases).

131
Makefile
View file

@ -1,16 +1,24 @@
TEST_BATS_VERSION = v1.10.0 TEST_BATS_VERSION = v1.10.0
TEST_BASH_VERSIONS = 3.2.57 4.1.17 TEST_BASH_VERSIONS = 3.2.57 4.1.17
TEST_UNIT_DOCKER_PREFIX = test-unit-docker TEST_UNIT_DOCKER_PREFIX = test-unit-docker
TEST_UNIT_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_UNIT_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_UNIT_DOCKER_PREFIX))) TEST_UNIT_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_UNIT_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_UNIT_DOCKER_PREFIX)))
TEST_PLUGIN_DOCKER_PREFIX = test-plugin-docker
TEST_PLUGIN_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_PLUGIN_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_PLUGIN_DOCKER_PREFIX))) TEST_PYTHON_BUILD_DOCKER_PREFIX = test-python-build-docker
TEST_PYTHON_BUILD_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_PYTHON_BUILD_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_PYTHON_BUILD_DOCKER_PREFIX)))
TEST_BINARY_DOCKER_PREFIX = test-binary-docker
TEST_BINARY_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BINARY_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BINARY_DOCKER_PREFIX)))
TEST_LINK_DOCKER_PREFIX = test-link-docker
TEST_LINK_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_LINK_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_LINK_DOCKER_PREFIX)))
TEST_BATS_IMAGE_PREFIX = test-pyenv-docker-image TEST_BATS_IMAGE_PREFIX = test-pyenv-docker-image
TEST_BATS_IMAGE_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BATS_IMAGE_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BATS_IMAGE_PREFIX))) TEST_BATS_IMAGE_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BATS_IMAGE_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BATS_IMAGE_PREFIX)))
.PHONY: .PHONY: test-docker
test-docker: $(TEST_UNIT_DOCKER_PREFIX) $(TEST_PLUGIN_DOCKER_PREFIX) test-docker: $(TEST_UNIT_DOCKER_PREFIX) $(TEST_PYTHON_BUILD_DOCKER_PREFIX) $(TEST_BINARY_DOCKER_PREFIX) $(TEST_LINK_DOCKER_PREFIX)
# Run all unit test under bats docker
.PHONY: $(TEST_UNIT_DOCKER_PREFIX) .PHONY: $(TEST_UNIT_DOCKER_PREFIX)
$(TEST_UNIT_DOCKER_PREFIX): $(TEST_UNIT_DOCKER_TARGETS) $(TEST_UNIT_DOCKER_PREFIX): $(TEST_UNIT_DOCKER_TARGETS)
@ -31,23 +39,22 @@ $(TEST_UNIT_DOCKER_TARGETS): $(TEST_UNIT_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PR
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \ -u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
$${BATS_TEST_FILTER:+-e BATS_TEST_FILTER="$${BATS_TEST_FILTER}"} \ $${BATS_TEST_FILTER:+-e BATS_TEST_FILTER="$${BATS_TEST_FILTER}"} \
$${BATS_FILE_FILTER:+-e BATS_FILE_FILTER="$${BATS_FILE_FILTER}"} \ $${BATS_FILE_FILTER:+-e BATS_FILE_FILTER="$${BATS_FILE_FILTER}"} \
$${CI+-e CI="$${CI}"} \ $${CI+-e CI="$${CI}"} \
$(INTERACTIVE) \ $(INTERACTIVE) \
$(DOCKER_IMAGE):$(DOCKER_TAG) \ $(DOCKER_IMAGE):$(DOCKER_TAG) \
test/run test/run
# Run all plugin test under bats docker .PHONY: $(TEST_PYTHON_BUILD_DOCKER_PREFIX)
.PHONY: $(TEST_PLUGIN_DOCKER_PREFIX) $(TEST_PYTHON_BUILD_DOCKER_PREFIX): $(TEST_PYTHON_BUILD_DOCKER_TARGETS)
$(TEST_PLUGIN_DOCKER_PREFIX): $(TEST_PLUGIN_DOCKER_TARGETS)
# Run each plugin test under bats docker # Run each plugin test under bats docker
.PHONY: $(TEST_PLUGIN_DOCKER_TARGETS) .PHONY: $(TEST_PYTHON_BUILD_DOCKER_TARGETS)
$(TEST_PLUGIN_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX) $(TEST_PYTHON_BUILD_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
$(TEST_PLUGIN_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False) $(TEST_PYTHON_BUILD_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
$(TEST_PLUGIN_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@)) $(TEST_PYTHON_BUILD_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
$(TEST_PLUGIN_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU) $(TEST_PYTHON_BUILD_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
$(TEST_PLUGIN_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti) $(TEST_PYTHON_BUILD_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
$(TEST_PLUGIN_DOCKER_TARGETS): $(TEST_PLUGIN_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-% $(TEST_PYTHON_BUILD_DOCKER_TARGETS): $(TEST_PYTHON_BUILD_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)') $(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
docker run \ docker run \
--init \ --init \
@ -55,10 +62,57 @@ $(TEST_PLUGIN_DOCKER_TARGETS): $(TEST_PLUGIN_DOCKER_PREFIX)-% : $(TEST_BATS_IMAG
-v /etc/passwd:/etc/passwd:ro \ -v /etc/passwd:/etc/passwd:ro \
-v /etc/group:/etc/group:ro \ -v /etc/group:/etc/group:ro \
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \ -u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
$${CI+-e CI="$${CI}"} \ $${CI+-e CI="$${CI}"} \
$(INTERACTIVE) \ $(INTERACTIVE) \
$(DOCKER_IMAGE):$(DOCKER_TAG) \ $(DOCKER_IMAGE):$(DOCKER_TAG) \
bats $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/python-build/test/$${BATS_FILE_FILTER} bats $${CI:+-F "/code/test/libexec/bats-format-tap-suite"} \
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/python-build/test/$${BATS_FILE_FILTER}
.PHONY: $(TEST_BINARY_DOCKER_PREFIX)
$(TEST_BINARY_DOCKER_PREFIX): $(TEST_BINARY_DOCKER_TARGETS)
.PHONY: $(TEST_BINARY_DOCKER_TARGETS)
$(TEST_BINARY_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
$(TEST_BINARY_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
$(TEST_BINARY_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
$(TEST_BINARY_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
$(TEST_BINARY_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
$(TEST_BINARY_DOCKER_TARGETS): $(TEST_BINARY_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
docker run \
--init \
-v $(PWD):/code:ro \
-v /etc/passwd:/etc/passwd:ro \
-v /etc/group:/etc/group:ro \
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
$${CI+-e CI="$${CI}"} \
$(INTERACTIVE) \
$(DOCKER_IMAGE):$(DOCKER_TAG) \
bats $${CI:+-F "/code/test/libexec/bats-format-tap-suite"} \
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/pyenv-binary/test/$${BATS_FILE_FILTER}
.PHONY: $(TEST_LINK_DOCKER_PREFIX)
$(TEST_LINK_DOCKER_PREFIX): $(TEST_LINK_DOCKER_TARGETS)
.PHONY: $(TEST_LINK_DOCKER_TARGETS)
$(TEST_LINK_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
$(TEST_LINK_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
$(TEST_LINK_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
$(TEST_LINK_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
$(TEST_LINK_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
$(TEST_LINK_DOCKER_TARGETS): $(TEST_LINK_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
docker run \
--init \
-v $(PWD):/code:ro \
-v /etc/passwd:/etc/passwd:ro \
-v /etc/group:/etc/group:ro \
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
$${CI+-e CI="$${CI}"} \
$(INTERACTIVE) \
$(DOCKER_IMAGE):$(DOCKER_TAG) \
bats $${CI:+-F "/code/test/libexec/bats-format-tap-suite"} \
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/pyenv-link/test/$${BATS_FILE_FILTER}
# Build all images needed for bats under docker # Build all images needed for bats under docker
.PHONY: $(TEST_BATS_IMAGE_PREFIX) .PHONY: $(TEST_BATS_IMAGE_PREFIX)
@ -71,7 +125,7 @@ $(TEST_BATS_IMAGE_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
$(TEST_BATS_IMAGE_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@)) $(TEST_BATS_IMAGE_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
$(TEST_BATS_IMAGE_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU) $(TEST_BATS_IMAGE_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
$(TEST_BATS_IMAGE_TARGETS): $(TEST_BATS_IMAGE_TARGETS):
$(info Building docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)') if [ -z "$$(docker images -q '$(DOCKER_IMAGE):$(DOCKER_TAG)')" ]]; then \
docker build \ docker build \
--quiet \ --quiet \
-f "$(PWD)/test/Dockerfile" \ -f "$(PWD)/test/Dockerfile" \
@ -79,41 +133,38 @@ $(TEST_BATS_IMAGE_TARGETS):
--build-arg BASH="$(BASH)" \ --build-arg BASH="$(BASH)" \
--build-arg BATS_VERSION="$(TEST_BATS_VERSION)" \ --build-arg BATS_VERSION="$(TEST_BATS_VERSION)" \
-t $(DOCKER_IMAGE):$(DOCKER_TAG) \ -t $(DOCKER_IMAGE):$(DOCKER_TAG) \
./ ./ ; \
fi
.PHONY: test test-build test-unit test-plugin .PHONY: test test-unit test-python-build test-binary test-link
# Do not pass in user flags to build tests. # Do not pass in user flags to build tests.
unexport PYTHON_CFLAGS unexport PYTHON_CFLAGS
unexport PYTHON_CONFIGURE_OPTS unexport PYTHON_CONFIGURE_OPTS
test: test-unit test-plugin test: test-unit test-python-build test-binary test-link
test-unit: bats test-unit: bats
PATH="./bats/bin:$$PATH" test/run PATH="./bats/bin:$$PATH" test/run
test-plugin: bats test-python-build: bats
cd plugins/python-build && $(PWD)/bats/bin/bats $${CI:+--tap} $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER} cd plugins/python-build && $(PWD)/bats/bin/bats $${CI:+-F "$(PWD)/test/libexec/bats-format-tap-suite"} \
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
PYTHON_BUILD_ROOT := $(CURDIR)/plugins/python-build test-binary: bats
PYTHON_BUILD_OPTS ?= --verbose cd plugins/pyenv-binary && $(PWD)/bats/bin/bats $${CI:+-F "$(PWD)/test/libexec/bats-format-tap-suite"} \
PYTHON_BUILD_VERSION ?= 3.8-dev $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
PYTHON_BUILD_TEST_PREFIX ?= $(PYTHON_BUILD_ROOT)/test/build/tmp/dist
test-build: test-link: bats
$(RM) -r $(PYTHON_BUILD_TEST_PREFIX) cd plugins/pyenv-link && $(PWD)/bats/bin/bats $${CI:+-F "$(PWD)/test/libexec/bats-format-tap-suite"} \
$(PYTHON_BUILD_ROOT)/bin/python-build $(PYTHON_BUILD_OPTS) $(PYTHON_BUILD_VERSION) $(PYTHON_BUILD_TEST_PREFIX) $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
[ -e $(PYTHON_BUILD_TEST_PREFIX)/bin/python ]
$(PYTHON_BUILD_TEST_PREFIX)/bin/python -V
[ -e $(PYTHON_BUILD_TEST_PREFIX)/bin/pip ]
$(PYTHON_BUILD_TEST_PREFIX)/bin/pip -V
.SECONDARY: bats-$(TEST_BATS_VERSION) .SECONDARY: bats-$(TEST_BATS_VERSION)
bats-$(TEST_BATS_VERSION): bats-$(TEST_BATS_VERSION):
rm -rf bats
ln -sf bats-$(TEST_BATS_VERSION) bats
git clone --depth 1 --branch $(TEST_BATS_VERSION) https://github.com/bats-core/bats-core.git bats-$(TEST_BATS_VERSION) git clone --depth 1 --branch $(TEST_BATS_VERSION) https://github.com/bats-core/bats-core.git bats-$(TEST_BATS_VERSION)
.PHONY: bats .PHONY: bats
bats: bats-$(TEST_BATS_VERSION) bats: bats-$(TEST_BATS_VERSION)
ln -sf bats-$(TEST_BATS_VERSION) bats if [ \( ! -L bats \) -o \( "x$$(readlink bats)" != "xbats-$(TEST_BATS_VERSION)" \) ]; then \
rm -rf bats; ln -s bats-$(TEST_BATS_VERSION) bats; \
fi

269
README.md
View file

@ -13,6 +13,8 @@ This project was forked from [rbenv](https://github.com/rbenv/rbenv) and
* Lets you **change the global Python version** on a per-user basis. * Lets you **change the global Python version** on a per-user basis.
* Provides support for **per-project Python versions**. * Provides support for **per-project Python versions**.
* Supports **multiple Python distributions**: CPython, PyPy, Stackless Python, Jython, and more.
See the [full list of available versions](https://github.com/pyenv/pyenv/tree/master/plugins/python-build/share/python-build).
* Allows you to **override the Python version** with an environment * Allows you to **override the Python version** with an environment
variable. variable.
* Searches for commands from **multiple versions of Python at a time**. * Searches for commands from **multiple versions of Python at a time**.
@ -67,7 +69,9 @@ This project was forked from [rbenv](https://github.com/rbenv/rbenv) and
* [Locating Pyenv-provided Python Installations](#locating-pyenv-provided-python-installations) * [Locating Pyenv-provided Python Installations](#locating-pyenv-provided-python-installations)
* [Advanced Configuration](#advanced-configuration) * [Advanced Configuration](#advanced-configuration)
* [Using Pyenv without shims](#using-pyenv-without-shims) * [Using Pyenv without shims](#using-pyenv-without-shims)
* [Running nested shells from Python-based programs](#running-nested-shells-from-python-based-programs)
* [Environment variables](#environment-variables) * [Environment variables](#environment-variables)
* [Manual shell setup](#manual-shell-setup)
* **[Development](#development)** * **[Development](#development)**
* [Contributing](#contributing) * [Contributing](#contributing)
* [Version History](#version-history) * [Version History](#version-history)
@ -176,101 +180,28 @@ which does install native Windows Python versions.
---- ----
The below setup should work for the vast majority of users for common use cases. The below setup should work for the vast majority of users for common use cases.
See [Advanced configuration](#advanced-configuration) for details and more configuration options. See [Advanced configuration](#advanced-configuration)
and specifically [Manual shell setup](#manual-shell-setup) for details and more configuration options.
#### Bash To add the suggested setup code to the startup files of the running shell,
<details> run `<path/to/pyenv> --install`.
Specifically:
Stock Bash startup files vary widely between distributions in which of them source * If you installed Pyenv with the installer script:
which, under what circumstances, in what order and what additional configuration they perform.
As such, the most reliable way to get Pyenv in all environments is to append Pyenv
configuration commands to both `.bashrc` (for interactive shells)
and the profile file that Bash would use (for login shells).
1. First, add the commands to `~/.bashrc` by running the following in your terminal: ```sh
~/.pyenv/bin/pyenv init --install
```
```bash * If you installed Pyenv with Homebrew:
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init - bash)"' >> ~/.bashrc
```
2. Then, if you have `~/.profile`, `~/.bash_profile` or `~/.bash_login`, add the commands there as well.
If you have none of these, create a `~/.profile` and add the commands there.
* to add to `~/.profile`: ```sh
``` bash pyenv init --install
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.profile ```
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.profile
echo 'eval "$(pyenv init - bash)"' >> ~/.profile
```
* to add to `~/.bash_profile`:
```bash
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(pyenv init - bash)"' >> ~/.bash_profile
```
**Bash warning**: There are some systems where the `BASH_ENV` variable is configured
to point to `.bashrc`. On such systems, you should almost certainly put the
`eval "$(pyenv init - bash)"` line into `.bash_profile`, and **not** into `.bashrc`. Otherwise, you
may observe strange behaviour, such as `pyenv` getting into an infinite loop.
See [#264](https://github.com/pyenv/pyenv/issues/264) for details.
</details>
#### Zsh
<details>
Add Pyenv startup commands to `~/.zshrc` by running the following in your terminal:
```zsh
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init - zsh)"' >> ~/.zshrc
```
If you wish to get Pyenv in noninteractive login shells as well, also add the commands to `~/.zprofile` or `~/.zlogin`.
</details>
#### Fish
<details>
1. If you have Fish 3.2.0 or newer, execute this interactively:
```fish
set -Ux PYENV_ROOT $HOME/.pyenv
test -d $PYENV_ROOT/bin; and fish_add_path $PYENV_ROOT/bin
```
2. Otherwise, execute the snippet below:
```fish
set -Ux PYENV_ROOT $HOME/.pyenv
test -d $PYENV_ROOT/bin; and set -U fish_user_paths $PYENV_ROOT/bin $fish_user_paths
```
3. Now, add this to `~/.config/fish/config.fish`:
```fish
pyenv init - fish | source
```
</details>
#### Nushell
<details>
Add the following lines to your `config.nu` to add Pyenv and its shims to your `PATH`.
Shell integration (completions and subcommands changing the shell's state)
isn't currently supported.
~~~ nu
$env.PYENV_ROOT = "~/.pyenv" | path expand
if (( $"($env.PYENV_ROOT)/bin" | path type ) == "dir") {
$env.PATH = $env.PATH | prepend $"($env.PYENV_ROOT)/bin" }
$env.PATH = $env.PATH | prepend $"(pyenv root)/shims"
~~~
</details>
For Bash, avoid the automatic `--install` path if your `BASH_ENV` points to
`.bashrc`; use the manual Bash instructions below so the `eval "$(pyenv init - bash)"`
line only goes in your login startup file.
### C. Restart your shell ### C. Restart your shell
---- ----
@ -392,7 +323,7 @@ of the following commands:
* [`pyenv shell <version>`](COMMANDS.md#pyenv-shell) -- select just for current shell session * [`pyenv shell <version>`](COMMANDS.md#pyenv-shell) -- select just for current shell session
* [`pyenv local <version>`](COMMANDS.md#pyenv-local) -- automatically select whenever you are in the current directory (or its subdirectories) * [`pyenv local <version>`](COMMANDS.md#pyenv-local) -- automatically select whenever you are in the current directory (or its subdirectories)
* [`pyenv global <version>`](COMMANDS.md#pyenv-shell) -- select globally for your user account * [`pyenv global <version>`](COMMANDS.md#pyenv-global) -- select globally for your user account
E.g. to select the above-mentioned newly-installed Python 3.10.4 as your preferred version to use: E.g. to select the above-mentioned newly-installed Python 3.10.4 as your preferred version to use:
@ -689,7 +620,7 @@ opposed to this idea. Here's what `eval "$(pyenv init -)"` actually does:
3. **Installs autocompletion.** This is entirely optional but pretty 3. **Installs autocompletion.** This is entirely optional but pretty
useful. Sourcing `<pyenv installation prefix>/completions/pyenv.bash` will set that useful. Sourcing `<pyenv installation prefix>/completions/pyenv.bash` will set that
up. There are also completions for Zsh and Fish. up. There are also completions for Zsh, Fish and PowerShell.
4. **Rehashes shims.** From time to time you'll need to rebuild your 4. **Rehashes shims.** From time to time you'll need to rebuild your
shim files. Doing this on init makes sure everything is up to shim files. Doing this on init makes sure everything is up to
@ -740,6 +671,32 @@ as currently configured.
`pyenv exec` works by prepending `$(pyenv root)/versions/<selected version>/bin` `pyenv exec` works by prepending `$(pyenv root)/versions/<selected version>/bin`
to `PATH` in the `<command>`'s environment, the same as what e.g. RVM does. to `PATH` in the `<command>`'s environment, the same as what e.g. RVM does.
### Running nested shells from Python-based programs
In addition to altering `PATH`, `pyenv exec` sets `PYENV_VERSION` in the
executed program's environment to ensure that it won't spontaneously switch to
using a different Python version.
Some Python-based programs (e.g. Jupyter) can spawn nested shell sessions.
`pyenv version` in such a shell would dutily report that the current version was
set by an environment variable.
Depending on your use case, this version lock may be undesirable.
In this case, you need to change or unset the environment variable,
either directly or via `pyenv shell`.
To automate this, you can do so in your shell's interactive startup file,
detecting the nested shell session via some characteristic environment variable
that the spawning application sets.
E.g. in Jupyter's case (as of this writing), it's `JUPYTER_SERVER_ROOT`,
and the corresponding `~/.bashrc` line may look like this:
```bash
[[ -n $JUPYTER_SERVER_ROOT ]] && unset PYENV_VERSION
```
### Environment variables ### Environment variables
@ -756,18 +713,136 @@ name | default | description
See also [_Special environment variables_ in Python-Build's README](plugins/python-build/README.md#special-environment-variables) See also [_Special environment variables_ in Python-Build's README](plugins/python-build/README.md#special-environment-variables)
for environment variables that can be used to customize the build. for environment variables that can be used to customize the build.
### Manual shell setup
Below is the suggested shell setup added to shell startup files by `pyenv init --install`.
* To automatically install Pyenv for a shell different than the running shell, run
```sh
path/to/pyenv --install <shell executable name>
```
e.g. `~/.pyenv --install bash`.
#### Bash
<details>
Stock Bash startup files vary widely between distributions in which of them source
which, under what circumstances, in what order and what additional configuration they perform.
As such, the most reliable way to get Pyenv in all environments is to append Pyenv
configuration commands to both `.bashrc` (for interactive shells)
and the profile file that Bash would use (for login shells).
1. First, add the commands to `~/.bashrc` by running the following in your terminal:
```bash
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init - bash)"' >> ~/.bashrc
```
2. Then, if you have `~/.profile`, `~/.bash_profile` or `~/.bash_login`, add the commands there as well.
If you have none of these, create a `~/.profile` and add the commands there.
* to add to `~/.profile`:
``` bash
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.profile
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.profile
echo 'eval "$(pyenv init - bash)"' >> ~/.profile
```
* to add to `~/.bash_profile`:
```bash
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(pyenv init - bash)"' >> ~/.bash_profile
```
**Bash warning**: There are some systems where the `BASH_ENV` variable is configured
to point to `.bashrc`. On such systems, you should almost certainly put the
`eval "$(pyenv init - bash)"` line into `.bash_profile`, and **not** into `.bashrc`. Otherwise, you
may observe strange behaviour, such as `pyenv` getting into an infinite loop.
See [#264](https://github.com/pyenv/pyenv/issues/264) for details.
</details>
#### Zsh
<details>
Add Pyenv startup commands to `~/.zshrc` by running the following in your terminal:
```zsh
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init - zsh)"' >> ~/.zshrc
```
If you wish to get Pyenv in noninteractive login shells as well, also add the commands to `~/.zprofile` or `~/.zlogin`.
</details>
#### Fish
<details>
1. If you have Fish 3.2.0 or newer, execute this interactively:
```fish
set -Ux PYENV_ROOT $HOME/.pyenv
test -d $PYENV_ROOT/bin; and fish_add_path $PYENV_ROOT/bin
```
2. Otherwise, execute the snippet below:
```fish
set -Ux PYENV_ROOT $HOME/.pyenv
test -d $PYENV_ROOT/bin; and set -U fish_user_paths $PYENV_ROOT/bin $fish_user_paths
```
3. Now, add this to `~/.config/fish/config.fish`:
```fish
pyenv init - fish | source
```
</details>
#### Nushell
<details>
Add the following lines to your `config.nu` to add Pyenv and its shims to your `PATH`.
Shell integration (completions and subcommands changing the shell's state)
isn't currently supported.
~~~ nu
$env.PYENV_ROOT = "~/.pyenv" | path expand
if (( $"($env.PYENV_ROOT)/bin" | path type ) == "dir") {
$env.PATH = $env.PATH | prepend $"($env.PYENV_ROOT)/bin" }
$env.PATH = $env.PATH | prepend $"(pyenv root)/shims"
~~~
</details>
#### Microsoft PowerShell
<details>
Add the commands to `$profile.CurrentUserAllHosts` by running the following in your terminal:
~~~ pwsh
echo '$Env:PYENV_ROOT="$Env:HOME/.pyenv"' >> $profile.CurrentUserAllHosts
echo 'if (Test-Path -LP "$Env:PYENV_ROOT/bin" -PathType Container) {
$Env:PATH="$Env:PYENV_ROOT/bin:$Env:PATH" }' >> $profile.CurrentUserAllHosts
echo 'iex ((pyenv init -) -join "`n")' >> $profile.CurrentUserAllHosts
~~~
</details>
---- ----
## Development ## Development
The pyenv source code is [hosted on The pyenv source code is [hosted on
GitHub](https://github.com/pyenv/pyenv). It's clean, modular, GitHub](https://github.com/pyenv/pyenv).
and easy to understand, even if you're not a shell hacker.
Tests are executed using [Bats](https://github.com/bats-core/bats-core): Tests are executed using [Bats](https://github.com/bats-core/bats-core).
See the [tests README](test/README.md) for details.
bats test
bats/test/<file>.bats
### Contributing ### Contributing

17
completions/pyenv.pwsh Normal file
View file

@ -0,0 +1,17 @@
$scriptblock = {
param($wordToComplete, $commandAst, $cursorPosition)
$words = $commandAst.ToString()
if ( $wordToComplete ) {
$matches = (($words[0..$cursorPosition] -join '') | Select-String -Pattern "\s+" -AllMatches).Matches
if ( $matches ) {
$cursorPosition = $matches[-1].Index - 1
}
}
$words = $words[0..$cursorPosition] -join '' -split "\s+"
if ( $words.Count -ge 2 ) {
pyenv completions $words[1] | where { $_ -match $wordToComplete }
} else {
pyenv commands | where { $_ -match $wordToComplete }
}
}
Register-ArgumentCompleter -Native -CommandName pyenv -ScriptBlock $scriptblock

View file

@ -76,23 +76,27 @@ export PYENV_DIR
shopt -s nullglob shopt -s nullglob
bin_path="$(abs_dirname "$0")" _PYENV_INSTALL_PREFIX="$(abs_dirname "$0")"
for plugin_bin in "${bin_path%/*}"/plugins/*/bin; do _PYENV_INSTALL_PREFIX="${_PYENV_INSTALL_PREFIX%/*}"
export _PYENV_INSTALL_PREFIX
for plugin_bin in "${_PYENV_INSTALL_PREFIX}"/plugins/*/bin; do
PATH="${plugin_bin}:${PATH}" PATH="${plugin_bin}:${PATH}"
done done
# PYENV_ROOT can be set to anything, so it may happen to be equal to the base path above, # PYENV_ROOT can be set to anything, so it may happen to be equal to the base path above,
# resulting in duplicate PATH entries # resulting in duplicate PATH entries
if [ "${bin_path%/*}" != "$PYENV_ROOT" ]; then if [ "${_PYENV_INSTALL_PREFIX}" != "$PYENV_ROOT" ]; then
for plugin_bin in "${PYENV_ROOT}"/plugins/*/bin; do for plugin_bin in "${PYENV_ROOT}"/plugins/*/bin; do
PATH="${plugin_bin}:${PATH}" PATH="${plugin_bin}:${PATH}"
done done
fi fi
export PATH="${bin_path}:${PATH}" export PATH="${_PYENV_INSTALL_PREFIX}/libexec:${PATH}"
PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:${PYENV_ROOT}/pyenv.d" PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:${PYENV_ROOT}/pyenv.d"
if [ "${bin_path%/*}" != "$PYENV_ROOT" ]; then if [ "${_PYENV_INSTALL_PREFIX}" != "$PYENV_ROOT" ]; then
# Add pyenv's own `pyenv.d` unless pyenv was cloned to PYENV_ROOT # Add pyenv's own `pyenv.d` unless pyenv was cloned to PYENV_ROOT
PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:${bin_path%/*}/pyenv.d" PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:${_PYENV_INSTALL_PREFIX}/pyenv.d"
fi fi
PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:/usr/etc/pyenv.d:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks" PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:/usr/etc/pyenv.d:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks"
for plugin_hook in "${PYENV_ROOT}/plugins/"*/etc/pyenv.d; do for plugin_hook in "${PYENV_ROOT}/plugins/"*/etc/pyenv.d; do

View file

@ -12,7 +12,7 @@
set -e set -e
[ -n "$PYENV_DEBUG" ] && set -x [ -n "$PYENV_DEBUG" ] && set -x
version="2.6.8" version="2.8.5"
git_revision="" git_revision=""
if cd "${BASH_SOURCE%/*}" 2>/dev/null && git remote -v 2>/dev/null | grep -q pyenv; then if cd "${BASH_SOURCE%/*}" 2>/dev/null && git remote -v 2>/dev/null | grep -q pyenv; then

View file

@ -3,19 +3,19 @@
# Usage: pyenv commands [--sh|--no-sh] # Usage: pyenv commands [--sh|--no-sh]
set -e set -e
[ -n "$PYENV_DEBUG" ] && set -x [[ -n $PYENV_DEBUG ]] && set -x
# Provide pyenv completions # Provide pyenv completions
if [ "$1" = "--complete" ]; then if [[ $1 = "--complete" ]]; then
echo --sh echo --sh
echo --no-sh echo --no-sh
exit exit
fi fi
if [ "$1" = "--sh" ]; then if [[ $1 = "--sh" ]]; then
sh=1 sh=1
shift shift
elif [ "$1" = "--no-sh" ]; then elif [[ $1 = "--no-sh" ]]; then
nosh=1 nosh=1
shift shift
fi fi
@ -24,20 +24,25 @@ IFS=: paths=($PATH)
shopt -s nullglob shopt -s nullglob
{ for path in "${paths[@]}"; do {
for command in "${path}/pyenv-"*; do if [[ -n $sh ]]; then
command="${command##*pyenv-}" for path in "${paths[@]}"; do
if [ -n "$sh" ]; then for command in "${path}"/pyenv-sh-*; do
if [ "${command:0:3}" = "sh-" ]; then echo "${command##*/pyenv-sh-}"
echo "${command##sh-}" done
fi
elif [ -n "$nosh" ]; then
if [ "${command:0:3}" != "sh-" ]; then
echo "${command##sh-}"
fi
else
echo "${command##sh-}"
fi
done done
done else
} | sort | uniq for path in "${paths[@]}"; do
for command in "${path}"/pyenv-*; do
command="${command##*/pyenv-}"
if [[ -n $nosh ]]; then
if [[ ${command:0:3} != "sh-" ]]; then
echo "$command"
fi
else
echo "${command##sh-}"
fi
done
done
fi
} | sort -u

View file

@ -29,8 +29,17 @@ if [ -z "$PYENV_COMMAND" ]; then
exit 1 exit 1
fi fi
if [[ -n $_PYENV_SHIM_PATH ]]; then
PROGRAM="$(echo "$PYENV_COMMAND" | tr a-z- A-Z_ | sed 's/[^A-Z0-9_]/_/g')"
NR_PYENV_SHIM_PATHS_PROGRAM="_PYENV_SHIM_PATHS_${PROGRAM}"
export -- "$NR_PYENV_SHIM_PATHS_PROGRAM=$_PYENV_SHIM_PATH${!NR_PYENV_SHIM_PATHS_PROGRAM:+:${!NR_PYENV_SHIM_PATHS_PROGRAM}}"
unset PROGRAM NR_PYENV_SHIM_PATHS_PROGRAM
unset _PYENV_SHIM_PATH
fi
PYENV_COMMAND_PATH="$(pyenv-which "$PYENV_COMMAND")" PYENV_COMMAND_PATH="$(pyenv-which "$PYENV_COMMAND")"
PYENV_BIN_PATH="${PYENV_COMMAND_PATH%/*}" PYENV_BIN_PATH="${PYENV_COMMAND_PATH%/*}"
export PYENV_VERSION export PYENV_VERSION
OLDIFS="$IFS" OLDIFS="$IFS"

View file

@ -36,12 +36,16 @@ if [ -n "$versions" ]; then
pyenv-version-file-write "$PYENV_VERSION_FILE" "${versions[@]}" pyenv-version-file-write "$PYENV_VERSION_FILE" "${versions[@]}"
else else
OLDIFS="$IFS" OLDIFS="$IFS"
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
# which is undesired
set -f
IFS=: versions=($( IFS=: versions=($(
pyenv-version-file-read "$PYENV_VERSION_FILE" || pyenv-version-file-read "$PYENV_VERSION_FILE" ||
pyenv-version-file-read "${PYENV_ROOT}/global" || pyenv-version-file-read "${PYENV_ROOT}/global" ||
pyenv-version-file-read "${PYENV_ROOT}/default" || pyenv-version-file-read "${PYENV_ROOT}/default" ||
echo system echo system
)) ))
set +f
IFS="$OLDIFS" IFS="$OLDIFS"
for version in "${versions[@]}"; do for version in "${versions[@]}"; do
echo "$version" echo "$version"

View file

@ -1,6 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Summary: Configure the shell environment for pyenv # Summary: Configure the shell environment for pyenv
# Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--detect-shell] [--no-rehash] [<shell>])" # Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [<shell>])"
# pyenv init --install [<shell>]
# pyenv init --detect-shell [<shell>]
set -e set -e
[ -n "$PYENV_DEBUG" ] && set -x [ -n "$PYENV_DEBUG" ] && set -x
@ -9,12 +11,14 @@ set -e
if [ "$1" = "--complete" ]; then if [ "$1" = "--complete" ]; then
echo - echo -
echo --path echo --path
echo --install
echo --no-push-path echo --no-push-path
echo --no-rehash echo --no-rehash
echo --detect-shell echo --detect-shell
echo bash echo bash
echo fish echo fish
echo ksh echo ksh
echo pwsh
echo zsh echo zsh
exit exit
fi fi
@ -29,6 +33,9 @@ while [ "$#" -gt 0 ]; do
--path) --path)
mode="path" mode="path"
;; ;;
--install)
mode="install"
;;
--detect-shell) --detect-shell)
mode="detect-shell" mode="detect-shell"
;; ;;
@ -80,21 +87,23 @@ function main() {
exit 0 exit 0
;; ;;
"detect-shell") "detect-shell")
detect_profile 1 detect_profile
print_detect_shell print_detect_shell
exit 0 exit 0
;; ;;
"install")
install_shell_startup_files
exit 0
;;
esac esac
# should never get here # should never get here
exit 2 exit 2
} }
function detect_profile() { function detect_profile() {
local detect_for_detect_shell="$1"
case "$shell" in case "$shell" in
bash ) bash )
if [ -e '~/.bash_profile' ]; then if [ -e "${HOME}/.bash_profile" ]; then
profile='~/.bash_profile' profile='~/.bash_profile'
else else
profile='~/.profile' profile='~/.profile'
@ -102,6 +111,14 @@ function detect_profile() {
profile_explain="~/.bash_profile if it exists, otherwise ~/.profile" profile_explain="~/.bash_profile if it exists, otherwise ~/.profile"
rc='~/.bashrc' rc='~/.bashrc'
;; ;;
fish )
profile='~/.config/fish/config.fish'
rc='~/.config/fish/config.fish'
;;
pwsh )
profile='~/.config/powershell/profile.ps1'
rc='~/.config/powershell/profile.ps1'
;;
zsh ) zsh )
profile='~/.zprofile' profile='~/.zprofile'
rc='~/.zshrc' rc='~/.zshrc'
@ -116,13 +133,10 @@ function detect_profile() {
rc='~/.profile' rc='~/.profile'
;; ;;
* ) * )
if [ -n "$detect_for_detect_shell" ]; then profile=
profile= rc=
rc= profile_explain='your shell'\''s login startup file'
else rc_explain='your shell'\''s interactive startup file'
profile='your shell'\''s login startup file'
rc='your shell'\''s interactive startup file'
fi
;; ;;
esac esac
} }
@ -141,29 +155,32 @@ function help_() {
echo "# Add pyenv executable to PATH by running" echo "# Add pyenv executable to PATH by running"
echo "# the following interactively:" echo "# the following interactively:"
echo echo
echo 'set -Ux PYENV_ROOT $HOME/.pyenv' print_fish_user_path_setup
echo 'set -U fish_user_paths $PYENV_ROOT/bin $fish_user_paths'
echo echo
echo "# Load pyenv automatically by appending" echo "# Load pyenv automatically by appending"
echo "# the following to ~/.config/fish/config.fish:" echo "# the following to ~/.config/fish/config.fish:"
echo echo
echo 'pyenv init - fish | source' print_fish_shell_setup
echo echo
;; ;;
pwsh )
echo '# Load pyenv automatically by appending'
echo "# the following to $profile :"
echo
print_pwsh_shell_setup
;;
* ) * )
echo '# Load pyenv automatically by appending' echo '# Load pyenv automatically by appending'
echo -n "# the following to " echo -n "# the following to "
if [ "$profile" == "$rc" ]; then if [[ "$profile" == "$rc" && -z $rc_explain ]]; then
echo "$profile :" echo "${profile_explain:-$profile} :"
else else
echo echo
echo "# ${profile_explain:-$profile} (for login shells)" echo "# ${profile_explain:-$profile} (for login shells)"
echo "# and $rc (for interactive shells) :" echo "# and ${rc_explain:-$rc} (for interactive shells) :"
fi fi
echo echo
echo 'export PYENV_ROOT="$HOME/.pyenv"' print_posix_shell_setup
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"'
echo 'eval "$(pyenv init - '$shell')"'
;; ;;
esac esac
echo echo
@ -172,6 +189,154 @@ function help_() {
} >&2 } >&2
} }
function print_posix_shell_setup() {
echo 'export PYENV_ROOT="$HOME/.pyenv"'
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"'
echo 'eval "$(pyenv init - '$shell')"'
}
function print_fish_shell_setup() {
echo 'pyenv init - fish | source'
}
function print_fish_user_path_setup() {
echo 'set -Ux PYENV_ROOT $HOME/.pyenv'
echo 'if functions -q fish_add_path'
echo ' test -d $PYENV_ROOT/bin; and fish_add_path $PYENV_ROOT/bin'
echo 'else'
echo ' test -d $PYENV_ROOT/bin; and set -U fish_user_paths $PYENV_ROOT/bin $fish_user_paths'
echo 'end'
}
function print_pwsh_shell_setup() {
echo '$Env:PYENV_ROOT="$Env:HOME/.pyenv"'
echo 'if (Test-Path -LP "$Env:PYENV_ROOT/bin" -PathType Container) {'
echo ' $Env:PATH="$Env:PYENV_ROOT/bin:$Env:PATH" }'
echo 'iex ((pyenv init -) -join "`n")'
}
function expand_home_path() {
local path="$1"
printf '%s\n' "${path/#\~/$HOME}"
}
function install_shell_startup_files() {
if [[ -z $HOME ]]; then
echo "pyenv: HOME must be set to configure shell startup files" >&2
return 1
fi
detect_profile
local files=()
local lines=()
local profile_path rc_path setup
case "$shell" in
bash | zsh | ksh | ksh93 | mksh )
rc_path="$(expand_home_path "$rc")"
profile_path="$(expand_home_path "$profile")"
setup="$(print_posix_shell_setup)"
files=("$rc_path")
lines=("$setup")
if [[ $profile_path != "$rc_path" ]]; then
files+=("$profile_path")
lines+=("$setup")
fi
;;
fish )
rc_path="$(expand_home_path "$rc")"
files=("$rc_path")
lines=("$(print_fish_shell_setup)")
;;
pwsh )
rc_path="$(expand_home_path "$rc")"
files=("$rc_path")
lines=("$(print_pwsh_shell_setup)")
;;
* )
echo "pyenv: cannot automatically configure startup files for $shell" >&2
return 1
;;
esac
local index
for ((index = 0; index < ${#files[@]}; index++)); do
check_startup_file "${files[$index]}" || return 1
done
if [[ $shell == fish ]]; then
install_fish_user_paths || return 1
fi
for ((index = 0; index < ${#files[@]}; index++)); do
append_lines "${files[$index]}" "${lines[$index]}"
done
}
function check_startup_file() {
local file="$1"
local grep_status
if [[ ! -e $file ]]; then
return 0
fi
if [[ ! -f $file || ! -r $file ]]; then
echo "pyenv: failed to inspect $file" >&2
return 1
fi
if grep -Fi pyenv "$file" >/dev/null; then
echo "pyenv: cannot automatically apply changes to $file: it appears to already contain Pyenv-related code." >&2
echo "pyenv: review the file's contents and apply changes manually if necessary." >&2
echo "pyenv: run \`pyenv init $shell\` to see the suggested setup." >&2
return 1
else
grep_status=$?
if [[ $grep_status == 1 ]]; then
return 0
fi
echo "pyenv: failed to inspect $file" >&2
return "$grep_status"
fi
}
function install_fish_user_paths() {
local fish_setup
if ! command -v fish >/dev/null; then
echo "pyenv: fish is not available to configure fish universal variables" >&2
return 1
fi
fish_setup="$(print_fish_user_path_setup)"
if ! fish -c "$fish_setup"; then
echo "pyenv: failed to configure fish universal variables" >&2
return 1
fi
}
function append_lines() {
local file="$1"
local lines="$2"
local dir last_char
dir="${file%/*}"
if [[ $dir != "$file" ]]; then
mkdir -p "$dir"
fi
if [[ -s $file ]]; then
last_char="$(tail -c 1 "$file")" || return 1
if [[ -n $last_char ]]; then
echo >> "$file"
fi
fi
printf '%s\n' "$lines" >> "$file"
}
function init_dirs() { function init_dirs() {
mkdir -p "${PYENV_ROOT}/"{shims,versions} mkdir -p "${PYENV_ROOT}/"{shims,versions}
} }
@ -186,6 +351,11 @@ function print_path() {
print_path_prepend_shims print_path_prepend_shims
echo 'end' echo 'end'
;; ;;
pwsh )
echo 'if ( $Env:PATH -notmatch "'"${PYENV_ROOT}/shims"'" ) {'
print_path_prepend_shims
echo '}'
;;
* ) * )
echo 'if [[ ":$PATH:" != *'\':"${PYENV_ROOT}"/shims:\''* ]]; then' echo 'if [[ ":$PATH:" != *'\':"${PYENV_ROOT}"/shims:\''* ]]; then'
print_path_prepend_shims print_path_prepend_shims
@ -199,6 +369,10 @@ function print_path() {
echo 'set -eg PATH[$pyenv_index]; end; set -e pyenv_index' echo 'set -eg PATH[$pyenv_index]; end; set -e pyenv_index'
print_path_prepend_shims print_path_prepend_shims
;; ;;
pwsh )
echo '$Env:PATH="$(($Env:PATH -split '"':'"' | where { -not ($_ -match '"'${PYENV_ROOT}/shims'"') }) -join '"':'"')"'
print_path_prepend_shims
;;
* ) * )
# Some distros (notably Debian-based) set Bash's SSH_SOURCE_BASHRC compilation option # Some distros (notably Debian-based) set Bash's SSH_SOURCE_BASHRC compilation option
# that makes it source `bashrc` under SSH even when not interactive. # that makes it source `bashrc` under SSH even when not interactive.
@ -223,6 +397,9 @@ function print_path_prepend_shims() {
fish ) fish )
echo 'set -gx PATH '\'"${PYENV_ROOT}/shims"\'' $PATH' echo 'set -gx PATH '\'"${PYENV_ROOT}/shims"\'' $PATH'
;; ;;
pwsh )
echo '$Env:PATH="'"${PYENV_ROOT}"'/shims:$Env:PATH"'
;;
* ) * )
echo 'export PATH="'"${PYENV_ROOT}"'/shims:${PATH}"' echo 'export PATH="'"${PYENV_ROOT}"'/shims:${PATH}"'
;; ;;
@ -234,6 +411,9 @@ function print_env() {
fish ) fish )
echo "set -gx PYENV_SHELL $shell" echo "set -gx PYENV_SHELL $shell"
;; ;;
pwsh )
echo '$Env:PYENV_SHELL="'"$shell"'"'
;;
* ) * )
echo "export PYENV_SHELL=$shell" echo "export PYENV_SHELL=$shell"
;; ;;
@ -241,15 +421,29 @@ function print_env() {
} }
function print_completion() { function print_completion() {
completion="${0%/*/*}/completions/pyenv.${shell}" completion="${_PYENV_INSTALL_PREFIX}/completions/pyenv.${shell}"
if [ -r "$completion" ]; then if [ -r "$completion" ]; then
echo "source '$completion'" case "$shell" in
pwsh )
echo "iex (gc $completion -Raw)"
;;
* )
echo "source '$completion'"
;;
esac
fi fi
} }
function print_rehash() { function print_rehash() {
if [ -z "$no_rehash" ]; then if [ -z "$no_rehash" ]; then
echo 'command pyenv rehash 2>/dev/null' case "$shell" in
pwsh )
echo '& pyenv rehash'
;;
* )
echo 'command pyenv rehash'
;;
esac
fi fi
} }
@ -270,6 +464,25 @@ function print_shell_function() {
end end
end' end'
;; ;;
pwsh )
cat <<EOS
function pyenv {
\$command=""
if ( \$args.Count -gt 0 ) {
\$command, \$args = \$args
}
if ( ("${commands[*]}" -split ' ') -contains \$command ) {
\$shell_cmds = (& (get-command -commandtype application pyenv -totalcount 1) sh-\$command \$args)
if ( \$shell_cmds.Count -gt 0 ) {
iex (\$shell_cmds -join "\`n")
}
} else {
& (get-command -commandtype application pyenv -totalcount 1) \$command \$args
}
}
EOS
;;
ksh | ksh93 | mksh ) ksh | ksh93 | mksh )
echo \ echo \
'function pyenv { 'function pyenv {
@ -282,7 +495,7 @@ end'
;; ;;
esac esac
if [ "$shell" != "fish" ]; then if [ "$shell" != "fish" ] && [ "$shell" != "pwsh" ]; then
IFS="|" IFS="|"
echo \ echo \
' [ "$#" -gt 0 ] && shift ' [ "$#" -gt 0 ] && shift

View file

@ -10,6 +10,7 @@
set -e set -e
[ -n "$PYENV_DEBUG" ] && set -x [ -n "$PYENV_DEBUG" ] && set -x
while [[ $# -gt 0 ]] while [[ $# -gt 0 ]]
do do
case "$1" in case "$1" in
@ -38,6 +39,10 @@ exitcode=0
IFS=$'\n' IFS=$'\n'
if [[ -z $FROM_KNOWN ]]; then if [[ -z $FROM_KNOWN ]]; then
if [[ -d $PYENV_ROOT/versions/$prefix ]]; then
echo "$prefix"
exit $exitcode;
fi
DEFINITION_CANDIDATES=( $(pyenv-versions --bare --skip-envs) ) DEFINITION_CANDIDATES=( $(pyenv-versions --bare --skip-envs) )
else else
DEFINITION_CANDIDATES=( $(python-build --definitions ) ) DEFINITION_CANDIDATES=( $(python-build --definitions ) )

View file

@ -59,7 +59,11 @@ elif [ -n "$versions" ]; then
pyenv-version-file-write ${FORCE:+-f }.python-version "${versions[@]}" pyenv-version-file-write ${FORCE:+-f }.python-version "${versions[@]}"
else else
if version_file="$(pyenv-version-file "$PWD")"; then if version_file="$(pyenv-version-file "$PWD")"; then
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
# which is undesired
set -f
IFS=: versions=($(pyenv-version-file-read "$version_file")) IFS=: versions=($(pyenv-version-file-read "$version_file"))
set +f
for version in "${versions[@]}"; do for version in "${versions[@]}"; do
echo "$version" echo "$version"
done done

View file

@ -28,6 +28,9 @@ fi
PYENV_PREFIX_PATHS=() PYENV_PREFIX_PATHS=()
OLDIFS="$IFS" OLDIFS="$IFS"
{ IFS=: { IFS=:
# Unquoted $VAR does glob expansion (against the current dir's contents) after IFS-splitting
# which is undesired
set -f
for version in ${PYENV_VERSION}; do for version in ${PYENV_VERSION}; do
if [ "$version" = "system" ]; then if [ "$version" = "system" ]; then
if PYTHON_PATH="$(PYENV_VERSION="${version}" pyenv-which python --skip-advice 2>/dev/null)" || \ if PYTHON_PATH="$(PYENV_VERSION="${version}" pyenv-which python --skip-advice 2>/dev/null)" || \
@ -52,6 +55,7 @@ OLDIFS="$IFS"
exit 1 exit 1
fi fi
done done
set +f
} }
IFS="$OLDIFS" IFS="$OLDIFS"

View file

@ -2,7 +2,7 @@
# Summary: Rehash pyenv shims (run this after installing executables) # Summary: Rehash pyenv shims (run this after installing executables)
set -e set -e
[ -n "$PYENV_DEBUG" ] && set -x [[ -n "$PYENV_DEBUG" ]] && set -x
SHIM_PATH="${PYENV_ROOT}/shims" SHIM_PATH="${PYENV_ROOT}/shims"
PROTOTYPE_SHIM_PATH="${SHIM_PATH}/.pyenv-shim" PROTOTYPE_SHIM_PATH="${SHIM_PATH}/.pyenv-shim"
@ -10,21 +10,37 @@ PROTOTYPE_SHIM_PATH="${SHIM_PATH}/.pyenv-shim"
# Create the shims directory if it doesn't already exist. # Create the shims directory if it doesn't already exist.
mkdir -p "$SHIM_PATH" mkdir -p "$SHIM_PATH"
acquire_lock() { declare last_acquire_error
# Ensure only one instance of pyenv-rehash is running at a time by
# setting the shell's `noclobber` option and attempting to write to
# the prototype shim file. If the file already exists, print a warning
# to stderr and exit with a non-zero status.
local ret
set -o noclobber
echo > "$PROTOTYPE_SHIM_PATH" 2>| /dev/null || ret=1
set +o noclobber
[ -z "${ret}" ]
}
# If we were able to obtain a lock, register a trap to clean up the acquire_lock() {
# prototype shim when the process exits. local ret
trap release_lock EXIT # An old lock file is presumed stale. We assume no healthy rehash takes this long.
# The time is picked very small so that a killed rehash holds up new shell sessions
# for as little as possible
find "$PROTOTYPE_SHIM_PATH" -mmin +2 -exec rm -f {} \; 2>/dev/null || true
set -o noclobber
last_acquire_error="$( { ( echo -n > "$PROTOTYPE_SHIM_PATH"; ) 2>&1 1>&3 3>&1-; } 3>&1)" \
&& trap release_lock EXIT \
|| {
# Linux Landlock and MacOS Seatbelt sandbox subsystems return false information in access(),
# making -w "$SHIM_PATH" not catch the fact that the shims dir is not writable in this case.
# Bash doesn't provide access to errno to check for non-EEXIST error code in `echo >'.
# So check for writablity by trying to write to a different file,
# in a way that taxes the usual use case as little as possible.
if [[ -z $tested_for_other_write_errors ]]; then
# if lots of (50+) rehashes run concurrently, another concurrent rehash
# may delete the temporary file in remove_*_shims() before we do so
( t="$(TMPDIR="$SHIM_PATH" mktemp)" && rm -f "$t" ) \
&& tested_for_other_write_errors=1 \
|| { echo "pyenv: cannot rehash: $SHIM_PATH isn't writable" >&2
set +o noclobber
exit 1; }
fi
ret=1
}
set +o noclobber
[[ -z "${ret}" ]]
}
remove_prototype_shim() { remove_prototype_shim() {
rm -f "$PROTOTYPE_SHIM_PATH" rm -f "$PROTOTYPE_SHIM_PATH"
@ -32,17 +48,19 @@ remove_prototype_shim() {
release_lock() { release_lock() {
remove_prototype_shim remove_prototype_shim
trap - EXIT
} }
if [ ! -w "$SHIM_PATH" ]; then if [ ! -w "$SHIM_PATH" ]; then
echo "pyenv: cannot rehash: $SHIM_PATH isn't writable" echo "pyenv: cannot rehash: $SHIM_PATH isn't writable" >&2
exit 1 exit 1
fi fi
unset acquired declare acquired tested_for_other_write_errors
start=$SECONDS declare start=$SECONDS
while (( SECONDS <= start + ${PYENV_REHASH_TIMEOUT:-60} )); do PYENV_REHASH_TIMEOUT=${PYENV_REHASH_TIMEOUT:-60}
if acquire_lock 2>/dev/null; then while (( SECONDS <= start + PYENV_REHASH_TIMEOUT )); do
if acquire_lock; then
acquired=1 acquired=1
break break
else else
@ -52,9 +70,14 @@ while (( SECONDS <= start + ${PYENV_REHASH_TIMEOUT:-60} )); do
done done
if [ -z "${acquired}" ]; then if [ -z "${acquired}" ]; then
echo "pyenv: cannot rehash: $PROTOTYPE_SHIM_PATH exists" if [[ -n $tested_for_other_write_errors ]]; then
echo "pyenv: cannot rehash: couldn't acquire lock"\
"$PROTOTYPE_SHIM_PATH for $PYENV_REHASH_TIMEOUT seconds. Last error message:" >&2
echo "$last_acquire_error" >&2
fi
exit 1 exit 1
fi fi
unset tested_for_other_write_errors
# The prototype shim file is a script that re-execs itself, passing # The prototype shim file is a script that re-execs itself, passing
# its filename and any arguments to `pyenv exec`. This file is # its filename and any arguments to `pyenv exec`. This file is
@ -70,6 +93,10 @@ set -e
program="\${0##*/}" program="\${0##*/}"
export PYENV_ROOT="$PYENV_ROOT" export PYENV_ROOT="$PYENV_ROOT"
SHIM_PATH=\${0%/*}
if [[ \$SHIM_PATH != "$PYENV_ROOT/shims" ]]; then
export _PYENV_SHIM_PATH="\$SHIM_PATH"
fi
exec "$(command -v pyenv)" exec "\$program" "\$@" exec "$(command -v pyenv)" exec "\$program" "\$@"
SH SH
chmod +x "$PROTOTYPE_SHIM_PATH" chmod +x "$PROTOTYPE_SHIM_PATH"
@ -88,17 +115,6 @@ remove_outdated_shims() {
done done
} }
# List basenames of executables for every Python version
list_executable_names() {
local version file
pyenv-versions --bare --skip-aliases | \
while read -r version; do
for file in "${PYENV_ROOT}/versions/${version}/bin/"*; do
echo "${file##*/}"
done
done
}
# The basename of each argument passed to `make_shims` will be # The basename of each argument passed to `make_shims` will be
# registered for installation as a shim. In this way, plugins may call # registered for installation as a shim. In this way, plugins may call
# `make_shims` with a glob to register many shims at once. # `make_shims` with a glob to register many shims at once.
@ -174,7 +190,7 @@ shopt -s nullglob
create_prototype_shim create_prototype_shim
remove_outdated_shims remove_outdated_shims
# shellcheck disable=SC2046 # shellcheck disable=SC2046
make_shims $(list_executable_names | sort -u) make_shims $(pyenv-versions --executables)
# Allow plugins to register shims. # Allow plugins to register shims.

View file

@ -11,11 +11,17 @@ shell="$(basename "${PYENV_SHELL:-$SHELL}")"
# When pyenv shell integration is enabled, delegate to pyenv-rehash, # When pyenv shell integration is enabled, delegate to pyenv-rehash,
# then tell the shell to empty its command lookup cache. # then tell the shell to empty its command lookup cache.
pyenv-rehash case "$shell" in
pwsh)
echo "& (get-command pyenv -commandtype application) rehash"
;;
*)
echo "command pyenv rehash"
esac
case "$shell" in case "$shell" in
fish ) fish | pwsh )
# no rehash support # no executable cache
;; ;;
* ) * )
echo "hash -r 2>/dev/null || true" echo "hash -r 2>/dev/null || true"

View file

@ -48,6 +48,9 @@ if [ "$versions" = "--unset" ]; then
echo 'set -gu PYENV_VERSION_OLD "$PYENV_VERSION"' echo 'set -gu PYENV_VERSION_OLD "$PYENV_VERSION"'
echo "set -e PYENV_VERSION" echo "set -e PYENV_VERSION"
;; ;;
pwsh )
echo '$Env:PYENV_VERSION, $Env:PYENV_VERSION_OLD = $null, $Env:PYENV_VERSION'
;;
* ) * )
echo 'PYENV_VERSION_OLD="${PYENV_VERSION-}"' echo 'PYENV_VERSION_OLD="${PYENV_VERSION-}"'
echo "unset PYENV_VERSION" echo "unset PYENV_VERSION"
@ -74,6 +77,16 @@ else
echo "pyenv: PYENV_VERSION_OLD is not set" >&2 echo "pyenv: PYENV_VERSION_OLD is not set" >&2
false false
end end
EOS
;;
pwsh )
cat <<EOS
if ( Get-Item -Path Env:\PYENV_VERSION* ) {
\$Env:PYENV_VERSION, \$Env:PYENV_VERSION_OLD = \$Env:PYENV_VERSION_OLD, \$Env:PYENV_VERSION
} else {
Write-Error "pyenv: Env:PYENV_VERSION_OLD is not set"
return \$false
}
EOS EOS
;; ;;
* ) * )
@ -109,6 +122,9 @@ if pyenv-prefix "${versions[@]}" >/dev/null; then
echo 'set -gu PYENV_VERSION_OLD "$PYENV_VERSION"' echo 'set -gu PYENV_VERSION_OLD "$PYENV_VERSION"'
echo "set -gx PYENV_VERSION \"$version\"" echo "set -gx PYENV_VERSION \"$version\""
;; ;;
pwsh )
echo '$Env:PYENV_VERSION, $Env:PYENV_VERSION_OLD = "'"${version}"'", $Env:PYENV_VERSION'
;;
* ) * )
echo 'PYENV_VERSION_OLD="${PYENV_VERSION-}"' echo 'PYENV_VERSION_OLD="${PYENV_VERSION-}"'
echo "export PYENV_VERSION=\"${version}\"" echo "export PYENV_VERSION=\"${version}\""

View file

@ -9,7 +9,11 @@ set -e
exitcode=0 exitcode=0
OLDIFS="$IFS" OLDIFS="$IFS"
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
# which is undesired
set -f
IFS=: PYENV_VERSION_NAMES=($(pyenv-version-name)) || exitcode=$? IFS=: PYENV_VERSION_NAMES=($(pyenv-version-name)) || exitcode=$?
set +f
IFS="$OLDIFS" IFS="$OLDIFS"
unset bare unset bare

View file

@ -8,12 +8,28 @@ target_dir="$1"
find_local_version_file() { find_local_version_file() {
local root="$1" local root="$1"
while ! [[ "$root" =~ ^//[^/]*$ ]]; do # Nonexistent paths are UB as of this writing.
if [ -f "${root}/.python-version" ]; then # Relative ones cause a failure but absolute ones don't
echo "${root}/.python-version" [[ $root != /* ]] && root=$(CDPATH= cd -- "$root" && pwd)
# Original Rbenv code supports UNC notation for Cygwin/MinGW
# (https://github.com/rbenv/rbenv/pull/529)
# POSIX.1-2024 still allows to treat //<name> in implementation-specific manner
# (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap04.html#tag_04_16)
# even though few UNIX variants do that
local unc; [[ $root =~ ^//[^/] && ! / -ef // ]] && unc=1
root="${root%/}"
# when testing root, $root is ""
while true; do
# don't test //.python-version if // is special
# as it's pointless and possibly very slow
# if it e.g. leads to a network search
[[ $unc && $root == / ]] && break
if [[ -f $root/.python-version ]]; then
echo "$root/.python-version"
return 0 return 0
fi fi
[ -n "$root" ] || break [[ -n $root ]] || break
root="${root%/*}" root="${root%/*}"
done done
return 1 return 1
@ -22,7 +38,9 @@ find_local_version_file() {
if [ -n "$target_dir" ]; then if [ -n "$target_dir" ]; then
find_local_version_file "$target_dir" find_local_version_file "$target_dir"
else else
find_local_version_file "$PYENV_DIR" || { # In tests, PYENV_DIR is not set, ultimately leading to "cd: null directory" in OpenSUSE
# While most `cd` implementations treat an empty argument the same as missing, that's still wrong
find_local_version_file "${PYENV_DIR:=$PWD}" || {
[ "$PYENV_DIR" != "$PWD" ] && find_local_version_file "$PWD" [ "$PYENV_DIR" != "$PWD" ] && find_local_version_file "$PWD"
} || echo "${PYENV_ROOT}/version" } || echo "${PYENV_ROOT}/version"
fi fi

View file

@ -46,17 +46,22 @@ versions=()
OLDIFS="$IFS" OLDIFS="$IFS"
{ IFS=: { IFS=:
any_not_installed=0 any_not_installed=0
normalization_done=
# Unquoted $VAR does glob expansion (against the current dir's contents) after IFS-splitting
# which is undesired
set -f
for version in ${PYENV_VERSION}; do for version in ${PYENV_VERSION}; do
# Remove the explicit 'python-' prefix from versions like 'python-3.12'. # Remove the explicit 'python-' prefix from versions like 'python-3.12'.
normalised_version="${version#python-}" normalised_version="${version#python-}"
if version_exists "${version}" || [ "$version" = "system" ]; then [[ $version != "${normalised_version}" ]] && normalization_done=1
versions+=("${version}") if [[ $version == "system" ]] || version_exists "${normalised_version}" ; then
elif version_exists "${normalised_version}"; then
versions+=("${normalised_version}") versions+=("${normalised_version}")
elif resolved_version="$(pyenv-latest -b "${version}")"; then elif [[ -n $normalization_done ]] && version_exists "${version}"; then
versions+=("${resolved_version}") versions+=("${version}")
elif resolved_version="$(pyenv-latest -b "${normalised_version}")"; then elif resolved_version="$(pyenv-latest -b "${normalised_version}")"; then
versions+=("${resolved_version}") versions+=("${resolved_version}")
elif [[ -n $normalization_done ]] && resolved_version="$(pyenv-latest -b "${version}")"; then
versions+=("${resolved_version}")
else else
if [[ -n $FORCE ]]; then if [[ -n $FORCE ]]; then
versions+=("${normalised_version}") versions+=("${normalised_version}")
@ -66,6 +71,7 @@ OLDIFS="$IFS"
fi fi
fi fi
done done
set +f
} }
IFS="$OLDIFS" IFS="$OLDIFS"

View file

@ -1,13 +1,21 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Summary: List all Python versions available to pyenv # Summary: List all Python versions available to pyenv
# Usage: pyenv versions [--bare] [--skip-aliases] [--skip-envs] # Usage: pyenv versions [--bare] [--skip-aliases] [--skip-envs] [--executables]
# #
# Lists all Python versions found in `$PYENV_ROOT/versions/*'. # Lists all Python versions found in `$PYENV_ROOT/versions/*'.
#
# --bare List just the names, omit `system'
# --skip-aliases Skip symlinks to other versions and to virtual environments
# --skip-envs Skip virtual environments (under <version>/envs)
# --executables Internal. Overrides other options.
# Optimally get a deduplicated list of all executable names in Pyenv-managed
# versions and environments for `pyenv rehash'
#
set -e set -e
[ -n "$PYENV_DEBUG" ] && set -x [ -n "$PYENV_DEBUG" ] && set -x
unset bare skip_aliases skip_envs unset bare skip_aliases skip_envs executables
# Provide pyenv completions # Provide pyenv completions
for arg; do for arg; do
case "$arg" in case "$arg" in
@ -16,6 +24,7 @@ for arg; do
echo --skip-aliases echo --skip-aliases
echo --skip-envs echo --skip-envs
exit ;; exit ;;
--executables ) executables=1; break ;;
--bare ) bare=1 ;; --bare ) bare=1 ;;
--skip-aliases ) skip_aliases=1 ;; --skip-aliases ) skip_aliases=1 ;;
--skip-envs ) skip_envs=1 ;; --skip-envs ) skip_envs=1 ;;
@ -26,8 +35,21 @@ for arg; do
esac esac
done done
versions_dir="${PYENV_ROOT}/versions" versions_dir="${PYENV_ROOT}/versions"
# Fast path for rehash: skip filtering and link resolution
if [[ -n "$executables" ]]; then
if [ -d "$versions_dir" ]; then
shopt -s dotglob nullglob
# MacOS 12+ and FreeBSD 15 support `xargs -r -0' and `basename -a'
# `sort -u` is simpler and a bit faster than `awk '!seen[$0]++'`, with the same result for rehash purposes
printf '%s\0' "$versions_dir"/*/bin/* "$versions_dir"/*/envs/*/bin/* | xargs -0 -r basename -a | sort -u
shopt -u dotglob nullglob
fi
exit 0
fi
if ! enable -f "${BASH_SOURCE%/*}"/pyenv-realpath.dylib realpath 2>/dev/null; then if ! enable -f "${BASH_SOURCE%/*}"/pyenv-realpath.dylib realpath 2>/dev/null; then
if [ -n "$PYENV_NATIVE_EXT" ]; then if [ -n "$PYENV_NATIVE_EXT" ]; then
echo "pyenv: failed to load \`realpath' builtin" >&2 echo "pyenv: failed to load \`realpath' builtin" >&2
@ -77,6 +99,9 @@ else
miss_prefix=" " miss_prefix=" "
OLDIFS="$IFS" OLDIFS="$IFS"
IFS=: IFS=:
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
# which is undesired
set -f
if ((${BASH_VERSINFO[0]} > 3)); then if ((${BASH_VERSINFO[0]} > 3)); then
for i in $(pyenv-version-name || true); do for i in $(pyenv-version-name || true); do
current_versions["$i"]="1" current_versions["$i"]="1"
@ -84,6 +109,7 @@ else
else else
current_versions=($(pyenv-version-name || true)) current_versions=($(pyenv-version-name || true))
fi fi
set +f
IFS="$OLDIFS" IFS="$OLDIFS"
include_system="1" include_system="1"
fi fi
@ -139,11 +165,15 @@ versions_dir_entries=("$versions_dir"/*)
if sort --version-sort </dev/null >/dev/null 2>&1; then if sort --version-sort </dev/null >/dev/null 2>&1; then
# system sort supports version sorting # system sort supports version sorting
OLDIFS="$IFS" OLDIFS="$IFS"
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
# which is undesired
set -f
IFS=$'\n' IFS=$'\n'
versions_dir_entries=($( versions_dir_entries=($(
printf "%s\n" "${versions_dir_entries[@]}" | printf "%s\n" "${versions_dir_entries[@]}" |
sort --version-sort sort --version-sort
)) ))
set +f
IFS="$OLDIFS" IFS="$OLDIFS"
fi fi

View file

@ -42,15 +42,19 @@ done
remove_from_path() { remove_from_path() {
local path_to_remove="$1" local -a paths_to_remove
local path_before IFS=: paths_to_remove=($1)
local path_to_remove path_before
local result=":${PATH//\~/$HOME}:" local result=":${PATH//\~/$HOME}:"
while [ "$path_before" != "$result" ]; do for path_to_remove in "${paths_to_remove[@]}"; do
path_before="$result" while true; do
result="${result//:$path_to_remove:/:}" path_before="$result"
result="${result//:$path_to_remove:/:}"
if [[ ${#path_before} == "${#result}" ]]; then break; fi
done
done done
result="${result%:}" result="${result:1:${#result}-2}"
echo "${result#:}" echo "$result"
} }
if [ -z "$PYENV_COMMAND" ]; then if [ -z "$PYENV_COMMAND" ]; then
@ -59,15 +63,21 @@ if [ -z "$PYENV_COMMAND" ]; then
fi fi
OLDIFS="$IFS" OLDIFS="$IFS"
# Unquoted $VAR or VAR=() of unquoted value does glob expansion (against the current dir's contents)
# after IFS-splitting which is undesired
set -f
IFS=: versions=(${PYENV_VERSION:-$(pyenv-version-name -f)}) IFS=: versions=(${PYENV_VERSION:-$(pyenv-version-name -f)})
set +f
IFS="$OLDIFS" IFS="$OLDIFS"
declare -a nonexistent_versions declare -a nonexistent_versions
for version in "${versions[@]}" "$system"; do for version in "${versions[@]}" "$system"; do
if [ "$version" = "system" ]; then if [ "$version" = "system" ]; then
PATH="$(remove_from_path "${PYENV_ROOT}/shims")" PROGRAM="$(echo "$PYENV_COMMAND" | tr a-z- A-Z_ | sed 's/[^A-Z0-9_]/_/g')"
PYENV_COMMAND_PATH="$(command -v "$PYENV_COMMAND" || true)" NR_CUSTOM_SHIM_PATHS="_PYENV_SHIM_PATHS_$PROGRAM"
SEARCH_PATH="$(remove_from_path "${PYENV_ROOT}/shims${!NR_CUSTOM_SHIM_PATHS:+:${!NR_CUSTOM_SHIM_PATHS}}")"
PYENV_COMMAND_PATH="$(PATH="$SEARCH_PATH" command -v "$PYENV_COMMAND" || true)"
else else
# $version may be a prefix to be resolved by pyenv-latest # $version may be a prefix to be resolved by pyenv-latest
version_path="$(pyenv-prefix "${version}" 2>/dev/null)" || \ version_path="$(pyenv-prefix "${version}" 2>/dev/null)" || \
@ -106,8 +116,8 @@ else
echo "The \`$PYENV_COMMAND' command exists in these Python versions:" echo "The \`$PYENV_COMMAND' command exists in these Python versions:"
echo "$versions" | sed 's/^/ /g' echo "$versions" | sed 's/^/ /g'
echo echo
echo "Note: See 'pyenv help global' for tips on allowing both" echo "Note: See 'pyenv help global' for tips on allowing multiple"
echo " python2 and python3 to be found." echo " Python versions to be found at the same time."
} >&2 } >&2
fi fi
fi fi

2
plugins/.gitignore vendored
View file

@ -2,4 +2,6 @@
!/.gitignore !/.gitignore
!/version-ext-compat !/version-ext-compat
!/python-build !/python-build
!/pyenv-binary
!/pyenv-link
/python-build/test/build /python-build/test/build

View file

@ -0,0 +1,94 @@
# pyenv-binary (experimental)
Package an installed Python version into a relocatable archive that can be
installed on another machine.
This is experimental and intentionally decoupled: it does not change
`pyenv install` or any other command. You drive it explicitly through
`pyenv binary`. Run `pyenv binary <command> --help` for details on a command.
## Portability
An archive is portable across machines that share its build platform (OS,
architecture and a compatible libc) and have the recorded system libraries. It
is not portable across, say, glibc and musl, or to an older glibc; the platform
and dependency metadata exist to catch that.
## Commands
### `pyenv binary package [-v|--verbose] <version>[:<entry>] --archive-base-url <url>`
Installs `<version>` from source under a separate name, packages that install
with `save`, then emits a python-build definition for it with
`generate-installer`. With no explicit entry, the name is generated from the
current platform, platform version and architecture. An explicit entry keeps
the existing custom-build workflow.
Pass `-v` to show build progress from `pyenv install`.
```sh
pyenv binary package 3.12.7 \
--archive-base-url https://example.com/binaries
# On Debian 12 x86_64, writes 3.12.7-debian-12-x86_64.tar.gz,
# its .meta file and a `3.12.7-debian-12-x86_64' definition.
pyenv binary package 3.12.7:company-python \
--archive-base-url https://example.com/binaries
```
The archive, metadata and definition land in the current directory, named after
the entry. Host the archive under `<url>` and drop the definition into
python-build's definition directory.
### `pyenv binary package-name <version>`
Prints the automatically generated entry name without building anything. Linux
uses the distribution name and version, macOS uses the macOS version, and other
systems use the name and release reported by `uname`. All names include the
architecture.
```sh
pyenv binary package-name 3.12.7
# 3.12.7-debian-12-x86_64
```
### `pyenv binary save <version> [<output-dir>] [--name <name>]`
Packs an installed version into `<version>-<platform>.tar.gz` (relative paths)
and writes `<version>-<platform>.meta` describing the build platform (OS, arch,
distro and libc version) and the system libraries the build links against. Use
`--name` to set a different base name for both files.
```sh
pyenv binary save 3.12.7 ./dist
```
### `pyenv binary generate-installer <metadata-file> --archive-url <url> [-o <output>]`
Reads a `.meta` file and emits a python-build definition. Drop it into
python-build's definition directory and `pyenv install <name>` installs the
archive like any other version. The archive location is a parameter, so you can
host it anywhere (it does not have to be a pyenv location); the archive itself
must sit next to the `.meta` file so its checksum can be baked into the
definition.
The definition refuses to install on a different OS/architecture, or an older
glibc, than the archive was built for, and checks that the system libraries it
needs are present.
```sh
pyenv binary generate-installer ./dist/3.12.7-linux-x86_64.meta \
--archive-url https://example.com/3.12.7-linux-x86_64.tar.gz \
-o "$(pyenv root)/plugins/python-build/share/python-build/3.12.7-linux-x86_64"
pyenv install 3.12.7-linux-x86_64
```
### `pyenv binary relocate <prefix>`
Rewrites the rpaths of a Python tree unpacked into `<prefix>` so the interpreter
and its extension modules load the bundled libraries from there rather than from
the path the archive was built at. Uses `patchelf`. The generated definition
calls this; you rarely run it by hand.
Relocation is implemented for Linux; macOS is not wired up yet.

View file

@ -0,0 +1,65 @@
#!/usr/bin/env bash
#
# Summary: Package an installed Python version as a relocatable binary (experimental)
#
# Usage: pyenv binary <command> [<args>]
#
# `pyenv binary` packages an already-installed Python version into a relocatable
# archive that can be installed on another machine. It is experimental and does
# not touch `pyenv install' or any other command.
#
# Run `pyenv binary' to list its commands, or `pyenv binary <command> --help'
# for command-specific help.
#
set -e
[ -n "$PYENV_DEBUG" ] && set -x
libexec="${BASH_SOURCE%/*}/../libexec"
# The pyenv launcher only puts a plugin's bin on PATH, but `pyenv-help' looks a
# command up there. Add our libexec so the subcommands, and the help text they
# print, can be found.
export PATH="${libexec}:${PATH}"
list_commands() {
local path
for path in "$libexec"/pyenv-binary-*; do
[ -e "$path" ] && echo "${path##*/pyenv-binary-}"
done
}
# Provide pyenv completions
if [ "$1" = "--complete" ]; then
shift
if [ -z "$1" ]; then
list_commands
else
command_path="${libexec}/pyenv-binary-$1"
shift
[ -x "$command_path" ] && exec "$command_path" --complete "$@"
fi
exit
fi
subcommand="$1"
if [ -z "$subcommand" ]; then
{ pyenv-help binary
echo
echo "Commands:"
list_commands | sed 's/^/ /'
} >&2
exit 1
fi
shift
command_path="${libexec}/pyenv-binary-${subcommand}"
if [ ! -x "$command_path" ]; then
echo "pyenv-binary: no such command \`${subcommand}'" >&2
exit 1
fi
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
exec pyenv-help "binary-${subcommand}"
fi
exec "$command_path" "$@"

View file

@ -0,0 +1,224 @@
#!/usr/bin/env bash
#
# Summary: Generate a python-build definition for a saved binary archive
#
# Usage: pyenv binary generate-installer <metadata-file> --archive-url <url> [-o <output>]
#
# Reads a metadata file written by `pyenv binary save' and emits a python-build
# definition. Dropped into python-build's definition directory, it installs the
# archive like any other version: `pyenv install' downloads it from <url>,
# checks the platform and the required system libraries, unpacks it, then
# rewrites rpaths so the copy runs from its prefix.
#
# The archive must sit next to the metadata file so its checksum can be baked
# into the definition; `pyenv binary save' writes the two together.
#
# <metadata-file> A .meta file written by `pyenv binary save'.
# The corresponding binary package written by `pyenv binary save'
# needs to be present alongside it.
# --archive-url <url> The URL for the resulting installation script to download
# the package from.
# -o <output> Write the definition here (default: stdout).
#
set -e
[ -n "$PYENV_DEBUG" ] && set -x
# Provide pyenv completions
if [ "$1" = "--complete" ]; then
echo --archive-url
echo -o
exit
fi
metadata=""
archive_url=""
output=""
while [ $# -gt 0 ]; do
case "$1" in
--archive-url )
[ $# -ge 2 ] || { echo "pyenv-binary: --archive-url needs a value" >&2; exit 1; }
archive_url="$2"; shift 2 ;;
-o )
[ $# -ge 2 ] || { echo "pyenv-binary: -o needs a value" >&2; exit 1; }
output="$2"; shift 2 ;;
-* )
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
* )
[ -z "$metadata" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; }
metadata="$1"; shift ;;
esac
done
if [ -z "$metadata" ] || [ -z "$archive_url" ]; then
pyenv-help --usage binary-generate-installer >&2
exit 1
fi
version="" os="" arch="" distro="" libc="" build_prefix="" archive=""
deps=""
while IFS='=' read -r key value; do
case "$key" in
version ) version="$value" ;;
os ) os="$value" ;;
arch ) arch="$value" ;;
distro ) distro="$value" ;;
libc ) libc="$value" ;;
build_prefix ) build_prefix="$value" ;;
archive ) archive="$value" ;;
dep ) deps="${deps:+$deps }$value" ;;
esac
done < "$metadata"
# Fail here if the metadata is incomplete, rather than bake a blank into the
# definition where a missing arch would give a confusing platform error.
for field in version os arch archive; do
if [ -z "${!field}" ]; then
echo "pyenv-binary: metadata is missing \`${field}'" >&2
exit 1
fi
done
# Mach-O load commands contain the prefix where Python was built.
if [ "$os" = "Darwin" ] && [ -z "$build_prefix" ]; then
echo "pyenv-binary: metadata is missing \`build_prefix'" >&2
exit 1
fi
# The glibc floor only applies on Linux; require it there so the definition can
# tell whether the target is new enough to run the binaries.
if [ "$os" = "Linux" ] && [ -z "$libc" ]; then
echo "pyenv-binary: metadata is missing \`libc'" >&2
exit 1
fi
# python-build downloads the archive itself, so the checksum has to be baked in.
# The archive sits next to the metadata `save' wrote it alongside.
archive_path="$(dirname "$metadata")/${archive}"
if [ ! -r "$archive_path" ]; then
echo "pyenv-binary: cannot read the archive \`${archive_path}' to checksum it" >&2
exit 1
fi
if command -v sha256sum >/dev/null 2>&1; then
sha256="$(sha256sum "$archive_path")"; sha256="${sha256%% *}"
elif command -v shasum >/dev/null 2>&1; then
sha256="$(shasum -a 256 "$archive_path")"; sha256="${sha256%% *}"
elif command -v openssl >/dev/null 2>&1; then
sha256="$(openssl dgst -sha256 "$archive_path")"; sha256="${sha256##* }"
else
echo "pyenv-binary: need sha256sum, shasum or openssl to checksum the archive" >&2
exit 1
fi
emit() {
# The values known now go in as %q-quoted assignments so a stray quote or space
# in the URL or metadata cannot break the definition or inject into it. The body
# below is a quoted here-doc, verbatim.
{
printf '# python-build definition for prebuilt Python %s (%s/%s%s).\n' \
"$version" "$os" "$arch" "${distro:+, $distro}"
echo "# Generated by \`pyenv binary generate-installer'."
echo
printf 'EXPECT_OS=%q\n' "$os"
printf 'EXPECT_ARCH=%q\n' "$arch"
printf 'EXPECT_LIBC=%q\n' "$libc"
printf 'BUILD_PREFIX=%q\n' "$build_prefix"
printf 'VERSION=%q\n' "$version"
printf 'ARCHIVE_URL=%q\n' "$archive_url"
printf 'SHA256=%q\n' "$sha256"
printf 'DEPS=%q\n' "$deps"
}
cat <<'EOF'
os="$(uname -s)"
arch="$(uname -m)"
if [ "$os" != "$EXPECT_OS" ] || [ "$arch" != "$EXPECT_ARCH" ]; then
echo "pyenv-binary: this archive is for ${EXPECT_OS}/${EXPECT_ARCH}, not ${os}/${arch}" >&2
exit 1
fi
# Refuse a target whose glibc is older than the one the archive was built on;
# the dynamic loader would reject the binaries.
case "$EXPECT_LIBC" in
"glibc "* )
build_libc="${EXPECT_LIBC#glibc }"
target_libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
case "$target_libc" in
"glibc "* )
target_libc="${target_libc#glibc }"
older="$(printf '%s\n%s\n' "$build_libc" "$target_libc" | sort -V | head -n1)"
if [ "$older" = "$target_libc" ] && [ "$target_libc" != "$build_libc" ]; then
echo "pyenv-binary: archive needs glibc ${build_libc} or newer, but this system has ${target_libc}" >&2
exit 1
fi
;;
esac
;;
esac
# Required system libraries must already be present on the target. Linux lists
# the soname first; FreeBSD lists it in the resolved path, so normalize that
# path before matching exact names.
cache=""
if command -v ldconfig &>/dev/null; then
case "$os" in
FreeBSD )
cache="$(ldconfig -r 2>/dev/null)" &&
cache="$(printf '%s\n' "$cache" | awk -F ' *=> *' '{ sub(/^.*\//, "", $2); print $2 }')" \
|| cache=""
;;
* )
cache="$(ldconfig -p 2>/dev/null)" || cache=""
;;
esac
fi
if [ -n "$cache" ]; then
missing=""
for dep in $DEPS; do
printf '%s\n' "$cache" | awk -v d="$dep" '$1 == d { found = 1 } END { exit !found }' \
|| missing="${missing} ${dep}"
done
if [ -n "$missing" ]; then
echo "pyenv-binary: missing required system libraries:${missing}" >&2
exit 1
fi
elif [ -n "$DEPS" ]; then
echo "pyenv-binary: cannot check required system libraries (ldconfig cache unavailable)" >&2
fi
case "$os" in
Darwin )
for tool in otool install_name_tool; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "pyenv-binary: need ${tool} to relocate the binary" >&2
exit 1
fi
done
;;
* )
if ! command -v patchelf >/dev/null 2>&1; then
echo "pyenv-binary: need patchelf to relocate the binary" >&2
exit 1
fi
;;
esac
build_package_relocate() {
if [ "$os" = "Darwin" ]; then
pyenv binary relocate "$PREFIX_PATH" "$BUILD_PREFIX"
else
pyenv binary relocate "$PREFIX_PATH"
fi
}
install_package "Python-${VERSION}-binary" "${ARCHIVE_URL}#${SHA256}" copy relocate
EOF
}
if [ -n "$output" ]; then
emit > "$output"
echo "Wrote definition to ${output}"
else
emit
fi

View file

@ -0,0 +1,83 @@
#!/usr/bin/env bash
#
# Summary: Create an installable binary package from a Python version
#
# Usage: pyenv binary package [-v|--verbose] <version>[:<entry>] --archive-base-url <url>
#
# Installs <version> from source under a separate entry name, saves it as a
# binary package, then emits a python-build definition for that package.
# The archive, metadata and definition are written to the current directory
# as <entry>.tar.gz, <entry>.meta and <entry>.
#
# <version> A version `pyenv install' knows how to build.
# <entry> The optional name to build under and install the
# binary as. If omitted, a name is generated from the
# current platform, platform version and architecture.
# -v,--verbose Show build progress from `pyenv install'.
# --archive-base-url <url>
# Where the archive will be hosted; the definition
# downloads it from <url>/<entry>.tar.gz.
#
set -e
[ -n "$PYENV_DEBUG" ] && set -x
# Provide pyenv completions
if [ "$1" = "--complete" ]; then
echo --archive-base-url
echo --verbose
exec pyenv-install --list --bare
fi
spec=""
archive_base_url=""
verbose=""
while [ $# -gt 0 ]; do
case "$1" in
--archive-base-url )
[ $# -lt 2 ] && { echo "pyenv-binary: --archive-base-url needs a value" >&2; exit 1; }
archive_base_url="$2"; shift ;;
-v|--verbose)
verbose=1 ;;
-* )
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
* )
[ -z "$spec" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; }
spec="$1"
;;
esac
shift
done
if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then
pyenv-help --usage binary-package >&2
exit 1
fi
case "$spec" in
*?:?* ) entry="${spec##*:}" ;;
* ) entry="$(pyenv-binary-package-name "$spec")"; spec="${spec}:${entry}" ;;
esac
case "$entry" in
# `pyenv install' reads a trailing `:latest' as part of the version rather than
# as an alias, so nothing would end up installed under that name.
latest )
echo "pyenv-binary: \`latest' cannot be used as an entry name" >&2
exit 1
;;
# The entry becomes a directory name under versions/. `pyenv install' does not
# validate the alias, and `save' only checks once the build is done, so refuse
# a name that could point elsewhere before compiling anything.
*/* | .. | . )
echo "pyenv-binary: invalid entry name \`${entry}'" >&2
exit 1
;;
esac
# `pyenv install' puts a `<version>:<alias>' build under versions/<alias>.
pyenv-install ${verbose:+--verbose} "$spec"
pyenv-binary-save "$entry" "$PWD" --name "$entry"
pyenv-binary-generate-installer "${entry}.meta" \
--archive-url "${archive_base_url%/}/${entry}.tar.gz" -o "$entry"

View file

@ -0,0 +1,73 @@
#!/usr/bin/env bash
#
# Summary: Generate a binary package name for the current platform
#
# Usage: pyenv binary package-name <version>
#
# Prints a package name containing the Python version, platform name,
# platform version and architecture.
#
set -e
[ -n "$PYENV_DEBUG" ] && set -x
if [ "$1" = "--complete" ]; then
exec pyenv-install --list --bare
fi
if [ $# -ne 1 ]; then
pyenv-help --usage binary-package-name >&2
exit 1
fi
version="$1"
case "$version" in
*/* | .. | . | *[[:cntrl:]]* )
echo "pyenv-binary: invalid version name \`${version}'" >&2
exit 1
;;
esac
version="$(pyenv-latest -f -k "$version")"
os="$(uname -s)"
arch="$(uname -m)"
distro=""
distro_version=""
case "$os" in
Linux )
if type -p lsb_release >/dev/null; then
distro="$(lsb_release -si)"
distro_version="$(lsb_release -sr)"
elif [ -r /etc/os-release ]; then
distro="$(. /etc/os-release && printf '%s' "${ID:-}")"
distro_version="$(. /etc/os-release && printf '%s' "${VERSION_ID:-}")"
fi
;;
Darwin )
distro="macos"
distro_version="$(sw_vers -productVersion)"
;;
* )
distro="$os"
distro_version="$(uname -r)"
;;
esac
slug() {
printf '%s' "$1" |
tr '[:upper:] ' '[:lower:]-' |
tr -cd 'a-z0-9._-'
}
distro="$(slug "$distro")"
distro_version="$(slug "$distro_version")"
arch="$(slug "$arch")"
if [ -z "$distro" ] || [ -z "$distro_version" ] || [ -z "$arch" ]; then
echo "pyenv-binary: could not determine the package platform" >&2
exit 1
fi
printf '%s-%s-%s-%s\n' "$version" "$distro" "$distro_version" "$arch"

View file

@ -0,0 +1,137 @@
#!/usr/bin/env bash
#
# Summary: Rewrite an unpacked Python's library paths so it runs from its prefix
#
# Usage: pyenv binary relocate <prefix> [<build-prefix>]
#
# Rewrites the library paths of a Python tree that was unpacked into <prefix>
# so the interpreter and its extension modules load the bundled libraries from
# their new location rather than the path it was built at. Requires patchelf on
# Linux and FreeBSD, or otool and install_name_tool on macOS.
#
# The definition that `pyenv binary generate-installer' produces calls this
# after `install_package ... copy' has laid the tree down.
#
set -e
[ -n "$PYENV_DEBUG" ] && set -x
if [ "$1" = "--complete" ]; then
exit
fi
prefix="$1"
if [ -z "$prefix" ]; then
pyenv-help --usage binary-relocate >&2
exit 1
fi
if [ "$(uname -s)" = "Darwin" ]; then
build_prefix="${2%/}"
if [ -z "$build_prefix" ]; then
echo "pyenv-binary: need the original build prefix to relocate a macOS binary" >&2
exit 1
fi
for tool in otool install_name_tool; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "pyenv-binary: need ${tool} to relocate the binary" >&2
exit 1
fi
done
relocate_macho() {
local file="$1" new_rpath="$2"
local load_commands install_ids rpaths dependency install_id rpath replacement
load_commands="$(otool -L "$file")"
install_ids="$(otool -D "$file" 2>/dev/null || true)"
rpaths="$(otool -l "$file")"
install_id="$(printf '%s\n' "$install_ids" | sed -n '2s/^[[:space:]]*//p')"
while IFS= read -r dependency; do
case "$dependency" in
"$build_prefix"/lib/* )
if [ "$dependency" != "$install_id" ]; then
replacement="@rpath/${dependency#"$build_prefix"/lib/}"
install_name_tool -change "$dependency" "$replacement" "$file"
fi
;;
esac
done < <(printf '%s\n' "$load_commands" | tail -n +2 | sed -E \
's/^[[:space:]]*//; s/[[:space:]]+\(compatibility version.*$//')
case "$install_id" in
"$build_prefix"/lib/* )
replacement="@rpath/${install_id#"$build_prefix"/lib/}"
install_name_tool -id "$replacement" "$file"
;;
esac
while IFS= read -r rpath; do
if [ "$rpath" = "$build_prefix/lib" ]; then
install_name_tool -rpath "$rpath" "$new_rpath" "$file"
fi
done < <(printf '%s\n' "$rpaths" | awk '
/cmd LC_RPATH/ { found = 1; next }
found && /^[[:space:]]*path / {
sub(/^[[:space:]]*path /, "")
sub(/ \(offset [0-9]+\)$/, "")
print
found = 0
}
')
}
found=0
for file in "$prefix"/bin/*; do
[[ -f $file && -x $file && ! -L $file ]] || continue
otool -L "$file" >/dev/null 2>&1 || continue
relocate_macho "$file" '@executable_path/../lib'
found=1
done
if [ "$found" -eq 0 ]; then
echo "pyenv-binary: found no interpreter to relocate under \`${prefix}/bin'" >&2
exit 1
fi
for file in "$prefix"/lib/*.dylib; do
[[ -f $file && ! -L $file ]] || continue
relocate_macho "$file" '@loader_path'
done
while IFS= read -r so; do
relocate_macho "$so" '@loader_path/../..'
done < <(find "$prefix" -type f -path '*/lib/python*/lib-dynload/*.so')
exit
fi
# patchelf can add an rpath and write one of any length; chrpath can only shorten
# an existing one, which is not enough to relocate every build, so require it.
if ! command -v patchelf >/dev/null 2>&1; then
echo "pyenv-binary: need patchelf to relocate the binary" >&2
exit 1
fi
# The interpreter loads libpython from ../lib. Patch every ELF binary in bin/ so
# this holds whatever the interpreter is named (python3, python2.7, ...); the
# scripts alongside it (pip, idle) are not ELF, so `--print-rpath' fails on them
# and they are skipped. A tree with no interpreter at all did not unpack the way
# we expect, so treat that as an error rather than quietly relocating nothing.
found=0
for file in "$prefix"/bin/*; do
[[ -f $file && -x $file && ! -L $file ]] || continue
patchelf --print-rpath "$file" >/dev/null 2>&1 || continue
patchelf --set-rpath "$prefix/lib" "$file"
found=1
done
if [ "$found" -eq 0 ]; then
echo "pyenv-binary: found no interpreter to relocate under \`${prefix}/bin'" >&2
exit 1
fi
# The extension modules CPython built are the only other objects with an rpath
# into the old prefix, so point them at lib/ as well. Anything a wheel installed
# carries an rpath of its own, often into a bundled library directory beside it,
# and would stop loading if we overwrote it. Each match is an ELF object we expect
# to patch, so let a failure stop us rather than swallow it.
while IFS= read -r so; do
patchelf --set-rpath "$prefix/lib" "$so"
done < <(find "$prefix" -path '*/lib-dynload/*.so')

View file

@ -0,0 +1,157 @@
#!/usr/bin/env bash
#
# Summary: Save an installed Python version as a relocatable archive
#
# Usage: pyenv binary save <version> [<output-dir>] [--name <name>]
#
# Packs an installed version into a relocatable .tar.gz (relative paths) and
# writes a metadata file listing the build platform and the system libraries
# it links against, so an installer can check compatibility before unpacking.
#
# <version> An installed version, as listed by `pyenv versions --bare'.
# <output-dir> Where to write the archive and metadata (default: `.').
# --name <name> Use <name> as the archive and metadata base name instead of
# <version>-<platform>.
#
set -e
[ -n "$PYENV_DEBUG" ] && set -x
# Provide pyenv completions
if [ "$1" = "--complete" ]; then
echo --name
exec pyenv-versions --bare
fi
version=""
output_dir=""
package_name=""
while [ $# -gt 0 ]; do
case "$1" in
--name )
[ $# -ge 2 ] && [ -n "$2" ] || { echo "pyenv-binary: --name needs a value" >&2; exit 1; }
package_name="$2"; shift 2 ;;
-* )
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
* )
if [ -z "$version" ]; then
version="$1"
elif [ -z "$output_dir" ]; then
output_dir="$1"
else
echo "pyenv-binary: unexpected argument \`$1'" >&2
exit 1
fi
shift ;;
esac
done
output_dir="${output_dir:-$PWD}"
if [ -z "$version" ]; then
echo "Usage: pyenv binary save <version> [<output-dir>] [--name <name>]" >&2
exit 1
fi
# A version is a single directory name under versions/. With no slash allowed,
# the only remaining names that could point elsewhere are `.' and `..'.
case "$version" in
*/* | .. | . )
echo "pyenv-binary: invalid version name \`${version}'" >&2
exit 1
;;
esac
case "$package_name" in
*/* | .. | . | *[[:cntrl:]]* )
echo "pyenv-binary: invalid package name \`${package_name}'" >&2
exit 1
;;
esac
prefix="${PYENV_ROOT}/versions/${version}"
if [ ! -d "${prefix}/bin" ]; then
echo "pyenv-binary: version \`${version}' is not installed" >&2
exit 1
fi
os="$(uname -s)"
arch="$(uname -m)"
platform="$(printf '%s' "$os" | tr '[:upper:]' '[:lower:]')-${arch}"
# Record the distro and libc version. Platform and arch alone are too coarse to
# judge compatibility: a build is only portable to a matching libc (e.g. a
# glibc 2.36 build will not load on an older glibc, nor on musl at all).
distro=""
libc=""
if [ -r /etc/os-release ]; then
distro="$( . /etc/os-release && printf '%s %s' "${ID:-}" "${VERSION_ID:-}" )"
elif [ "$os" = "Darwin" ]; then
distro="macos $(sw_vers -productVersion 2>/dev/null)"
fi
if [ "$os" = "Linux" ]; then
libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
fi
if [ "$os" != "Darwin" ] && ! LC_ALL=C readelf --version &>/dev/null; then
echo "pyenv-binary: need readelf to inspect shared libraries" >&2
exit 1
fi
# List the external shared libraries the install links against: those that
# resolve outside its own prefix, so they must already exist on the target.
# The interpreter plus every bundled shared object are inspected.
system_deps() {
local f needed
{
for f in "${prefix}"/bin/python*; do
[ -e "$f" ] && printf '%s\n' "$f"
done
# CPython ships its extension modules as *.so (and *.dylib on macOS). A
# bare *.so.* is unusual for CPython itself, but the odd build carries a
# versioned copy alongside, so match it too rather than miss a dependency.
find "${prefix}" -type f \( -name '*.so' -o -name '*.so.*' -o -name '*.dylib' \)
} | sort -u | while IFS= read -r f; do
if [ "$os" = "Darwin" ]; then
otool -L "$f" 2>/dev/null | tail -n +2 | awk -v pfx="${prefix}/" \
'$1 !~ /^@/ && substr($1, 1, length(pfx)) != pfx { print $1 }'
else
# GNU binutils 2.44 writes "(NEEDED) ... [name]"; FreeBSD 15.1 writes
# "NEEDED ... [name]". readelf(1) documents -d, not its text format.
needed="$(LC_ALL=C readelf -dW "$f" 2>/dev/null | awk \
'$2 == "(NEEDED)" || $2 == "NEEDED" { sub(/^.*\[/, ""); sub(/\].*$/, ""); print }' | tr '\n' ' ')"
# Linux and FreeBSD ldd write resolved libraries as
# "name => /absolute/path (address)"; entries without "=>" are ignored.
LC_ALL=C ldd "$f" 2>/dev/null | awk -v needed="$needed" -v pfx="${prefix}/" '
BEGIN {
split(needed, deps, " ")
for (i in deps) direct[deps[i]] = 1
}
$1 in direct && $2 == "=>" && $3 ~ /^\// && substr($3, 1, length(pfx)) != pfx { print $1 }
'
fi
done | sort -u
}
mkdir -p "$output_dir"
package_name="${package_name:-${version}-${platform}}"
archive="${package_name}.tar.gz"
metadata="${package_name}.meta"
tar -C "$(dirname "$prefix")" -czf "${output_dir}/${archive}" "$(basename "$prefix")"
{
echo "# pyenv-binary metadata"
echo "version=${version}"
echo "os=${os}"
echo "arch=${arch}"
echo "platform=${platform}"
[ -n "$distro" ] && echo "distro=${distro}"
[ -n "$libc" ] && echo "libc=${libc}"
echo "build_prefix=${prefix}"
echo "archive=${archive}"
system_deps | while IFS= read -r dep; do
[ -n "$dep" ] && echo "dep=${dep}"
done
} > "${output_dir}/${metadata}"
echo "Saved ${archive} and ${metadata} to ${output_dir}"

View file

@ -0,0 +1,212 @@
#!/usr/bin/env bats
load test_helper
create_meta() {
local os="${1-Linux}"
local arch="${2-x86_64}"
local libc="${3-glibc 2.17}"
local build_prefix="${4-}"
local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
local meta="${BATS_TEST_TMPDIR}/sample.meta"
rm -rf "${BATS_TEST_TMPDIR}/archive"
mkdir -p "${BATS_TEST_TMPDIR}/archive/3.12.7/bin"
printf '#!/bin/sh\n' > "${BATS_TEST_TMPDIR}/archive/3.12.7/bin/python"
chmod +x "${BATS_TEST_TMPDIR}/archive/3.12.7/bin/python"
tar -C "${BATS_TEST_TMPDIR}/archive" -czf "$archive" 3.12.7
{
echo "version=3.12.7"
echo "os=${os}"
echo "arch=${arch}"
[ -z "$libc" ] || echo "libc=${libc}"
[ -z "$build_prefix" ] || echo "build_prefix=${build_prefix}"
echo "archive=${archive##*/}"
} > "$meta"
echo "$meta"
}
@test "completion lists the options" {
run pyenv-binary-generate-installer --complete
assert_success "--archive-url
-o"
}
@test "fails with no arguments" {
create_stub pyenv-help "echo usage"
run pyenv-binary-generate-installer
assert_failure "usage"
}
@test "fails without an archive url" {
create_stub pyenv-help "echo usage"
run pyenv-binary-generate-installer "$(create_meta)"
assert_failure "usage"
}
@test "fails when --archive-url has no value" {
run pyenv-binary-generate-installer "$(create_meta)" --archive-url
assert_failure "pyenv-binary: --archive-url needs a value"
}
@test "rejects a second positional argument" {
run pyenv-binary-generate-installer "$(create_meta)" extra --archive-url http://x/a.tar.gz
assert_failure "pyenv-binary: unexpected argument \`extra'"
}
@test "fails for a metadata file that cannot be read" {
run pyenv-binary-generate-installer /no/such.meta --archive-url http://x/a.tar.gz
assert_failure
}
@test "fails when macOS metadata is missing the build prefix" {
run pyenv-binary-generate-installer "$(create_meta Darwin arm64 '')" \
--archive-url http://x/a.tar.gz
assert_failure "pyenv-binary: metadata is missing \`build_prefix'"
}
@test "quotes the macOS build prefix in the generated definition" {
local out="${BATS_TEST_TMPDIR}/definition"
pyenv-binary-generate-installer \
"$(create_meta Darwin arm64 '' '/build prefix/$name')" \
--archive-url http://example.com/a.tar.gz -o "$out"
run grep '^BUILD_PREFIX=' "$out"
assert_success 'BUILD_PREFIX=/build\ prefix/\$name'
}
@test "fails when required metadata is missing" {
local field meta
for field in version os arch archive; do
meta="$(create_meta)"
#cannot use -i: GNU sed requires -i[suf], BSD sed required -i <suf>
sed "/^${field}=/d" "$meta" > "${meta}.tmp"; mv "${meta}"{.tmp,}
run pyenv-binary-generate-installer "$meta" --archive-url http://x/a.tar.gz
assert_failure "pyenv-binary: metadata is missing \`${field}'"
done
}
@test "fails when Linux metadata is missing libc" {
run pyenv-binary-generate-installer "$(create_meta Linux x86_64 '')" \
--archive-url http://x/a.tar.gz
assert_failure "pyenv-binary: metadata is missing \`libc'"
}
@test "refuses a host whose glibc is older than the archive" {
local out="${BATS_TEST_TMPDIR}/definition"
pyenv-binary-generate-installer "$(create_meta Linux x86_64 'glibc 99.0')" \
--archive-url http://example.com/a.tar.gz -o "$out"
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
create_stub getconf 'echo "glibc 2.31"'
run bash "$out"
assert_failure "pyenv-binary: archive needs glibc 99.0 or newer, but this system has 2.31"
}
@test "checks required libraries in the FreeBSD ldconfig cache" {
local out="${BATS_TEST_TMPDIR}/definition"
local meta="$(create_meta FreeBSD amd64 '')"
printf 'dep=libc.so.7\ndep=libmissing.so.1\n' >> "$meta"
pyenv-binary-generate-installer "$meta" \
--archive-url http://example.com/a.tar.gz -o "$out"
create_stub uname 'case "$1" in -s) echo FreeBSD;; -m) echo amd64;; esac'
create_stub ldconfig '[ "$1" = "-r" ] && echo "0:-lc.7=>/lib/libc.so.7"'
run bash "$out"
assert_failure "pyenv-binary: missing required system libraries: libmissing.so.1"
}
@test "checks for patchelf before installing the archive" {
local out="${BATS_TEST_TMPDIR}/definition"
pyenv-binary-generate-installer "$(create_meta)" \
--archive-url http://example.com/a.tar.gz -o "$out"
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
create_stub getconf 'echo "glibc 2.31"'
PATH="$(path_without patchelf)" run bash "$out"
assert_failure "pyenv-binary: need patchelf to relocate the binary"
}
@test "checks for otool before installing a macOS archive" {
local out="${BATS_TEST_TMPDIR}/definition"
pyenv-binary-generate-installer \
"$(create_meta Darwin arm64 '' /build/3.12.7)" \
--archive-url http://example.com/a.tar.gz -o "$out"
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
create_path_executable install_name_tool true
PATH="$(path_without otool)" run bash "$out"
assert_failure "pyenv-binary: need otool to relocate the binary"
}
@test "checks for install_name_tool before installing a macOS archive" {
local out="${BATS_TEST_TMPDIR}/definition"
pyenv-binary-generate-installer \
"$(create_meta Darwin arm64 '' /build/3.12.7)" \
--archive-url http://example.com/a.tar.gz -o "$out"
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
create_path_executable otool true
PATH="$(path_without install_name_tool)" run bash "$out"
assert_failure "pyenv-binary: need install_name_tool to relocate the binary"
}
@test "the generated macOS definition preserves both relocation arguments" {
local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
local cache="${BATS_TEST_TMPDIR}/cache"
local definition="${BATS_TEST_TMPDIR}/definition"
local prefix="${BATS_TEST_TMPDIR}/install"
pyenv-binary-generate-installer \
"$(create_meta Darwin arm64 '' '/build prefix/$name')" \
--archive-url http://example.com/3.12.7.tar.gz -o "$definition"
mkdir -p "$cache"
cp "$archive" "${cache}/Python-3.12.7-binary.tar.gz"
create_stub pyenv 'printf "argc=%s\narg1=%s\narg2=%s\narg3=%s\narg4=%s\n" "$#" "$1" "$2" "$3" "$4"'
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
create_stub sw_vers 'echo 15.5'
create_stub ldconfig true
create_path_executable otool true
create_path_executable install_name_tool true
PYTHON_BUILD_CACHE_PATH="$cache" run \
"${BATS_TEST_DIRNAME}/../../python-build/bin/python-build" "$definition" "$prefix"
assert_success
assert_line "argc=4"
assert_line "arg1=binary"
assert_line "arg2=relocate"
assert_line "arg3=${prefix}"
assert_line 'arg4=/build prefix/$name'
assert_line "Installed Python-3.12.7-binary to ${prefix}"
}
@test "fails when the archive is not beside the metadata" {
local meta="$(create_meta)"
rm "${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
run pyenv-binary-generate-installer "$meta" --archive-url http://x/a.tar.gz
assert_failure "pyenv-binary: cannot read the archive \`${BATS_TEST_TMPDIR}/3.12.7.tar.gz' to checksum it"
}
@test "the generated definition is installable with python-build" {
ldconfig -p &>/dev/null || skip "ldconfig with -p is not present"
local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
local cache="${BATS_TEST_TMPDIR}/cache"
local definition="${BATS_TEST_TMPDIR}/definition"
local prefix="${BATS_TEST_TMPDIR}/install"
pyenv-binary-generate-installer "$(create_meta)" \
--archive-url http://example.com/3.12.7.tar.gz -o "$definition"
mkdir -p "$cache"
cp "$archive" "${cache}/Python-3.12.7-binary.tar.gz"
create_stub pyenv 'echo "pyenv $*"'
create_path_executable patchelf "exit 0"
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
PYTHON_BUILD_CACHE_PATH="$cache" run \
"${BATS_TEST_DIRNAME}/../../python-build/bin/python-build" "$definition" "$prefix"
assert_success
assert_line "pyenv binary relocate ${prefix}"
assert_line "Installed Python-3.12.7-binary to ${prefix}"
assert [ -x "${prefix}/bin/python" ]
}

View file

@ -0,0 +1,59 @@
#!/usr/bin/env bats
load test_helper
_setup() {
create_stub pyenv-latest '[ "$1" = "-f" ] && [ "$2" = "-k" ] && shift 2 && echo "$*"'
}
@test "completion lists installable versions" {
create_stub pyenv-install \
'[ "$*" = "--list --bare" ] && echo 3.13.14'
run pyenv-binary-package-name --complete
assert_success "3.13.14"
}
@test "fails without a version" {
create_stub pyenv-help 'echo usage'
run pyenv-binary-package-name
assert_failure "usage"
}
@test "rejects a second argument" {
create_stub pyenv-help 'echo usage'
run pyenv-binary-package-name 3.13.14 extra
assert_failure "usage"
}
@test "generates a package name for Linux" {
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
create_stub lsb_release 'case "$1" in -si) echo Debian;; -sr) echo 12;; esac'
run pyenv-binary-package-name 3.13.14
assert_success "3.13.14-debian-12-x86_64"
}
@test "generates a package name for macOS" {
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
create_stub sw_vers 'echo 15.5'
run pyenv-binary-package-name 3.13.14
assert_success "3.13.14-macos-15.5-arm64"
}
@test "resolves a version prefix when generating a package name" {
create_stub pyenv-latest '[ "$*" = "-f -k 3" ] && echo 3.14.7'
create_stub uname \
'case "$1" in -s) echo FreeBSD;; -m) echo amd64;; -r) echo 14.2-RELEASE-p3;; esac'
run pyenv-binary-package-name 3
assert_success "3.14.7-freebsd-14.2-release-p3-amd64"
}
@test "rejects an invalid version name" {
run pyenv-binary-package-name ../3.13.14
assert_failure "pyenv-binary: invalid version name \`../3.13.14'"
}

View file

@ -0,0 +1,131 @@
#!/usr/bin/env bats
load test_helper
# Make the build deterministic: `pyenv-install' just creates the prefix, and
# the platform tools report a fixed Linux target so the real `save' and
# `generate-installer' behave the same on any test host.
stub_build_environment() {
create_stub pyenv-install 'echo "${0##*/} $*"; mkdir -p "${PYENV_ROOT}/versions/${1##*:}/bin"'
create_stub pyenv-latest 'while (($#)); do case "$1" in -f|-k);; *)break;; esac; shift; done; echo "$*"'
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
create_stub getconf 'echo "glibc 2.17"'
create_stub readelf true
}
@test "-v|--verbose runs pyenv install verbosely" {
stub_build_environment
create_stub pyenv-binary-save true
create_stub pyenv-binary-generate-installer true
for opt in "" -v --verbose; do
run pyenv-binary-package $opt 3.12.7:3.12.7-test \
--archive-base-url http://example.com/binaries
assert_success "pyenv-install ${opt:+--verbose }3.12.7:3.12.7-test"
done
}
@test "completions" {
create_stub pyenv-install 'echo "${0##*/} $*"'
run pyenv-binary-package --complete
assert_success <<!
--archive-base-url
--verbose
pyenv-install --list --bare
!
}
@test "fails with no arguments" {
create_stub pyenv-help "echo usage"
run pyenv-binary-package
assert_failure "usage"
}
@test "fails without an archive base url" {
create_stub pyenv-help "echo usage"
run pyenv-binary-package 3.12.7:3.12.7-test
assert_failure "usage"
}
@test "fails when --archive-base-url has no value" {
run pyenv-binary-package 3.12.7:3.12.7-test --archive-base-url
assert_failure "pyenv-binary: --archive-base-url needs a value"
}
@test "rejects a second positional argument" {
run pyenv-binary-package 3.12.7:3.12.7-test extra --archive-base-url http://x/b
assert_failure "pyenv-binary: unexpected argument \`extra'"
}
@test "generates an entry name for a bare version" {
stub_build_environment
create_stub lsb_release 'case "$1" in -si) echo Debian;; -sr) echo 12;; esac'
cd "${BATS_TEST_TMPDIR}"
run pyenv-binary-package 3.12.7 --archive-base-url http://example.com/binaries
assert_success
assert [ -d "${PYENV_ROOT}/versions/3.12.7-debian-12-x86_64" ]
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-debian-12-x86_64.tar.gz" ]
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-debian-12-x86_64.meta" ]
run grep '^ARCHIVE_URL=' "${BATS_TEST_TMPDIR}/3.12.7-debian-12-x86_64"
assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-debian-12-x86_64.tar.gz"
}
@test "rejects an entry name containing a slash" {
run pyenv-binary-package "3.12.7:foo/bar" --archive-base-url http://x/b
assert_failure "pyenv-binary: invalid entry name \`foo/bar'"
}
@test "rejects \`latest' as an entry name" {
run pyenv-binary-package 3.12:latest --archive-base-url http://x/b
assert_failure "pyenv-binary: \`latest' cannot be used as an entry name"
}
@test "packages on macOS" {
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
create_stub pyenv-install 'echo install'
create_stub pyenv-binary-save 'echo save'
create_stub pyenv-binary-generate-installer 'echo generate-installer'
run pyenv-binary-package 3.12.7:3.12.7-test --archive-base-url http://x/b
assert_success "install
save
generate-installer"
}
@test "writes the archive, metadata and definition under the entry name (integration)" {
stub_build_environment
cd "${BATS_TEST_TMPDIR}"
run pyenv-binary-package 3.12.7:3.12.7-test \
--archive-base-url http://example.com/binaries
assert_success
assert [ -d "${PYENV_ROOT}/versions/3.12.7-test" ]
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test.tar.gz" ]
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test.meta" ]
run grep '^ARCHIVE_URL=' "${BATS_TEST_TMPDIR}/3.12.7-test"
assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz"
}
@test "correctly joins archive base url with a trailing slash" {
stub_build_environment
create_stub pyenv-binary-save true
create_stub pyenv-binary-generate-installer <<'!'
echo -n "${0##*/} "
while (($#)); do
case $1 in
--archive-url)
echo "$1 ${2:?}"
break
;;
esac
shift
done
!
run pyenv-binary-package 3.12.7:3.12.7-test \
--archive-base-url http://example.com/binaries/
assert_success
assert_line "pyenv-binary-generate-installer --archive-url http://example.com/binaries/3.12.7-test.tar.gz"
}

View file

@ -0,0 +1,221 @@
#!/usr/bin/env bats
load test_helper
_setup() {
create_stub pyenv-help "echo usage"
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
}
stub_patchelf() {
create_path_executable patchelf <<STUB
if [ "\$1" = "--print-rpath" ]; then
exit 0
fi
echo "\$*" >> "${BATS_TEST_TMPDIR}/patchelf.log"
STUB
}
create_interpreter() {
mkdir -p "${BATS_TEST_TMPDIR}/prefix/bin"
printf '#!/bin/sh\n' > "${BATS_TEST_TMPDIR}/prefix/bin/python2.7"
chmod +x "${BATS_TEST_TMPDIR}/prefix/bin/python2.7"
}
create_macos_tree() {
local prefix="${BATS_TEST_TMPDIR}/prefix"
local lib="${prefix}/lib/python3.12"
mkdir -p "${prefix}/bin" "${lib}/lib-dynload" "${lib}/site-packages/numpy"
printf '#!/bin/sh\n' > "${prefix}/bin/python3.12"
chmod +x "${prefix}/bin/python3.12"
touch "${prefix}/lib/libpython3.12.dylib"
touch "${lib}/lib-dynload/_ssl.cpython-312-darwin.so"
touch "${lib}/site-packages/numpy/_multiarray.so"
}
stub_macos_tools() {
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
create_path_executable install_name_tool \
'echo "$*" >> "${BATS_TEST_TMPDIR}/install-name-tool.log"'
create_path_executable otool <<'STUB'
file="${!#}"
echo "$*" >> "${BATS_TEST_TMPDIR}/otool.log"
old="/build prefix/3.12.7"
case "$1" in
-L )
echo "${file}:"
case "$file" in
*/bin/python3.12 )
if [ -n "$OTOOL_RELOCATED" ]; then
echo " @rpath/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)"
else
echo " ${old}/lib/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)"
fi
echo " ${old}-other/lib/libother.dylib (compatibility version 1.0.0, current version 1.0.0)"
echo " /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1.0.0)"
echo " /opt/homebrew/lib/libintl.8.dylib (compatibility version 1.0.0, current version 1.0.0)"
echo " @loader_path/liblocal.dylib (compatibility version 1.0.0, current version 1.0.0)"
;;
*/lib/libpython3.12.dylib )
if [ -n "$OTOOL_RELOCATED" ]; then
echo " @rpath/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)"
else
echo " ${old}/lib/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)"
fi
;;
esac
;;
-D )
case "$file" in
*/lib/libpython3.12.dylib )
echo "${file}:"
if [ -n "$OTOOL_RELOCATED" ]; then
echo '@rpath/libpython3.12.dylib'
else
echo "${old}/lib/libpython3.12.dylib"
fi
;;
* )
exit 1
;;
esac
;;
-l )
if [ -n "$OTOOL_RELOCATED" ]; then
case "$file" in
*/bin/* ) rpath='@executable_path/../lib' ;;
*/lib/*.dylib ) rpath='@loader_path' ;;
* ) rpath='@loader_path/../..' ;;
esac
else
rpath="${old}/lib"
fi
cat <<EOF
cmd LC_RPATH
path ${rpath} (offset 12)
cmd LC_RPATH
path /opt/homebrew/lib (offset 12)
cmd LC_RPATH
path @loader_path/vendor (offset 12)
EOF
;;
esac
STUB
}
@test "completion produces nothing" {
run pyenv-binary-relocate --complete
assert_success ""
}
@test "fails without a prefix" {
run pyenv-binary-relocate
assert_failure "usage"
}
@test "fails when patchelf is not available" {
PATH="$(path_without patchelf)" run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
assert_failure "pyenv-binary: need patchelf to relocate the binary"
}
@test "fails without the original build prefix on macOS" {
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
assert_failure "pyenv-binary: need the original build prefix to relocate a macOS binary"
}
@test "fails when otool is not available on macOS" {
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
create_path_executable install_name_tool true
PATH="$(path_without otool)" run \
pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix" /build/3.12.7
assert_failure "pyenv-binary: need otool to relocate the binary"
}
@test "fails when install_name_tool is not available on macOS" {
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
create_path_executable otool true
PATH="$(path_without install_name_tool)" run \
pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix" /build/3.12.7
assert_failure "pyenv-binary: need install_name_tool to relocate the binary"
}
@test "fails when the prefix has no interpreter" {
create_path_executable patchelf "exit 0"
mkdir -p "${BATS_TEST_TMPDIR}/prefix"
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
assert_failure "pyenv-binary: found no interpreter to relocate under \`${BATS_TEST_TMPDIR}/prefix/bin'"
}
@test "relocates an executable interpreter" {
stub_patchelf
create_interpreter
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
assert_success
run cat "${BATS_TEST_TMPDIR}/patchelf.log"
assert_output "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${BATS_TEST_TMPDIR}/prefix/bin/python2.7"
}
@test "relocates the extension modules but not what a wheel installed" {
local lib="${BATS_TEST_TMPDIR}/prefix/lib/python3.12"
stub_patchelf
create_interpreter
mkdir -p "${lib}/lib-dynload" "${lib}/site-packages/numpy"
touch "${lib}/lib-dynload/_ssl.cpython-312-x86_64-linux-gnu.so"
# A wheel points its extensions at the libraries it bundles alongside them, so
# its rpath is its own business and must survive relocation.
touch "${lib}/site-packages/numpy/_multiarray.so"
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
assert_success
run cat "${BATS_TEST_TMPDIR}/patchelf.log"
assert_line "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${lib}/lib-dynload/_ssl.cpython-312-x86_64-linux-gnu.so"
refute_line "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${lib}/site-packages/numpy/_multiarray.so"
}
@test "relocates macOS load commands without changing unrelated entries" {
local prefix="${BATS_TEST_TMPDIR}/prefix"
local old="/build prefix/3.12.7"
local lib="${prefix}/lib/python3.12"
create_macos_tree
stub_macos_tools
run pyenv-binary-relocate "$prefix" "$old"
assert_success
run cat "${BATS_TEST_TMPDIR}/install-name-tool.log"
assert_output <<EOF
-change ${old}/lib/libpython3.12.dylib @rpath/libpython3.12.dylib ${prefix}/bin/python3.12
-rpath ${old}/lib @executable_path/../lib ${prefix}/bin/python3.12
-id @rpath/libpython3.12.dylib ${prefix}/lib/libpython3.12.dylib
-rpath ${old}/lib @loader_path ${prefix}/lib/libpython3.12.dylib
-rpath ${old}/lib @loader_path/../.. ${lib}/lib-dynload/_ssl.cpython-312-darwin.so
EOF
run grep -F "${lib}/site-packages/numpy/_multiarray.so" "${BATS_TEST_TMPDIR}/otool.log"
assert_failure
}
@test "skips already relocated macOS load commands" {
local prefix="${BATS_TEST_TMPDIR}/prefix"
create_macos_tree
stub_macos_tools
export OTOOL_RELOCATED=1
run pyenv-binary-relocate "$prefix" "/build prefix/3.12.7"
assert_success
assert [ ! -e "${BATS_TEST_TMPDIR}/install-name-tool.log" ]
}
@test "fails when install_name_tool cannot modify a selected file" {
local prefix="${BATS_TEST_TMPDIR}/prefix"
create_macos_tree
stub_macos_tools
create_path_executable install_name_tool 'exit 1'
run pyenv-binary-relocate "$prefix" "/build prefix/3.12.7"
assert_failure ""
}

View file

@ -0,0 +1,155 @@
#!/usr/bin/env bats
load test_helper
create_version() {
mkdir -p "${PYENV_ROOT}/versions/$1/bin"
}
platform() {
echo "$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)"
}
@test "fails with no version given" {
run pyenv-binary-save
assert_failure "Usage: pyenv binary save <version> [<output-dir>] [--name <name>]"
}
@test "fails for a version that is not installed" {
run pyenv-binary-save 9.9.9
assert_failure "pyenv-binary: version \`9.9.9' is not installed"
}
@test "rejects a version name containing a slash" {
run pyenv-binary-save "foo/bar"
assert_failure "pyenv-binary: invalid version name \`foo/bar'"
}
@test "rejects the parent directory reference" {
run pyenv-binary-save ".."
assert_failure "pyenv-binary: invalid version name \`..'"
}
@test "packages an installed version" {
create_version "3.12.7"
local out="${BATS_TEST_TMPDIR}/dist"
local archive="${out}/3.12.7-$(platform).tar.gz"
run pyenv-binary-save "3.12.7" "$out"
assert_success "Saved 3.12.7-$(platform).tar.gz and 3.12.7-$(platform).meta to $out"
assert [ -f "$archive" ]
assert [ -f "${out}/3.12.7-$(platform).meta" ]
run tar -tzf "$archive"
assert_success
assert_line 0 "3.12.7/"
}
@test "uses an explicit package name" {
create_version "3.12.7"
local out="${BATS_TEST_TMPDIR}/dist"
run pyenv-binary-save "3.12.7" "$out" --name "custom"
assert_success "Saved custom.tar.gz and custom.meta to $out"
assert [ -f "${out}/custom.tar.gz" ]
run grep '^archive=' "${out}/custom.meta"
assert_success "archive=custom.tar.gz"
}
@test "fails when --name has no value" {
run pyenv-binary-save "3.12.7" --name
assert_failure "pyenv-binary: --name needs a value"
}
@test "rejects an invalid package name" {
create_version "3.12.7"
run pyenv-binary-save "3.12.7" --name "foo/bar"
assert_failure "pyenv-binary: invalid package name \`foo/bar'"
}
@test "records the platform in the metadata" {
create_version "3.12.7"
local out="${BATS_TEST_TMPDIR}/dist"
pyenv-binary-save "3.12.7" "$out" >/dev/null
run cat "${out}/3.12.7-$(platform).meta"
assert_success
assert_line "version=3.12.7"
assert_line "platform=$(platform)"
assert_line "archive=3.12.7-$(platform).tar.gz"
}
@test "records the original installation prefix" {
create_version "3.12.7"
local out="${BATS_TEST_TMPDIR}/dist"
pyenv-binary-save "3.12.7" "$out" >/dev/null
run grep '^build_prefix=' "${out}/3.12.7-$(platform).meta"
assert_success "build_prefix=${PYENV_ROOT}/versions/3.12.7"
}
@test "fails when readelf is not available" {
create_version "3.12.7"
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
PATH="$(path_without readelf)" run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist"
assert_failure "pyenv-binary: need readelf to inspect shared libraries"
assert [ ! -e "${BATS_TEST_TMPDIR}/dist/3.12.7-$(platform).tar.gz" ]
}
@test "records only direct libraries resolved outside the prefix" {
create_version "3.12.7"
touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12"
create_path_executable uname <<'STUB'
case "$1" in
-s) echo Linux ;;
-m) echo x86_64 ;;
esac
STUB
create_path_executable ldd <<'STUB'
prefix="${PYENV_ROOT}/versions/3.12.7"
cat <<EOF
linux-vdso.so.1 (0x00007ffd1adfe000)
libpython3.12.so.1.0 => ${prefix}/lib/libpython3.12.so.1.0 (0x00007f4a3c000000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4a3bc00000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4a3b800000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4a3c200000)
EOF
STUB
create_path_executable readelf <<'STUB'
cat <<EOF
0x0000000000000001 (NEEDED) Shared library: [libpython3.12.so.1.0]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
EOF
STUB
run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist"
assert_success
run grep '^dep=' "${BATS_TEST_TMPDIR}/dist/"*.meta
assert_output "dep=libm.so.6"
}
@test "records only the libraries otool resolves outside the prefix" {
create_version "3.12.7"
touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12"
create_path_executable uname <<'STUB'
case "$1" in
-s) echo Darwin ;;
-m) echo arm64 ;;
esac
STUB
create_path_executable otool <<'STUB'
prefix="${PYENV_ROOT}/versions/3.12.7"
cat <<EOF
${2}:
@rpath/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)
${prefix}/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.0.0)
EOF
STUB
run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist"
assert_success
run grep '^dep=' "${BATS_TEST_TMPDIR}/dist/"*.meta
assert_output "dep=/usr/lib/libSystem.B.dylib"
}

View file

@ -0,0 +1 @@
../../../test/test_helper.bash

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 yfprojects
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,33 @@
# pyenv-link
A [pyenv](https://github.com/pyenv/pyenv) plugin for linking Python installations and virtual environments into your pyenv root.
This plugin recommends the [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv/) plugin.
## Usage
Make an arbitrary virtualenv available through pyenv. This automatically guesses a fitting name from the prompt, the directory name or the location of the venv.
```console
$ pyenv link version .venv
Linked new version named myproject
```
You can also specify a name to use for the venv.
```console
$ pyenv link version .venv myname
Linked new version named myname
```
The same command can give a platform-specific binary installation a shorter name:
```console
$ pyenv link version "$(pyenv root)/versions/3.13.14-linux-x86_64" 3.13.14
Linked new version named 3.13.14
```
Now you can make pyenv activate/use the venv automatically:
```console
$ pyenv local myproject
```

View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
#
# Summary: Link a Python environment as a pyenv version
#
# Usage: pyenv link version [--dry] [--quiet] <path> [<name>]
#
# Link a virtual python environment to pyenvs version directory
# so that the venv can be used like one created with *pyenv-virtualenv*.
#
# <path> should be a path to a Python installation or virtual environment.
set -e
[ -n "$PYENV_DEBUG" ] && set -x
# Provide pyenv completions
case "$1" in
--complete)
if [ -z "$2" ]; then
echo version
else
shift 2
pyenv-link-version --complete "$@"
fi
;;
version)
shift
pyenv-link-version "$@"
exit $?
;;
*)
pyenv-help --usage link >&2
exit 1
;;
esac

View file

@ -0,0 +1,150 @@
#!/usr/bin/env bash
#
# Summary: Link a Python environment as a pyenv version
#
# Usage: pyenv link version [--dry] [--quiet] <path> [<name>]
#
# Link a virtual python environment to pyenvs version directory
# so that the venv can be used like one created with *pyenv-virtualenv*.
#
# <path> should be a path to a Python installation or virtual environment.
# <name> should be string used as a version name it may not be present
# in the pyenv version directory yet.
# If not specified a matching name is guessed from pyvenv.cfg
# or directory name of the venv.
set -e
[ -n "$PYENV_DEBUG" ] && set -x
# functions
if ! {
enable -f "${BASH_SOURCE%/*}"/../../../libexec/pyenv-realpath.dylib realpath ||
type realpath # realpath available?
} >/dev/null 2>&1; then
# realpath requires GNU coreutils to be installed. That's why its bundled with pyenv
echo realpath not available >&2
# work-around when realpath is unavailable
realpath() {
(cd "$1" && pwd)
}
fi
guess_name() {
local venv_dir=$1
local name
# guess venv name from pyvenv.cfg
local pyvenv_cfg_path=$venv_dir/pyvenv.cfg
if [ -f "$pyvenv_cfg_path" ]; then
# if `prompt` key wasn't found the var remains empty
name=$(cut -b 1-1024 "$pyvenv_cfg_path" | sed -n '/^ *prompt *= */s///p')
case "$name" in
\"*\" | \'*\')
name=${name:1:${#name}-2}
;;
esac
fi
# guess venv name from name of venv directory
local venv_dir_name=${venv_dir##*/}
if [ -z "$name" ]; then
case "$venv_dir_name" in
venv | env | .venv | .env | ENV | VENV) ;;
*) name=$venv_dir_name ;;
esac
fi
# guess venv name from name of the parent directory of the venv directory
local venv_dir_parent=${venv_dir%/*}
local venv_dir_parent_name=${venv_dir_parent##*/}
if [ -z "$name" ]; then
name=$venv_dir_parent_name
fi
echo "$name"
}
# process args
POSARGS=()
# Provide pyenv completions
if [ "$1" = "--complete" ]; then
echo --dry
echo --quiet
exit
fi
for arg in "$@"; do
case $arg in
--dry)
dry=true # any value
;;
--quiet)
quiet=true # any value
;;
-*)
pyenv-help --usage link-version >&2
exit 1
;;
*)
POSARGS+=("$arg")
;;
esac
done
if [ ! ${#POSARGS[@]} -le 2 ] || [ ! ${#POSARGS[@]} -ge 1 ]; then
pyenv-help --usage link-version >&2
exit 1
fi
venv_dir=${POSARGS[0]%/}
venv_name=${POSARGS[1]}
# Check venv exists
if [ ! -d "$venv_dir" ]; then
echo "The virtual env you specified doesn't exist"
exit 2
fi
# make venv dir absolute
venv_dir=$(realpath "$venv_dir")
# determine venv name
if [ -z "$venv_name" ]; then
venv_name=$(guess_name "$venv_dir")
fi
case "$venv_name" in
"" | */* | . | .. | *:* | system | *[[:cntrl:]]* )
echo "pyenv-link: invalid version name \`${venv_name}'" >&2
exit 1
;;
esac
versions_dir="$PYENV_ROOT/versions"
pyenv_prefix_path="$versions_dir/$venv_name"
if [ -e "$pyenv_prefix_path" ] || [ -L "$pyenv_prefix_path" ]; then
echo Version "$venv_name" already exists >&2
exit 3
fi
case "$venv_dir" in
"$versions_dir"/*)
link_target=${venv_dir#"$versions_dir"/}
;;
*)
link_target=$venv_dir
;;
esac
# link to pyenv version directory
if [ -z "$dry" ]; then
mkdir -p "$versions_dir"
ln -s "$link_target" "$pyenv_prefix_path"
fi
# output
if [ -z "$quiet" ]; then
echo "Linked new version named $venv_name"
fi

View file

@ -0,0 +1,61 @@
#!/usr/bin/env bats
load test_helper
@test "link completions use the dispatcher" {
run pyenv completions link
assert_success
assert_output <<OUT
--help
version
OUT
run pyenv completions link version
assert_success
assert_output <<OUT
--help
--dry
--quiet
OUT
}
@test "link requires a version path" {
run pyenv-link version
assert_failure "Usage: pyenv link version [--dry] [--quiet] <path> [<name>]"
}
@test "links an installed binary under its plain version name" {
create_alt_executable_in_version "3.13.14-linux-x86_64" python 'echo linked-python'
run pyenv-link version "$PYENV_ROOT/versions/3.13.14-linux-x86_64" 3.13.14
assert_success "Linked new version named 3.13.14"
assert_equal "3.13.14-linux-x86_64" "$(readlink "$PYENV_ROOT/versions/3.13.14")"
PYENV_VERSION=3.13.14 run pyenv exec python
assert_success "linked-python"
}
@test "links external paths containing spaces into a fresh root" {
mkdir -p "$PYENV_TEST_DIR/external env/bin"
run pyenv-link version "$PYENV_TEST_DIR/external env" custom
assert_success "Linked new version named custom"
assert_equal "$PYENV_TEST_DIR/external env" "$(readlink "$PYENV_ROOT/versions/custom")"
}
@test "refuses a dangling destination link" {
mkdir -p "$PYENV_ROOT/versions" "$PYENV_TEST_DIR/source"
ln -s missing "$PYENV_ROOT/versions/dangling"
run pyenv-link version "$PYENV_TEST_DIR/source" dangling
assert_failure "Version dangling already exists"
assert_equal missing "$(readlink "$PYENV_ROOT/versions/dangling")"
}
@test "rejects invalid version names" {
mkdir -p "$PYENV_TEST_DIR/source"
for name in ../outside . .. 'foo/bar' 'foo:bar' system; do
run pyenv-link version "$PYENV_TEST_DIR/source" "$name"
assert_failure "pyenv-link: invalid version name \`$name'"
assert [ ! -e "$PYENV_ROOT/outside" ]
done
echo "prompt = '../outside'" > "$PYENV_TEST_DIR/source/pyvenv.cfg"
run pyenv-link version "$PYENV_TEST_DIR/source"
assert_failure "pyenv-link: invalid version name \`../outside'"
assert [ ! -e "$PYENV_ROOT/outside" ]
}

View file

@ -0,0 +1 @@
../../../test/test_helper.bash

View file

@ -54,8 +54,11 @@ Or, if you would like to install the latest development release:
## Usage ## Usage
Before you begin, you should ensure that your build environment has the proper On Ubuntu, Debian, and Mint, install the recommended build dependencies with:
system dependencies for compiling the wanted Python Version (see our [recommendations](https://github.com/pyenv/pyenv/wiki#suggested-build-environment)).
pyenv install-prerequisites
For other environments, see our [build environment recommendations](https://github.com/pyenv/pyenv/wiki#suggested-build-environment).
### Using `pyenv install` with pyenv ### Using `pyenv install` with pyenv
@ -67,6 +70,13 @@ exact name of the version you want to install. For example,
Python versions will be installed into a directory of the same name under Python versions will be installed into a directory of the same name under
`~/.pyenv/versions`. `~/.pyenv/versions`.
To install a version under a different name -- for instance, to keep several
builds of the same version side by side -- append `:<alias>` to the version:
pyenv install 3.12.0:3.12-custom
This installs into `~/.pyenv/versions/3.12-custom`.
To see a list of all available Python versions, run `pyenv install --list`. You To see a list of all available Python versions, run `pyenv install --list`. You
may also tab-complete available Python versions if your pyenv installation is may also tab-complete available Python versions if your pyenv installation is
properly configured. properly configured.
@ -175,6 +185,7 @@ You can set certain environment variables to control the build process.
* `PYTHON_BUILD_SKIP_HOMEBREW`, if set, will not search for libraries installed by Homebrew when it would normally will. * `PYTHON_BUILD_SKIP_HOMEBREW`, if set, will not search for libraries installed by Homebrew when it would normally will.
* `PYTHON_BUILD_USE_HOMEBREW`, if set, will search for libraries installed by Homebrew when it would normally not. * `PYTHON_BUILD_USE_HOMEBREW`, if set, will search for libraries installed by Homebrew when it would normally not.
* `PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA`, override the Homebrew OpenSSL formula to use. * `PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA`, override the Homebrew OpenSSL formula to use.
* `PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA`, override the MacPorts OpenSSL formula to use. The default is `openssl3 openssl`.
* `PYTHON_BUILD_TCLTK_FORMULA`, override the Homebrew Tcl/Tk formula to use. * `PYTHON_BUILD_TCLTK_FORMULA`, override the Homebrew Tcl/Tk formula to use.
* `PYTHON_BUILD_SKIP_MACPORTS`, if set, will not search for libraries installed by MacPorts when it would normally will. * `PYTHON_BUILD_SKIP_MACPORTS`, if set, will not search for libraries installed by MacPorts when it would normally will.
* `PYTHON_BUILD_USE_MACPORTS`, if set, will search for libraries installed by MacPorts when it would normally not. * `PYTHON_BUILD_USE_MACPORTS`, if set, will search for libraries installed by MacPorts when it would normally not.
@ -261,7 +272,7 @@ would be to make symlinks at the mirror's root:
``` ```
The rationale is to abstract away difference between directory structures of sites The rationale is to abstract away difference between directory structures of sites
of various Python flavors and their occasional changes as well as to accomodate of various Python flavors and their occasional changes as well as to accommodate
people who only wish to cache some select downloads. This also allows to mirror multiple sites at once. people who only wish to cache some select downloads. This also allows to mirror multiple sites at once.
If the mirror being used does not have the same checksum (*e.g.* with a If the mirror being used does not have the same checksum (*e.g.* with a
@ -353,4 +364,3 @@ git diff --name-only master \
- Filter out any which don't live where python-build keeps its build scripts - Filter out any which don't live where python-build keeps its build scripts
- Look only at the file name (i.e. the python version name) - Look only at the file name (i.e. the python version name)
- Run a new docker container for each, building that version - Run a new docker container for each, building that version

View file

@ -2,9 +2,9 @@
# #
# Summary: Install a Python version using python-build # Summary: Install a Python version using python-build
# #
# Usage: pyenv install [-f] [-kvp] <version>... # Usage: pyenv install [-f] [-kvp] <version>[:<alias>]...
# pyenv install [-f] [-kvp] <definition-file> # pyenv install [-f] [-kvp] <definition-file>[:<alias>]
# pyenv install -l|--list # pyenv install -l|--list [--bare]
# pyenv install --version # pyenv install --version
# #
# -l/--list List all available versions # -l/--list List all available versions
@ -20,6 +20,13 @@
# --version Show version of python-build # --version Show version of python-build
# -g/--debug Build a debug version # -g/--debug Build a debug version
# #
# Append `:<alias>' to a version to install it under a custom name, so that
# several builds of the same version can coexist:
#
# pyenv install 3.12.0:my-3.12
#
# This installs into $PYENV_ROOT/versions/my-3.12.
#
# For detailed information on installing Python versions with # For detailed information on installing Python versions with
# python-build, including a list of environment variables for adjusting # python-build, including a list of environment variables for adjusting
# compilation, see: https://github.com/pyenv/pyenv#readme # compilation, see: https://github.com/pyenv/pyenv#readme
@ -38,6 +45,7 @@ shopt -u nullglob
# Provide pyenv completions # Provide pyenv completions
if [ "$1" = "--complete" ]; then if [ "$1" = "--complete" ]; then
echo --bare
echo --list echo --list
echo --force echo --force
echo --skip-existing echo --skip-existing
@ -72,6 +80,8 @@ unset KEEP
unset VERBOSE unset VERBOSE
unset HAS_PATCH unset HAS_PATCH
unset DEBUG unset DEBUG
unset BARE
unset LIST
[ -n "$PYENV_DEBUG" ] && VERBOSE="-v" [ -n "$PYENV_DEBUG" ] && VERBOSE="-v"
@ -81,10 +91,11 @@ for option in "${OPTIONS[@]}"; do
"h" | "help" ) "h" | "help" )
usage 0 usage 0
;; ;;
"bare" )
BARE=1
;;
"l" | "list" ) "l" | "list" )
echo "Available versions:" LIST=1
definitions | indent
exit
;; ;;
"f" | "force" ) "f" | "force" )
FORCE=true FORCE=true
@ -113,6 +124,16 @@ for option in "${OPTIONS[@]}"; do
esac esac
done done
if [[ -n $LIST ]]; then
if [[ -n $BARE ]]; then
definitions
else
echo "Available versions:"
definitions | indent
fi
exit
fi
unset VERSION_NAME unset VERSION_NAME
# The first argument contains the definition to install. If the # The first argument contains the definition to install. If the
@ -123,6 +144,20 @@ DEFINITIONS=("${ARGUMENTS[@]}")
[[ "${#DEFINITIONS[*]}" -eq 0 ]] && DEFINITIONS=($(pyenv-local 2>/dev/null || true)) [[ "${#DEFINITIONS[*]}" -eq 0 ]] && DEFINITIONS=($(pyenv-local 2>/dev/null || true))
[[ "${#DEFINITIONS[*]}" -eq 0 ]] && usage 1 >&2 [[ "${#DEFINITIONS[*]}" -eq 0 ]] && usage 1 >&2
# A `<version>:<alias>` argument installs <version> under the custom name
# <alias>, so that several builds of the same version can coexist. The `latest`
# suffix is reserved for latest-version resolution (see the `install` hook), so
# it is left untouched here.
declare -a ALIASES
for i in "${!DEFINITIONS[@]}"; do
definition="${DEFINITIONS[$i]}"
alias="${definition##*:}"
if [[ "$definition" == *:* ]] && [ "$alias" != "latest" ]; then
DEFINITIONS[$i]="${definition%:*}"
ALIASES[$i]="$alias"
fi
done
# Define `before_install` and `after_install` functions that allow # Define `before_install` and `after_install` functions that allow
# plugin hooks to register a string of code for execution before or # plugin hooks to register a string of code for execution before or
# after the installation process. # after the installation process.
@ -152,7 +187,9 @@ IFS="$OLDIFS"
for script in "${scripts[@]}"; do source "$script"; done for script in "${scripts[@]}"; do source "$script"; done
COMBINED_STATUS=0 COMBINED_STATUS=0
for DEFINITION in "${DEFINITIONS[@]}"; do for i in "${!DEFINITIONS[@]}"; do
DEFINITION="${DEFINITIONS[$i]}"
VERSION_ALIAS="${ALIASES[$i]}"
STATUS=0 STATUS=0
# Try to resolve a prefix if user indeed gave a prefix. # Try to resolve a prefix if user indeed gave a prefix.
@ -161,9 +198,12 @@ for DEFINITION in "${DEFINITIONS[@]}"; do
DEFINITION="$(pyenv-latest -f -k "$DEFINITION")" DEFINITION="$(pyenv-latest -f -k "$DEFINITION")"
# Set VERSION_NAME from $DEFINITION. Then compute the installation prefix. # Set VERSION_NAME from $DEFINITION. Then compute the installation prefix.
# With a `<version>:<alias>` argument, install under the alias instead;
# VERSION_NAME still reflects the real version so version-specific build logic
# (e.g. the bootstrap version detection below) keeps working.
VERSION_NAME="${DEFINITION##*/}" VERSION_NAME="${DEFINITION##*/}"
[ -n "$DEBUG" ] && VERSION_NAME="${VERSION_NAME}-debug" [ -n "$DEBUG" ] && VERSION_NAME="${VERSION_NAME}-debug"
PREFIX="${PYENV_ROOT}/versions/${VERSION_NAME}" PREFIX="${PYENV_ROOT}/versions/${VERSION_ALIAS:-$VERSION_NAME}"
[ -d "${PREFIX}" ] && PREFIX_EXISTS=1 [ -d "${PREFIX}" ] && PREFIX_EXISTS=1
@ -188,7 +228,7 @@ for DEFINITION in "${DEFINITIONS[@]}"; do
# If PYENV_BUILD_ROOT is set, always pass keep options to python-build. # If PYENV_BUILD_ROOT is set, always pass keep options to python-build.
if [ -n "${PYENV_BUILD_ROOT}" ]; then if [ -n "${PYENV_BUILD_ROOT}" ]; then
export PYTHON_BUILD_BUILD_PATH="${PYENV_BUILD_ROOT}/${VERSION_NAME}" export PYTHON_BUILD_BUILD_PATH="${PYENV_BUILD_ROOT}/${VERSION_ALIAS:-$VERSION_NAME}"
KEEP="-k" KEEP="-k"
fi fi
@ -256,7 +296,6 @@ for DEFINITION in "${DEFINITIONS[@]}"; do
# Display a more helpful message if the definition wasn't found. # Display a more helpful message if the definition wasn't found.
if [ "$STATUS" == "2" ]; then if [ "$STATUS" == "2" ]; then
{ candidates="$(definitions "$DEFINITION")" { candidates="$(definitions "$DEFINITION")"
here="$(dirname "${0%/*}")/../.."
if [ -n "$candidates" ]; then if [ -n "$candidates" ]; then
echo echo
echo "The following versions contain \`$DEFINITION' in the name:" echo "The following versions contain \`$DEFINITION' in the name:"
@ -266,12 +305,13 @@ for DEFINITION in "${DEFINITIONS[@]}"; do
echo "See all available versions with \`pyenv install --list'." echo "See all available versions with \`pyenv install --list'."
echo echo
echo -n "If the version you need is missing, try upgrading pyenv" echo -n "If the version you need is missing, try upgrading pyenv"
if [ "$here" != "${here#$(brew --prefix 2>/dev/null)}" ]; then declare brew_prefix
if brew_prefix="$(brew --prefix 2>/dev/null)" && [[ $_PYENV_INSTALL_PREFIX/ == "$brew_prefix/"* ]]; then
printf ":\n\n" printf ":\n\n"
echo " brew update && brew upgrade pyenv" echo " brew update && brew upgrade pyenv"
elif [ -d "${here}/.git" ]; then elif [ -d "${_PYENV_INSTALL_PREFIX}/.git" ]; then
printf ":\n\n" printf ":\n\n"
echo " cd ${here} && git pull && cd -" echo " cd ${_PYENV_INSTALL_PREFIX} && git pull && cd -"
else else
printf ".\n" printf ".\n"
fi fi

View file

@ -0,0 +1,47 @@
#!/usr/bin/env bash
#
# Summary: Install Python build prerequisites on Debian-based systems
#
# Usage: pyenv install-prerequisites
#
set -e
[ -n "$PYENV_DEBUG" ] && set -x
# Provide pyenv completions
if [ "$1" = "--complete" ]; then
exit
fi
usage() {
pyenv-help install-prerequisites 2>/dev/null
[ -z "$1" ] || exit "$1"
}
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage 0
fi
[ "$#" -eq 0 ] || usage 1 >&2
if ! command -v apt-get >/dev/null; then
echo "pyenv: installing build prerequisites is not supported on this system" >&2
exit 1
fi
prerequisites=(
make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev
libsqlite3-dev curl git llvm libncurses5-dev libncursesw5-dev xz-utils
tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev libzstd-dev
)
if [ "$(id -u)" -eq 0 ]; then
apt_get=(apt-get)
elif command -v sudo >/dev/null; then
apt_get=(sudo apt-get)
else
echo "pyenv: sudo is required to install build prerequisites" >&2
exit 1
fi
"${apt_get[@]}" update -q
"${apt_get[@]}" install -yq "${prerequisites[@]}"

View file

@ -14,7 +14,7 @@
# -g/--debug Build a debug version # -g/--debug Build a debug version
# #
PYTHON_BUILD_VERSION="2.6.8" PYTHON_BUILD_VERSION="2.8.5"
OLDIFS="$IFS" OLDIFS="$IFS"
@ -202,7 +202,14 @@ build_failed() {
colorize 33 "Results logged to ${LOG_PATH}" colorize 33 "Results logged to ${LOG_PATH}"
printf "\n\n" printf "\n\n"
echo "Last 10 log lines:" echo "Last 10 log lines:"
tail -n 10 "$LOG_PATH" TAIL=`tail -n 10 "$LOG_PATH"`
echo "$TAIL"
if echo "$TAIL" | grep -q "no acceptable C compiler found"; then
printf "\n"
echo "Are the build dependencies for Python correctly installed?"
echo "Please consult to the Wiki page for more info."
echo "https://github.com/pyenv/pyenv/wiki#suggested-build-environment"
fi
fi fi
} >&3 } >&3
exit 1 exit 1
@ -1660,15 +1667,28 @@ use_macports_ncurses() {
} }
prefer_openssl11() { prefer_openssl11() {
# Allow overriding the preference of OpenSSL version per definition basis (#1302, #1325, #1326)
PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@1.1 openssl}" PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@1.1 openssl}"
export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA
PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA="${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl11 openssl3 openssl}"
export PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA
} }
prefer_openssl3() { prefer_openssl3() {
# Allow overriding the preference of OpenSSL version per definition basis (#1302, #1325, #1326)
PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@3 openssl@1.1 openssl}" PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@3 openssl@1.1 openssl}"
export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA
PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA="${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl3 openssl openssl11}"
export PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA
}
prefer_openssl3_to_4() {
PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@3 openssl@4 openssl@1.1 openssl}"
export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA
PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA="${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl3 openssl4 openssl openssl11}"
export PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA
} }
build_package_mac_readline() { build_package_mac_readline() {
@ -1706,14 +1726,29 @@ use_homebrew_openssl() {
local ssldir="$(brew --prefix "${openssl}" || true)" local ssldir="$(brew --prefix "${openssl}" || true)"
if [ -d "$ssldir" ]; then if [ -d "$ssldir" ]; then
echo "python-build: use ${openssl} from homebrew" echo "python-build: use ${openssl} from homebrew"
# Since 970acdcad3be4262451e9a5180a385dd2158eda3 (openssl@3 3.1.1),
# Homebrew's openssl@3 is no longer keg-only.
# Python's --with-openssl* appends flags to the compiler's command line,
# which in combination with adding Homebrew's general dir to flags
# always causes the build to link to the non-keg `openssl'
# when the non-keg-only formula is installed.
# To counter that, we have to prepend the openssl path to flags
# regardless of using Configure options.
if [[ -n "${PYTHON_BUILD_CONFIGURE_WITH_OPENSSL:-}" ]]; then if [[ -n "${PYTHON_BUILD_CONFIGURE_WITH_OPENSSL:-}" ]]; then
# configure script of newer CPython versions support `--with-openssl` # configure script of newer CPython versions support `--with-openssl`
# https://bugs.python.org/issue21541 # https://bugs.python.org/issue21541
package_option python configure --with-openssl="${ssldir}" package_option python configure --with-openssl="${ssldir}"
else
export CPPFLAGS="-I$ssldir/include ${CPPFLAGS:+ $CPPFLAGS}"
export LDFLAGS="-L$ssldir/lib${LDFLAGS:+ $LDFLAGS}"
fi fi
# 3.10.0+ (https://github.com/python/cpython/pull/24820)
# but has no effect until 3.11.0 (b9e9292d75fdea621e05e39b8629e6935d282d0d)
# and broken in MacOS until 3.12.2 (cc13eabc7ce08accf49656e258ba500f74a1dae8)
if [[ -n $PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH ]]; then
package_option python configure --with-openssl-rpath="${ssldir}/lib"
fi
export CPPFLAGS="-I${ssldir}/include ${CPPFLAGS:+ $CPPFLAGS}"
export LDFLAGS="-L${ssldir}/lib -Wl,-rpath,${ssldir}/lib${LDFLAGS:+ $LDFLAGS}"
export PKG_CONFIG_PATH="$ssldir/lib/pkgconfig/:${PKG_CONFIG_PATH}" export PKG_CONFIG_PATH="$ssldir/lib/pkgconfig/:${PKG_CONFIG_PATH}"
lock_in homebrew lock_in homebrew
return 0 return 0
@ -1727,7 +1762,8 @@ use_macports_openssl() {
command -v port >/dev/null || return 1 command -v port >/dev/null || return 1
local port_location="$(command -v port)" local port_location="$(command -v port)"
local prefix="${port_location%/bin/port}" local prefix="${port_location%/bin/port}"
for openssl in ${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl}; do # MacPorts uses a different package naming convention and does not use the @ symbol.
for openssl in ${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl3 openssl}; do
if [[ $(port -q installed ${openssl} | awk '{print $3}') == "(active)" ]]; then if [[ $(port -q installed ${openssl} | awk '{print $3}') == "(active)" ]]; then
echo "python-build: use ${openssl} from MacPorts" echo "python-build: use ${openssl} from MacPorts"
if [[ -n "${PYTHON_BUILD_CONFIGURE_WITH_OPENSSL:-}" ]]; then if [[ -n "${PYTHON_BUILD_CONFIGURE_WITH_OPENSSL:-}" ]]; then
@ -1769,12 +1805,16 @@ build_package_mac_openssl() {
local nokerberos local nokerberos
[[ "$1" != openssl-1.0.* ]] || nokerberos=1 [[ "$1" != openssl-1.0.* ]] || nokerberos=1
# switches introduced in OpenSSL 1.1.1
local notests
[[ $(openssl_version $1) -ge 10101 ]] && notests=1
# switches introduced in OpenSSL 3.2 # switches introduced in OpenSSL 3.2
local extra_no_features local extra_no_features
[[ $(openssl_version $1) -ge 30200 ]] && extra_no_features=1 [[ $(openssl_version $1) -ge 30200 ]] && extra_no_features=1
# Compile a shared lib with zlib dynamically linked. # Compile a shared lib with zlib dynamically linked.
package_option openssl configure --openssldir="$OPENSSLDIR" zlib-dynamic no-ssl3 shared ${nokerberos:+no-ssl2 no-krb5} ${extra_no_features:+no-docs no-apps} no-tests package_option openssl configure --openssldir="$OPENSSLDIR" zlib-dynamic no-ssl3 shared ${nokerberos:+no-ssl2 no-krb5} ${extra_no_features:+no-docs no-apps} ${notests:+no-tests}
build_package_standard "$@" build_package_standard "$@"
@ -1861,6 +1901,9 @@ use_xcode_sdk_zlib() {
#distutils.unixcompiler and setup.py specifically search CFLAGS for "-isysroot" #distutils.unixcompiler and setup.py specifically search CFLAGS for "-isysroot"
export CFLAGS="${CFLAGS:+$CFLAGS }-isysroot ${sdkroot}" export CFLAGS="${CFLAGS:+$CFLAGS }-isysroot ${sdkroot}"
#while one can get away with adding it just to CFLAGS, this may cause linker failures
#if XCode is for a different major MacOS version (e.g. reported for XCode 12.4 on MacOS 10.15)
export LDFLAGS="${LDFLAGS:+$LDFLAGS }-isysroot ${sdkroot}"
return 0 return 0
} }
@ -1880,26 +1923,24 @@ use_macports_zlib() {
use_homebrew_tcltk() { use_homebrew_tcltk() {
can_use_homebrew || return 1 can_use_homebrew || return 1
local tcltk_formula
# Since https://github.com/Homebrew/homebrew-core/commit/f10e88617b41555193c22fdcba6109fe82155ee2 (10.11.2024), # Since https://github.com/Homebrew/homebrew-core/commit/f10e88617b41555193c22fdcba6109fe82155ee2 (10.11.2024),
# tcl-tk is 9.0 which is not compatible with CPython as of this writing # tcl-tk is 9.0. It's supported in CPython since 3.14.0a1 (gh-124111) but is not officially supported as of 3.14.0
# but we'll keep it as backup for cases like non-updated Homebrew # so we prefer 8
local tcltk for tcltk_formula in ${PYTHON_BUILD_TCLTK_FORMULA:-tcl-tk@8 tcl-tk}; do
for tcltk in ${PYTHON_BUILD_TCLTK_FORMULA:-tcl-tk@8 tcl-tk}; do local tcltk_prefix="$(brew --prefix "${tcltk_formula}" 2>/dev/null || true)"
local tcltk_libdir="$(brew --prefix "${tcltk}" 2>/dev/null || true)" if [ -d "$tcltk_prefix" ]; then
if [ -d "$tcltk_libdir" ]; then echo "python-build: use ${tcltk_formula} from homebrew"
echo "python-build: use tcl-tk from homebrew"
local tcltk_version="$(sh -c '. '"$tcltk_libdir"'/lib/tclConfig.sh; echo $TCL_VERSION')"
local tcltk_includes tcltk_libs
# In Homebrew Tcl/Tk 8.6.13, headers have been moved to the 'tcl-tk' subdir. # In Homebrew Tcl/Tk 8.6.13, headers have been moved to the 'tcl-tk' subdir.
# We're not using tclConfig.sh here 'cuz it produces the version-specific path to <brew prefix>/Cellar local tcltk_includes="$(sh -c 'cd '"$tcltk_prefix"'/lib; . ./tclConfig.sh; . ./tkConfig.sh; echo "$TCL_INCLUDE_SPEC $TK_INCLUDE_SPEC"')"
# and we'd rather have rpath set to <brew prefix>/opt/<...> to allow micro release upgrades without rebuilding # Homebrew Tcl/Tk 9 is built with external libtommath. CPython's build as of 3.14.0 does not detect that and fails to link to tommath symbols
# XXX: do use tclConfig.sh and translate the paths if more shenanigans appear in later releases local tcltk_cflags
if [ -d "$tcltk_libdir/include/tcl-tk" ]; then if sh -c '. '"$tcltk_prefix"'/lib/tclConfig.sh; echo "$TCL_DEFS"' | grep -qwFe '-DTCL_WITH_EXTERNAL_TOMMATH=1'; then
tcltk_includes="-I$tcltk_libdir/include/tcl-tk" tcltk_cflags="-DTCL_WITH_EXTERNAL_TOMMATH=1"
else
tcltk_includes="-I$tcltk_libdir/include"
fi fi
tcltk_libs="-L$tcltk_libdir/lib -ltcl$tcltk_version -ltk$tcltk_version" # For some reason, keg-only tcl-tk@8 successfully links with Tkinter without specifying rpath, with `/opt' rpath
# so no need to translate /Cellar path to /opt path
local tcltk_libs="$(sh -c 'cd '"$tcltk_prefix"'/lib; . ./tclConfig.sh; . ./tkConfig.sh; echo "$TCL_LIB_SPEC $TK_LIB_SPEC"')"
# Since 2.7.6, 3.3.3, 3.4.0 (Issue #1584): --with-tcltk-includes + --with-tcltk-libs Configure options # Since 2.7.6, 3.3.3, 3.4.0 (Issue #1584): --with-tcltk-includes + --with-tcltk-libs Configure options
# Since 3.11.0 (bpo-45847): `pkg-config` call, TCLTK_CFLAGS + TCLTK_LIBS override # Since 3.11.0 (bpo-45847): `pkg-config` call, TCLTK_CFLAGS + TCLTK_LIBS override
if [[ -n "$PYTHON_BUILD_TCLTK_USE_PKGCONFIG" ]]; then if [[ -n "$PYTHON_BUILD_TCLTK_USE_PKGCONFIG" ]]; then
@ -1907,14 +1948,15 @@ use_homebrew_tcltk() {
# There's no way to provide a fallback only if it's is not present # There's no way to provide a fallback only if it's is not present
# and Configure's logic of detecting if it's present is complicated. # and Configure's logic of detecting if it's present is complicated.
# So we just override it always # So we just override it always
export TCLTK_CFLAGS="$tcltk_includes" export TCLTK_CFLAGS="$tcltk_includes${tcltk_cflags:+ $tcltk_cflags}"
export TCLTK_LIBS="$tcltk_libs" export TCLTK_LIBS="$tcltk_libs"
else else
package_option python configure --with-tcltk-includes="$tcltk_includes" package_option python configure --with-tcltk-includes="$tcltk_includes"
package_option python configure --with-tcltk-libs="$tcltk_libs" package_option python configure --with-tcltk-libs="$tcltk_libs"
[[ -n $tcltk_cflags ]] && export CFLAGS="${tcltk_cflags}${CFLAGS:+ $CFLAGS}"
fi fi
#set in either case as a failsafe #set in either case as a failsafe
export PKG_CONFIG_PATH="${tcltk_libdir}/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" export PKG_CONFIG_PATH="${tcltk_prefix}/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
lock_in homebrew lock_in homebrew
return 0 return 0
@ -2071,9 +2113,11 @@ build_package_symlink_version_suffix() {
verify_python() { verify_python() {
build_package_symlink_version_suffix build_package_symlink_version_suffix
if [ ! -x "${PYTHON_BIN}" ]; then local python_bin="${PYTHON_BIN%/*}/python${1:?}"
if [ ! -x "${python_bin}" ]; then
{ colorize 1 "ERROR" { colorize 1 "ERROR"
echo ": invalid Python executable: ${PYTHON_BIN}" echo ": invalid Python executable: ${python_bin}"
echo echo
echo "The python-build could not find proper executable of Python after successful build." echo "The python-build could not find proper executable of Python after successful build."
echo "Please open an issue for future improvements." echo "Please open an issue for future improvements."
@ -2254,6 +2298,11 @@ build_package_verify_py315() {
build_package_verify_py314 "$1" "${2:-3.15}" build_package_verify_py314 "$1" "${2:-3.15}"
} }
# Post-install check for Python 3.16.x
build_package_verify_py316() {
build_package_verify_py315 "$1" "${2:-3.16}"
}
# Post-install check for Python 3.x rolling release scripts # Post-install check for Python 3.x rolling release scripts
# XXX: Will need splitting into project-specific ones if there emerge # XXX: Will need splitting into project-specific ones if there emerge
# multiple rolling-release scripts with different checks needed # multiple rolling-release scripts with different checks needed
@ -2629,7 +2678,7 @@ if [ -z "${GET_PIP_URL}" ]; then
2.6 | 2.6.* ) 2.6 | 2.6.* )
GET_PIP_URL="https://bootstrap.pypa.io/pip/2.6/get-pip.py" GET_PIP_URL="https://bootstrap.pypa.io/pip/2.6/get-pip.py"
;; ;;
2.7 | 2.7.* | pypy2.7 | pypy2.7-* ) 2.7 | 2.7.* | pypy2.7 | pypy2.7-* | pypy-c-jit-* )
GET_PIP_URL="https://bootstrap.pypa.io/pip/2.7/get-pip.py" GET_PIP_URL="https://bootstrap.pypa.io/pip/2.7/get-pip.py"
;; ;;
3.2 | 3.2.* ) 3.2 | 3.2.* )
@ -2653,6 +2702,9 @@ if [ -z "${GET_PIP_URL}" ]; then
3.8 | 3.8.* | pypy3.8 | pypy3.8-* | pyston* ) 3.8 | 3.8.* | pypy3.8 | pypy3.8-* | pyston* )
GET_PIP_URL="https://bootstrap.pypa.io/pip/3.8/get-pip.py" GET_PIP_URL="https://bootstrap.pypa.io/pip/3.8/get-pip.py"
;; ;;
3.9 | 3.9.* | pypy3.9 | pypy3.9-* )
GET_PIP_URL="https://bootstrap.pypa.io/pip/3.9/get-pip.py"
;;
* ) * )
GET_PIP_URL="https://bootstrap.pypa.io/get-pip.py" GET_PIP_URL="https://bootstrap.pypa.io/get-pip.py"
;; ;;

View file

@ -0,0 +1 @@
/venv

View file

@ -0,0 +1,758 @@
#!/usr/bin/env python3
"""Script to add CPython releases.
Checks the CPython download archives for new versions,
then writes a build script for any which do not exist locally,
saving it to plugins/python-build/share/python-build.
"""
import argparse
import dataclasses
import hashlib
import io
import itertools
import logging
import operator
import os.path
import pathlib
import pprint
import re
import shutil
import subprocess
import sys
import typing
import urllib.parse
import jc
import more_itertools
import packaging.version
import requests
import requests_html
import sortedcontainers
import tqdm
logger = logging.getLogger(__name__)
CUTOFF_VERSION=packaging.version.Version('3.10')
EXCLUDED_VERSIONS= {
}
here = pathlib.Path(__file__).resolve()
OUT_DIR: pathlib.Path = here.parent.parent / "share" / "python-build"
AUTO_ADD_VERSION_REF_RE = re.compile(
r"^(?P<object_id>[0-9a-f]{40,64})\t"
r"refs/heads/auto_add_version/(?P<versions>\S+)$"
)
OPENSSL_RELEASE_TAG_RE = re.compile(
r"^openssl-(?P<major>\d+)\.\d+(?:\.\d+)*(?:[a-z]+\d*)?$"
)
T_THUNK=\
'''export PYTHON_BUILD_FREE_THREADING=1
source "${BASH_SOURCE[0]%t}"
'''
def adapt_script(version: packaging.version.Version,
previous_version: packaging.version.Version) -> typing.Union[pathlib.Path, None]:
previous_version_path = OUT_DIR.joinpath(str(previous_version))
with previous_version_path.open("r", encoding='utf-8') as f:
script = f.readlines()
result = io.StringIO()
for line in script:
if m:=re.match(r'\s*install_package\s+"(?P<package>Python-\S+)"\s+'
r'"(?P<url>\S+)"\s+.*\s+verify_py(?P<verify_py_suffix>\d+)\s+.*$',
line):
existing_url_path = urllib.parse.urlparse(m.group('url')).path
try:
matched_download = more_itertools.one(
item for item in VersionDirectory.available[version].downloads
if existing_url_path.endswith(item.extension))
except ValueError:
logger.error(f'Cannot match existing URL path\'s {existing_url_path} extension '
f'to available downloads {VersionDirectory.available[version].downloads}')
return
new_package_name, new_package_url = matched_download.package_name, matched_download.url
new_package_hash = Url.sha256_url(new_package_url, VersionDirectory.session)
verify_py_suffix = str(version.major)+str(version.minor)
line = Re.sub_groups(m,
package=new_package_name,
url=new_package_url+'#'+new_package_hash,
verify_py_suffix=verify_py_suffix)
elif m:=re.match(r'\s*install_package\s+'
r'"(?P<package>openssl-(?P<openssl_major>\d+)\.\S+)"\s+'
r'"(?P<url>\S+)"\s.*$',
line):
item = VersionDirectory.openssl.get_store_latest_release(
int(m.group('openssl_major'))
)
line = Re.sub_groups(m,
package=item.package_name,
url=item.url + '#' + item.hash)
elif m:=re.match(r'\s*install_package\s+"(?P<package>readline-\S+)"\s+'
r'"(?P<url>\S+)"\s.*$',
line):
item = VersionDirectory.readline.get_store_latest_release()
line = Re.sub_groups(m,
package=item.package_name,
url=item.url + '#' + item.hash)
result.write(line)
result_path = OUT_DIR.joinpath(str(version))
logger.info(f"Writing {result_path}")
result_path.write_text(result.getvalue(), encoding='utf-8')
result.close()
return result_path
def add_version(version: packaging.version.Version):
previous_version = VersionDirectory.existing.pick_previous_version(version).version
is_prerelease_upgrade = previous_version.major==version.major\
and previous_version.minor==version.minor\
and previous_version.micro==version.micro
logger.info(f"Adding {version} based on {previous_version}"
+ (" (prerelease upgrade)" if is_prerelease_upgrade else ""))
VersionDirectory.available.get_store_available_source_downloads(version)
new_path = adapt_script(version,
previous_version)
if not new_path:
return False
VersionDirectory.existing.append(_CPythonExistingScriptInfo(version,str(new_path)))
handle_version_patches(version, previous_version, is_prerelease_upgrade)
cleanup_prerelease_upgrade(is_prerelease_upgrade, previous_version, version)
handle_t_thunks(version, previous_version, is_prerelease_upgrade)
print(version)
return True
def handle_version_patches(
version: packaging.version.Version,
previous_version: packaging.version.Version,
is_prerelease_upgrade: bool)\
-> None:
if (previous_version.major, previous_version.minor) != (version.major, version.minor):
return
patches_dir = OUT_DIR / "patches"
previous_patches = patches_dir / str(previous_version)
if not previous_patches.exists():
return
new_patches = patches_dir / str(version)
if is_prerelease_upgrade:
logger.info(f"Git moving patches from {previous_version} to {version}")
subprocess.check_call((
"git", "-C", OUT_DIR, "mv",
f"patches/{previous_version}",
f"patches/{version}",
))
else:
logger.info(f"Copying patches from {previous_version} to {version}")
shutil.copytree(previous_patches, new_patches)
# Subdir rename as a separate step from upper dir copying/moving
# in case there are patches for dependency packages as well
previous_package_patches = new_patches / f"Python-{previous_version}"
new_package_patches = new_patches / f"Python-{version}"
if is_prerelease_upgrade:
subprocess.check_call((
"git", "-C", OUT_DIR, "mv",
f"patches/{version}/Python-{previous_version}",
f"patches/{version}/Python-{version}",
))
else:
previous_package_patches.rename(new_package_patches)
if uses_t_thunks(previous_version) and is_prerelease_upgrade:
(patches_dir / f"{previous_version}t").unlink(missing_ok=True)
if uses_t_thunks(version):
(patches_dir / f"{version}t").symlink_to(
str(version), target_is_directory=True
)
def cleanup_prerelease_upgrade(
is_prerelease_upgrade: bool,
previous_version: packaging.version.Version,
new_version: packaging.version.Version)\
-> None:
if not is_prerelease_upgrade:
return
previous_version_filename = str(previous_version)
new_version_filename = str(new_version)
new_version_path = OUT_DIR / new_version_filename
logger.info(f'Git moving {previous_version_filename} '
f'to {new_version_filename} (preserving new data)')
data = new_version_path.read_text()
new_version_path.unlink()
subprocess.check_call(("git","-C",OUT_DIR,
"mv",
previous_version_filename,
new_version_filename))
new_version_path.write_text(data)
del VersionDirectory.existing[previous_version]
def handle_t_thunks(version, previous_version, is_prerelease_upgrade):
if not uses_t_thunks(version):
return
# an old thunk may have older version-specific code
# so it's safer to write a known version-independent template
thunk_name = (str(version) + "t")
thunk_path = OUT_DIR / thunk_name
previous_thunk_name = str(previous_version) + "t"
if is_prerelease_upgrade:
logger.info(f"Git moving {previous_thunk_name} to {thunk_name}")
subprocess.check_call(("git","-C",OUT_DIR,
"mv",
previous_thunk_name,
thunk_name))
logger.info(f"Writing {thunk_path}")
thunk_path.write_text(T_THUNK, encoding='utf-8')
def uses_t_thunks(version: packaging.version.Version) -> bool:
return (version.major, version.minor) >= (3, 13)
Arguments: argparse.Namespace
def main():
global Arguments
Arguments = parse_args()
logging.basicConfig(level=logging.DEBUG if Arguments.verbose else logging.INFO)
cached_session=requests_html.HTMLSession()
global VersionDirectory
VersionDirectory = _VersionDirectory(cached_session)
VersionDirectory.existing.populate()
VersionDirectory.available.populate()
# Prereleases are placed under the same directory as the corresponding release.
# So until we know the release is out, its directory is a potential prerelease directory.
# Normally, prereleases are only made for initial releases (x.y.0) --
# but rarely, they may make them for other releases (e.g. 3.14.5).
for release in (v for v in frozenset(VersionDirectory.available.keys()) #refining alters the
#corresponding directory key
#which breaks iteration
#over the directory --
#so have to iterate over a copy
if v not in VersionDirectory.existing):
VersionDirectory.available.get_store_available_source_downloads(release, True)
del release
# Excluding versions for which there already are PRs.
# This will prevent us from using advanced features of
# peter-evans/create-pull-request Github Action
# like updating a PR and closing a superseded PR
# but we don't really need them as of this writing.
versions_to_add = sorted(
VersionDirectory.available.keys()
- VersionDirectory.existing.keys()
- get_pending_versions()
)
logger.info("Versions to add:\n"+pprint.pformat(versions_to_add))
result = False
for version_to_add in versions_to_add:
result = add_version(version_to_add) or result
return int(not result)
def get_pending_versions() -> typing.Set[packaging.version.Version]:
ls_remote = subprocess.check_output(
("git", "-C", OUT_DIR, "ls-remote", "origin",
"refs/heads/auto_add_version/*"),
text=True,
timeout=30,
)
pending_versions = set()
for line in ls_remote.splitlines():
if not (match := AUTO_ADD_VERSION_REF_RE.fullmatch(line)):
raise ValueError(f"Unexpected git ls-remote output line: {line!r}")
pending_versions.update(
packaging.version.Version(version)
for version in match.group("versions").split("_")
)
return pending_versions
def parse_args():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"-d", "--dry-run", action="store_true",
help="Do not write scripts, just report them to stdout",
)
parser.add_argument(
"-v", "--verbose", action="store_true", default=0,
help="Increase verbosity of logging",
)
parsed = parser.parse_args()
return parsed
T = typing.TypeVar('T', bound=object)
K = typing.TypeVar('K', bound=typing.Hashable)
class KeyedList(typing.List[T], typing.Mapping[K, T]):
key_field: str
item_init: typing.Callable[..., T] = None
def __init__(self, seq: typing.Union[typing.Iterable[T], None] = None):
super().__init__()
self._map = {}
if seq is not None:
self.__iadd__(seq)
# read
def __getitem__(self, key: K) -> T:
return self._map[key]
def __contains__(self, key: K):
return key in self._map
def keys(self) -> typing.AbstractSet[K]:
return self._map.keys()
# write
def append(self, item: T) -> None:
key = self._getkey(item)
if key in self:
raise ValueError(f"Key '{key:r}' already present")
super().append(item)
self._map[key] = item
def __iadd__(self, other: typing.Iterable[T]):
for item in other:
self.append(item)
return self
def __delitem__(self, key: K):
super().remove(self[key])
del self._map[key]
def clear(self):
super().__delitem__(slice(None,None))
self._map.clear()
# read-write
def get_or_create(self, key: K, **kwargs):
try:
return self[key]
except KeyError as e:
if self.item_init is None:
raise AttributeError("'item_init' must be set to use automatic item creation") from e
kwargs[self.key_field] = key
item = self.item_init(**kwargs)
self.append(item)
return item
# info
def __repr__(self):
return self.__class__.__name__ + "([" + ", ".join(repr(i) for i in self) + "])"
# private
def _getkey(self, item: T) -> K:
return getattr(item, self.key_field)
del T, K
@dataclasses.dataclass(frozen=True)
class _CPythonAvailableVersionDownloadInfo:
extension: str
package_name: str
url: str
class _CPythonAvailableVersionDownloadsDirectory(KeyedList[_CPythonAvailableVersionDownloadInfo, str]):
key_field = "extension"
@dataclasses.dataclass(frozen=True)
class _CPythonAvailableVersionInfo:
version: packaging.version.Version
download_page_url: str
downloads: _CPythonAvailableVersionDownloadsDirectory = dataclasses.field(
default_factory=lambda:_CPythonAvailableVersionDownloadsDirectory()
)
class CPythonAvailableVersionsDirectory(KeyedList[_CPythonAvailableVersionInfo, packaging.version.Version]):
key_field = "version"
_session: requests.Session
item_init = _CPythonAvailableVersionInfo
def __init__(self, session: requests.Session, seq=None):
super().__init__(seq)
self._session = session
def populate(self):
"""
Fetch remote versions
"""
logger.info("Fetching available CPython versions")
for name, url in DownloadPage.enum_download_entries(
"https://www.python.org/ftp/python/",
r'^(\d+.*)/$', self._session,
make_name= lambda m: m.group(1)
):
v = packaging.version.Version(name)
if v < CUTOFF_VERSION or v in EXCLUDED_VERSIONS:
continue
logger.debug(f'Available version: {name} ({v}), {url}')
self.append(_CPythonAvailableVersionInfo(
v,
url
))
def get_store_available_source_downloads(self, version, refine_mode=False):
entry = self[version]
if entry.downloads:
#already retrieved
return
additional_versions_found =\
CPythonAvailableVersionsDirectory(self._session) if refine_mode else None
exact_download_found = False
for name, url in DownloadPage.enum_download_entries(
entry.download_page_url,
r'Python-.*\.(tar\.xz|tgz)$',
self._session):
m = re.match(r'(?P<package>Python-(?P<version>.*))\.(?P<extension>tar\.xz|tgz)$', name)
download_version = packaging.version.Version(m.group("version"))
if download_version != version:
if not refine_mode:
logger.warning(f"Ignoring download {name} ({download_version}) "
f"for {version} at page {entry.download_page_url}")
continue
entry_to_fill = additional_versions_found.get_or_create(
download_version,
download_page_url=entry.download_page_url
)
else:
exact_download_found = True
entry_to_fill = entry
entry_to_fill.downloads.append(_CPythonAvailableVersionDownloadInfo(
m.group("extension"), m.group('package'), url
))
# XXX: Exact download not found in non-refine mode never happens now
# 'cuz we first call the function in refine mode.
# Decide what's best to do if it starts to after a logic change.
if not exact_download_found and refine_mode:
actual_version = max(additional_versions_found.keys())
logger.debug(f"Refining available version {version} to {actual_version}")
del self[version]
self.append(
additional_versions_found[
actual_version
])
class _CPythonExistingScriptInfo(typing.NamedTuple):
version: packaging.version.Version
filename: str
class CPythonExistingScriptsDirectory(KeyedList[_CPythonExistingScriptInfo, packaging.version.Version]):
key_field = "version"
_filename_pattern = r'^\d+\.\d+(?:(t?)(-\w+)|(.\d+((?:a|b|rc)\d)?(t?)))$'
def populate(self):
"""
Enumerate existing installation scripts in share/python-build/ by pattern
"""
logger.info(f"Enumerating existing versions in {OUT_DIR}")
for entry_name in (p.name for p in OUT_DIR.iterdir() if p.is_file()):
if (not (m := re.match(self._filename_pattern, entry_name))
or m.group(1) == 't' or m.group(5) == 't'):
continue
try:
v = packaging.version.Version(entry_name)
if v < CUTOFF_VERSION:
continue
# branch tip scripts are different from release scripts and thus unusable as a pattern
if v.dev is not None:
continue
logger.debug(f"Existing version {v}")
self.append(_CPythonExistingScriptInfo(v, entry_name))
except ValueError as e:
logger.error(f"Unable to parse existing version {entry_name}: {e}")
def pick_previous_version(self,
version: packaging.version.Version) \
-> _CPythonExistingScriptInfo:
return max(v for v in self if v.version < version)
class _OpenSSLVersionInfo(typing.NamedTuple):
version: packaging.version.Version
package_name: str
url: str
hash: str
class OpenSSLVersionsDirectory(KeyedList[_OpenSSLVersionInfo, packaging.version.Version]):
key_field = "version"
def get_store_latest_release(self, major: int) \
-> _OpenSSLVersionInfo:
matching = [
release for release in self
if release.version.major == major
]
if matching:
return max(matching, key=lambda release: release.version)
url = "https://api.github.com/repos/openssl/openssl/releases"
while url:
response = Requests.get(url)
matching = [
release
for release in response.json()
if not release['draft']
and not release['prerelease']
and (match := OPENSSL_RELEASE_TAG_RE.fullmatch(
release['tag_name']
))
and int(match.group('major')) == major
]
if matching:
j_release = matching[0]
break
url = response.links.get('next', {}).get('url')
else:
raise ValueError(f"No OpenSSL {major}.x release found")
# noinspection PyTypeChecker
# urlparse can parse str as well as bytes
shasum_url = more_itertools.one(
asset['browser_download_url']
for asset in j_release['assets']
if urllib.parse.urlparse(asset['browser_download_url']).path.split('/')[-1].endswith('.sha256')
)
shasum_text = Requests.get(shasum_url).text
shasum_data = jc.parse("hashsum", shasum_text, quiet=True)[0]
package_hash, package_filename = shasum_data["hash"], shasum_data["filename"]
del shasum_data, shasum_text, shasum_url
# OpenSSL Github repo has tag names "openssl-<version>" as of this writing like we need
# but let's not rely on that
# splitext doesn't work with a chained extension, it only splits off the last one
package_name, package_version_str = re.match(r"([^-]+-(.*?))\.\D", package_filename).groups()
package_version = packaging.version.Version(package_version_str)
package_url = more_itertools.one(
asset['browser_download_url']
for asset in j_release['assets']
if urllib.parse.urlparse(asset['browser_download_url']).path.split('/')[-1] == package_filename
)
result = _OpenSSLVersionInfo(package_version, package_name, package_url, package_hash)
self.append(result)
return result
class _ReadlineVersionInfo(typing.NamedTuple):
version : packaging.version.Version
package_name : str
url : str
hash : str
class ReadlineVersionsDirectory(KeyedList[_ReadlineVersionInfo, packaging.version.Version]):
key_field = "version"
def get_store_latest_release(self):
if not self:
self._store_latest_release()
return self._latest_release()
def _store_latest_release(self):
candidates = ReadlineVersionsDirectory()
pattern = r'(?P<package_name>readline-(?P<version>\d+(?:\.\d+)+)).tar\.gz$'
for name, url in DownloadPage.enum_download_entries(
'https://ftpmirror.gnu.org/readline/', pattern, VersionDirectory.session):
m = re.match(pattern, name)
version = packaging.version.Version(m.group('version'))
candidates.append(_ReadlineVersionInfo(
version,
m.group('package_name'),
url,
""
))
max_item = candidates._latest_release()
hash_ = Url.sha256_url(max_item.url, VersionDirectory.session)
permalink = 'https://ftpmirror.gnu.org/readline/' +\
os.path.basename(urllib.parse.urlparse(max_item.url).path)
result = _ReadlineVersionInfo(
max_item.version,
max_item.package_name,
permalink,
hash_)
self.append(result)
return result
def _latest_release(self):
return self[max(self.keys())]
class _VersionDirectory:
def __init__(self, session):
self.existing = CPythonExistingScriptsDirectory()
self.available = CPythonAvailableVersionsDirectory(session)
self.openssl = OpenSSLVersionsDirectory()
self.readline = ReadlineVersionsDirectory()
self.session = session
VersionDirectory : _VersionDirectory
class DownloadPage:
class _DownloadPageEntry(typing.NamedTuple):
name: str
url: str
@classmethod
def enum_download_entries(cls, url, pattern, session=None,
make_name = lambda m: m.string ) \
-> typing.Generator[_DownloadPageEntry, None, None]:
"""
Enum download entries in a standard Apache directory page
(incl. CPython download page https://www.python.org/ftp/python/)
or a GNU mirror directory page
(https://ftpmirror.gnu.org/<package>/ destinations)
"""
if session is None:
session = requests_html.HTMLSession()
response = session.get(url, timeout=30)
response.raise_for_status()
page = response.html
table = page.find("pre", first=True)
# some GNU mirrors format entries as a table
# (e.g. https://mirrors.ibiblio.org/gnu/readline/)
if table is None:
table = page.find("table", first=True)
links = table.find("a")
for link in links:
href = link.attrs['href']
# CPython entries are directories
name = link.text
# skip directory entries
if not (m:=re.match(pattern, name)):
continue
name = make_name(m)
yield cls._DownloadPageEntry(name, urllib.parse.urljoin(response.url, href))
class Re:
@dataclasses.dataclass
class _interval:
group: typing.Union[int, str, None]
start: int
end: int
@staticmethod
def sub_groups(match: re.Match,
/, *args: [typing.AnyStr],
**kwargs: [typing.AnyStr])\
-> typing.AnyStr:
repls={i:repl for i,repl in enumerate(args) if repl is not None}
repls.update({n:repl for n,repl in kwargs.items() if repl is not None})
intervals: sortedcontainers.SortedList[Re._interval]=\
sortedcontainers.SortedKeyList(key=operator.attrgetter("start","end"))
for group_id in itertools.chain(range(1,len(match.groups())), match.groupdict().keys()):
if group_id not in repls:
continue
if match.start(group_id) == -1:
continue
intervals.add(Re._interval(group_id,match.start(group_id),match.end(group_id)))
del group_id
last_interval=Re._interval(None,0,0)
result=""
for interval in intervals:
if interval.start < last_interval.end:
raise ValueError(f"Cannot replace intersecting matches "
f"for groups {last_interval.group} and {interval.group} "
f"(position {interval.start})")
if interval.end == interval.start and \
last_interval.start == last_interval.end == interval.start:
raise ValueError(f"Cannot replace consecutive zero-length matches "
f"for groups {last_interval.group} and {interval.group} "
f"(position {interval.start})")
result+=match.string[last_interval.end:interval.start]+repls[interval.group]
last_interval = interval
result+=match.string[last_interval.end:]
return result
class Url:
@staticmethod
def sha256_url(url, session=None):
if session is None:
session = requests_html.HTMLSession()
logger.info(f"Downloading and computing hash of {url}")
h=hashlib.sha256()
r=session.get(url,stream=True,timeout=30)
total_bytes=int(r.headers.get('content-length',0)) or float('inf')
with tqdm.tqdm(total=total_bytes, unit='B', unit_scale=True, unit_divisor=1024) as t:
for c in r.iter_content(1024):
t.update(len(c))
h.update(c)
return h.hexdigest()
class Requests:
@staticmethod
def get(url: str) -> requests.Response:
response = requests.get(url, timeout=30)
response.raise_for_status()
return response
if __name__ == "__main__":
#sys.excepthook seems to have no effect in Github Actions
try:
sys.exit(main())
except Exception:
logging.exception("Unhandled exception occurred")
sys.exit(2)

View file

@ -32,7 +32,11 @@ CONDA_REPO = "https://repo.anaconda.com"
MINICONDA_REPO = CONDA_REPO + "/miniconda" MINICONDA_REPO = CONDA_REPO + "/miniconda"
ANACONDA_REPO = CONDA_REPO + "/archive" ANACONDA_REPO = CONDA_REPO + "/archive"
auto_accept_tos_fmt="""export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
""".strip()
install_script_fmt = """ install_script_fmt = """
{auto_accept_tos}
case "$(anaconda_architecture 2>/dev/null || true)" in case "$(anaconda_architecture 2>/dev/null || true)" in
{install_lines} {install_lines}
* ) * )
@ -215,7 +219,12 @@ class CondaVersion(NamedTuple):
# since 4.8, Miniconda specifies versions explicitly in the file name # since 4.8, Miniconda specifies versions explicitly in the file name
raise ValueError("Miniconda 4.8+ is supposed to specify a Python version explicitly") raise ValueError("Miniconda 4.8+ is supposed to specify a Python version explicitly")
if self.flavor == "anaconda": if self.flavor == "anaconda":
# https://docs.anaconda.com/free/anaconda/reference/release-notes/ # Info about the bundled Python version
# https://www.anaconda.com/docs/getting-started/anaconda/release-notes
if v >= (2026,7):
return PyVersion.PY314
if v >= (2025,6):
return PyVersion.PY313
if v >= (2024,6): if v >= (2024,6):
return PyVersion.PY312 return PyVersion.PY312
if v >= (2023,7): if v >= (2023,7):
@ -234,6 +243,13 @@ class CondaVersion(NamedTuple):
raise ValueError(self.flavor) raise ValueError(self.flavor)
def requires_tos_accept(self):
"""
requires to accept TOS for installation
"""
return self.flavor == Flavor.MINICONDA \
and self.version_str.info() >= (25,)
class CondaSpec(NamedTuple): class CondaSpec(NamedTuple):
tflavor: TFlavor tflavor: TFlavor
@ -302,7 +318,8 @@ def make_script(specs: List[CondaSpec]):
return install_script_fmt.format( return install_script_fmt.format(
install_lines="\n".join(install_lines), install_lines="\n".join(install_lines),
tflavor=specs[0].tflavor, tflavor=specs[0].tflavor,
) auto_accept_tos = auto_accept_tos_fmt if specs[0].version.requires_tos_accept() else ""
).lstrip()
def get_existing_condas(name): def get_existing_condas(name):

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
'Adds the latest miniforge and mambaforge releases.' 'Adds the latest miniforge and mambaforge releases.'
from pathlib import Path from pathlib import Path
import argparse
import logging import logging
import os import os
import string import string
@ -8,7 +9,7 @@ import string
import requests import requests
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.basicConfig(level=os.environ.get('LOGLEVEL', 'INFO')) logging.basicConfig(level=logging.INFO)
MINIFORGE_REPO = 'conda-forge/miniforge' MINIFORGE_REPO = 'conda-forge/miniforge'
DISTRIBUTIONS = ['miniforge'] DISTRIBUTIONS = ['miniforge']
@ -20,6 +21,9 @@ SKIPPED_RELEASES = [
'22.11.1-1', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403) '22.11.1-1', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403)
'22.11.1-2', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403) '22.11.1-2', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403)
'25.3.0-0', #marked as prerelease, no Linux version '25.3.0-0', #marked as prerelease, no Linux version
'25.11.0-0', #regression reported in constructor, re-released as 25.11.0-1 with hotfix bumping to constructor>=3.14 (was >=3.12, 3.13 implicit)
'26.1.1-0', #prerelease, no binary assets
'26.1.1-1', #prerelease, no binary assets
] ]
install_script_fmt = """ install_script_fmt = """
@ -46,9 +50,9 @@ here = Path(__file__).resolve()
out_dir: Path = here.parent.parent / "share" / "python-build" out_dir: Path = here.parent.parent / "share" / "python-build"
def download_sha(url): def download_sha(url):
logger.info('Downloading SHA file %(url)s', locals()) logger.info(f'Downloading SHA file {url}')
tup = tuple(reversed(requests.get(url).text.replace('./', '').rstrip().split())) tup = tuple(reversed(requests.get(url).text.replace('./', '').rstrip().split()))
logger.debug('Got %(tup)s', locals()) logger.debug(f'Got {tup}')
return tup return tup
def create_spec(filename, sha, url): def create_spec(filename, sha, url):
@ -71,7 +75,7 @@ def create_spec(filename, sha, url):
'installer_filename': f'{flavor_with_suffix.lower()}-{version}-{subversion}', 'installer_filename': f'{flavor_with_suffix.lower()}-{version}-{subversion}',
} }
logger.debug('Created spec %(spec)s', locals()) logger.debug(f'Created spec {spec}')
return spec return spec
@ -85,6 +89,9 @@ def py_version(version):
# transition points: # transition points:
# https://github.com/conda-forge/miniforge/blame/main/Miniforge3/construct.yaml # https://github.com/conda-forge/miniforge/blame/main/Miniforge3/construct.yaml
# look for "- python <version>" in non-pypy branch and which tag the commit is first in # look for "- python <version>" in non-pypy branch and which tag the commit is first in
if version_tuple_ >= (26,1):
# https://github.com/conda-forge/miniforge/commit/0016367731e52c67234d6d0e7e6a24c6bf7673e4
return "313"
if version_tuple_ >= (24,5): if version_tuple_ >= (24,5):
# yes, they jumped from 3.10 directly to 3.12 # yes, they jumped from 3.10 directly to 3.12
# https://github.com/conda-forge/miniforge/commit/bddad0baf22b37cfe079e47fd1680fdfb2183590 # https://github.com/conda-forge/miniforge/commit/bddad0baf22b37cfe079e47fd1680fdfb2183590
@ -94,13 +101,14 @@ def py_version(version):
raise ValueError("Bundled Python version unknown for release `%s'"%version) raise ValueError("Bundled Python version unknown for release `%s'"%version)
def supported(filename): def supported(filename):
return ('pypy' not in filename) and ('Windows' not in filename) return ('pypy' not in filename) and ('Windows' not in filename) and (not filename.endswith('.pkg'))
def add_version(release, distributions): def add_version(release, distributions):
tag_name = release['tag_name'] tag_name = release['tag_name']
download_urls = { f['name']: f['browser_download_url'] for f in release['assets'] } download_urls = { f['name']: f['browser_download_url'] for f in release['assets'] }
# can assume that sha files are named similar to release files so can also check supported(on their names) # can assume that sha files are named similar to release files so can also check supported(on their names)
shas = dict([download_sha(url) for (name, url) in download_urls.items() if name.endswith('.sha256') and supported(os.path.basename(name)) and tag_name in name]) shas = dict([download_sha(url) for (name, url) in download_urls.items()
if name.endswith('.sha256') and supported(os.path.splitext(name)[0]) and tag_name in name])
specs = [create_spec(filename, sha, download_urls[filename]) for (filename, sha) in shas.items() if supported(filename)] specs = [create_spec(filename, sha, download_urls[filename]) for (filename, sha) in shas.items() if supported(filename)]
@ -111,7 +119,7 @@ def add_version(release, distributions):
if count > 0: if count > 0:
output_file = out_dir / distribution_specs[0]['installer_filename'] output_file = out_dir / distribution_specs[0]['installer_filename']
logger.info('Writing %(count)d specs for %(distribution)s to %(output_file)s', locals()) logger.info(f'Writing {count} specs for {distribution} to {output_file}')
script_str = install_script_fmt.format( script_str = install_script_fmt.format(
install_lines="\n".join([install_line_fmt.format_map(s) for s in distribution_specs]), install_lines="\n".join([install_line_fmt.format_map(s) for s in distribution_specs]),
@ -121,22 +129,40 @@ def add_version(release, distributions):
with open(output_file, 'w') as f: with open(output_file, 'w') as f:
f.write(script_str) f.write(script_str)
else: else:
logger.info('Did not find specs for %(distribution)s', locals()) logger.info(f'Did not find specs for {distribution}')
for release in requests.get(f'https://api.github.com/repos/{MINIFORGE_REPO}/releases').json():
version = release['tag_name']
if version in SKIPPED_RELEASES: def main():
continue for release in requests.get(f'https://api.github.com/repos/{MINIFORGE_REPO}/releases').json():
version = release['tag_name']
logger.info('Looking for %(version)s in %(out_dir)s', locals()) if version in SKIPPED_RELEASES:
continue
# mambaforge is retired https://github.com/conda-forge/miniforge/releases/tag/24.11.2-0 logger.debug(f'Looking for {version} in {out_dir}')
if version_tuple(version) >= (24,11,2):
distributions = DISTRIBUTIONS
else:
distributions = DISTRIBUTIONS_PRE25
if any(not list(out_dir.glob(f'{distribution}*-{version}')) for distribution in distributions): # mambaforge is retired https://github.com/conda-forge/miniforge/releases/tag/24.11.2-0
logger.info('Downloading %(version)s', locals()) if version_tuple(version) >= (24, 11, 2):
add_version(release, distributions) distributions = DISTRIBUTIONS
else:
distributions = DISTRIBUTIONS_PRE25
if any(not list(out_dir.glob(f'{distribution}*-{version}')) for distribution in distributions):
logger.info(f'Downloading {version}')
add_version(release, distributions)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"-d", "--dry-run", action="store_true",
help="Do not write scripts, just report them to stdout",
)
parser.add_argument(
"-v", "--verbose", action="store_true", default=0,
help="Increase verbosity of logging",
)
parsed = parser.parse_args()
if parsed.verbose: logging.getLogger.setLevel(logging.DEBUG)
main()

View file

@ -1 +1,9 @@
more_itertools
requests-html requests-html
fake_useragent<2; python_version < "3.9"
lxml[html_clean]
packaging
requests
sortedcontainers
tqdm
jc

View file

@ -1,4 +1,6 @@
prefer_openssl11 export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@1.1 openssl@1.0}"
export PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA="${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl11 openssl10}"
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.1.0j" "https://www.openssl.org/source/old/1.1.0/openssl-1.1.0j.tar.gz#31bec6c203ce1a8e93d5994f4ed304c63ccf07676118b6634edded12ad1b3246" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.1.0j" "https://www.openssl.org/source/old/1.1.0/openssl-1.1.0j.tar.gz#31bec6c203ce1a8e93d5994f4ed304c63ccf07676118b6634edded12ad1b3246" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
install_git "Python-2.7-dev" "https://github.com/python/cpython" "2.7" standard verify_py27 copy_python_gdb ensurepip install_git "Python-2.7-dev" "https://github.com/python/cpython" "2.7" standard verify_py27 copy_python_gdb ensurepip

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
install_package "Python-2.7" "https://www.python.org/ftp/python/2.7/Python-2.7.tgz#5670dd6c0c93b0b529781d070852f7b51ce6855615b16afcd318341af2910fb5" standard verify_py27 copy_python_gdb ensurepip install_package "Python-2.7" "https://www.python.org/ftp/python/2.7/Python-2.7.tgz#5670dd6c0c93b0b529781d070852f7b51ce6855615b16afcd318341af2910fb5" standard verify_py27 copy_python_gdb ensurepip

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
install_package "Python-2.7.1" "https://www.python.org/ftp/python/2.7.1/Python-2.7.1.tgz#ca13e7b1860821494f70de017202283ad73b1fb7bd88586401c54ef958226ec8" standard verify_py27 copy_python_gdb ensurepip install_package "Python-2.7.1" "https://www.python.org/ftp/python/2.7.1/Python-2.7.1.tgz#ca13e7b1860821494f70de017202283ad73b1fb7bd88586401c54ef958226ec8" standard verify_py27 copy_python_gdb ensurepip

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,4 +1,6 @@
prefer_openssl11 export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@1.1 openssl@1.0}"
export PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA="${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl11 openssl10}"
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.1.0j" "https://www.openssl.org/source/old/1.1.0/openssl-1.1.0j.tar.gz#31bec6c203ce1a8e93d5994f4ed304c63ccf07676118b6634edded12ad1b3246" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.1.0j" "https://www.openssl.org/source/old/1.1.0/openssl-1.1.0j.tar.gz#31bec6c203ce1a8e93d5994f4ed304c63ccf07676118b6634edded12ad1b3246" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,4 +1,6 @@
prefer_openssl11 export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@1.1 openssl@1.0}"
export PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA="${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl11 openssl10}"
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.1.0j" "https://www.openssl.org/source/old/1.1.0/openssl-1.1.0j.tar.gz#31bec6c203ce1a8e93d5994f4ed304c63ccf07676118b6634edded12ad1b3246" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.1.0j" "https://www.openssl.org/source/old/1.1.0/openssl-1.1.0j.tar.gz#31bec6c203ce1a8e93d5994f4ed304c63ccf07676118b6634edded12ad1b3246" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,3 +1,6 @@
export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@1.1 openssl@1.0}"
export PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA="${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl11 openssl10}"
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2q" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2q.tar.gz#5744cfcbcec2b1b48629f7354203bc1e5e9b5466998bbccc5b5fcde3b18eb684" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2q" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2q.tar.gz#5744cfcbcec2b1b48629f7354203bc1e5e9b5466998bbccc5b5fcde3b18eb684" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,3 +1,6 @@
export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@1.1 openssl@1.0}"
export PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA="${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl11 openssl10}"
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2q" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2q.tar.gz#5744cfcbcec2b1b48629f7354203bc1e5e9b5466998bbccc5b5fcde3b18eb684" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2q" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2q.tar.gz#5744cfcbcec2b1b48629f7354203bc1e5e9b5466998bbccc5b5fcde3b18eb684" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,4 +1,6 @@
export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="openssl@1.1 openssl@1.0 openssl" export PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA="${PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA:-openssl@1.1 openssl@1.0}"
export PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA="${PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA:-openssl11 openssl10}"
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.1.1v" "https://www.openssl.org/source/openssl-1.1.1v.tar.gz" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.1.1v" "https://www.openssl.org/source/openssl-1.1.1v.tar.gz" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
install_package "Python-2.7.2" "https://www.python.org/ftp/python/2.7.2/Python-2.7.2.tgz#1d54b7096c17902c3f40ffce7e5b84e0072d0144024184fff184a84d563abbb3" standard verify_py27 copy_python_gdb ensurepip install_package "Python-2.7.2" "https://www.python.org/ftp/python/2.7.2/Python-2.7.2.tgz#1d54b7096c17902c3f40ffce7e5b84e0072d0144024184fff184a84d563abbb3" standard verify_py27 copy_python_gdb ensurepip

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
install_package "Python-2.7.3" "https://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz#d4c20f2b5faf95999fd5fecb3f7d32071b0820516224a6d2b72932ab47a1cb8e" standard verify_py27 copy_python_gdb ensurepip install_package "Python-2.7.3" "https://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz#d4c20f2b5faf95999fd5fecb3f7d32071b0820516224a6d2b72932ab47a1cb8e" standard verify_py27 copy_python_gdb ensurepip

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
install_package "Python-2.7.4" "https://www.python.org/ftp/python/2.7.4/Python-2.7.4.tgz#98c5eb9c8e65effcc0122112ba17a0bce880aa23ecb560af56b55eb55632b81a" standard verify_py27 copy_python_gdb ensurepip install_package "Python-2.7.4" "https://www.python.org/ftp/python/2.7.4/Python-2.7.4.tgz#98c5eb9c8e65effcc0122112ba17a0bce880aa23ecb560af56b55eb55632b81a" standard verify_py27 copy_python_gdb ensurepip

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
install_package "Python-2.7.5" "https://www.python.org/ftp/python/2.7.5/Python-2.7.5.tgz#8e1b5fa87b91835afb376a9c0d319d41feca07ffebc0288d97ab08d64f48afbf" standard verify_py27 copy_python_gdb ensurepip install_package "Python-2.7.5" "https://www.python.org/ftp/python/2.7.5/Python-2.7.5.tgz#8e1b5fa87b91835afb376a9c0d319d41feca07ffebc0288d97ab08d64f48afbf" standard verify_py27 copy_python_gdb ensurepip

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
install_package "Python-2.7.6" "https://www.python.org/ftp/python/2.7.6/Python-2.7.6.tgz#99c6860b70977befa1590029fae092ddb18db1d69ae67e8b9385b66ed104ba58" standard verify_py27 copy_python_gdb ensurepip install_package "Python-2.7.6" "https://www.python.org/ftp/python/2.7.6/Python-2.7.6.tgz#99c6860b70977befa1590029fae092ddb18db1d69ae67e8b9385b66ed104ba58" standard verify_py27 copy_python_gdb ensurepip

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -1,3 +1,4 @@
export PYTHON_CFLAGS="${PYTHON_CFLAGS:+$PYTHON_CFLAGS }-std=c99"
install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.0.2k" "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2k.tar.gz#6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then if has_tar_xz_support; then

View file

@ -0,0 +1,9 @@
prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
install_package "openssl-3.2.4" "https://github.com/openssl/openssl/releases/download/openssl-3.2.4/openssl-3.2.4.tar.gz#b23ad7fd9f73e43ad1767e636040e88ba7c9e5775bfa5618436a0dd2c17c3716" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.1" "https://ftpmirror.gnu.org/readline/readline-8.1.tar.gz#f8ceb4ee131e3232226a17f51b164afc46cd0b9e6cef344be87c65962cb82b02" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then
install_package "Python-3.10.19" "https://www.python.org/ftp/python/3.10.19/Python-3.10.19.tar.xz#c8f4a596572201d81dd7df91f70e177e19a70f1d489968b54b5fbbf29a97c076" standard verify_py310 copy_python_gdb ensurepip
else
install_package "Python-3.10.19" "https://www.python.org/ftp/python/3.10.19/Python-3.10.19.tgz#a078fb2d7a216071ebbe2e34b5f5355dd6b6e9b0cd1bacc4a41c63990c5a0eec" standard verify_py310 copy_python_gdb ensurepip
fi

View file

@ -0,0 +1,9 @@
prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
install_package "openssl-3.6.1" "https://github.com/openssl/openssl/releases/download/openssl-3.6.1/openssl-3.6.1.tar.gz#b1bfedcd5b289ff22aee87c9d600f515767ebf45f77168cb6d64f231f518a82e" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.3" "https://ftpmirror.gnu.org/readline/readline-8.3.tar.gz#fe5383204467828cd495ee8d1d3c037a7eba1389c22bc6a041f627976f9061cc" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then
install_package "Python-3.10.20" "https://www.python.org/ftp/python/3.10.20/Python-3.10.20.tar.xz#de6517421601e39a9a3bc3e1bc4c7b2f239297423ee05e282598c83ec0647505" standard verify_py310 copy_python_gdb ensurepip
else
install_package "Python-3.10.20" "https://www.python.org/ftp/python/3.10.20/Python-3.10.20.tgz#4ff5fd4c5bab803b935019f3e31d7219cebd6f870d00389cea53b88bbe935d1a" standard verify_py310 copy_python_gdb ensurepip
fi

View file

@ -0,0 +1,9 @@
prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
install_package "openssl-3.6.3" "https://github.com/openssl/openssl/releases/download/openssl-3.6.3/openssl-3.6.3.tar.gz#243a86649cf6f23eeb6a2ff2456e09e5d77dd9018a54d3d96b0c6bdd6ba6c7f1" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.3" "https://ftpmirror.gnu.org/readline/readline-8.3.tar.gz#fe5383204467828cd495ee8d1d3c037a7eba1389c22bc6a041f627976f9061cc" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then
install_package "Python-3.10.21" "https://www.python.org/ftp/python/3.10.21/Python-3.10.21.tar.xz#a0da1e72132e950154eca0f6f47d5db828454700de20e5113667940d81e0db04" standard verify_py310 copy_python_gdb ensurepip
else
install_package "Python-3.10.21" "https://www.python.org/ftp/python/3.10.21/Python-3.10.21.tgz#f276987f06270ae6c1fb4da620bd105edf78c31368c2f7e85e6c1d51c560b04b" standard verify_py310 copy_python_gdb ensurepip
fi

View file

@ -1,5 +1,6 @@
prefer_openssl3 prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-1.1.1q" "https://www.openssl.org/source/openssl-1.1.1q.tar.gz#d7939ce614029cdff0b6c20f0e2e5703158a489a72b2507b8bd51bf8c8fd10ca" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.1.1q" "https://www.openssl.org/source/openssl-1.1.1q.tar.gz#d7939ce614029cdff0b6c20f0e2e5703158a489a72b2507b8bd51bf8c8fd10ca" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline

View file

@ -1,5 +1,6 @@
prefer_openssl3 prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-1.1.1s" "https://www.openssl.org/source/openssl-1.1.1s.tar.gz#c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.1.1s" "https://www.openssl.org/source/openssl-1.1.1s.tar.gz#c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline

View file

@ -1,5 +1,6 @@
prefer_openssl3 prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-3.2.2" "https://openssl.org/source/old/3.2/openssl-3.2.2.tar.gz#197149c18d9e9f292c43f0400acaba12e5f52cacfe050f3d199277ea738ec2e7" mac_openssl --if has_broken_mac_openssl install_package "openssl-3.2.2" "https://openssl.org/source/old/3.2/openssl-3.2.2.tar.gz#197149c18d9e9f292c43f0400acaba12e5f52cacfe050f3d199277ea738ec2e7" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline

View file

@ -1,5 +1,6 @@
prefer_openssl3 prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-3.2.2" "https://openssl.org/source/old/3.2/openssl-3.2.2.tar.gz#197149c18d9e9f292c43f0400acaba12e5f52cacfe050f3d199277ea738ec2e7" mac_openssl --if has_broken_mac_openssl install_package "openssl-3.2.2" "https://openssl.org/source/old/3.2/openssl-3.2.2.tar.gz#197149c18d9e9f292c43f0400acaba12e5f52cacfe050f3d199277ea738ec2e7" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline

View file

@ -1,5 +1,6 @@
prefer_openssl3 prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-3.2.4" "https://github.com/openssl/openssl/releases/download/openssl-3.2.4/openssl-3.2.4.tar.gz#b23ad7fd9f73e43ad1767e636040e88ba7c9e5775bfa5618436a0dd2c17c3716" mac_openssl --if has_broken_mac_openssl install_package "openssl-3.2.4" "https://github.com/openssl/openssl/releases/download/openssl-3.2.4/openssl-3.2.4.tar.gz#b23ad7fd9f73e43ad1767e636040e88ba7c9e5775bfa5618436a0dd2c17c3716" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline

View file

@ -1,5 +1,6 @@
prefer_openssl3 prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-3.2.4" "https://github.com/openssl/openssl/releases/download/openssl-3.2.4/openssl-3.2.4.tar.gz#b23ad7fd9f73e43ad1767e636040e88ba7c9e5775bfa5618436a0dd2c17c3716" mac_openssl --if has_broken_mac_openssl install_package "openssl-3.2.4" "https://github.com/openssl/openssl/releases/download/openssl-3.2.4/openssl-3.2.4.tar.gz#b23ad7fd9f73e43ad1767e636040e88ba7c9e5775bfa5618436a0dd2c17c3716" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline

View file

@ -0,0 +1,11 @@
prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-3.2.4" "https://github.com/openssl/openssl/releases/download/openssl-3.2.4/openssl-3.2.4.tar.gz#b23ad7fd9f73e43ad1767e636040e88ba7c9e5775bfa5618436a0dd2c17c3716" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then
install_package "Python-3.11.14" "https://www.python.org/ftp/python/3.11.14/Python-3.11.14.tar.xz#8d3ed8ec5c88c1c95f5e558612a725450d2452813ddad5e58fdb1a53b1209b78" standard verify_py311 copy_python_gdb ensurepip
else
install_package "Python-3.11.14" "https://www.python.org/ftp/python/3.11.14/Python-3.11.14.tgz#563d2a1b2a5ba5d5409b5ecd05a0e1bf9b028cf3e6a6f0c87a5dc8dc3f2d9182" standard verify_py311 copy_python_gdb ensurepip
fi

View file

@ -0,0 +1,11 @@
prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-3.6.1" "https://github.com/openssl/openssl/releases/download/openssl-3.6.1/openssl-3.6.1.tar.gz#b1bfedcd5b289ff22aee87c9d600f515767ebf45f77168cb6d64f231f518a82e" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.3" "https://ftpmirror.gnu.org/readline/readline-8.3.tar.gz#fe5383204467828cd495ee8d1d3c037a7eba1389c22bc6a041f627976f9061cc" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then
install_package "Python-3.11.15" "https://www.python.org/ftp/python/3.11.15/Python-3.11.15.tar.xz#272179ddd9a2e41a0fc8e42e33dfbdca0b3711aa5abf372d3f2d51543d09b625" standard verify_py311 copy_python_gdb ensurepip
else
install_package "Python-3.11.15" "https://www.python.org/ftp/python/3.11.15/Python-3.11.15.tgz#f4de1b10bd6c70cbb9fa1cd71fc5038b832747a74ee59d599c69ce4846defb50" standard verify_py311 copy_python_gdb ensurepip
fi

View file

@ -0,0 +1,11 @@
prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-3.6.3" "https://github.com/openssl/openssl/releases/download/openssl-3.6.3/openssl-3.6.3.tar.gz#243a86649cf6f23eeb6a2ff2456e09e5d77dd9018a54d3d96b0c6bdd6ba6c7f1" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.3" "https://ftpmirror.gnu.org/readline/readline-8.3.tar.gz#fe5383204467828cd495ee8d1d3c037a7eba1389c22bc6a041f627976f9061cc" mac_readline --if has_broken_mac_readline
if has_tar_xz_support; then
install_package "Python-3.11.16" "https://www.python.org/ftp/python/3.11.16/Python-3.11.16.tar.xz#91bcdebfdde239a003ae93738a7fce0f9230fee5c4bc2b86f6e6e8c6f98aabe8" standard verify_py311 copy_python_gdb ensurepip
else
install_package "Python-3.11.16" "https://www.python.org/ftp/python/3.11.16/Python-3.11.16.tgz#6c0bd76ab0ec7d94ed400b1497f01ac6c7751c8822615ee0855a3eb2d893ea76" standard verify_py311 copy_python_gdb ensurepip
fi

View file

@ -1,5 +1,6 @@
prefer_openssl3 prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-1.1.1s" "https://www.openssl.org/source/openssl-1.1.1s.tar.gz#c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.1.1s" "https://www.openssl.org/source/openssl-1.1.1s.tar.gz#c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline

View file

@ -1,5 +1,6 @@
prefer_openssl3 prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-1.1.1s" "https://www.openssl.org/source/openssl-1.1.1s.tar.gz#c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.1.1s" "https://www.openssl.org/source/openssl-1.1.1s.tar.gz#c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline

View file

@ -1,5 +1,6 @@
prefer_openssl3 prefer_openssl3
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1
export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-1.1.1s" "https://www.openssl.org/source/openssl-1.1.1s.tar.gz#c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa" mac_openssl --if has_broken_mac_openssl install_package "openssl-1.1.1s" "https://www.openssl.org/source/openssl-1.1.1s.tar.gz#c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa" mac_openssl --if has_broken_mac_openssl
install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline install_package "readline-8.2" "https://ftpmirror.gnu.org/readline/readline-8.2.tar.gz#3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35" mac_readline --if has_broken_mac_readline

Some files were not shown because too many files have changed in this diff Show more