mirror of
https://github.com/pyenv/pyenv.git
synced 2026-09-10 07:36:16 -04:00
Compare commits
359 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef4905cd81 | ||
|
|
88e9ab13e8 | ||
|
|
798a1ad603 | ||
|
|
fb7f2071fb | ||
|
|
ef7a9f5d14 | ||
|
|
265b0dcefa | ||
|
|
2548d53706 | ||
|
|
c293de3650 | ||
|
|
bdfc6b03f3 | ||
|
|
8c46508bd0 | ||
|
|
a00e0bc475 | ||
|
|
981559003f | ||
|
|
4733cca31b | ||
|
|
ef2fb22235 | ||
|
|
97900a4aa4 | ||
|
|
ecf9a7f9e6 | ||
|
|
495550afc8 | ||
|
|
57f1df294b | ||
|
|
14830b18bf | ||
|
|
032e7af407 | ||
|
|
b0cff001ff | ||
|
|
234a87e400 | ||
|
|
50fbc8c6cc | ||
|
|
0f16606e4f | ||
|
|
7c2226386b | ||
|
|
3a98f8c1c3 | ||
|
|
f99638cb82 | ||
|
|
3b9604c093 | ||
|
|
920052eb0b | ||
|
|
cb20e375d3 | ||
|
|
f11926a274 | ||
|
|
0fc5dfe630 | ||
|
|
2bcc7d8fc4 | ||
|
|
2bd4f6c4da | ||
|
|
8771e721c1 | ||
|
|
d92b6e1bbe | ||
|
|
9962f8cb39 | ||
|
|
f9ffb73a7e | ||
|
|
bd7c969e7f | ||
|
|
4a73bd17e2 | ||
|
|
2c1f405180 | ||
|
|
e234812f46 | ||
|
|
1a591b3963 | ||
|
|
48e70e1fe9 | ||
|
|
195b9fe940 | ||
|
|
b7e7adf4ec | ||
|
|
a6ef1e70ba | ||
|
|
5a31fea21b | ||
|
|
17be308600 | ||
|
|
9d6cf587b7 | ||
|
|
1cc9e770ae | ||
|
|
0e2e214f56 | ||
|
|
340348d98d | ||
|
|
2d9a224e6c | ||
|
|
da172db42d | ||
|
|
b0a64af8cc | ||
|
|
9e4d1ca95a | ||
|
|
b9e165271e | ||
|
|
3088203e33 | ||
|
|
8f70d418bb | ||
|
|
bdf8d254fe | ||
|
|
f5915fc866 | ||
|
|
84e0f3a887 | ||
|
|
7126870320 | ||
|
|
26dd5d9dda | ||
|
|
bac39a0ef8 | ||
|
|
bec9a562a5 | ||
|
|
ae9e6594d6 | ||
|
|
16c7cc6de4 | ||
|
|
017ad4a573 | ||
|
|
6188e9efa9 | ||
|
|
286a167cf0 | ||
|
|
135adbb192 | ||
|
|
b3a3e702f1 | ||
|
|
958e3aca95 | ||
|
|
74c6efe2f4 | ||
|
|
95df7dbc7b | ||
|
|
e4c462dc70 | ||
|
|
790bedd821 | ||
|
|
19f4c7cf20 | ||
|
|
46cc3e552b | ||
|
|
8f68aab443 | ||
|
|
a27d2723d6 | ||
|
|
4c78a0dd50 | ||
|
|
91aacc5dbf | ||
|
|
3974647f0f | ||
|
|
ebd788d08c | ||
|
|
4b77d83d9d | ||
|
|
933d0aaf1d | ||
|
|
09dce106a4 | ||
|
|
b52a8e3f52 | ||
|
|
f122a9d764 | ||
|
|
3323ba3ed9 | ||
|
|
b43c9e5d42 | ||
|
|
532b659ca2 | ||
|
|
7565df6eab | ||
|
|
e55613a267 | ||
|
|
6c0c5cfa96 | ||
|
|
cf6f3c3200 | ||
|
|
45180928d3 | ||
|
|
c12fd3ae6b | ||
|
|
455d1a31db | ||
|
|
efc77132f7 | ||
|
|
c425c9ec3a | ||
|
|
95ddd7d479 | ||
|
|
29057a6c69 | ||
|
|
23eb9a9ee3 | ||
|
|
bfc595b0a0 | ||
|
|
034e15b810 | ||
|
|
4c6d4c9be7 | ||
|
|
549a644688 | ||
|
|
7f1139ebe6 | ||
|
|
874b27c749 | ||
|
|
08a015100e | ||
|
|
8decb512b4 | ||
|
|
84414c0121 | ||
|
|
9ea5bd3005 | ||
|
|
0a12a9cce2 | ||
|
|
e52da606ff | ||
|
|
4f47143108 | ||
|
|
70f096a95c | ||
|
|
06fd1ce788 | ||
|
|
0864daa5ea | ||
|
|
522164fbd5 | ||
|
|
8c59c44902 | ||
|
|
c639152ac7 | ||
|
|
331ff66e2d | ||
|
|
f6a5b409e9 | ||
|
|
3399b2e2e5 | ||
|
|
5bb6b27d61 | ||
|
|
97573e156e | ||
|
|
40dba8256f | ||
|
|
e30c23eac2 | ||
|
|
a7cc776274 | ||
|
|
e08497a2b7 | ||
|
|
709f42cc08 | ||
|
|
966819a10f | ||
|
|
a736e6af27 | ||
|
|
6bdb85d8b7 | ||
|
|
36f37e96ea | ||
|
|
3f62ee82b5 | ||
|
|
27b4b4dc84 | ||
|
|
0b0335a378 | ||
|
|
b8a6e9c33c | ||
|
|
3966e507ba | ||
|
|
485090e713 | ||
|
|
6481d1451d | ||
|
|
dfb1a96c3e | ||
|
|
ae1bb9c061 | ||
|
|
46e4e6ab07 | ||
|
|
914ff80062 | ||
|
|
840f3ff7fd | ||
|
|
bfec0e43f5 | ||
|
|
ce491008e7 | ||
|
|
d26308df49 | ||
|
|
8397a19c64 | ||
|
|
0d71a264c2 | ||
|
|
a6521a0d35 | ||
|
|
044d816d35 | ||
|
|
6a246fad63 | ||
|
|
174c61526f | ||
|
|
397312d0c3 | ||
|
|
5bd084d816 | ||
|
|
974e820478 | ||
|
|
a47dae9131 | ||
|
|
d5cd462a35 | ||
|
|
1534421e56 | ||
|
|
e985ffab82 | ||
|
|
8037f22665 | ||
|
|
d5fa1e225e | ||
|
|
23bcdebe18 | ||
|
|
aa2e8b8260 | ||
|
|
c9d3829169 | ||
|
|
05d3a598c4 | ||
|
|
c89b6b46dc | ||
|
|
c4ed80826f | ||
|
|
47871b2dc3 | ||
|
|
43e83b52a2 | ||
|
|
9279111a60 | ||
|
|
3100654510 | ||
|
|
06bac52676 | ||
|
|
47b6fce29d | ||
|
|
81befc1592 | ||
|
|
c44f03c058 | ||
|
|
b2a43bbcdb | ||
|
|
2f10394d33 | ||
|
|
57ec277d6a | ||
|
|
63edb91375 | ||
|
|
6d8c0e4021 | ||
|
|
fbc7067e3f | ||
|
|
82f4de5593 | ||
|
|
968cd11daa | ||
|
|
5447f1f859 | ||
|
|
3441980e83 | ||
|
|
bd2f32dd7d | ||
|
|
94faa168cb | ||
|
|
92c9ab5273 | ||
|
|
37bf92af84 | ||
|
|
05ba9388a7 | ||
|
|
c62be31e8c | ||
|
|
c8129ceee6 | ||
|
|
d9182d6edc | ||
|
|
4cf95be5ee | ||
|
|
8c50260ba2 | ||
|
|
f614e5caa5 | ||
|
|
3c539aa0bc | ||
|
|
935e1a68cd | ||
|
|
98a7a6809c | ||
|
|
45ef1677c4 | ||
|
|
c35f26af3e | ||
|
|
967ed2c367 | ||
|
|
1d8240a641 | ||
|
|
e5a6b49e02 | ||
|
|
4a3aaf4327 | ||
|
|
2c27f44612 | ||
|
|
ff94d498db | ||
|
|
99faf7f217 | ||
|
|
bf37e78693 | ||
|
|
956f78df42 | ||
|
|
56bc05d5d6 | ||
|
|
8ef33165b8 | ||
|
|
48743aa8f3 | ||
|
|
2c38423a98 | ||
|
|
aa6c9b9694 | ||
|
|
a944da76c0 | ||
|
|
aeea3ac825 | ||
|
|
7998159daa | ||
|
|
5855d9dfca | ||
|
|
22993a239e | ||
|
|
6113214f65 | ||
|
|
7418835908 | ||
|
|
b259813357 | ||
|
|
22b192913e | ||
|
|
b50afe69a8 | ||
|
|
91931a4dd9 | ||
|
|
dabaa901e9 | ||
|
|
4b33cb68b6 | ||
|
|
9b85239875 | ||
|
|
f19fe0f2be | ||
|
|
293798582a | ||
|
|
4b36ddf5c6 | ||
|
|
4e7626721a | ||
|
|
3b25462407 | ||
|
|
70294ef6f1 | ||
|
|
bdb0508220 | ||
|
|
e805257c1f | ||
|
|
6cbce8616e | ||
|
|
a6ea5b3179 | ||
|
|
0f7b8fa8ee | ||
|
|
59204a6c89 | ||
|
|
6f0e2d3458 | ||
|
|
61d869f67e | ||
|
|
a63e48cf75 | ||
|
|
d1ae7fd65d | ||
|
|
b230a338df | ||
|
|
94e4bf0ae8 | ||
|
|
a30c26ddf5 | ||
|
|
fdde91269b | ||
|
|
fa741b861a | ||
|
|
2afd1733a4 | ||
|
|
032022dace | ||
|
|
c6ea700cdf | ||
|
|
97fd8f436d | ||
|
|
1b7d2f08fd | ||
|
|
74fea7edc6 | ||
|
|
94071a9374 | ||
|
|
7661437864 | ||
|
|
ea3a9fe895 | ||
|
|
c1c8646d75 | ||
|
|
fc28287273 | ||
|
|
8d3bba2f92 | ||
|
|
a419b003b4 | ||
|
|
1c05252dfc | ||
|
|
8087e811cd | ||
|
|
faddc0d3fe | ||
|
|
ce9d396ccc | ||
|
|
ba968fda2d | ||
|
|
8e34326347 | ||
|
|
519ce9dbf0 | ||
|
|
3e51bae343 | ||
|
|
ca551b3a55 | ||
|
|
bbe6301f01 | ||
|
|
8c3b705f04 | ||
|
|
90b1c28fa6 | ||
|
|
0052372fa3 | ||
|
|
1068875cc6 | ||
|
|
22a7c46d54 | ||
|
|
7881c26b5a | ||
|
|
4033a0a260 | ||
|
|
dee36c348d | ||
|
|
d275391f65 | ||
|
|
a7897ca0f6 | ||
|
|
92a28e531b | ||
|
|
8990110dcf | ||
|
|
68edd564cf | ||
|
|
0d987b0e4b | ||
|
|
857806e6ca | ||
|
|
85561398ea | ||
|
|
17fb66cd8b | ||
|
|
2ecd676a2a | ||
|
|
b57bdef584 | ||
|
|
cd78412b80 | ||
|
|
692ac77559 | ||
|
|
97eda4403f | ||
|
|
ab1331d3c0 | ||
|
|
7d3854b64d | ||
|
|
11c1898abb | ||
|
|
25e5989261 | ||
|
|
cd2f410f75 | ||
|
|
89b8e5f8da | ||
|
|
71a8d47b81 | ||
|
|
bc44cb8fc0 | ||
|
|
3dbdfdf39a | ||
|
|
43da49d6df | ||
|
|
10675af5a6 | ||
|
|
685f281526 | ||
|
|
d56241330c | ||
|
|
73b7420b3d | ||
|
|
bd9d13b9fe | ||
|
|
05cc41d273 | ||
|
|
df84c42653 | ||
|
|
23fe500a33 | ||
|
|
609daca27c | ||
|
|
40f46083db | ||
|
|
418a61b7b8 | ||
|
|
16508177fb | ||
|
|
945f4b216d | ||
|
|
0c680e8ede | ||
|
|
20296fc53a | ||
|
|
7725fe2cf2 | ||
|
|
d8384fcc05 | ||
|
|
e150257413 | ||
|
|
798d21e0ca | ||
|
|
9114929e4f | ||
|
|
f1c5371752 | ||
|
|
907cd78f35 | ||
|
|
e13b5848be | ||
|
|
bf19366669 | ||
|
|
a71a378dac | ||
|
|
807a4e0cac | ||
|
|
b9ede4ae0e | ||
|
|
0e05ee8694 | ||
|
|
bfbe76e6a9 | ||
|
|
d88de11543 | ||
|
|
70b973fdd4 | ||
|
|
f24ff8d152 | ||
|
|
5e82f5cf81 | ||
|
|
6c86ae4175 | ||
|
|
da3fcb7eb7 | ||
|
|
018ca73444 | ||
|
|
90fa430eca | ||
|
|
2d8bd15ccd | ||
|
|
9656bcf0b9 | ||
|
|
2f8cb2cf1f | ||
|
|
b8d8dee191 | ||
|
|
ba9099392a | ||
|
|
d428be59ec | ||
|
|
dffc750fa2 | ||
|
|
f46b669f1d |
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
* @pyenv/pyenv-core-maintainers @pyenv/pyenv-core-committers
|
||||||
26
.github/ISSUE_TEMPLATE.md
vendored
26
.github/ISSUE_TEMPLATE.md
vendored
|
|
@ -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`.
|
|
||||||
54
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
54
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
* [ ] 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?q=is%3Aissue). For build errors, a reported issue typically mentions a key error message. This key error message is often not in the 10 last build log lines reported to the console but is rather earlier in the build log -- typically, it's the first error message encountered in the log.
|
||||||
|
* [ ] Make sure you are reporting a problem in Pyenv and not seeking consultation with Pyenv usage.
|
||||||
|
* GitHub issues are intended mainly for Pyenv development purposes. If you are seeking help with Pyenv usage, check [Pyenv documentation](https://github.com/pyenv/pyenv?tab=readme-ov-file#simple-python-version-management-pyenv), go to a user community site like [Gitter](https://gitter.im/yyuu/pyenv), [StackOverflow](https://stackoverflow.com/questions/tagged/pyenv), etc, or to [Discussions](https://github.com/orgs/pyenv/discussions).
|
||||||
|
* [ ] 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.
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
Do specify what the expected behaviour is if that's not obvious from the bug's nature.
|
||||||
|
|
||||||
|
#### Reproduction steps
|
||||||
|
Listing the commands to run in a new console session and their output is usually sufficient.
|
||||||
|
Please use a Markdown code block (three backticks on a line by themselves before and after the text) to denote a console output excerpt.
|
||||||
|
Usually not needed for build errors (since the arguments can already be seen in the debug trace) unless you are using an unusual invocation (e.g. setting environment variables that affect the build).
|
||||||
|
|
||||||
|
#### Diagnostic details
|
||||||
|
- [ ] Platform information (e.g. Ubuntu Linux 24.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](https://gist.github.com/):
|
||||||
|
* 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`.
|
||||||
|
- [ ] If the problem happens in another Pyenv invocation, turn on debug logging by setting `PYENV_DEBUG=1`, e.g. `env PYENV_DEBUG=1 pyenv local 3.6.4`, and attach the resulting trace as a gist
|
||||||
|
- [ ] If the problem happens outside of a Pyenv invocation, get the debug trace like this:
|
||||||
|
```
|
||||||
|
export PYENV_DEBUG=1
|
||||||
|
# for Bash
|
||||||
|
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
||||||
|
# for Zsh
|
||||||
|
export PS4='+(%x:%I): %N(%i): '
|
||||||
|
|
||||||
|
set -x
|
||||||
|
<reproduce the problem>
|
||||||
|
set +x
|
||||||
|
```
|
||||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the intended use case**
|
||||||
|
* What your general environment is if it's relevant to the feature request and is not a generic console with a typical Pyenv installation (CI, server with a custom setup, cloud environment, IDE)
|
||||||
|
* What you are trying to achieve
|
||||||
|
* What specifically you are doing for that regarding Pyenv
|
||||||
|
* Where you are stuck
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
[ ] In particular, did you consider [writing a plugin](https://github.com/pyenv/pyenv/blob/master/README.md#pyenv-plugins)? Note that if your plugin has general applicability, you can publish it in the 3rd-party plugin catalog on the Pyenv Wiki as per the link above.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -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
44
.github/actions/build-python/action.yml
vendored
Normal 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"))
|
||||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
|
|
@ -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:
|
||||||
|
|
|
||||||
364
.github/scripts/generate_release_notes_sponsors.py
vendored
Executable file
364
.github/scripts/generate_release_notes_sponsors.py
vendored
Executable 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
58
.github/workflows/add_version.yml
vendored
Normal 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
62
.github/workflows/build.yml
vendored
Normal 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 }}
|
||||||
|
|
||||||
43
.github/workflows/macos_build.yml
vendored
43
.github/workflows/macos_build.yml
vendored
|
|
@ -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-14
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
# 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 zlib
|
|
||||||
# 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"))
|
|
||||||
209
.github/workflows/modified_scripts_build.yml
vendored
209
.github/workflows/modified_scripts_build.yml
vendored
|
|
@ -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@v4
|
- 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"]
|
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@v4
|
- 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
|
||||||
|
|
@ -55,7 +115,8 @@ jobs:
|
||||||
fi
|
fi
|
||||||
- run: |
|
- run: |
|
||||||
#build
|
#build
|
||||||
pyenv --debug install ${{ matrix.python-version }}
|
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
|
||||||
|
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
|
# Micropython doesn't support --version
|
||||||
- run: |
|
- run: |
|
||||||
|
|
@ -88,6 +149,7 @@ jobs:
|
||||||
- run: |
|
- run: |
|
||||||
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
|
||||||
|
|
@ -96,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"]
|
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@v4
|
- uses: actions/checkout@v7
|
||||||
- run: |
|
- run: |
|
||||||
#envvars
|
#envvars
|
||||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||||
|
|
@ -107,48 +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 }}
|
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
|
||||||
|
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
|
||||||
|
|
@ -157,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@v4
|
- uses: actions/checkout@v7
|
||||||
- run: |
|
- run: |
|
||||||
#envvars
|
#envvars
|
||||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||||
|
|
@ -168,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
|
||||||
|
|
@ -179,7 +228,8 @@ jobs:
|
||||||
fi
|
fi
|
||||||
- run: |
|
- run: |
|
||||||
#build
|
#build
|
||||||
pyenv install -v ${{ matrix.python-version }}
|
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
|
||||||
|
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
|
# Micropython doesn't support --version
|
||||||
- run: |
|
- run: |
|
||||||
|
|
@ -208,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 != '[""]'
|
||||||
|
|
@ -215,55 +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@v4
|
- 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 install -v ${{ matrix.python-version }}
|
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
|
||||||
|
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
|
|
||||||
|
|
|
||||||
6
.github/workflows/no-response.yml
vendored
6
.github/workflows/no-response.yml
vendored
|
|
@ -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:
|
||||||
|
|
|
||||||
46
.github/workflows/pyenv_tests.yml
vendored
46
.github/workflows/pyenv_tests.yml
vendored
|
|
@ -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,40 +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@v4
|
- 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: |
|
run: |
|
||||||
echo $PYENV_ROOT
|
|
||||||
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
|
|
||||||
- run: |
|
|
||||||
make test
|
make test
|
||||||
- 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
|
||||||
|
|
|
||||||
45
.github/workflows/ubuntu_build.yml
vendored
45
.github/workflows/ubuntu_build.yml
vendored
|
|
@ -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@v4
|
|
||||||
# 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"))
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -6,7 +6,9 @@
|
||||||
/libexec/*.dylib
|
/libexec/*.dylib
|
||||||
/src/Makefile
|
/src/Makefile
|
||||||
/src/*.o
|
/src/*.o
|
||||||
/bats/
|
/bats
|
||||||
|
/bats-*/
|
||||||
/default-packages
|
/default-packages
|
||||||
.idea
|
.idea
|
||||||
*.un~
|
*.un~
|
||||||
|
*.swp
|
||||||
|
|
|
||||||
255
CHANGELOG.md
255
CHANGELOG.md
|
|
@ -1,5 +1,256 @@
|
||||||
# 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
|
||||||
|
* 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
|
||||||
|
* Fix Readline and OpenSSL detection for MacPorts without the corresponding package by @native-api in https://github.com/pyenv/pyenv/pull/3317
|
||||||
|
* Bump actions/checkout from 4 to 5 in the github-actions group by @dependabot[bot] in https://github.com/pyenv/pyenv/pull/3318
|
||||||
|
* Fix rehash hook for pipx/pipx.x/pipx.xx by @rockandska in https://github.com/pyenv/pyenv/pull/3320
|
||||||
|
* Add miniconda 25.7.0-2 by @native-api in https://github.com/pyenv/pyenv/pull/3323
|
||||||
|
* Support curl from a snap by @native-api in https://github.com/pyenv/pyenv/pull/3329
|
||||||
|
* CI: Bats test suite in docker by @rockandska in https://github.com/pyenv/pyenv/pull/3324
|
||||||
|
* Add GraalPy 25.0.0 by @msimacek in https://github.com/pyenv/pyenv/pull/3331
|
||||||
|
* Upgrade pip detetcion in pip-rehash to accept multiple variations by @rockandska in https://github.com/pyenv/pyenv/pull/3330
|
||||||
|
* Add CPython 3.14.0rc3 by @nedbat in https://github.com/pyenv/pyenv/pull/3333
|
||||||
|
* Use TCLTK_* and pkg-config for 3.11+ linking with Homebrew Tcl/Tk; add PYTHON_BUILD_TCLTK_FORMULA envvar by @native-api in https://github.com/pyenv/pyenv/pull/3327
|
||||||
|
|
||||||
|
## Release v2.6.7
|
||||||
|
* Skip Zlib from XCode for XCode 16+ by @native-api in https://github.com/pyenv/pyenv/pull/3298
|
||||||
|
* Don't fail the build if `xcodebuild` fails by @native-api in https://github.com/pyenv/pyenv/pull/3302
|
||||||
|
* Add CPython 3.14.0rc2 and 3.13.7 by @nedbat in https://github.com/pyenv/pyenv/pull/3303
|
||||||
|
|
||||||
|
## Release v2.6.6
|
||||||
|
* Add anaconda3-2025.06-0, anaconda3-2025.06-1, miniconda3-25.3.1-1, miniconda3-25.5.1-0, miniconda3-25.5.1-1 by @native-api in https://github.com/pyenv/pyenv/pull/3295
|
||||||
|
* Add CPython 3.13.6 by @cesarcoatl in https://github.com/pyenv/pyenv/pull/3297
|
||||||
|
|
||||||
|
## Release v2.6.5
|
||||||
|
* Bump OpenSSL to 3.5.1 in the latest releases and branch tips in CPython 3.12 - 3.15 by @edgarrmondragon in https://github.com/pyenv/pyenv/pull/3287
|
||||||
|
* Add Jython 2.7.4 by @cesarcoatl in https://github.com/pyenv/pyenv/pull/3290
|
||||||
|
* Add CPython 3.14.0rc1 by @nedbat in https://github.com/pyenv/pyenv/pull/3293
|
||||||
|
* Add GraalPy 24.2.2 by @msimacek in https://github.com/pyenv/pyenv/pull/3291
|
||||||
|
|
||||||
|
## Release v2.6.4
|
||||||
|
* Backport bpo_36106 to 3.5.10 and 3.6.15 by @native-api in https://github.com/pyenv/pyenv/pull/3280
|
||||||
|
* (regression) Use Zlib from XCode SDK in MacOS without Homebrew or Macports by @native-api in https://github.com/pyenv/pyenv/pull/3282
|
||||||
|
* Add PyPy v7.3.20 by @jsirois in https://github.com/pyenv/pyenv/pull/3284
|
||||||
|
* Add CPython 3.14.0b4 by @edgarrmondragon in https://github.com/pyenv/pyenv/pull/3285
|
||||||
|
|
||||||
|
## Release v2.6.3
|
||||||
|
* README: Re-promote "Python versions with extended support" to a section by @native-api in https://github.com/pyenv/pyenv/pull/3276
|
||||||
|
* Support linking to MacPorts ports installed from binary archives by @0916dhkim in https://github.com/pyenv/pyenv/pull/3272
|
||||||
|
* Add CPython 3.14.0b3 by @nedbat in https://github.com/pyenv/pyenv/pull/3278
|
||||||
|
|
||||||
|
## Release v2.6.2
|
||||||
|
* Add CPython 3.13.5 by @nedbat in https://github.com/pyenv/pyenv/pull/3269
|
||||||
|
|
||||||
|
## Release v2.6.1
|
||||||
|
* Add CPython 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4 by @nedbat in https://github.com/pyenv/pyenv/pull/3266
|
||||||
|
|
||||||
|
## Release v2.6.0
|
||||||
|
* python-build: Support patch paths with spaces/special characters by @native-api in https://github.com/pyenv/pyenv/pull/3251
|
||||||
|
* Add CODEOWNERS file by @native-api in https://github.com/pyenv/pyenv/pull/3247
|
||||||
|
* README: make fish PATH add conditional on the directory existing by @BooleanCat in https://github.com/pyenv/pyenv/pull/2786
|
||||||
|
* Add --bare option to `pyenv version` by @jjwatt in https://github.com/pyenv/pyenv/pull/2783
|
||||||
|
* README: Add shell setup instructions for nushell by @jordanst3wart in https://github.com/pyenv/pyenv/pull/2916
|
||||||
|
* Add MacPorts support for python-build by @studnitskiy in https://github.com/pyenv/pyenv/pull/3186
|
||||||
|
* python-build: fix `has_broken_mac_readline` when Readline is explicitly configured by @fofoni in https://github.com/pyenv/pyenv/pull/3254
|
||||||
|
* Add CPython 3.14.0b2 by @nedbat in https://github.com/pyenv/pyenv/pull/3259
|
||||||
|
|
||||||
## Release v2.5.7
|
## Release v2.5.7
|
||||||
* Point 3.14-dev to `3.14` branch by @edgarrmondragon in https://github.com/pyenv/pyenv/pull/3246
|
* Point 3.14-dev to `3.14` branch by @edgarrmondragon in https://github.com/pyenv/pyenv/pull/3246
|
||||||
|
|
||||||
|
|
@ -166,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
|
||||||
|
|
@ -1302,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
|
||||||
|
|
|
||||||
32
COMMANDS.md
32
COMMANDS.md
|
|
@ -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`
|
||||||
|
|
||||||
|
|
@ -272,8 +268,15 @@ version of Python, or install a package that provides binaries.
|
||||||
Displays the currently active Python version, along with information on
|
Displays the currently active Python version, along with information on
|
||||||
how it was set.
|
how it was set.
|
||||||
|
|
||||||
|
Usage: pyenv version [--bare]
|
||||||
|
|
||||||
|
--bare show just the version name. An alias to `pyenv version-name'
|
||||||
|
|
||||||
|
|
||||||
$ pyenv version
|
$ pyenv version
|
||||||
2.7.6 (set by /home/yyuu/.pyenv/version)
|
2.7.6 (set by /home/yyuu/.pyenv/version)
|
||||||
|
$ pyenv version --bare
|
||||||
|
2.7.6
|
||||||
|
|
||||||
|
|
||||||
## `pyenv versions`
|
## `pyenv versions`
|
||||||
|
|
@ -345,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.
|
||||||
|
|
||||||
|
|
@ -391,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`
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,6 @@ General guidance
|
||||||
You can still add performance optimizations etc that take advantage of newer Bash features
|
You can still add performance optimizations etc that take advantage of newer Bash features
|
||||||
as long as there is a fallback execution route for Bash 3.
|
as long as there is a fallback execution route for Bash 3.
|
||||||
|
|
||||||
* Be extra careful when submitting logic specific for the Apple Silicon platform
|
|
||||||
|
|
||||||
As of this writing, Github Actions do not support it and only one team member has the necessary hardware.
|
|
||||||
So we may be unable to test your changes and may have to take your word for it.
|
|
||||||
|
|
||||||
|
|
||||||
Formatting PRs
|
Formatting PRs
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|
|
||||||
35
Dockerfile
35
Dockerfile
|
|
@ -1,35 +0,0 @@
|
||||||
FROM ubuntu:18.04
|
|
||||||
|
|
||||||
LABEL maintainer="Chris L. Barnes <chrislloydbarnes@gmail.com>"
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
RUN apt-get update -y \
|
|
||||||
&& apt-get install -y \
|
|
||||||
make \
|
|
||||||
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 \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
ENV PYENV_ROOT "/pyenv"
|
|
||||||
ENV PATH "$PYENV_ROOT/bin:$PATH"
|
|
||||||
|
|
||||||
COPY . /pyenv
|
|
||||||
|
|
||||||
RUN eval "$(pyenv init -)"
|
|
||||||
|
|
||||||
|
|
@ -7,11 +7,19 @@ 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`
|
* 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).
|
||||||
* Major version is pushed if there are breaking changes
|
* Major version is pushed if there are breaking changes
|
||||||
* Commit the changes locally into `master`
|
* Commit the changes locally into `master`
|
||||||
* Create a new tag locally with the same name as specified in the new release window
|
* Create a new tag locally with the same name as specified in the new release window
|
||||||
* Push the changes including the tag
|
* Push the changes including the tag
|
||||||
* In the still open new release window, press "Publish release". The now-existing tag will be used.
|
* In the still open new release window, press "Publish release". The now-existing tag will be used.
|
||||||
|
|
|
||||||
175
Makefile
175
Makefile
|
|
@ -1,29 +1,170 @@
|
||||||
.PHONY: test test-build test-unit test-plugin
|
TEST_BATS_VERSION = v1.10.0
|
||||||
|
TEST_BASH_VERSIONS = 3.2.57 4.1.17
|
||||||
|
|
||||||
|
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_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_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BATS_IMAGE_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BATS_IMAGE_PREFIX)))
|
||||||
|
|
||||||
|
.PHONY: test-docker
|
||||||
|
test-docker: $(TEST_UNIT_DOCKER_PREFIX) $(TEST_PYTHON_BUILD_DOCKER_PREFIX) $(TEST_BINARY_DOCKER_PREFIX) $(TEST_LINK_DOCKER_PREFIX)
|
||||||
|
|
||||||
|
.PHONY: $(TEST_UNIT_DOCKER_PREFIX)
|
||||||
|
$(TEST_UNIT_DOCKER_PREFIX): $(TEST_UNIT_DOCKER_TARGETS)
|
||||||
|
|
||||||
|
# Run each unit test under bats docker
|
||||||
|
.PHONY: $(TEST_UNIT_DOCKER_TARGETS)
|
||||||
|
$(TEST_UNIT_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||||
|
$(TEST_UNIT_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||||
|
$(TEST_UNIT_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||||
|
$(TEST_UNIT_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||||
|
$(TEST_UNIT_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
|
||||||
|
$(TEST_UNIT_DOCKER_TARGETS): $(TEST_UNIT_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))" \
|
||||||
|
$${BATS_TEST_FILTER:+-e BATS_TEST_FILTER="$${BATS_TEST_FILTER}"} \
|
||||||
|
$${BATS_FILE_FILTER:+-e BATS_FILE_FILTER="$${BATS_FILE_FILTER}"} \
|
||||||
|
$${CI+-e CI="$${CI}"} \
|
||||||
|
$(INTERACTIVE) \
|
||||||
|
$(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||||
|
test/run
|
||||||
|
|
||||||
|
.PHONY: $(TEST_PYTHON_BUILD_DOCKER_PREFIX)
|
||||||
|
$(TEST_PYTHON_BUILD_DOCKER_PREFIX): $(TEST_PYTHON_BUILD_DOCKER_TARGETS)
|
||||||
|
|
||||||
|
# Run each plugin test under bats docker
|
||||||
|
.PHONY: $(TEST_PYTHON_BUILD_DOCKER_TARGETS)
|
||||||
|
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||||
|
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||||
|
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||||
|
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||||
|
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
|
||||||
|
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): $(TEST_PYTHON_BUILD_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/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
|
||||||
|
.PHONY: $(TEST_BATS_IMAGE_PREFIX)
|
||||||
|
$(TEST_BATS_IMAGE_PREFIX): $(TEST_BATS_IMAGE_TARGETS)
|
||||||
|
|
||||||
|
# Build each image needed for bats under docker
|
||||||
|
.PHONY: $(TEST_BATS_IMAGE_TARGETS)
|
||||||
|
$(TEST_BATS_IMAGE_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||||
|
$(TEST_BATS_IMAGE_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||||
|
$(TEST_BATS_IMAGE_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||||
|
$(TEST_BATS_IMAGE_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||||
|
$(TEST_BATS_IMAGE_TARGETS):
|
||||||
|
if [ -z "$$(docker images -q '$(DOCKER_IMAGE):$(DOCKER_TAG)')" ]]; then \
|
||||||
|
docker build \
|
||||||
|
--quiet \
|
||||||
|
-f "$(PWD)/test/Dockerfile" \
|
||||||
|
--build-arg GNU="$(GNU)" \
|
||||||
|
--build-arg BASH="$(BASH)" \
|
||||||
|
--build-arg BATS_VERSION="$(TEST_BATS_VERSION)" \
|
||||||
|
-t $(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||||
|
./ ; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.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} test
|
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
|
|
||||||
|
|
||||||
bats:
|
.SECONDARY: bats-$(TEST_BATS_VERSION)
|
||||||
git clone --depth 1 --branch v1.2.0 https://github.com/bats-core/bats-core.git bats
|
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
|
||||||
|
bats: bats-$(TEST_BATS_VERSION)
|
||||||
|
if [ \( ! -L bats \) -o \( "x$$(readlink bats)" != "xbats-$(TEST_BATS_VERSION)" \) ]; then \
|
||||||
|
rm -rf bats; ln -s bats-$(TEST_BATS_VERSION) bats; \
|
||||||
|
fi
|
||||||
|
|
|
||||||
258
README.md
258
README.md
|
|
@ -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**.
|
||||||
|
|
@ -50,6 +52,7 @@ This project was forked from [rbenv](https://github.com/rbenv/rbenv) and
|
||||||
* **[Usage](#usage)**
|
* **[Usage](#usage)**
|
||||||
* [Install additional Python versions](#install-additional-python-versions)
|
* [Install additional Python versions](#install-additional-python-versions)
|
||||||
* [Prefix auto-resolution to the latest version](#prefix-auto-resolution-to-the-latest-version)
|
* [Prefix auto-resolution to the latest version](#prefix-auto-resolution-to-the-latest-version)
|
||||||
|
* [Python versions with extended support](#python-versions-with-extended-support)
|
||||||
* [Switch between Python versions](#switch-between-python-versions)
|
* [Switch between Python versions](#switch-between-python-versions)
|
||||||
* [Making multiple versions available](#making-multiple-versions-available)
|
* [Making multiple versions available](#making-multiple-versions-available)
|
||||||
* [Uninstall Python versions](#uninstall-python-versions)
|
* [Uninstall Python versions](#uninstall-python-versions)
|
||||||
|
|
@ -66,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)
|
||||||
|
|
@ -83,7 +88,7 @@ This project was forked from [rbenv](https://github.com/rbenv/rbenv) and
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
The Homebrew option from the [MacOS section below](#macos) would also work if you have Homebrew installed.
|
The Homebrew option from the [MacOS section below](#macos) would also work if you have Homebrew installed.
|
||||||
|
|
||||||
##### 1. Automatic installer (Recommended)
|
##### 1. Automatic installer (Recommended)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -114,7 +119,7 @@ easy to fork and contribute any changes back upstream.
|
||||||
#### MacOS
|
#### MacOS
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
The options from the [Linux section above](#linuxunix) also work but Homebrew is recommended for basic usage.
|
The options from the [Linux section above](#linuxunix) also work but Homebrew is recommended for basic usage.
|
||||||
|
|
||||||
##### [Homebrew](https://brew.sh) in macOS
|
##### [Homebrew](https://brew.sh) in macOS
|
||||||
|
|
@ -175,82 +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
|
For Bash, avoid the automatic `--install` path if your `BASH_ENV` points to
|
||||||
to point to `.bashrc`. On such systems, you should almost certainly put the
|
`.bashrc`; use the manual Bash instructions below so the `eval "$(pyenv init - bash)"`
|
||||||
`eval "$(pyenv init - bash)"` line into `.bash_profile`, and **not** into `.bashrc`. Otherwise, you
|
line only goes in your login startup file.
|
||||||
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>
|
|
||||||
|
|
||||||
```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
|
|
||||||
fish_add_path $PYENV_ROOT/bin
|
|
||||||
~~~
|
|
||||||
|
|
||||||
2. Otherwise, execute the snippet below:
|
|
||||||
~~~ fish
|
|
||||||
set -Ux PYENV_ROOT $HOME/.pyenv
|
|
||||||
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>
|
|
||||||
|
|
||||||
### C. Restart your shell
|
### C. Restart your shell
|
||||||
----
|
----
|
||||||
|
|
@ -349,8 +300,9 @@ You can run [`pyenv latest -k <prefix>`](COMMANDS.md#pyenv-latest) to see how `p
|
||||||
|
|
||||||
See the [`pyenv latest` documentation](COMMANDS.md#pyenv-latest) for details.
|
See the [`pyenv latest` documentation](COMMANDS.md#pyenv-latest) for details.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
<details> <summary> Python versions with extended support </summary>
|
#### Python versions with extended support
|
||||||
|
|
||||||
For the following Python releases, Pyenv applies user-provided patches that add support for some newer environments.
|
For the following Python releases, Pyenv applies user-provided patches that add support for some newer environments.
|
||||||
Though we don't actively maintain those patches, since existing releases never change,
|
Though we don't actively maintain those patches, since existing releases never change,
|
||||||
|
|
@ -371,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:
|
||||||
|
|
||||||
|
|
@ -668,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
|
||||||
|
|
@ -719,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
|
||||||
|
|
||||||
|
|
@ -735,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
17
completions/pyenv.pwsh
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
set -e
|
set -e
|
||||||
[ -n "$PYENV_DEBUG" ] && set -x
|
[ -n "$PYENV_DEBUG" ] && set -x
|
||||||
|
|
||||||
version="2.5.7"
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
;;
|
;;
|
||||||
|
|
@ -47,7 +54,11 @@ done
|
||||||
|
|
||||||
# If shell is not provided, detect it.
|
# If shell is not provided, detect it.
|
||||||
if [ -z "$shell" ]; then
|
if [ -z "$shell" ]; then
|
||||||
shell="$(ps -p "$PPID" -o 'args=' 2>/dev/null || true)"
|
if shell=$(tr '\0' ' ' 2>/dev/null </proc/"$PPID"/cmdline);then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
shell=$(ps p "$PPID" -o 'args=' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
shell="${shell%% *}"
|
shell="${shell%% *}"
|
||||||
shell="${shell##-}"
|
shell="${shell##-}"
|
||||||
shell="${shell:-$SHELL}"
|
shell="${shell:-$SHELL}"
|
||||||
|
|
@ -76,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'
|
||||||
|
|
@ -98,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'
|
||||||
|
|
@ -112,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
|
||||||
}
|
}
|
||||||
|
|
@ -137,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
|
||||||
|
|
@ -168,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}
|
||||||
}
|
}
|
||||||
|
|
@ -182,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
|
||||||
|
|
@ -195,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.
|
||||||
|
|
@ -219,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}"'
|
||||||
;;
|
;;
|
||||||
|
|
@ -230,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"
|
||||||
;;
|
;;
|
||||||
|
|
@ -237,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,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 {
|
||||||
|
|
@ -277,8 +494,8 @@ end'
|
||||||
local command=${1:-}'
|
local command=${1:-}'
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [ "$shell" != "fish" ]; then
|
if [ "$shell" != "fish" ] && [ "$shell" != "pwsh" ]; then
|
||||||
IFS="|"
|
IFS="|"
|
||||||
echo \
|
echo \
|
||||||
' [ "$#" -gt 0 ] && shift
|
' [ "$#" -gt 0 ] && shift
|
||||||
|
|
|
||||||
|
|
@ -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 ) )
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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}\""
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,40 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Summary: Show the current Python version(s) and its origin
|
# Summary: Show the current Python version(s) and its origin
|
||||||
|
# Usage: pyenv version [--bare]
|
||||||
#
|
#
|
||||||
# Shows the currently selected Python version(s) and how it was
|
# --bare show just the version name. An alias to `pyenv version-name'
|
||||||
# selected. To obtain only the version string, use `pyenv
|
|
||||||
# version-name'.
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
[ -n "$PYENV_DEBUG" ] && set -x
|
[ -n "$PYENV_DEBUG" ] && set -x
|
||||||
|
|
||||||
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
|
||||||
|
for arg; do
|
||||||
|
case "$arg" in
|
||||||
|
--complete )
|
||||||
|
echo --bare
|
||||||
|
exit ;;
|
||||||
|
--bare ) bare=1 ;;
|
||||||
|
* )
|
||||||
|
pyenv-help --usage version >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
for PYENV_VERSION_NAME in "${PYENV_VERSION_NAMES[@]}"; do
|
for PYENV_VERSION_NAME in "${PYENV_VERSION_NAMES[@]}"; do
|
||||||
echo "$PYENV_VERSION_NAME (set by $(pyenv-version-origin))"
|
if [[ -n $bare ]]; then
|
||||||
|
echo "$PYENV_VERSION_NAME"
|
||||||
|
else
|
||||||
|
echo "$PYENV_VERSION_NAME (set by $(pyenv-version-origin))"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
exit $exitcode
|
exit $exitcode
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
2
plugins/.gitignore
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
94
plugins/pyenv-binary/README.md
Normal file
94
plugins/pyenv-binary/README.md
Normal 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.
|
||||||
65
plugins/pyenv-binary/bin/pyenv-binary
Executable file
65
plugins/pyenv-binary/bin/pyenv-binary
Executable 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" "$@"
|
||||||
224
plugins/pyenv-binary/libexec/pyenv-binary-generate-installer
Executable file
224
plugins/pyenv-binary/libexec/pyenv-binary-generate-installer
Executable 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
|
||||||
83
plugins/pyenv-binary/libexec/pyenv-binary-package
Executable file
83
plugins/pyenv-binary/libexec/pyenv-binary-package
Executable 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"
|
||||||
73
plugins/pyenv-binary/libexec/pyenv-binary-package-name
Executable file
73
plugins/pyenv-binary/libexec/pyenv-binary-package-name
Executable 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"
|
||||||
137
plugins/pyenv-binary/libexec/pyenv-binary-relocate
Executable file
137
plugins/pyenv-binary/libexec/pyenv-binary-relocate
Executable 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')
|
||||||
157
plugins/pyenv-binary/libexec/pyenv-binary-save
Executable file
157
plugins/pyenv-binary/libexec/pyenv-binary-save
Executable 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}"
|
||||||
212
plugins/pyenv-binary/test/generate-installer.bats
Normal file
212
plugins/pyenv-binary/test/generate-installer.bats
Normal 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" ]
|
||||||
|
}
|
||||||
59
plugins/pyenv-binary/test/package-name.bats
Normal file
59
plugins/pyenv-binary/test/package-name.bats
Normal 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'"
|
||||||
|
}
|
||||||
131
plugins/pyenv-binary/test/package.bats
Normal file
131
plugins/pyenv-binary/test/package.bats
Normal 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"
|
||||||
|
}
|
||||||
221
plugins/pyenv-binary/test/relocate.bats
Normal file
221
plugins/pyenv-binary/test/relocate.bats
Normal 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 ""
|
||||||
|
}
|
||||||
155
plugins/pyenv-binary/test/save.bats
Normal file
155
plugins/pyenv-binary/test/save.bats
Normal 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"
|
||||||
|
}
|
||||||
1
plugins/pyenv-binary/test/test_helper.bash
Symbolic link
1
plugins/pyenv-binary/test/test_helper.bash
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../test/test_helper.bash
|
||||||
21
plugins/pyenv-link/LICENSE
Normal file
21
plugins/pyenv-link/LICENSE
Normal 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.
|
||||||
33
plugins/pyenv-link/README.md
Normal file
33
plugins/pyenv-link/README.md
Normal 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
|
||||||
|
```
|
||||||
34
plugins/pyenv-link/bin/pyenv-link
Executable file
34
plugins/pyenv-link/bin/pyenv-link
Executable 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
|
||||||
150
plugins/pyenv-link/bin/pyenv-link-version
Executable file
150
plugins/pyenv-link/bin/pyenv-link-version
Executable 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
|
||||||
61
plugins/pyenv-link/test/link.bats
Normal file
61
plugins/pyenv-link/test/link.bats
Normal 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" ]
|
||||||
|
}
|
||||||
1
plugins/pyenv-link/test/test_helper.bash
Symbolic link
1
plugins/pyenv-link/test/test_helper.bash
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../test/test_helper.bash
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -138,6 +148,17 @@ would break all Pyenv-managed installations that depend on it.
|
||||||
You can use a [community plugin `fix-version`](https://github.com/pyenv/pyenv/wiki/Plugins#community-plugins)
|
You can use a [community plugin `fix-version`](https://github.com/pyenv/pyenv/wiki/Plugins#community-plugins)
|
||||||
to fix installations in such a case.
|
to fix installations in such a case.
|
||||||
|
|
||||||
|
##### MacPorts
|
||||||
|
|
||||||
|
MacPorts Homebrew is used to find dependency packages if `port` is found on `PATH` in MacOS.
|
||||||
|
|
||||||
|
Set `PYTHON_BUILD_USE_MACPORTS` or `PYTHON_BUILD_SKIP_MACPORTS` to override this default.
|
||||||
|
|
||||||
|
###### Interaction with Homebrew
|
||||||
|
|
||||||
|
If both Homebrew and MacPorts are installed and allowed to be used, Homebrew takes preference.
|
||||||
|
There first ecosystem where any of the required dependency packages is found is used.
|
||||||
|
|
||||||
##### Portage
|
##### Portage
|
||||||
|
|
||||||
In FreeBSD, if `pkg` is on PATH, Ports are searched for some dependencies that Configure is known to not search for via `pkg-config`.
|
In FreeBSD, if `pkg` is on PATH, Ports are searched for some dependencies that Configure is known to not search for via `pkg-config`.
|
||||||
|
|
@ -164,6 +185,10 @@ 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_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_ROOT` overrides the default location from where build definitions
|
* `PYTHON_BUILD_ROOT` overrides the default location from where build definitions
|
||||||
in `share/python-build/` are looked up.
|
in `share/python-build/` are looked up.
|
||||||
* `PYTHON_BUILD_DEFINITIONS` can be a list of colon-separated paths that get
|
* `PYTHON_BUILD_DEFINITIONS` can be a list of colon-separated paths that get
|
||||||
|
|
@ -247,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
|
||||||
|
|
@ -339,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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
47
plugins/python-build/bin/pyenv-install-prerequisites
Executable file
47
plugins/python-build/bin/pyenv-install-prerequisites
Executable 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[@]}"
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
# -g/--debug Build a debug version
|
# -g/--debug Build a debug version
|
||||||
#
|
#
|
||||||
|
|
||||||
PYTHON_BUILD_VERSION="20180424"
|
PYTHON_BUILD_VERSION="2.8.5"
|
||||||
|
|
||||||
OLDIFS="$IFS"
|
OLDIFS="$IFS"
|
||||||
|
|
||||||
|
|
@ -27,7 +27,6 @@ shopt -s extglob
|
||||||
|
|
||||||
exec 3<&2 # preserve original stderr at fd 3
|
exec 3<&2 # preserve original stderr at fd 3
|
||||||
|
|
||||||
|
|
||||||
lib() {
|
lib() {
|
||||||
parse_options() {
|
parse_options() {
|
||||||
OPTIONS=()
|
OPTIONS=()
|
||||||
|
|
@ -81,7 +80,12 @@ abs_dirname() {
|
||||||
cd "$cd_path"
|
cd "$cd_path"
|
||||||
fi
|
fi
|
||||||
name="${path##*/}"
|
name="${path##*/}"
|
||||||
path="$(resolve_link "$name" || true)"
|
if [[ $name == ".." ]]; then
|
||||||
|
cd ..
|
||||||
|
path="$PWD"
|
||||||
|
else
|
||||||
|
path="$(resolve_link "$name" || true)"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "$PWD"
|
echo "$PWD"
|
||||||
|
|
@ -122,17 +126,56 @@ is_mac() {
|
||||||
}
|
}
|
||||||
|
|
||||||
can_use_homebrew() {
|
can_use_homebrew() {
|
||||||
|
if locked_in; then
|
||||||
|
locked_in homebrew && rc=$? || rc=$?; return $rc
|
||||||
|
fi
|
||||||
[[ -n "$PYTHON_BUILD_USE_HOMEBREW" && -n "$PYTHON_BUILD_SKIP_HOMEBREW" ]] && {
|
[[ -n "$PYTHON_BUILD_USE_HOMEBREW" && -n "$PYTHON_BUILD_SKIP_HOMEBREW" ]] && {
|
||||||
echo "error: mutually exclusive environment variables PYTHON_BUILD_USE_HOMEBREW and PYTHON_BUILD_SKIP_HOMEBREW are set" >&3
|
echo "error: mutually exclusive environment variables PYTHON_BUILD_USE_HOMEBREW and PYTHON_BUILD_SKIP_HOMEBREW are set" >&3
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
[[ -n "$PYTHON_BUILD_USE_HOMEBREW" ]] && return 0
|
[[ -n "$PYTHON_BUILD_USE_HOMEBREW" ]] && return 0
|
||||||
[[ -n "$PYTHON_BUILD_SKIP_HOMEBREW" ]] && return 1
|
[[ -n "$PYTHON_BUILD_SKIP_HOMEBREW" ]] && return 1
|
||||||
is_mac && return 0
|
is_mac && command -v brew &>/dev/null && return 0
|
||||||
# In Linux, if Pyenv itself is installed with Homebrew,
|
# In Linux, if Pyenv itself is installed with Homebrew,
|
||||||
# we assume the user wants to take dependencies from there as well by default
|
# we assume the user wants to take dependencies from there as well by default
|
||||||
command -v brew &>/dev/null && [[ $(abs_dirname "${BASH_SOURCE}") == "$(abs_dirname "$(brew --prefix 2>/dev/null ||true)")"/* ]] && return 0
|
local brew_prefix
|
||||||
return 1
|
command -v brew &>/dev/null && \
|
||||||
|
# tests can have non-functional `brew' stub aliased to `false'
|
||||||
|
# in Bash 3.2, var="$(cmd)" errexits on failure even if part of a conditional chain
|
||||||
|
brew_prefix="$(brew --prefix || true)" && [[ -n "$brew_prefix" ]] && \
|
||||||
|
[[ $(abs_dirname "${BASH_SOURCE}") == "$(abs_dirname "${brew_prefix}")"/* ]] && \
|
||||||
|
{ lock_in homebrew; return 0; }
|
||||||
|
|
||||||
|
# do not check the same stuff multiple times
|
||||||
|
PYTHON_BUILD_SKIP_HOMEBREW=1; return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
can_use_macports() {
|
||||||
|
if locked_in; then
|
||||||
|
locked_in macports && rc=$? || rc=$?; return $rc
|
||||||
|
fi
|
||||||
|
[[ -n "$PYTHON_BUILD_USE_MACPORTS" && -n "$PYTHON_BUILD_SKIP_MACPORTS" ]] && {
|
||||||
|
echo "error: mutually exclusive environment variables PYTHON_BUILD_USE_MACPORTS and PYTHON_BUILD_SKIP_MACPORTS are set" >&3
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ -n "$PYTHON_BUILD_USE_MACPORTS" ]] && return 0
|
||||||
|
[[ -n "$PYTHON_BUILD_SKIP_MACPORTS" ]] && return 1
|
||||||
|
is_mac && command -v port &>/dev/null && return 0
|
||||||
|
|
||||||
|
# do not check the same stuff multiple times
|
||||||
|
PYTHON_BUILD_SKIP_MACPORTS=1; return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
locked_in() {
|
||||||
|
if [[ -z "$1" ]]; then
|
||||||
|
[[ -n $_PYTHON_BUILD_ECOSYSTEM_LOCKED_IN ]]
|
||||||
|
else
|
||||||
|
[[ $_PYTHON_BUILD_ECOSYSTEM_LOCKED_IN == "$1" ]]
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
lock_in() {
|
||||||
|
_PYTHON_BUILD_ECOSYSTEM_LOCKED_IN=${1:?}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 9.1 -> 901
|
# 9.1 -> 901
|
||||||
|
|
@ -159,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
|
||||||
|
|
@ -415,7 +465,14 @@ http_head_curl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
http_get_curl() {
|
http_get_curl() {
|
||||||
curl -q -o "${2:--}" -sSLf ${CURL_OPTS} "$1"
|
local out;
|
||||||
|
# Curl is also available as a snap. Snaps cannot read or write /tmp
|
||||||
|
# (files cannot be found, any write result is silently discarded).
|
||||||
|
local curl_is_snap;
|
||||||
|
if [[ $(command -v curl) == "/snap/"* ]]; then curl_is_snap=1; fi
|
||||||
|
if [[ -n $2 && -n $curl_is_snap ]]; then out="$HOME/$(basename "$2")"; else out="$2"; fi
|
||||||
|
curl -q -o "${out:--}" -sSLf ${CURL_OPTS} "$1" || return $?
|
||||||
|
if [[ -n $out && -n $curl_is_snap ]]; then mv "$out" "$2"; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
http_head_wget() {
|
http_head_wget() {
|
||||||
|
|
@ -815,15 +872,42 @@ build_package_standard_build() {
|
||||||
local PACKAGE_LDFLAGS="${package_var_name}_LDFLAGS"
|
local PACKAGE_LDFLAGS="${package_var_name}_LDFLAGS"
|
||||||
|
|
||||||
if [ "$package_var_name" = "PYTHON" ]; then
|
if [ "$package_var_name" = "PYTHON" ]; then
|
||||||
use_homebrew || true
|
if can_use_homebrew; then
|
||||||
use_custom_tcltk || use_homebrew_tcltk || true
|
use_custom_tcltk || use_homebrew_tcltk || true
|
||||||
use_homebrew_readline || use_freebsd_pkg || true
|
use_homebrew_readline || true
|
||||||
use_homebrew_ncurses || true
|
use_homebrew_ncurses || true
|
||||||
if is_mac -ge 1014; then
|
if is_mac -ge 1014; then
|
||||||
use_xcode_sdk_zlib || use_homebrew_zlib || true
|
# While XCode SDK is "always available",
|
||||||
else
|
# still need a fallback in case we are using an alternate compiler
|
||||||
use_homebrew_zlib || true
|
use_xcode_sdk_zlib || use_homebrew_zlib || true
|
||||||
|
else
|
||||||
|
use_homebrew_zlib || true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
if can_use_macports; then
|
||||||
|
use_custom_tcltk || true
|
||||||
|
use_macports_readline || true
|
||||||
|
use_macports_ncurses || true
|
||||||
|
if is_mac -ge 1014; then
|
||||||
|
# While XCode SDK is "always available",
|
||||||
|
# still need a fallback in case we are using an alternate compiler
|
||||||
|
use_xcode_sdk_zlib || use_homebrew_zlib || true
|
||||||
|
else
|
||||||
|
use_macports_zlib || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if can_use_homebrew; then
|
||||||
|
use_homebrew || true
|
||||||
|
fi
|
||||||
|
if can_use_macports; then
|
||||||
|
use_macports || true
|
||||||
|
fi
|
||||||
|
if is_mac -ge 1014 && ! can_use_homebrew && ! can_use_macports; then
|
||||||
|
use_xcode_sdk_zlib || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
use_freebsd_pkg || true
|
||||||
|
|
||||||
use_dsymutil || true
|
use_dsymutil || true
|
||||||
use_free_threading || true
|
use_free_threading || true
|
||||||
fi
|
fi
|
||||||
|
|
@ -1160,8 +1244,8 @@ setup_builtin_patches() {
|
||||||
cat >"${package_name}.patch"
|
cat >"${package_name}.patch"
|
||||||
HAS_PATCH=true
|
HAS_PATCH=true
|
||||||
elif [[ -d "${package_patch_path}" ]]; then
|
elif [[ -d "${package_patch_path}" ]]; then
|
||||||
{ find "${package_patch_path}" -maxdepth 1 -type f
|
{ find "${package_patch_path}" -maxdepth 1 -type f -print0
|
||||||
} 2>/dev/null | sort | xargs cat 1>"${package_name}.patch"
|
} 2>/dev/null | sort -z | xargs -0 cat 1>"${package_name}.patch"
|
||||||
HAS_PATCH=true
|
HAS_PATCH=true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
@ -1431,16 +1515,36 @@ use_homebrew() {
|
||||||
if command -v brew &>/dev/null; then
|
if command -v brew &>/dev/null; then
|
||||||
local brew_prefix="$(brew --prefix 2>/dev/null || true)"
|
local brew_prefix="$(brew --prefix 2>/dev/null || true)"
|
||||||
# /usr/local/lib:/usr/lib is the default library search path
|
# /usr/local/lib:/usr/lib is the default library search path
|
||||||
if [[ -n $brew_prefix && $brew_prefix != "/usr" && $brew_prefix != "/usr/local" ]]; then
|
if [[ -n $brew_prefix && ( ( $brew_prefix != "/usr" && $brew_prefix != "/usr/local" )
|
||||||
|
#when -isysroot is passed to Apple CLang, /usr/(local) are excluded from the default search path
|
||||||
|
|| ( is_mac && osx_using_default_compiler && $CFLAGS =~ (^|\ )-isysroot\ ) ) ]]; then
|
||||||
export CPPFLAGS="${CPPFLAGS:+$CPPFLAGS }-I${brew_prefix}/include"
|
export CPPFLAGS="${CPPFLAGS:+$CPPFLAGS }-I${brew_prefix}/include"
|
||||||
append_ldflags_libs "-L${brew_prefix}/lib -Wl,-rpath,${brew_prefix}/lib"
|
append_ldflags_libs "-L${brew_prefix}/lib -Wl,-rpath,${brew_prefix}/lib"
|
||||||
|
lock_in homebrew
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use_macports() {
|
||||||
|
can_use_macports || return 1
|
||||||
|
local port_location="$(command -v port)"
|
||||||
|
if [ -n "$port_location" ]; then
|
||||||
|
local prefix="${port_location%/bin/port}"
|
||||||
|
export CPPFLAGS="-I${prefix}/include${CPPFLAGS:+ $CPPFLAGS}"
|
||||||
|
prepend_ldflags_libs "-L${prefix}/lib -Wl,-rpath,${prefix}/lib"
|
||||||
|
export PKG_CONFIG_PATH="$prefix/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
|
||||||
|
lock_in macports
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
needs_yaml() {
|
needs_yaml() {
|
||||||
! configured_with_package_dir "python" "yaml.h" &&
|
if ! configured_with_package_dir "python" "yaml.h"; then
|
||||||
! use_homebrew_yaml
|
if can_use_homebrew; then
|
||||||
|
use_homebrew_yaml && return 1
|
||||||
|
elif can_use_macports; then
|
||||||
|
use_macports_yaml && return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
use_homebrew_yaml() {
|
use_homebrew_yaml() {
|
||||||
|
|
@ -1450,6 +1554,17 @@ use_homebrew_yaml() {
|
||||||
echo "python-build: use libyaml from homebrew"
|
echo "python-build: use libyaml from homebrew"
|
||||||
export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}"
|
export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}"
|
||||||
export LDFLAGS="-L$libdir/lib${LDFLAGS:+ ${LDFLAGS% }}"
|
export LDFLAGS="-L$libdir/lib${LDFLAGS:+ ${LDFLAGS% }}"
|
||||||
|
lock_in homebrew
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
use_macports_yaml() {
|
||||||
|
can_use_macports || return 1
|
||||||
|
if [[ $(port -q installed libyaml | awk '{print $3}') == "(active)" ]]; then
|
||||||
|
echo "python-build: use libyaml from MacPorts"
|
||||||
|
lock_in macports
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
@ -1488,9 +1603,17 @@ use_freebsd_pkg() {
|
||||||
has_broken_mac_readline() {
|
has_broken_mac_readline() {
|
||||||
# Mac OS X 10.4 has broken readline.
|
# Mac OS X 10.4 has broken readline.
|
||||||
# https://github.com/pyenv/pyenv/issues/23
|
# https://github.com/pyenv/pyenv/issues/23
|
||||||
is_mac &&
|
if ! is_mac || configured_with_package_dir "python" "readline/rlconf.h"; then
|
||||||
! configured_with_package_dir "python" "readline/rlconf.h" &&
|
# Not applicable.
|
||||||
! use_homebrew_readline
|
return 1
|
||||||
|
fi
|
||||||
|
if can_use_homebrew; then
|
||||||
|
use_homebrew_readline && return 1
|
||||||
|
fi
|
||||||
|
if can_use_macports; then
|
||||||
|
use_macports_readline && return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
use_homebrew_readline() {
|
use_homebrew_readline() {
|
||||||
|
|
@ -1501,6 +1624,19 @@ use_homebrew_readline() {
|
||||||
echo "python-build: use readline from homebrew"
|
echo "python-build: use readline from homebrew"
|
||||||
export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}"
|
export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}"
|
||||||
export LDFLAGS="-L$libdir/lib${LDFLAGS:+ $LDFLAGS}"
|
export LDFLAGS="-L$libdir/lib${LDFLAGS:+ $LDFLAGS}"
|
||||||
|
lock_in homebrew
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
use_macports_readline() {
|
||||||
|
can_use_macports || return 1
|
||||||
|
if ! configured_with_package_dir "python" "readline/rlconf.h"; then
|
||||||
|
if [[ $(port -q installed readline | awk '{print $3}') == "(active)" ]]; then
|
||||||
|
echo "python-build: use readline from MacPorts"
|
||||||
|
lock_in macports
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
@ -1514,21 +1650,45 @@ use_homebrew_ncurses() {
|
||||||
echo "python-build: use ncurses from homebrew"
|
echo "python-build: use ncurses from homebrew"
|
||||||
export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}"
|
export CPPFLAGS="-I$libdir/include${CPPFLAGS:+ $CPPFLAGS}"
|
||||||
export LDFLAGS="-L$libdir/lib${LDFLAGS:+ $LDFLAGS}"
|
export LDFLAGS="-L$libdir/lib${LDFLAGS:+ $LDFLAGS}"
|
||||||
|
lock_in homebrew
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
use_macports_ncurses() {
|
||||||
|
can_use_macports || return 1
|
||||||
|
if [[ $(port -q installed ncurses | awk '{print $3}') == "(active)" ]]; then
|
||||||
|
echo "python-build: use ncurses from MacPorts"
|
||||||
|
lock_in macports
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
||||||
|
|
@ -1548,8 +1708,15 @@ build_package_mac_readline() {
|
||||||
has_broken_mac_openssl() {
|
has_broken_mac_openssl() {
|
||||||
is_mac || return 1
|
is_mac || return 1
|
||||||
local openssl_version="$(/usr/bin/openssl version 2>/dev/null || true)"
|
local openssl_version="$(/usr/bin/openssl version 2>/dev/null || true)"
|
||||||
[[ $openssl_version = "OpenSSL 0.9.8"?* || $openssl_version = "LibreSSL"* ]] &&
|
if [[ $openssl_version = "OpenSSL 0.9.8"?* || $openssl_version = "LibreSSL"* ]]; then
|
||||||
! use_homebrew_openssl
|
if can_use_homebrew; then
|
||||||
|
use_homebrew_openssl && return 1
|
||||||
|
fi
|
||||||
|
if can_use_macports; then
|
||||||
|
use_macports_openssl && return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
use_homebrew_openssl() {
|
use_homebrew_openssl() {
|
||||||
|
|
@ -1559,16 +1726,53 @@ 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}"
|
||||||
return
|
lock_in homebrew
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
use_macports_openssl() {
|
||||||
|
can_use_macports || return 1
|
||||||
|
command -v port >/dev/null || return 1
|
||||||
|
local port_location="$(command -v port)"
|
||||||
|
local prefix="${port_location%/bin/port}"
|
||||||
|
# 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
|
||||||
|
echo "python-build: use ${openssl} from MacPorts"
|
||||||
|
if [[ -n "${PYTHON_BUILD_CONFIGURE_WITH_OPENSSL:-}" ]]; then
|
||||||
|
# configure script of newer CPython versions support `--with-openssl`
|
||||||
|
# https://bugs.python.org/issue21541
|
||||||
|
package_option python configure --with-openssl="${prefix}"
|
||||||
|
fi
|
||||||
|
lock_in macports
|
||||||
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -1601,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 "$@"
|
||||||
|
|
||||||
|
|
@ -1667,50 +1875,90 @@ use_homebrew_zlib() {
|
||||||
local brew_zlib="$(brew --prefix zlib 2>/dev/null || true)"
|
local brew_zlib="$(brew --prefix zlib 2>/dev/null || true)"
|
||||||
if [ -d "$brew_zlib" ]; then
|
if [ -d "$brew_zlib" ]; then
|
||||||
echo "python-build: use zlib from homebrew"
|
echo "python-build: use zlib from homebrew"
|
||||||
export CFLAGS="-I${brew_zlib} ${CFLAGS}"
|
export CPPFLAGS="-I${brew_zlib}/include${CPPFLAGS:+ ${CPPFLAGS}}"
|
||||||
|
export LDFLAGS="-L${brew_zlib}/lib${LDFLAGS:+ ${LDFLAGS}}"
|
||||||
|
lock_in homebrew
|
||||||
|
rc=0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
use_xcode_sdk_zlib() {
|
use_xcode_sdk_zlib() {
|
||||||
# If a custom compiler is used, including XCode SDK will likely break it
|
# If a custom compiler is used, including XCode SDK will likely break it
|
||||||
[[ "${CC:-clang}" != "clang" || "$(command -v clang 2>/dev/null || true)" != "/usr/bin/clang" ]] && return 1
|
osx_using_default_compiler || return 1
|
||||||
local xc_sdk_path="$(xcrun --show-sdk-path 2>/dev/null || true)"
|
|
||||||
if [ -d "$xc_sdk_path" ]; then
|
local sdkroot="$(xcrun --sdk macosx --show-sdk-path 2>/dev/null || true)"
|
||||||
echo "python-build: use zlib from xcode sdk"
|
[[ -z ${sdkroot} || ! -d ${sdkroot} ]] && return 1
|
||||||
# Even though SDK's compiler uses the SDK dirs implicitly,
|
|
||||||
# CPython's setup.py has to have nonstandard paths specified explicitly
|
# Since Xcode 16 / macOS SDK 15, Apple tightened how the linker uses SDK content:
|
||||||
# to search for zlib.h in them
|
# forcing -L "$SDK/usr/lib" now leads to odd link failures.
|
||||||
export CPPFLAGS="${CPPFLAGS:+$CPPFLAGS }-I${xc_sdk_path}/usr/include"
|
echo "python-build: use zlib from xcode sdk"
|
||||||
if is_mac -ge 1100; then
|
# Since 3.9.1 (bpo-41116), CPython's setup.py logic can search default SDK's sysroot itself
|
||||||
export LDFLAGS="${LDFLAGS:+$LDFLAGS }-L${xc_sdk_path}/usr/lib"
|
# so we don't need to do anything
|
||||||
fi
|
# Since 2.7.12 and 3.5.2 (Issue #25136), distutils knows to look for Apple XCode 7+ stub libraries (.tbd)
|
||||||
|
# when searching in the SDK
|
||||||
|
# Since 2.7.4 and 3.2.1 (issue #7724), setup.py and distutils can search in the MacOS SDK when
|
||||||
|
# it's explicitly specified with -isysroot
|
||||||
|
|
||||||
|
#distutils.unixcompiler and setup.py specifically search CFLAGS for "-isysroot"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
osx_using_default_compiler() {
|
||||||
|
[[ "${CC:-clang}" == "clang" && "$(command -v clang 2>/dev/null || true)" == "/usr/bin/clang" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
use_macports_zlib() {
|
||||||
|
can_use_macports || return 1
|
||||||
|
if [[ $(port -q installed zlib | awk '{print $3}') == "(active)" ]]; then
|
||||||
|
echo "python-build: use zlib from MacPorts"
|
||||||
|
lock_in macports
|
||||||
|
else
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
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 "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"
|
# In Homebrew Tcl/Tk 8.6.13, headers have been moved to the 'tcl-tk' subdir.
|
||||||
if [[ -z "$PYTHON_BUILD_TCLTK_USE_PKGCONFIG" ]]; then
|
local tcltk_includes="$(sh -c 'cd '"$tcltk_prefix"'/lib; . ./tclConfig.sh; . ./tkConfig.sh; echo "$TCL_INCLUDE_SPEC $TK_INCLUDE_SPEC"')"
|
||||||
local tcltk_version="$(sh -c '. '"$tcltk_libdir"'/lib/tclConfig.sh; echo $TCL_VERSION')"
|
# 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
|
||||||
package_option python configure --with-tcltk-libs="-L$tcltk_libdir/lib -ltcl$tcltk_version -ltk$tcltk_version"
|
local tcltk_cflags
|
||||||
# In Homebrew Tcl/Tk 8.6.13, headers have been moved to the 'tcl-tk' subdir.
|
if sh -c '. '"$tcltk_prefix"'/lib/tclConfig.sh; echo "$TCL_DEFS"' | grep -qwFe '-DTCL_WITH_EXTERNAL_TOMMATH=1'; then
|
||||||
# We're not using tclConfig.sh here 'cuz it produces the version-specific path to <brew prefix>/Cellar
|
tcltk_cflags="-DTCL_WITH_EXTERNAL_TOMMATH=1"
|
||||||
# and we'd rather have rpath set to <brew prefix>/opt/<...> to allow micro release upgrades without rebuilding
|
|
||||||
# XXX: do use tclConfig.sh and translate the paths if more path shenanigans appear in later releases
|
|
||||||
if [ -d "$tcltk_libdir/include/tcl-tk" ]; then
|
|
||||||
package_option python configure --with-tcltk-includes="-I$tcltk_libdir/include/tcl-tk"
|
|
||||||
else
|
|
||||||
package_option python configure --with-tcltk-includes="-I$tcltk_libdir/include"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
export PKG_CONFIG_PATH="${tcltk_libdir}/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
|
# 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 3.11.0 (bpo-45847): `pkg-config` call, TCLTK_CFLAGS + TCLTK_LIBS override
|
||||||
|
if [[ -n "$PYTHON_BUILD_TCLTK_USE_PKGCONFIG" ]]; then
|
||||||
|
# pkg-config is not present out of the box in MacOS.
|
||||||
|
# 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.
|
||||||
|
# So we just override it always
|
||||||
|
export TCLTK_CFLAGS="$tcltk_includes${tcltk_cflags:+ $tcltk_cflags}"
|
||||||
|
export TCLTK_LIBS="$tcltk_libs"
|
||||||
|
else
|
||||||
|
package_option python configure --with-tcltk-includes="$tcltk_includes"
|
||||||
|
package_option python configure --with-tcltk-libs="$tcltk_libs"
|
||||||
|
[[ -n $tcltk_cflags ]] && export CFLAGS="${tcltk_cflags}${CFLAGS:+ $CFLAGS}"
|
||||||
|
fi
|
||||||
|
#set in either case as a failsafe
|
||||||
|
export PKG_CONFIG_PATH="${tcltk_prefix}/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
|
||||||
|
|
||||||
|
lock_in homebrew
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -1864,10 +2112,12 @@ build_package_symlink_version_suffix() {
|
||||||
|
|
||||||
verify_python() {
|
verify_python() {
|
||||||
build_package_symlink_version_suffix
|
build_package_symlink_version_suffix
|
||||||
|
|
||||||
|
local python_bin="${PYTHON_BIN%/*}/python${1:?}"
|
||||||
|
|
||||||
if [ ! -x "${PYTHON_BIN}" ]; then
|
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."
|
||||||
|
|
@ -2048,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
|
||||||
|
|
@ -2423,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.* )
|
||||||
|
|
@ -2447,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"
|
||||||
;;
|
;;
|
||||||
|
|
|
||||||
1
plugins/python-build/scripts/.gitignore
vendored
Normal file
1
plugins/python-build/scripts/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/venv
|
||||||
758
plugins/python-build/scripts/add_cpython.py
Executable file
758
plugins/python-build/scripts/add_cpython.py
Executable 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)
|
||||||
|
|
@ -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):
|
||||||
|
|
@ -233,6 +242,13 @@ class CondaVersion(NamedTuple):
|
||||||
return PyVersion.PY36
|
return PyVersion.PY36
|
||||||
|
|
||||||
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):
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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,16 +9,21 @@ 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', 'mambaforge']
|
DISTRIBUTIONS = ['miniforge']
|
||||||
|
DISTRIBUTIONS_PRE25 = ['miniforge', 'mambaforge']
|
||||||
|
|
||||||
SKIPPED_RELEASES = [
|
SKIPPED_RELEASES = [
|
||||||
'4.13.0-0', #has no Mambaforge. We already generated scripts for Miniforge
|
'4.13.0-0', #has no Mambaforge. We already generated scripts for Miniforge
|
||||||
'22.11.1-0', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403)
|
'22.11.1-0', #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-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.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 = """
|
||||||
|
|
@ -44,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):
|
||||||
|
|
@ -69,43 +75,51 @@ 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
|
||||||
|
|
||||||
def py_version(release):
|
def version_tuple(version):
|
||||||
|
return tuple(int(part) for part in version.split('-')[0].split("."))
|
||||||
|
|
||||||
|
def py_version(version):
|
||||||
"""Suffix for `verify_pyXXX` to call in the generated build script"""
|
"""Suffix for `verify_pyXXX` to call in the generated build script"""
|
||||||
release_line = tuple(int(part) for part in release.split(".")[:2])
|
version_tuple_ = version_tuple(version)
|
||||||
# current version: mentioned under https://github.com/conda-forge/miniforge?tab=readme-ov-file#miniforge3
|
# current version: mentioned under https://github.com/conda-forge/miniforge?tab=readme-ov-file#requirements-and-installers
|
||||||
# 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 release_line >= (24,5):
|
if version_tuple_ >= (26,1):
|
||||||
|
# https://github.com/conda-forge/miniforge/commit/0016367731e52c67234d6d0e7e6a24c6bf7673e4
|
||||||
|
return "313"
|
||||||
|
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
|
||||||
return "312"
|
return "312"
|
||||||
if release_line >= (4,14):
|
if version_tuple_ >= (4,14):
|
||||||
return "310"
|
return "310"
|
||||||
raise ValueError("Bundled Python version unknown for release `%s'"%release)
|
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):
|
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)]
|
||||||
|
|
||||||
|
|
||||||
for distribution in DISTRIBUTIONS:
|
for distribution in distributions:
|
||||||
distribution_specs = [spec for spec in specs if distribution in spec['flavor'].lower()]
|
distribution_specs = [spec for spec in specs if distribution in spec['flavor'].lower()]
|
||||||
count = len(distribution_specs)
|
count = len(distribution_specs)
|
||||||
|
|
||||||
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]),
|
||||||
|
|
@ -115,20 +129,40 @@ def add_version(release):
|
||||||
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']
|
|
||||||
|
|
||||||
logger.info('Looking for %(version)s in %(out_dir)s', locals())
|
def main():
|
||||||
|
for release in requests.get(f'https://api.github.com/repos/{MINIFORGE_REPO}/releases').json():
|
||||||
|
version = release['tag_name']
|
||||||
|
|
||||||
# This release has no mambaforge artifacts which causes the next check to always trigger.
|
if version in SKIPPED_RELEASES:
|
||||||
# Build scripts for miniforge3-4.13.0-0 have already been generated.
|
continue
|
||||||
# Assuming this was a fluke, we don't yet need to implement proactively checking all releases for contents
|
|
||||||
# or ignoring a release if _any_ of the flavors is already present in Pyenv.
|
|
||||||
if version in SKIPPED_RELEASES:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if any(not list(out_dir.glob(f'{distribution}*-{version}')) for distribution in DISTRIBUTIONS):
|
logger.debug(f'Looking for {version} in {out_dir}')
|
||||||
logger.info('Downloading %(version)s', locals())
|
|
||||||
add_version(release)
|
# mambaforge is retired https://github.com/conda-forge/miniforge/releases/tag/24.11.2-0
|
||||||
|
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):
|
||||||
|
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()
|
||||||
|
|
|
||||||
|
|
@ -1 +1,9 @@
|
||||||
|
more_itertools
|
||||||
requests-html
|
requests-html
|
||||||
|
fake_useragent<2; python_version < "3.9"
|
||||||
|
lxml[html_clean]
|
||||||
|
packaging
|
||||||
|
requests
|
||||||
|
sortedcontainers
|
||||||
|
tqdm
|
||||||
|
jc
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
9
plugins/python-build/share/python-build/3.10.18
Normal file
9
plugins/python-build/share/python-build/3.10.18
Normal 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.18" "https://www.python.org/ftp/python/3.10.18/Python-3.10.18.tar.xz#ae665bc678abd9ab6a6e1573d2481625a53719bc517e9a634ed2b9fefae3817f" standard verify_py310 copy_python_gdb ensurepip
|
||||||
|
else
|
||||||
|
install_package "Python-3.10.18" "https://www.python.org/ftp/python/3.10.18/Python-3.10.18.tgz#1b19ab802518eb36a851f5ddef571862c7a31ece533109a99df6d5af0a1ceb99" standard verify_py310 copy_python_gdb ensurepip
|
||||||
|
fi
|
||||||
9
plugins/python-build/share/python-build/3.10.19
Normal file
9
plugins/python-build/share/python-build/3.10.19
Normal 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
|
||||||
9
plugins/python-build/share/python-build/3.10.20
Normal file
9
plugins/python-build/share/python-build/3.10.20
Normal 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
|
||||||
9
plugins/python-build/share/python-build/3.10.21
Normal file
9
plugins/python-build/share/python-build/3.10.21
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
11
plugins/python-build/share/python-build/3.11.13
Normal file
11
plugins/python-build/share/python-build/3.11.13
Normal 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.13" "https://www.python.org/ftp/python/3.11.13/Python-3.11.13.tar.xz#8fb5f9fbc7609fa822cb31549884575db7fd9657cbffb89510b5d7975963a83a" standard verify_py311 copy_python_gdb ensurepip
|
||||||
|
else
|
||||||
|
install_package "Python-3.11.13" "https://www.python.org/ftp/python/3.11.13/Python-3.11.13.tgz#0f1a22f4dfd34595a29cf69ee7ea73b9eff8b1cc89d7ab29b3ab0ec04179dad8" standard verify_py311 copy_python_gdb ensurepip
|
||||||
|
fi
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue