Compare commits

..

No commits in common. "master" and "v2.8.2" have entirely different histories.

73 changed files with 285 additions and 11639 deletions

View file

@ -3,6 +3,9 @@ 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

View file

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

View file

@ -1,62 +0,0 @@
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 Normal file
View file

@ -0,0 +1,43 @@
name: macos_build
on:
push:
branches: [master]
pull_request: {}
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
macos_build:
strategy:
fail-fast: false
matrix:
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
runs-on: macos-latest
steps:
- uses: actions/checkout@v7
- run: |
brew install openssl readline sqlite3 xz tcl-tk@8 libb2 zstd
- run: |
#envvars
export PYENV_ROOT="$GITHUB_WORKSPACE"
echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
- run: |
#build
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 }}
- run: |
#print version
python --version
python -m pip --version
- shell: python # Prove that actual Python == expected Python
env:
EXPECTED_PYTHON: ${{ matrix.python-version }}
run: import os, sys ; assert sys.version.startswith(os.getenv("EXPECTED_PYTHON"))

View file

@ -2,7 +2,7 @@ name: modified_scripts
on: [pull_request] on: [pull_request]
jobs: jobs:
discover_modified_scripts: discover_modified_scripts:
runs-on: ubuntu-slim runs-on: ubuntu-latest
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_cpython_only: ${{steps.modified-versions.outputs.versions_cpython_only}}
@ -43,32 +43,28 @@ jobs:
import packaging.version import packaging.version
result=[] 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(): for line in os.environ['versions'].splitlines():
if m:=re.match(r'miniconda3-\d+\.\d+-(\d+\.\d+\.\d+)', line): if m:=re.match(r'([^-]+)-(\d+\.\d+)-(\d+\.\d+.\d+)', line):
version = packaging.version.Version(m.group(1)) name, version = m.group(1), packaging.version.Version(m.group(3))
# Miniconda dropped MacOS x64 support # Miniconda dropped MacOS x64 support
if version >= packaging.version.Version('25.9.1'): if (name == 'miniconda3' and version >= packaging.version.Version('25.9.1')):
exclude_macos_intel(result, line) result.append({'os':'macos-15-intel','python-version':line})
if m:=re.match(r'anaconda3-(\d+\.\d+)', line): if m:=re.match(r'([^-]+)-(\d+\.\d+)', line):
version = packaging.version.Version(m.group(1)) name, version = m.group(1), packaging.version.Version(m.group(2))
# Anaconda dropped MacOS x64 support # Anaconda dropped MacOS x64 support
if version >= packaging.version.Version('2025.12'): if name == 'anaconda3' and version >= packaging.version.Version('2025.12'):
exclude_macos_intel(result, line) result.append({'os':'macos-15-intel','python-version':line})
if m:=re.match(r'graalpy[^-]*-(community-)?(\d+\.\d+\.\d+)', line): if m:=re.match(r'graalpy[^-]*-(community-)?(\d+\.\d+.\d+)', line):
version = packaging.version.Version(m.group(2)) version = packaging.version.Version(m.group(2))
# GraalPy dropped MacOS x64 support # GraalPy dropped MacOS x64 support
if version >= packaging.version.Version('25.0.2'): if version >= packaging.version.Version('25.0.2'):
exclude_macos_intel(result, line) result.append({'os':'macos-15-intel','python-version':line})
EOF = str(random.getrandbits(15*8)) EOF = str(random.getrandbits(15*8))
@ -95,7 +91,6 @@ jobs:
- macos-15 - macos-15
- macos-15-intel - macos-15-intel
- macos-26 - macos-26
- macos-26-intel
exclude: ${{fromJson(needs.discover_modified_scripts.outputs.versions_macos_build_exclude)}} exclude: ${{fromJson(needs.discover_modified_scripts.outputs.versions_macos_build_exclude)}}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
@ -158,12 +153,7 @@ 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: os: ["macos-14", "macos-15", "macos-15-intel"]
- 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@v7 - uses: actions/checkout@v7
@ -175,7 +165,12 @@ jobs:
- run: | - run: |
#prerequisites #prerequisites
brew install sqlite3 xz tcl-tk@8 libb2 zstd brew install sqlite3 xz tcl-tk@8 libb2 zstd
"$GITHUB_WORKSPACE/.github/workflows/scripts/brew-uninstall-cascade.sh" $(brew list | grep -E '^openssl(@|$)') readline "$GITHUB_WORKSPACE/.github/workflows/scripts/brew-uninstall-cascade.sh" openssl@3 openssl@1.1 readline
if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then
export PYENV_BOOTSTRAP_VERSION=pypy2.7-7
echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV
pyenv install $PYENV_BOOTSTRAP_VERSION
fi
- run: | - run: |
#build #build
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$? pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
@ -207,9 +202,7 @@ jobs:
python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions)}} python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions)}}
os: os:
- ubuntu-22.04 - ubuntu-22.04
- ubuntu-22.04-arm
- ubuntu-24.04 - ubuntu-24.04
- ubuntu-24.04-arm
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
@ -220,7 +213,10 @@ jobs:
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
- run: | - run: |
#prerequisites #prerequisites
pyenv install-prerequisites sudo apt-get update -q; sudo apt-get install -yq make build-essential \
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
@ -269,7 +265,8 @@ 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)}}
runs-on: ubuntu-latest os: ["ubuntu-latest"]
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- run: | - run: |
@ -277,10 +274,18 @@ jobs:
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_ENV echo "_PYTHON_BUILD_FORCE_SKIP_XZ=1" >> $GITHUB_PATH
- run: | - run: |
#prerequisites #prerequisites
pyenv install-prerequisites sudo apt-get update -q; sudo apt-get install -yq make build-essential \
libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev
if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then
export PYENV_BOOTSTRAP_VERSION=pypy2.7-7
echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV
pyenv install $PYENV_BOOTSTRAP_VERSION
fi
- run: | - run: |
#build #build
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$? pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?

View file

@ -14,9 +14,7 @@ jobs:
matrix: matrix:
os: os:
- ubuntu-22.04 - ubuntu-22.04
- ubuntu-22.04-arm
- ubuntu-24.04 - ubuntu-24.04
- ubuntu-24.04-arm
- macos-14 - macos-14
- macos-15 - macos-15
- macos-15-intel - macos-15-intel
@ -24,22 +22,25 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- name: Install prerequisites - run: |
run: |
if test "$RUNNER_OS" == "macOS"; then if test "$RUNNER_OS" == "macOS"; then
brew install coreutils fish brew install coreutils fish
fi fi
- name: Run tests - run: pwd
- env:
PYENV_ROOT: /home/runner/work/pyenv/pyenv
run: |
echo $PYENV_ROOT
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
- name: Run test on the host
run: | run: |
make test make test
- name: Run test with docker
if: ${{ ! contains(matrix.os, 'macos') }}
run: |
make test-docker
- env: - env:
PYENV_NATIVE_EXT: 1 PYENV_NATIVE_EXT: 1
run: | run: |
(cd src; ./configure; make) (cd src; ./configure; make)
bats/bin/bats test/{pyenv,hooks,versions}.bats bats/bin/bats test/{pyenv,hooks,versions}.bats
pyenv_tests_docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: |
make test-docker

43
.github/workflows/ubuntu_build.yml vendored Normal file
View file

@ -0,0 +1,43 @@
name: ubuntu_build
on:
push:
branches: [master]
pull_request: {}
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
ubuntu_build:
strategy:
fail-fast: false
matrix:
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- 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"))

View file

@ -1,26 +1,5 @@
# 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 ## 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 * 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 * Add graalpy3.12-25.2.4 by @msimacek in https://github.com/pyenv/pyenv/pull/3502
@ -34,7 +13,6 @@
* Add CPython 3.15.0b4 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3495 * Add CPython 3.15.0b4 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3495
## Release v2.8.0 ## 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 * 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 * 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 * rehash: fix race condition in landlock writability check by @Sheile in https://github.com/pyenv/pyenv/pull/3483

View file

@ -10,14 +10,11 @@ TEST_PYTHON_BUILD_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuf
TEST_BINARY_DOCKER_PREFIX = test-binary-docker 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_BINARY_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BINARY_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BINARY_DOCKER_PREFIX)))
TEST_LINK_DOCKER_PREFIX = test-link-docker
TEST_LINK_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_LINK_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_LINK_DOCKER_PREFIX)))
TEST_BATS_IMAGE_PREFIX = test-pyenv-docker-image TEST_BATS_IMAGE_PREFIX = test-pyenv-docker-image
TEST_BATS_IMAGE_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BATS_IMAGE_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BATS_IMAGE_PREFIX))) TEST_BATS_IMAGE_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BATS_IMAGE_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BATS_IMAGE_PREFIX)))
.PHONY: test-docker .PHONY: test-docker
test-docker: $(TEST_UNIT_DOCKER_PREFIX) $(TEST_PYTHON_BUILD_DOCKER_PREFIX) $(TEST_BINARY_DOCKER_PREFIX) $(TEST_LINK_DOCKER_PREFIX) test-docker: $(TEST_UNIT_DOCKER_PREFIX) $(TEST_PYTHON_BUILD_DOCKER_PREFIX) $(TEST_BINARY_DOCKER_PREFIX)
.PHONY: $(TEST_UNIT_DOCKER_PREFIX) .PHONY: $(TEST_UNIT_DOCKER_PREFIX)
$(TEST_UNIT_DOCKER_PREFIX): $(TEST_UNIT_DOCKER_TARGETS) $(TEST_UNIT_DOCKER_PREFIX): $(TEST_UNIT_DOCKER_TARGETS)
@ -65,8 +62,7 @@ $(TEST_PYTHON_BUILD_DOCKER_TARGETS): $(TEST_PYTHON_BUILD_DOCKER_PREFIX)-% : $(TE
$${CI+-e CI="$${CI}"} \ $${CI+-e CI="$${CI}"} \
$(INTERACTIVE) \ $(INTERACTIVE) \
$(DOCKER_IMAGE):$(DOCKER_TAG) \ $(DOCKER_IMAGE):$(DOCKER_TAG) \
bats $${CI:+-F "/code/test/libexec/bats-format-tap-suite"} \ bats $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/python-build/test/$${BATS_FILE_FILTER}
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/python-build/test/$${BATS_FILE_FILTER}
.PHONY: $(TEST_BINARY_DOCKER_PREFIX) .PHONY: $(TEST_BINARY_DOCKER_PREFIX)
$(TEST_BINARY_DOCKER_PREFIX): $(TEST_BINARY_DOCKER_TARGETS) $(TEST_BINARY_DOCKER_PREFIX): $(TEST_BINARY_DOCKER_TARGETS)
@ -88,31 +84,7 @@ $(TEST_BINARY_DOCKER_TARGETS): $(TEST_BINARY_DOCKER_PREFIX)-% : $(TEST_BATS_IMAG
$${CI+-e CI="$${CI}"} \ $${CI+-e CI="$${CI}"} \
$(INTERACTIVE) \ $(INTERACTIVE) \
$(DOCKER_IMAGE):$(DOCKER_TAG) \ $(DOCKER_IMAGE):$(DOCKER_TAG) \
bats $${CI:+-F "/code/test/libexec/bats-format-tap-suite"} \ bats $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/pyenv-binary/test/$${BATS_FILE_FILTER}
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/pyenv-binary/test/$${BATS_FILE_FILTER}
.PHONY: $(TEST_LINK_DOCKER_PREFIX)
$(TEST_LINK_DOCKER_PREFIX): $(TEST_LINK_DOCKER_TARGETS)
.PHONY: $(TEST_LINK_DOCKER_TARGETS)
$(TEST_LINK_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
$(TEST_LINK_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
$(TEST_LINK_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
$(TEST_LINK_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
$(TEST_LINK_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
$(TEST_LINK_DOCKER_TARGETS): $(TEST_LINK_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
docker run \
--init \
-v $(PWD):/code:ro \
-v /etc/passwd:/etc/passwd:ro \
-v /etc/group:/etc/group:ro \
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
$${CI+-e CI="$${CI}"} \
$(INTERACTIVE) \
$(DOCKER_IMAGE):$(DOCKER_TAG) \
bats $${CI:+-F "/code/test/libexec/bats-format-tap-suite"} \
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/pyenv-link/test/$${BATS_FILE_FILTER}
# Build all images needed for bats under docker # Build all images needed for bats under docker
.PHONY: $(TEST_BATS_IMAGE_PREFIX) .PHONY: $(TEST_BATS_IMAGE_PREFIX)
@ -136,28 +108,22 @@ $(TEST_BATS_IMAGE_TARGETS):
./ ; \ ./ ; \
fi fi
.PHONY: test test-unit test-python-build test-binary test-link .PHONY: test test-unit test-python-build test-binary
# 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-python-build test-binary test-link test: test-unit test-python-build test-binary
test-unit: bats test-unit: bats
PATH="./bats/bin:$$PATH" test/run PATH="./bats/bin:$$PATH" test/run
test-python-build: bats test-python-build: bats
cd plugins/python-build && $(PWD)/bats/bin/bats $${CI:+-F "$(PWD)/test/libexec/bats-format-tap-suite"} \ cd plugins/python-build && $(PWD)/bats/bin/bats $${CI:+--tap} $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
test-binary: bats test-binary: bats
cd plugins/pyenv-binary && $(PWD)/bats/bin/bats $${CI:+-F "$(PWD)/test/libexec/bats-format-tap-suite"} \ cd plugins/pyenv-binary && $(PWD)/bats/bin/bats $${CI:+--tap} $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
test-link: bats
cd plugins/pyenv-link && $(PWD)/bats/bin/bats $${CI:+-F "$(PWD)/test/libexec/bats-format-tap-suite"} \
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
.SECONDARY: bats-$(TEST_BATS_VERSION) .SECONDARY: bats-$(TEST_BATS_VERSION)
bats-$(TEST_BATS_VERSION): bats-$(TEST_BATS_VERSION):

View file

@ -13,8 +13,6 @@ 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**.

View file

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

View file

@ -473,12 +473,12 @@ function pyenv {
} }
if ( ("${commands[*]}" -split ' ') -contains \$command ) { if ( ("${commands[*]}" -split ' ') -contains \$command ) {
\$shell_cmds = (& (get-command -commandtype application pyenv -totalcount 1) sh-\$command \$args) \$shell_cmds = (& (get-command -commandtype application pyenv) sh-\$command \$args)
if ( \$shell_cmds.Count -gt 0 ) { if ( \$shell_cmds.Count -gt 0 ) {
iex (\$shell_cmds -join "\`n") iex (\$shell_cmds -join "\`n")
} }
} else { } else {
& (get-command -commandtype application pyenv -totalcount 1) \$command \$args & (get-command -commandtype application pyenv) \$command \$args
} }
} }
EOS EOS

View file

@ -8,28 +8,12 @@ target_dir="$1"
find_local_version_file() { find_local_version_file() {
local root="$1" local root="$1"
# Nonexistent paths are UB as of this writing. while ! [[ "$root" =~ ^//[^/]*$ ]]; do
# Relative ones cause a failure but absolute ones don't if [ -f "${root}/.python-version" ]; then
[[ $root != /* ]] && root=$(CDPATH= cd -- "$root" && pwd) echo "${root}/.python-version"
# 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
@ -38,9 +22,7 @@ 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
# In tests, PYENV_DIR is not set, ultimately leading to "cd: null directory" in OpenSUSE find_local_version_file "$PYENV_DIR" || {
# 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

1
plugins/.gitignore vendored
View file

@ -3,5 +3,4 @@
!/version-ext-compat !/version-ext-compat
!/python-build !/python-build
!/pyenv-binary !/pyenv-binary
!/pyenv-link
/python-build/test/build /python-build/test/build

View file

@ -16,7 +16,7 @@ and dependency metadata exist to catch that.
## Commands ## Commands
### `pyenv binary package [-v|--verbose] <version>[:<entry>] --archive-base-url <url>` ### `pyenv binary package <version>[:<entry>] --archive-base-url <url>`
Installs `<version>` from source under a separate name, packages that install Installs `<version>` from source under a separate name, packages that install
with `save`, then emits a python-build definition for it with with `save`, then emits a python-build definition for it with
@ -24,8 +24,6 @@ with `save`, then emits a python-build definition for it with
current platform, platform version and architecture. An explicit entry keeps current platform, platform version and architecture. An explicit entry keeps
the existing custom-build workflow. the existing custom-build workflow.
Pass `-v` to show build progress from `pyenv install`.
```sh ```sh
pyenv binary package 3.12.7 \ pyenv binary package 3.12.7 \
--archive-base-url https://example.com/binaries --archive-base-url https://example.com/binaries

View file

@ -14,7 +14,7 @@
# into the definition; `pyenv binary save' writes the two together. # into the definition; `pyenv binary save' writes the two together.
# #
# <metadata-file> A .meta file written by `pyenv binary save'. # <metadata-file> A .meta file written by `pyenv binary save'.
# The corresponding binary package written by `pyenv binary save' # The corresponsing binary package written by `pyenv binary save'
# needs to be present alongside it. # needs to be present alongside it.
# --archive-url <url> The URL for the resulting installation script to download # --archive-url <url> The URL for the resulting installation script to download
# the package from. # the package from.
@ -55,7 +55,7 @@ if [ -z "$metadata" ] || [ -z "$archive_url" ]; then
exit 1 exit 1
fi fi
version="" os="" arch="" distro="" libc="" build_prefix="" archive="" version="" os="" arch="" distro="" libc="" archive=""
deps="" deps=""
while IFS='=' read -r key value; do while IFS='=' read -r key value; do
case "$key" in case "$key" in
@ -64,7 +64,6 @@ while IFS='=' read -r key value; do
arch ) arch="$value" ;; arch ) arch="$value" ;;
distro ) distro="$value" ;; distro ) distro="$value" ;;
libc ) libc="$value" ;; libc ) libc="$value" ;;
build_prefix ) build_prefix="$value" ;;
archive ) archive="$value" ;; archive ) archive="$value" ;;
dep ) deps="${deps:+$deps }$value" ;; dep ) deps="${deps:+$deps }$value" ;;
esac esac
@ -79,9 +78,10 @@ for field in version os arch archive; do
fi fi
done done
# Mach-O load commands contain the prefix where Python was built. # macOS relocation needs install_name_tool and a different rpath scheme, so it is
if [ "$os" = "Darwin" ] && [ -z "$build_prefix" ]; then # not supported yet. Other systems relocate with patchelf.
echo "pyenv-binary: metadata is missing \`build_prefix'" >&2 if [ "$os" = "Darwin" ]; then
echo "pyenv-binary: macOS archives are not supported yet" >&2
exit 1 exit 1
fi fi
@ -122,7 +122,6 @@ emit() {
printf 'EXPECT_OS=%q\n' "$os" printf 'EXPECT_OS=%q\n' "$os"
printf 'EXPECT_ARCH=%q\n' "$arch" printf 'EXPECT_ARCH=%q\n' "$arch"
printf 'EXPECT_LIBC=%q\n' "$libc" printf 'EXPECT_LIBC=%q\n' "$libc"
printf 'BUILD_PREFIX=%q\n' "$build_prefix"
printf 'VERSION=%q\n' "$version" printf 'VERSION=%q\n' "$version"
printf 'ARCHIVE_URL=%q\n' "$archive_url" printf 'ARCHIVE_URL=%q\n' "$archive_url"
printf 'SHA256=%q\n' "$sha256" printf 'SHA256=%q\n' "$sha256"
@ -156,23 +155,10 @@ case "$EXPECT_LIBC" in
;; ;;
esac esac
# Required system libraries must already be present on the target. Linux lists # Required system libraries must already be present on the target. ldconfig -p
# the soname first; FreeBSD lists it in the resolved path, so normalize that # lists each library as "<soname> (...) => <path>"; match the soname column
# path before matching exact names. # exactly so a longer name like libc.so.6.1 does not satisfy libc.so.6.
cache="" if command -v ldconfig &>/dev/null && cache="$(ldconfig -p)"; then
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="" missing=""
for dep in $DEPS; do for dep in $DEPS; do
printf '%s\n' "$cache" | awk -v d="$dep" '$1 == d { found = 1 } END { exit !found }' \ printf '%s\n' "$cache" | awk -v d="$dep" '$1 == d { found = 1 } END { exit !found }' \
@ -183,32 +169,11 @@ if [ -n "$cache" ]; then
exit 1 exit 1
fi fi
elif [ -n "$DEPS" ]; then elif [ -n "$DEPS" ]; then
echo "pyenv-binary: cannot check required system libraries (ldconfig cache unavailable)" >&2 echo "pyenv-binary: cannot check required system libraries (ldconfig with -p not found)" >&2
fi 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() { build_package_relocate() {
if [ "$os" = "Darwin" ]; then
pyenv binary relocate "$PREFIX_PATH" "$BUILD_PREFIX"
else
pyenv binary relocate "$PREFIX_PATH" pyenv binary relocate "$PREFIX_PATH"
fi
} }
install_package "Python-${VERSION}-binary" "${ARCHIVE_URL}#${SHA256}" copy relocate install_package "Python-${VERSION}-binary" "${ARCHIVE_URL}#${SHA256}" copy relocate

View file

@ -2,7 +2,7 @@
# #
# Summary: Create an installable binary package from a Python version # Summary: Create an installable binary package from a Python version
# #
# Usage: pyenv binary package [-v|--verbose] <version>[:<entry>] --archive-base-url <url> # Usage: pyenv binary package <version>[:<entry>] --archive-base-url <url>
# #
# Installs <version> from source under a separate entry name, saves it as a # Installs <version> from source under a separate entry name, saves it as a
# binary package, then emits a python-build definition for that package. # binary package, then emits a python-build definition for that package.
@ -13,7 +13,6 @@
# <entry> The optional name to build under and install the # <entry> The optional name to build under and install the
# binary as. If omitted, a name is generated from the # binary as. If omitted, a name is generated from the
# current platform, platform version and architecture. # current platform, platform version and architecture.
# -v,--verbose Show build progress from `pyenv install'.
# --archive-base-url <url> # --archive-base-url <url>
# Where the archive will be hosted; the definition # Where the archive will be hosted; the definition
# downloads it from <url>/<entry>.tar.gz. # downloads it from <url>/<entry>.tar.gz.
@ -24,29 +23,23 @@ set -e
# Provide pyenv completions # Provide pyenv completions
if [ "$1" = "--complete" ]; then if [ "$1" = "--complete" ]; then
echo --archive-base-url echo --archive-base-url
echo --verbose
exec pyenv-install --list --bare exec pyenv-install --list --bare
fi fi
spec="" spec=""
archive_base_url="" archive_base_url=""
verbose=""
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
--archive-base-url ) --archive-base-url )
[ $# -lt 2 ] && { echo "pyenv-binary: --archive-base-url needs a value" >&2; exit 1; } [ $# -ge 2 ] || { echo "pyenv-binary: --archive-base-url needs a value" >&2; exit 1; }
archive_base_url="$2"; shift ;; archive_base_url="$2"; shift 2 ;;
-v|--verbose)
verbose=1 ;;
-* ) -* )
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;; echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
* ) * )
[ -z "$spec" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; } [ -z "$spec" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; }
spec="$1" spec="$1"; shift ;;
;;
esac esac
shift
done done
if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then
@ -75,8 +68,17 @@ latest )
;; ;;
esac esac
os="$(uname -s)"
# `generate-installer' refuses a macOS archive, so the last step here cannot
# succeed on one. Give up before compiling rather than after it.
if [ "$os" = "Darwin" ]; then
echo "pyenv-binary: macOS archives are not supported yet" >&2
exit 1
fi
# `pyenv install' puts a `<version>:<alias>' build under versions/<alias>. # `pyenv install' puts a `<version>:<alias>' build under versions/<alias>.
pyenv-install ${verbose:+--verbose} "$spec" pyenv-install "$spec"
pyenv-binary-save "$entry" "$PWD" --name "$entry" pyenv-binary-save "$entry" "$PWD" --name "$entry"
pyenv-binary-generate-installer "${entry}.meta" \ pyenv-binary-generate-installer "${entry}.meta" \

View file

@ -28,8 +28,6 @@ case "$version" in
;; ;;
esac esac
version="$(pyenv-latest -f -k "$version")"
os="$(uname -s)" os="$(uname -s)"
arch="$(uname -m)" arch="$(uname -m)"
distro="" distro=""

View file

@ -1,13 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Summary: Rewrite an unpacked Python's library paths so it runs from its prefix # Summary: Rewrite an unpacked Python's rpaths so it runs from its prefix
# #
# Usage: pyenv binary relocate <prefix> [<build-prefix>] # Usage: pyenv binary relocate <prefix>
# #
# Rewrites the library paths of a Python tree that was unpacked into <prefix> # Rewrites the rpaths of a Python tree that was unpacked into <prefix> so the
# so the interpreter and its extension modules load the bundled libraries from # interpreter and its extension modules load the bundled libraries from their
# their new location rather than the path it was built at. Requires patchelf on # new location rather than the path it was built at. Requires patchelf.
# Linux and FreeBSD, or otool and install_name_tool on macOS.
# #
# The definition that `pyenv binary generate-installer' produces calls this # The definition that `pyenv binary generate-installer' produces calls this
# after `install_package ... copy' has laid the tree down. # after `install_package ... copy' has laid the tree down.
@ -25,84 +24,6 @@ if [ -z "$prefix" ]; then
exit 1 exit 1
fi 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 # 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. # an existing one, which is not enough to relocate every build, so require it.
if ! command -v patchelf >/dev/null 2>&1; then if ! command -v patchelf >/dev/null 2>&1; then

View file

@ -92,16 +92,12 @@ fi
if [ "$os" = "Linux" ]; then if [ "$os" = "Linux" ]; then
libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)" libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
fi 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 # List the external shared libraries the install links against: those that
# resolve outside its own prefix, so they must already exist on the target. # resolve outside its own prefix, so they must already exist on the target.
# The interpreter plus every bundled shared object are inspected. # The interpreter plus every bundled shared object are inspected.
system_deps() { system_deps() {
local f needed local f
{ {
for f in "${prefix}"/bin/python*; do for f in "${prefix}"/bin/python*; do
[ -e "$f" ] && printf '%s\n' "$f" [ -e "$f" ] && printf '%s\n' "$f"
@ -115,19 +111,8 @@ system_deps() {
otool -L "$f" 2>/dev/null | tail -n +2 | awk -v pfx="${prefix}/" \ otool -L "$f" 2>/dev/null | tail -n +2 | awk -v pfx="${prefix}/" \
'$1 !~ /^@/ && substr($1, 1, length(pfx)) != pfx { print $1 }' '$1 !~ /^@/ && substr($1, 1, length(pfx)) != pfx { print $1 }'
else else
# GNU binutils 2.44 writes "(NEEDED) ... [name]"; FreeBSD 15.1 writes ldd "$f" 2>/dev/null | awk -v pfx="${prefix}/" \
# "NEEDED ... [name]". readelf(1) documents -d, not its text format. '$2 == "=>" && $3 ~ /^\// && substr($3, 1, length(pfx)) != pfx { print $1 }'
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 fi
done | sort -u done | sort -u
} }
@ -147,7 +132,6 @@ tar -C "$(dirname "$prefix")" -czf "${output_dir}/${archive}" "$(basename "$pref
echo "platform=${platform}" echo "platform=${platform}"
[ -n "$distro" ] && echo "distro=${distro}" [ -n "$distro" ] && echo "distro=${distro}"
[ -n "$libc" ] && echo "libc=${libc}" [ -n "$libc" ] && echo "libc=${libc}"
echo "build_prefix=${prefix}"
echo "archive=${archive}" echo "archive=${archive}"
system_deps | while IFS= read -r dep; do system_deps | while IFS= read -r dep; do
[ -n "$dep" ] && echo "dep=${dep}" [ -n "$dep" ] && echo "dep=${dep}"

View file

@ -6,7 +6,6 @@ create_meta() {
local os="${1-Linux}" local os="${1-Linux}"
local arch="${2-x86_64}" local arch="${2-x86_64}"
local libc="${3-glibc 2.17}" local libc="${3-glibc 2.17}"
local build_prefix="${4-}"
local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz" local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
local meta="${BATS_TEST_TMPDIR}/sample.meta" local meta="${BATS_TEST_TMPDIR}/sample.meta"
@ -21,7 +20,6 @@ create_meta() {
echo "os=${os}" echo "os=${os}"
echo "arch=${arch}" echo "arch=${arch}"
[ -z "$libc" ] || echo "libc=${libc}" [ -z "$libc" ] || echo "libc=${libc}"
[ -z "$build_prefix" ] || echo "build_prefix=${build_prefix}"
echo "archive=${archive##*/}" echo "archive=${archive##*/}"
} > "$meta" } > "$meta"
echo "$meta" echo "$meta"
@ -60,20 +58,10 @@ create_meta() {
assert_failure assert_failure
} }
@test "fails when macOS metadata is missing the build prefix" { @test "refuses macOS metadata" {
run pyenv-binary-generate-installer "$(create_meta Darwin arm64 '')" \ run pyenv-binary-generate-installer "$(create_meta Darwin arm64 '')" \
--archive-url http://x/a.tar.gz --archive-url http://x/a.tar.gz
assert_failure "pyenv-binary: metadata is missing \`build_prefix'" assert_failure "pyenv-binary: macOS archives are not supported yet"
}
@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" { @test "fails when required metadata is missing" {
@ -105,82 +93,6 @@ create_meta() {
assert_failure "pyenv-binary: archive needs glibc 99.0 or newer, but this system has 2.31" 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" { @test "fails when the archive is not beside the metadata" {
local meta="$(create_meta)" local meta="$(create_meta)"
rm "${BATS_TEST_TMPDIR}/3.12.7.tar.gz" rm "${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
@ -200,7 +112,6 @@ create_meta() {
mkdir -p "$cache" mkdir -p "$cache"
cp "$archive" "${cache}/Python-3.12.7-binary.tar.gz" cp "$archive" "${cache}/Python-3.12.7-binary.tar.gz"
create_stub pyenv 'echo "pyenv $*"' 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' create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
PYTHON_BUILD_CACHE_PATH="$cache" run \ PYTHON_BUILD_CACHE_PATH="$cache" run \

View file

@ -2,10 +2,6 @@
load test_helper load test_helper
_setup() {
create_stub pyenv-latest '[ "$1" = "-f" ] && [ "$2" = "-k" ] && shift 2 && echo "$*"'
}
@test "completion lists installable versions" { @test "completion lists installable versions" {
create_stub pyenv-install \ create_stub pyenv-install \
'[ "$*" = "--list --bare" ] && echo 3.13.14' '[ "$*" = "--list --bare" ] && echo 3.13.14'
@ -44,13 +40,12 @@ _setup() {
assert_success "3.13.14-macos-15.5-arm64" assert_success "3.13.14-macos-15.5-arm64"
} }
@test "resolves a version prefix when generating a package name" { @test "generates a package name for FreeBSD" {
create_stub pyenv-latest '[ "$*" = "-f -k 3" ] && echo 3.14.7'
create_stub uname \ create_stub uname \
'case "$1" in -s) echo FreeBSD;; -m) echo amd64;; -r) echo 14.2-RELEASE-p3;; esac' 'case "$1" in -s) echo FreeBSD;; -m) echo amd64;; -r) echo 14.2-RELEASE-p3;; esac'
run pyenv-binary-package-name 3 run pyenv-binary-package-name 3.13.14
assert_success "3.14.7-freebsd-14.2-release-p3-amd64" assert_success "3.13.14-freebsd-14.2-release-p3-amd64"
} }
@test "rejects an invalid version name" { @test "rejects an invalid version name" {

View file

@ -6,34 +6,21 @@ load test_helper
# the platform tools report a fixed Linux target so the real `save' and # the platform tools report a fixed Linux target so the real `save' and
# `generate-installer' behave the same on any test host. # `generate-installer' behave the same on any test host.
stub_build_environment() { stub_build_environment() {
create_stub pyenv-install 'echo "${0##*/} $*"; mkdir -p "${PYENV_ROOT}/versions/${1##*:}/bin"' create_stub pyenv-install '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 uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
create_stub getconf 'echo "glibc 2.17"' create_stub getconf 'echo "glibc 2.17"'
create_stub readelf true
} }
@test "-v|--verbose runs pyenv install verbosely" { @test "completion lists the option and definitions provided by another plugin" {
stub_build_environment mkdir -p "${PYENV_ROOT}/plugins/example/share/python-build"
create_stub pyenv-binary-save true touch "${PYENV_ROOT}/plugins/example/share/python-build/3.12.7-example"
create_stub pyenv-binary-generate-installer true PATH="${BATS_TEST_DIRNAME}/../../python-build/bin:${PATH}"
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 run pyenv-binary-package --complete
assert_success <<! assert_success
--archive-base-url assert_line "--archive-base-url"
--verbose assert_line "3.12.7-example"
pyenv-install --list --bare refute_line "Available versions:"
!
} }
@test "fails with no arguments" { @test "fails with no arguments" {
@ -82,19 +69,13 @@ pyenv-install --list --bare
assert_failure "pyenv-binary: \`latest' cannot be used as an entry name" assert_failure "pyenv-binary: \`latest' cannot be used as an entry name"
} }
@test "packages on macOS" { @test "refuses to package on macOS before compiling anything" {
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac' 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 run pyenv-binary-package 3.12.7:3.12.7-test --archive-base-url http://x/b
assert_success "install assert_failure "pyenv-binary: macOS archives are not supported yet"
save
generate-installer"
} }
@test "writes the archive, metadata and definition under the entry name (integration)" { @test "writes the archive, metadata and definition under the entry name" {
stub_build_environment stub_build_environment
cd "${BATS_TEST_TMPDIR}" cd "${BATS_TEST_TMPDIR}"
@ -108,24 +89,13 @@ generate-installer"
assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz" assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz"
} }
@test "correctly joins archive base url with a trailing slash" { @test "strips a trailing slash from the archive base url" {
stub_build_environment stub_build_environment
create_stub pyenv-binary-save true cd "${BATS_TEST_TMPDIR}"
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 \ run pyenv-binary-package 3.12.7:3.12.7-test \
--archive-base-url http://example.com/binaries/ --archive-base-url http://example.com/binaries/
assert_success assert_success
assert_line "pyenv-binary-generate-installer --archive-url http://example.com/binaries/3.12.7-test.tar.gz" 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"
} }

View file

@ -4,7 +4,6 @@ load test_helper
_setup() { _setup() {
create_stub pyenv-help "echo usage" create_stub pyenv-help "echo usage"
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
} }
stub_patchelf() { stub_patchelf() {
@ -22,87 +21,6 @@ create_interpreter() {
chmod +x "${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" { @test "completion produces nothing" {
run pyenv-binary-relocate --complete run pyenv-binary-relocate --complete
assert_success "" assert_success ""
@ -118,31 +36,6 @@ STUB
assert_failure "pyenv-binary: need patchelf to relocate the binary" 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" { @test "fails when the prefix has no interpreter" {
create_path_executable patchelf "exit 0" create_path_executable patchelf "exit 0"
mkdir -p "${BATS_TEST_TMPDIR}/prefix" mkdir -p "${BATS_TEST_TMPDIR}/prefix"
@ -177,45 +70,3 @@ STUB
assert_line "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${lib}/lib-dynload/_ssl.cpython-312-x86_64-linux-gnu.so" 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" refute_line "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${lib}/site-packages/numpy/_multiarray.so"
} }
@test "relocates macOS load commands without changing unrelated entries" {
local prefix="${BATS_TEST_TMPDIR}/prefix"
local old="/build prefix/3.12.7"
local lib="${prefix}/lib/python3.12"
create_macos_tree
stub_macos_tools
run pyenv-binary-relocate "$prefix" "$old"
assert_success
run cat "${BATS_TEST_TMPDIR}/install-name-tool.log"
assert_output <<EOF
-change ${old}/lib/libpython3.12.dylib @rpath/libpython3.12.dylib ${prefix}/bin/python3.12
-rpath ${old}/lib @executable_path/../lib ${prefix}/bin/python3.12
-id @rpath/libpython3.12.dylib ${prefix}/lib/libpython3.12.dylib
-rpath ${old}/lib @loader_path ${prefix}/lib/libpython3.12.dylib
-rpath ${old}/lib @loader_path/../.. ${lib}/lib-dynload/_ssl.cpython-312-darwin.so
EOF
run grep -F "${lib}/site-packages/numpy/_multiarray.so" "${BATS_TEST_TMPDIR}/otool.log"
assert_failure
}
@test "skips already relocated macOS load commands" {
local prefix="${BATS_TEST_TMPDIR}/prefix"
create_macos_tree
stub_macos_tools
export OTOOL_RELOCATED=1
run pyenv-binary-relocate "$prefix" "/build prefix/3.12.7"
assert_success
assert [ ! -e "${BATS_TEST_TMPDIR}/install-name-tool.log" ]
}
@test "fails when install_name_tool cannot modify a selected file" {
local prefix="${BATS_TEST_TMPDIR}/prefix"
create_macos_tree
stub_macos_tools
create_path_executable install_name_tool 'exit 1'
run pyenv-binary-relocate "$prefix" "/build prefix/3.12.7"
assert_failure ""
}

View file

@ -79,25 +79,7 @@ platform() {
assert_line "archive=3.12.7-$(platform).tar.gz" assert_line "archive=3.12.7-$(platform).tar.gz"
} }
@test "records the original installation prefix" { @test "records only the libraries ldd resolves outside the 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" create_version "3.12.7"
touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12" touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12"
create_path_executable uname <<'STUB' create_path_executable uname <<'STUB'
@ -115,18 +97,13 @@ cat <<EOF
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4a3b800000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4a3b800000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4a3c200000) /lib64/ld-linux-x86-64.so.2 (0x00007f4a3c200000)
EOF 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 STUB
run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist" run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist"
assert_success assert_success
run grep '^dep=' "${BATS_TEST_TMPDIR}/dist/"*.meta run grep '^dep=' "${BATS_TEST_TMPDIR}/dist/"*.meta
assert_output "dep=libm.so.6" assert_output "dep=libc.so.6
dep=libm.so.6"
} }
@test "records only the libraries otool resolves outside the prefix" { @test "records only the libraries otool resolves outside the prefix" {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -54,11 +54,8 @@ Or, if you would like to install the latest development release:
## Usage ## Usage
On Ubuntu, Debian, and Mint, install the recommended build dependencies with: Before you begin, you should ensure that your build environment has the proper
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
@ -364,3 +361,4 @@ git diff --name-only master \
- Filter out any which don't live where python-build keeps its build scripts - Filter out any which don't live where python-build keeps its build scripts
- Look only at the file name (i.e. the python version name) - Look only at the file name (i.e. the python version name)
- Run a new docker container for each, building that version - Run a new docker container for each, building that version

View file

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

View file

@ -14,7 +14,7 @@
# -g/--debug Build a debug version # -g/--debug Build a debug version
# #
PYTHON_BUILD_VERSION="2.8.5" PYTHON_BUILD_VERSION="2.8.2"
OLDIFS="$IFS" OLDIFS="$IFS"

View file

@ -219,10 +219,7 @@ 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":
# Info about the bundled Python version # https://www.anaconda.com/docs/tools/anaconda-org/release-notes
# https://www.anaconda.com/docs/getting-started/anaconda/release-notes
if v >= (2026,7):
return PyVersion.PY314
if v >= (2025,6): if v >= (2025,6):
return PyVersion.PY313 return PyVersion.PY313
if v >= (2024,6): if v >= (2024,6):

View file

@ -1,7 +1,6 @@
#!/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
@ -9,7 +8,7 @@ import string
import requests import requests
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=os.environ.get('LOGLEVEL', 'INFO'))
MINIFORGE_REPO = 'conda-forge/miniforge' MINIFORGE_REPO = 'conda-forge/miniforge'
DISTRIBUTIONS = ['miniforge'] DISTRIBUTIONS = ['miniforge']
@ -22,8 +21,6 @@ SKIPPED_RELEASES = [
'22.11.1-2', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403) '22.11.1-2', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403)
'25.3.0-0', #marked as prerelease, no Linux version '25.3.0-0', #marked as prerelease, no Linux version
'25.11.0-0', #regression reported in constructor, re-released as 25.11.0-1 with hotfix bumping to constructor>=3.14 (was >=3.12, 3.13 implicit) '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 = """
@ -139,7 +136,7 @@ def main():
if version in SKIPPED_RELEASES: if version in SKIPPED_RELEASES:
continue continue
logger.debug(f'Looking for {version} in {out_dir}') logger.info(f'Looking for {version} in {out_dir}')
# mambaforge is retired https://github.com/conda-forge/miniforge/releases/tag/24.11.2-0 # mambaforge is retired https://github.com/conda-forge/miniforge/releases/tag/24.11.2-0
if version_tuple(version) >= (24, 11, 2): if version_tuple(version) >= (24, 11, 2):
@ -152,17 +149,4 @@ def main():
add_version(release, distributions) add_version(release, distributions)
if __name__ == '__main__': 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() main()

View file

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

View file

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

View file

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

View file

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

View file

@ -1,2 +0,0 @@
export PYTHON_BUILD_FREE_THREADING=1
source "${BASH_SOURCE[0]%t}"

View file

@ -1,11 +1,11 @@
prefer_openssl3_to_4 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_CONFIGURE_WITH_OPENSSL_RPATH=1
export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1 export PYTHON_BUILD_TCLTK_USE_PKGCONFIG=1
install_package "openssl-4.0.1" "https://github.com/openssl/openssl/releases/download/openssl-4.0.1/openssl-4.0.1.tar.gz#2db3f3a0d6ea4b59e1f094ace2c8cd536dffb87cdc39084c5afa1e6f7f37dd09" mac_openssl --if has_broken_mac_openssl install_package "openssl-4.0.1" "https://github.com/openssl/openssl/releases/download/openssl-4.0.1/openssl-4.0.1.tar.gz#2db3f3a0d6ea4b59e1f094ace2c8cd536dffb87cdc39084c5afa1e6f7f37dd09" 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 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 if has_tar_xz_support; then
install_package "Python-3.14.7" "https://www.python.org/ftp/python/3.14.7/Python-3.14.7.tar.xz#3b48dac8fb59f62eaa67ac83c1eb12bda1b7a08406dd286e252c11a66be27f81" standard verify_py314 copy_python_gdb ensurepip install_package "Python-3.15.0rc1" "https://www.python.org/ftp/python/3.15.0/Python-3.15.0rc1.tar.xz#f84dad680ab2147417d2739355c2678f0f9acffe4ae8ef77895de1454b384b07" standard verify_py315 copy_python_gdb ensurepip
else else
install_package "Python-3.14.7" "https://www.python.org/ftp/python/3.14.7/Python-3.14.7.tgz#62859805f6fdf25e2bcbf3fa3217801e1996887ca33e6a2af80674bdfa2dbe07" standard verify_py314 copy_python_gdb ensurepip install_package "Python-3.15.0rc1" "https://www.python.org/ftp/python/3.15.0/Python-3.15.0rc1.tgz#32c2b9878bbd6ac3ceb138c5b6c880345ce9d33388b399378c0da6fc7cb3bfbc" standard verify_py315 copy_python_gdb ensurepip
fi fi

View file

@ -1,11 +0,0 @@
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-4.0.2" "https://github.com/openssl/openssl/releases/download/openssl-4.0.2/openssl-4.0.2.tar.gz#736b467530f916737b7031310ccb21d8218c6229e61e8e160cd1d3458cd543a8" 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.15.0rc2" "https://www.python.org/ftp/python/3.15.0/Python-3.15.0rc2.tar.xz#8d93af5eaaaea5adfd41bd786a7ba3f03f2ad1ab57c6a65e0b963deab91d5ad7" standard verify_py315 copy_python_gdb ensurepip
else
install_package "Python-3.15.0rc2" "https://www.python.org/ftp/python/3.15.0/Python-3.15.0rc2.tgz#164dbb80f5e1fdd8768387724bfa014da1000401b2015d872a2396c8b858b829" standard verify_py315 copy_python_gdb ensurepip
fi

View file

@ -1,2 +0,0 @@
export PYTHON_BUILD_FREE_THREADING=1
source "${BASH_SOURCE[0]%t}"

View file

@ -1,19 +0,0 @@
case "$(anaconda_architecture 2>/dev/null || true)" in
"Linux-aarch64" )
install_script "Anaconda3-2026.07-1-Linux-aarch64" "https://repo.anaconda.com/archive/Anaconda3-2026.07-1-Linux-aarch64.sh#af2fd0025728a04408e0481d5668b92c53589cbfc66525a15119e6503742a04d" "anaconda" verify_py314
;;
"Linux-x86_64" )
install_script "Anaconda3-2026.07-1-Linux-x86_64" "https://repo.anaconda.com/archive/Anaconda3-2026.07-1-Linux-x86_64.sh#9f49042048e9a8220c1cddf9192054290724052d3b2a86f938f9f5edd911db8f" "anaconda" verify_py314
;;
"MacOSX-arm64" )
install_script "Anaconda3-2026.07-1-MacOSX-arm64" "https://repo.anaconda.com/archive/Anaconda3-2026.07-1-MacOSX-arm64.sh#6a9b63c645db4dbe293ffe9f168f7e380fe7a041fc863e67ee84db027425bce2" "anaconda" verify_py314
;;
* )
{ echo
colorize 1 "ERROR"
echo ": The binary distribution of Anaconda is not available for $(anaconda_architecture 2>/dev/null || true)."
echo
} >&2
exit 1
;;
esac

View file

@ -1,50 +0,0 @@
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
#
# 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.
VERSION='25.3.4.1'
RELEASE_TAG='graal-25.3.4'
colorize 1 "GraalPy 23.1 and later installed by python-build use the faster Oracle GraalVM distribution" && echo
colorize 1 "Oracle GraalVM uses the GFTC license, which is free for development and production use, see https://medium.com/graalvm/161527df3d76" && echo
colorize 1 "The GraalVM Community Edition variant of GraalPy is also available, under the name graalpy3.13-community-${VERSION}" && echo
graalpy_arch="$(graalpy_architecture 2>/dev/null || true)"
case "$graalpy_arch" in
"linux-amd64" )
checksum="23515b4e659d645960517b0ceedb90faa84fcb01f0f4497beca5868a1b1ae3ea"
;;
"linux-aarch64" )
checksum="53b80e284e444540ae7cb67349af3dd8f6c1848fbf6abb5051742ac745f1f33d"
;;
"macos-aarch64" )
checksum="8ac33b5a5d0ee872751595211508fd826f47051ff6e6614032aed1de071eebab"
;;
* )
{ echo
colorize 1 "ERROR"
echo ": No binary distribution of GraalPy is available for $(uname -sm)."
echo
} >&2
exit 1
;;
esac
install_package "graalpy3.13-${VERSION}" "https://github.com/oracle/graalpython/releases/download/${RELEASE_TAG}/graalpy3.13-${VERSION}-${graalpy_arch}.tar.gz#${checksum}" "copy" ensurepip

View file

@ -1,45 +0,0 @@
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
#
# 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.
VERSION='25.3.4.1'
RELEASE_TAG='graal-25.3.4'
graalpy_arch="$(graalpy_architecture 2>/dev/null || true)"
case "$graalpy_arch" in
"linux-amd64" )
checksum="5e239616c8352488585f58d33991d262e8c627483741778ad43d39a4830176ad"
;;
"linux-aarch64" )
checksum="13007ecfe8180e91f1060de60a2ba6ce88f38faa4a36574b34143a492e8c660e"
;;
"macos-aarch64" )
checksum="e2cf7ae230478ad8d100ad868f75e3addb174d81e7f550322ea5e74cab066644"
;;
* )
{ echo
colorize 1 "ERROR"
echo ": No binary distribution of GraalPy is available for $(uname -sm)."
echo
} >&2
exit 1
;;
esac
install_package "graalpy3.13-community-${VERSION}" "https://github.com/oracle/graalpython/releases/download/${RELEASE_TAG}/graalpy3.13-community-${VERSION}-${graalpy_arch}.tar.gz#${checksum}" "copy" ensurepip

View file

@ -1,20 +0,0 @@
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
case "$(anaconda_architecture 2>/dev/null || true)" in
"Linux-aarch64" )
install_script "Miniconda3-py310_26.5.3-2-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_26.5.3-2-Linux-aarch64.sh#69b30d25b1308596e39819d2eeb8d669fec8b35205254d4d38975e9a6eb525e5" "miniconda" verify_py310
;;
"Linux-x86_64" )
install_script "Miniconda3-py310_26.5.3-2-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_26.5.3-2-Linux-x86_64.sh#83e53ab0246ca320db28734bfd16f9bba895d96ae3e0ccf6f9b65e1383ab6a26" "miniconda" verify_py310
;;
"MacOSX-arm64" )
install_script "Miniconda3-py310_26.5.3-2-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_26.5.3-2-MacOSX-arm64.sh#fc25d37469e162e3073093c1e02a0a21a59681af10a097f73df3f303b89b050a" "miniconda" verify_py310
;;
* )
{ echo
colorize 1 "ERROR"
echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)."
echo
} >&2
exit 1
;;
esac

View file

@ -1,20 +0,0 @@
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
case "$(anaconda_architecture 2>/dev/null || true)" in
"Linux-aarch64" )
install_script "Miniconda3-py311_26.5.3-2-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_26.5.3-2-Linux-aarch64.sh#e50d1c902e94b6f64a19e0a3fbc35f8cadc09c5a1cf5b0fe10da14e4ce7ff24d" "miniconda" verify_py311
;;
"Linux-x86_64" )
install_script "Miniconda3-py311_26.5.3-2-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_26.5.3-2-Linux-x86_64.sh#cdca3dd8440759bb87c60b227e26946263fe2856b30b2bcfdd964c38254fb8eb" "miniconda" verify_py311
;;
"MacOSX-arm64" )
install_script "Miniconda3-py311_26.5.3-2-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_26.5.3-2-MacOSX-arm64.sh#129bee9cb8e23d0eed175439a8b41c9844bac5c75f3f2aefa0be7104b403706f" "miniconda" verify_py311
;;
* )
{ echo
colorize 1 "ERROR"
echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)."
echo
} >&2
exit 1
;;
esac

View file

@ -1,20 +0,0 @@
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
case "$(anaconda_architecture 2>/dev/null || true)" in
"Linux-aarch64" )
install_script "Miniconda3-py312_26.5.3-2-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_26.5.3-2-Linux-aarch64.sh#822923b30d789477964ef5589f86de5a72ac4c584bad3b9f72d97190e13d776f" "miniconda" verify_py312
;;
"Linux-x86_64" )
install_script "Miniconda3-py312_26.5.3-2-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_26.5.3-2-Linux-x86_64.sh#37606f9f03ced8ef60f4ffc76b21dda01728eac8a632dcab316c891cea4fe2f5" "miniconda" verify_py312
;;
"MacOSX-arm64" )
install_script "Miniconda3-py312_26.5.3-2-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_26.5.3-2-MacOSX-arm64.sh#ba7e4dede827e98c468c2dfa3b9a859728844ee8bde72a28eb216869af2d6ce1" "miniconda" verify_py312
;;
* )
{ echo
colorize 1 "ERROR"
echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)."
echo
} >&2
exit 1
;;
esac

View file

@ -1,20 +0,0 @@
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
case "$(anaconda_architecture 2>/dev/null || true)" in
"Linux-aarch64" )
install_script "Miniconda3-py313_26.5.3-2-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_26.5.3-2-Linux-aarch64.sh#549176858e194a15df5c8326b9f4e1b7ddf1464df47aec4115d0e6d14204e8bb" "miniconda" verify_py313
;;
"Linux-x86_64" )
install_script "Miniconda3-py313_26.5.3-2-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_26.5.3-2-Linux-x86_64.sh#66f7c434bbdc7a4c5687b7e56cde724f73954d1322ffb273c6e387f12fbcdc03" "miniconda" verify_py313
;;
"MacOSX-arm64" )
install_script "Miniconda3-py313_26.5.3-2-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_26.5.3-2-MacOSX-arm64.sh#0d4d44f5d0740dddc68d381bbc8eb8c056d4792c832a9f0e3b5c663f7dda9d67" "miniconda" verify_py313
;;
* )
{ echo
colorize 1 "ERROR"
echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)."
echo
} >&2
exit 1
;;
esac

View file

@ -1,20 +0,0 @@
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
case "$(anaconda_architecture 2>/dev/null || true)" in
"Linux-aarch64" )
install_script "Miniconda3-py314_26.5.3-2-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py314_26.5.3-2-Linux-aarch64.sh#999e8761f4dc74fb8d0dc9f74d5374f6708ff81eebbb0af677a4d5307d38b2e5" "miniconda" verify_py314
;;
"Linux-x86_64" )
install_script "Miniconda3-py314_26.5.3-2-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py314_26.5.3-2-Linux-x86_64.sh#80bc27f13c4de90f10e387aa45e864de4f0860692c1221aef5900009a2b55302" "miniconda" verify_py314
;;
"MacOSX-arm64" )
install_script "Miniconda3-py314_26.5.3-2-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py314_26.5.3-2-MacOSX-arm64.sh#e830bee5b9c6ebfb67e9df924b259a0d43c451bd17b965f7aa84523b69f7c3eb" "miniconda" verify_py314
;;
* )
{ echo
colorize 1 "ERROR"
echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)."
echo
} >&2
exit 1
;;
esac

View file

@ -1,27 +0,0 @@
From 3fec519d6b326918f39ceb142ef762042ce8365f Mon Sep 17 00:00:00 2001
From: Ivan Pozdeev <vano@mail.mipt.ru>
Date: Fri, 26 Dec 2025 13:39:21 +0300
Subject: [PATCH] Recognize an argument to -j in MAKEFLAGS
---
setup.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/setup.py b/setup.py
index ad8fb81b218..5666deaf67b 100644
--- a/setup.py
+++ b/setup.py
@@ -307,8 +307,8 @@ def __init__(self, dist):
self.failed_on_import = []
self.missing = []
self.disabled_configure = []
- if '-j' in os.environ.get('MAKEFLAGS', ''):
- self.parallel = True
+ if (m:=re.search(r'(?:^|\s)(?:-j|--jobs)(?:\s+|=)?(\d*)',os.environ.get('MAKEFLAGS', ''))) is not None:
+ self.parallel = int(m.group(1)) if m.group(1) else True
def add(self, ext):
self.extensions.append(ext)
--
2.36.1.windows.1

View file

@ -1,27 +0,0 @@
From 3fec519d6b326918f39ceb142ef762042ce8365f Mon Sep 17 00:00:00 2001
From: Ivan Pozdeev <vano@mail.mipt.ru>
Date: Fri, 26 Dec 2025 13:39:21 +0300
Subject: [PATCH] Recognize an argument to -j in MAKEFLAGS
---
setup.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/setup.py b/setup.py
index ad8fb81b218..5666deaf67b 100644
--- a/setup.py
+++ b/setup.py
@@ -307,8 +307,8 @@ def __init__(self, dist):
self.failed_on_import = []
self.missing = []
self.disabled_configure = []
- if '-j' in os.environ.get('MAKEFLAGS', ''):
- self.parallel = True
+ if (m:=re.search(r'(?:^|\s)(?:-j|--jobs)(?:\s+|=)?(\d*)',os.environ.get('MAKEFLAGS', ''))) is not None:
+ self.parallel = int(m.group(1)) if m.group(1) else True
def add(self, ext):
self.extensions.append(ext)
--
2.36.1.windows.1

View file

@ -1,27 +0,0 @@
From 3fec519d6b326918f39ceb142ef762042ce8365f Mon Sep 17 00:00:00 2001
From: Ivan Pozdeev <vano@mail.mipt.ru>
Date: Fri, 26 Dec 2025 13:39:21 +0300
Subject: [PATCH] Recognize an argument to -j in MAKEFLAGS
---
setup.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/setup.py b/setup.py
index ad8fb81b218..5666deaf67b 100644
--- a/setup.py
+++ b/setup.py
@@ -307,8 +307,8 @@ def __init__(self, dist):
self.failed_on_import = []
self.missing = []
self.disabled_configure = []
- if '-j' in os.environ.get('MAKEFLAGS', ''):
- self.parallel = True
+ if (m:=re.search(r'(?:^|\s)(?:-j|--jobs)(?:\s+|=)?(\d*)',os.environ.get('MAKEFLAGS', ''))) is not None:
+ self.parallel = int(m.group(1)) if m.group(1) else True
def add(self, ext):
self.extensions.append(ext)
--
2.36.1.windows.1

View file

@ -1,64 +0,0 @@
From cc13eabc7ce08accf49656e258ba500f74a1dae8 Mon Sep 17 00:00:00 2001
From: Ronald Oussoren <ronaldoussoren@mac.com>
Date: Thu, 28 Dec 2023 09:42:05 +0100
Subject: [PATCH] gh-110459: Make sure --with-openssl-rpath works on macOS
(#113441)
* gh-110459: Make sure --with-openssl-rpath works on macOS
On macOS the `-rpath` linker flag is spelled differently
than on on platforms.
---
.../macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst | 2 ++
configure | 7 ++++++-
configure.ac | 7 ++++++-
3 files changed, 14 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst
diff --git a/Misc/NEWS.d/next/macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst b/Misc/NEWS.d/next/macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst
new file mode 100644
index 00000000000..44ffd857785
--- /dev/null
+++ b/Misc/NEWS.d/next/macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst
@@ -0,0 +1,2 @@
+Running ``configure ... --with-openssl-rpath=X/Y/Z`` no longer fails to detect
+OpenSSL on macOS.
diff --git a/configure b/configure
index 7e50abc29d0..6d65d3abc18 100755
--- a/configure
+++ b/configure
@@ -27478,7 +27478,12 @@ then :
else $as_nop
- rpath_arg="-Wl,-rpath="
+ if test "$ac_sys_system" = "Darwin"
+ then
+ rpath_arg="-Wl,-rpath,"
+ else
+ rpath_arg="-Wl,-rpath="
+ fi
fi
diff --git a/configure.ac b/configure.ac
index e064848af9e..bfdabc4474e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6808,7 +6808,12 @@ AX_CHECK_OPENSSL([have_openssl=yes],[have_openssl=no])
AS_VAR_IF([GNULD], [yes], [
rpath_arg="-Wl,--enable-new-dtags,-rpath="
], [
- rpath_arg="-Wl,-rpath="
+ if test "$ac_sys_system" = "Darwin"
+ then
+ rpath_arg="-Wl,-rpath,"
+ else
+ rpath_arg="-Wl,-rpath="
+ fi
])
AC_MSG_CHECKING([for --with-openssl-rpath])
--
2.46.2.windows.1

View file

@ -1276,7 +1276,6 @@ OUT
mkdir -p "$INSTALL_ROOT/bin" mkdir -p "$INSTALL_ROOT/bin"
touch "$INSTALL_ROOT/bin/package" touch "$INSTALL_ROOT/bin/package"
chmod -w "$INSTALL_ROOT/bin/package" chmod -w "$INSTALL_ROOT/bin/package"
skip_if_nonposix_security -w "$INSTALL_ROOT/bin/package"
install_fixture definitions/without-checksum install_fixture definitions/without-checksum
assert_success assert_success
@ -1289,7 +1288,6 @@ OUT
export TMPDIR="${BATS_TEST_TMPDIR}/build" export TMPDIR="${BATS_TEST_TMPDIR}/build"
mkdir -p "$TMPDIR" mkdir -p "$TMPDIR"
chmod -w "$TMPDIR" chmod -w "$TMPDIR"
skip_if_nonposix_security -w "$TMPDIR"
touch "${BATS_TEST_TMPDIR}/build-definition" touch "${BATS_TEST_TMPDIR}/build-definition"
run python-build "${BATS_TEST_TMPDIR}/build-definition" "$INSTALL_ROOT" run python-build "${BATS_TEST_TMPDIR}/build-definition" "$INSTALL_ROOT"
@ -1300,7 +1298,6 @@ OUT
export TMPDIR="${BATS_TEST_TMPDIR}/build" export TMPDIR="${BATS_TEST_TMPDIR}/build"
mkdir -p "$TMPDIR" mkdir -p "$TMPDIR"
chmod -x "$TMPDIR" chmod -x "$TMPDIR"
skip_if_nonposix_security -x "$TMPDIR"
touch "${BATS_TEST_TMPDIR}/build-definition" touch "${BATS_TEST_TMPDIR}/build-definition"
run python-build "${BATS_TEST_TMPDIR}/build-definition" "$INSTALL_ROOT" run python-build "${BATS_TEST_TMPDIR}/build-definition" "$INSTALL_ROOT"

View file

@ -1,83 +0,0 @@
#!/usr/bin/env bats
load test_helper
_setup() {
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"
}
@test "completion does not install packages" {
stub apt-get
PATH="${BATS_TEST_DIRNAME}/../../../libexec:$PATH" \
run pyenv completions install-prerequisites
assert_success "--help"
unstub apt-get
}
@test "supports help" {
stub pyenv-help 'install-prerequisites : echo "Usage: pyenv install-prerequisites"'
run pyenv-install-prerequisites --help
assert_success "Usage: pyenv install-prerequisites"
unstub pyenv-help
}
@test "rejects arguments" {
stub pyenv-help 'install-prerequisites : echo "Usage: pyenv install-prerequisites"'
run pyenv-install-prerequisites unexpected
assert_failure "Usage: pyenv install-prerequisites"
unstub pyenv-help
}
@test "installs build prerequisites with apt-get as root" {
stub id '-u : echo 0'
stub apt-get \
'update -q : true' \
"install -yq ${prerequisites} : true"
run pyenv-install-prerequisites
assert_success
unstub apt-get
unstub id
}
@test "uses sudo when not running as root" {
stub id '-u : echo 1000'
stub apt-get
stub sudo \
'apt-get update -q : true' \
"apt-get install -yq ${prerequisites} : true"
run pyenv-install-prerequisites
assert_success
unstub sudo
unstub apt-get
unstub id
}
@test "fails when apt-get is unavailable" {
PATH="$(path_without apt-get)" run pyenv-install-prerequisites
assert_failure "pyenv: installing build prerequisites is not supported on this system"
}
@test "fails when sudo is unavailable for an unprivileged user" {
stub id '-u : echo 1000'
stub apt-get
PATH="$(path_without sudo)" run pyenv-install-prerequisites
assert_failure "pyenv: sudo is required to install build prerequisites"
unstub apt-get
unstub id
}

View file

@ -2,7 +2,6 @@ setup() {
export PYTHON_BUILD_CURL_OPTS= export PYTHON_BUILD_CURL_OPTS=
export PYTHON_BUILD_HTTP_CLIENT="curl" export PYTHON_BUILD_HTTP_CLIENT="curl"
export PYENV_TEST_DIR="${BATS_TEST_TMPDIR}/pyenv"
export FIXTURE_ROOT="${BATS_TEST_DIRNAME}/fixtures" export FIXTURE_ROOT="${BATS_TEST_DIRNAME}/fixtures"
export INSTALL_ROOT="${BATS_TEST_TMPDIR}/install" export INSTALL_ROOT="${BATS_TEST_TMPDIR}/install"
PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
@ -149,7 +148,7 @@ path_without() {
if [ "$found" != "${PYENV_ROOT}/shims" ]; then if [ "$found" != "${PYENV_ROOT}/shims" ]; then
alt="${PYENV_TEST_DIR}/$(echo "${found#/}" | tr '/' '-')" alt="${PYENV_TEST_DIR}/$(echo "${found#/}" | tr '/' '-')"
mkdir -p "$alt" mkdir -p "$alt"
for util in bash head cut readlink greadlink tr; do for util in bash head cut readlink greadlink; do
if [ -x "${found}/$util" ]; then if [ -x "${found}/$util" ]; then
ln -s "${found}/$util" "${alt}/$util" ln -s "${found}/$util" "${alt}/$util"
fi fi
@ -163,7 +162,3 @@ path_without() {
echo "$path" echo "$path"
} }
skip_if_nonposix_security() {
true "${1:?}" "${2:?}"
test "$@" && skip "UNIX permission bits are being overridden" || true
}

View file

@ -12,7 +12,7 @@ FROM bash:$BASH
apk add sed coreutils findutils \ apk add sed coreutils findutils \
;fi ;fi
# Bats # Bats
RUN apk add --update parallel ncurses git binutils \ RUN apk add --update parallel ncurses git \
&& mkdir -p ~/.parallel \ && mkdir -p ~/.parallel \
&& touch ~/.parallel/will-cite && touch ~/.parallel/will-cite
COPY --from=bats /root/bats-core /root/bats-core COPY --from=bats /root/bats-core /root/bats-core

View file

@ -1,45 +0,0 @@
#!/usr/bin/env bash
set -e
# Wraps Bats TAP formatter, adding file names to the output
_BATS_FORMATTER_LIB_RELPATH="lib/bats-core/formatter.bash"
source "$BATS_ROOT/$_BATS_FORMATTER_LIB_RELPATH"
# `bats-format-tap` calls this fn at the end
# We must stub it to be able to source and alter it
# https://stackoverflow.com/questions/1203583/how-do-i-rename-a-bash-function
eval orig_"$(declare -f bats_parse_internal_extended_tap)"
bats_parse_internal_extended_tap() { true; }
# stub sourcing lib from `bats-format-tap`
# as that would've reset the stubbed fn
FAKE_BATS_ROOT=
_trap_rm_fakeroot() { rm -rf "$FAKE_BATS_ROOT"; }
REAL_BATS_ROOT="$BATS_ROOT"
FAKE_BATS_ROOT=$(mktemp -d)
trap _trap_rm_fakeroot ERR EXIT
mkdir -p "$(dirname "$FAKE_BATS_ROOT/$_BATS_FORMATTER_LIB_RELPATH")"
touch "$FAKE_BATS_ROOT/$_BATS_FORMATTER_LIB_RELPATH"
BATS_ROOT="$FAKE_BATS_ROOT"
source "$REAL_BATS_ROOT/libexec/bats-core/bats-format-tap"
_trap_rm_fakeroot
trap - ERR EXIT
BATS_ROOT="$REAL_BATS_ROOT"
unset FAKE_BATS_ROOT REAL_BATS_ROOT _trap_rm_fakeroot
# Finally, alter `bats-format-tap`'s logic and invoke it
# print as "Anything" line
bats_tap_stream_suite() { # <file name>
# bats only passes --base-name to specific built-in formatters
# so we don't have info where the test root is
printf '%s\n' "${1##*/}"
}
orig_bats_parse_internal_extended_tap

View file

@ -13,8 +13,6 @@ load test_helper
@test "non-writable shims directory" { @test "non-writable shims directory" {
mkdir -p "${PYENV_ROOT}/shims" mkdir -p "${PYENV_ROOT}/shims"
chmod -w "${PYENV_ROOT}/shims" chmod -w "${PYENV_ROOT}/shims"
skip_if_nonposix_security -w "${PYENV_ROOT}/shims"
run pyenv-rehash run pyenv-rehash
assert_failure "pyenv: cannot rehash: ${PYENV_ROOT}/shims isn't writable" assert_failure "pyenv: cannot rehash: ${PYENV_ROOT}/shims isn't writable"
} }
@ -25,8 +23,7 @@ load test_helper
touch "${PYENV_ROOT}/shims/.pyenv-shim" touch "${PYENV_ROOT}/shims/.pyenv-shim"
#avoid failure due to a localized error message #avoid failure due to a localized error message
LANG=C run pyenv-rehash LANG=C run pyenv-rehash
assert_failure assert_failure <<!
assert_output_glob <<!
pyenv: cannot rehash: couldn't acquire lock ${PYENV_ROOT}/shims/.pyenv-shim for 1 seconds. Last error message: pyenv: cannot rehash: couldn't acquire lock ${PYENV_ROOT}/shims/.pyenv-shim for 1 seconds. Last error message:
*/pyenv-rehash: line *: ${PYENV_ROOT}/shims/.pyenv-shim: cannot overwrite existing file */pyenv-rehash: line *: ${PYENV_ROOT}/shims/.pyenv-shim: cannot overwrite existing file
! !

View file

@ -6,4 +6,4 @@ if [ -n "$PYENV_NATIVE_EXT" ]; then
make -C src make -C src
fi fi
exec bats ${CI:+-F "$PWD/test/libexec/bats-format-tap-suite"} ${BATS_TEST_FILTER:+--filter "${BATS_TEST_FILTER}"} test/${BATS_FILE_FILTER} exec bats ${CI:+--tap} ${BATS_TEST_FILTER:+--filter "${BATS_TEST_FILTER}"} test/${BATS_FILE_FILTER}

View file

@ -0,0 +1,39 @@
#!/usr/bin/env bats
load test_helper
@test "shims linked from elsewhere are chained, other programs at the same paths are unaffected (integration)" {
bats_require_minimum_version 1.5.0
progname=shimmed_program
called_progname=another_program
create_alt_executable_in_version "custom" "$progname" <<!
echo "called from \$0"
pyenv-exec "$called_progname"
!
pyenv-rehash
PATH="$(path_without "$progname" "$called_progname")"
for disguised_shim_path in "$BATS_TEST_TMPDIR/"{weird-location,even/weirder/location}; do
mkdir -p "$disguised_shim_path"
ln -s "${PYENV_ROOT}/shims/$progname" "$disguised_shim_path/$progname"
PATH="$PATH:$disguised_shim_path"
done
create_executable "$disguised_shim_path" "$called_progname" <<!
echo "convoluted call success!"
!
real_path="$BATS_TEST_TMPDIR/real-location"
mkdir -p "$real_path"
ln -s "${PYENV_ROOT}/versions/custom/bin/$progname" "$real_path/$progname"
PATH="$PATH:$real_path"
run "$progname"
assert_success
assert_output <<!
called from $real_path/$progname
convoluted call success!
!
rm "$real_path/$progname"
run -127 "$progname"
assert_failure
assert_line 0 "pyenv: shimmed_program: command not found"
}

View file

@ -27,39 +27,3 @@ load test_helper
assert_line "irb" assert_line "irb"
assert_line "python" assert_line "python"
} }
@test "shims linked from elsewhere are chained, other programs at the same paths are unaffected (integration)" {
bats_require_minimum_version 1.5.0
progname=shimmed_program
called_progname=another_program
create_alt_executable_in_version "custom" "$progname" <<!
echo "called from \$0"
pyenv-exec "$called_progname"
!
pyenv-rehash
PATH="$(path_without "$progname" "$called_progname")"
for disguised_shim_path in "$BATS_TEST_TMPDIR/"{weird-location,even/weirder/location}; do
mkdir -p "$disguised_shim_path"
ln -s "${PYENV_ROOT}/shims/$progname" "$disguised_shim_path/$progname"
PATH="$PATH:$disguised_shim_path"
done
create_executable "$disguised_shim_path" "$called_progname" <<!
echo "convoluted call success!"
!
real_path="$BATS_TEST_TMPDIR/real-location"
mkdir -p "$real_path"
ln -s "${PYENV_ROOT}/versions/custom/bin/$progname" "$real_path/$progname"
PATH="$PATH:$real_path"
run "$progname"
assert_success
assert_output <<!
called from $real_path/$progname
convoluted call success!
!
rm "$real_path/$progname"
run -127 "$progname"
assert_failure
assert_line 0 "pyenv: shimmed_program: command not found"
}

View file

@ -2,14 +2,8 @@ unset PYENV_VERSION
unset PYENV_DIR unset PYENV_DIR
setup() { setup() {
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
export _PYENV_INSTALL_PREFIX="${BATS_TEST_DIRNAME%/*}" export _PYENV_INSTALL_PREFIX="${BATS_TEST_DIRNAME%/*}"
local PLUGIN_PREFIX export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
if [[ $_PYENV_INSTALL_PREFIX =~ /plugins/[^/]+$ ]]; then
PLUGIN_PREFIX="$_PYENV_INSTALL_PREFIX"
_PYENV_INSTALL_PREFIX="${_PYENV_INSTALL_PREFIX::${#_PYENV_INSTALL_PREFIX}-${#BASH_REMATCH[0]}}"
fi
if ! enable -f "${_PYENV_INSTALL_PREFIX}"/libexec/pyenv-realpath.dylib realpath 2>/dev/null; then if ! enable -f "${_PYENV_INSTALL_PREFIX}"/libexec/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
@ -33,9 +27,6 @@ setup() {
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
PATH="${PYENV_TEST_DIR}/bin:$PATH" PATH="${PYENV_TEST_DIR}/bin:$PATH"
PATH="${_PYENV_INSTALL_PREFIX}/libexec:$PATH" PATH="${_PYENV_INSTALL_PREFIX}/libexec:$PATH"
if [[ -n $PLUGIN_PREFIX ]]; then
PATH="${PLUGIN_PREFIX}/libexec:${PLUGIN_PREFIX}/bin:$PATH"
fi
PATH="${BATS_TEST_DIRNAME}/libexec:$PATH" PATH="${BATS_TEST_DIRNAME}/libexec:$PATH"
PATH="${PYENV_ROOT}/shims:$PATH" PATH="${PYENV_ROOT}/shims:$PATH"
PATH="${BATS_TEST_TMPDIR}/stubs:$PATH" PATH="${BATS_TEST_TMPDIR}/stubs:$PATH"
@ -49,7 +40,6 @@ setup() {
# even if its output is redirected, breaking the comparison logic # even if its output is redirected, breaking the comparison logic
export NO_COLOR=1 export NO_COLOR=1
# If test specific setup exist, run it # If test specific setup exist, run it
if [[ $(type -t _setup) == function ]];then if [[ $(type -t _setup) == function ]];then
_setup _setup
@ -68,64 +58,34 @@ assert_success() {
if [ "$status" -ne 0 ]; then if [ "$status" -ne 0 ]; then
flunk "command failed with exit status $status" $'\n'\ flunk "command failed with exit status $status" $'\n'\
"output: $output" "output: $output"
elif [ "$#" -gt 0 ]; then
assert_output "$1"
fi fi
_assert_output_if_provided "$@"
} }
assert_failure() { assert_failure() {
if [ "$status" -eq 0 ]; then if [ "$status" -eq 0 ]; then
flunk "expected failed exit status" $'\n'\ flunk "expected failed exit status" $'\n'\
"output: $output" "output: $output"
elif [ "$#" -gt 0 ]; then
assert_output "$1"
fi fi
_assert_output_if_provided "$@"
} }
assert_equal() { assert_equal() {
_assert_equal_as text "$@" if [ "$1" != "$2" ]; then
} { echo "expected: \`$1'"
echo "actual: \`$2'"
_assert_equal_as() {
if case "$1" in
text)
[[ "$2" != "$3" ]];;
glob)
[[ "$3" != $2 ]];;
*)
echo "invalid comparison type: \`$1'" >&2; return 1;;
esac
then
{ diff -u --label expected --label actual <(echo "$2") <(echo "$3") | cat -te
} | flunk } | flunk
fi fi
} }
_assert_output_if_provided() {
if [ "$#" -gt 0 ]; then
assert_output "$1"
elif [ ! -t 0 ]; then
local expected=$(cat -)
if [[ -n $expected ]]; then
assert_output "$expected"
fi
fi
}
assert_output() { assert_output() {
_assert_output_as text "$@"
}
assert_output_glob() {
_assert_output_as glob "$@"
}
_assert_output_as() {
local kind="${1:?}"; shift
local expected local expected
if [ $# -eq 0 ] if [ $# -eq 0 ]; then expected="$(cat -)"
then expected="$(cat -)"
else expected="$1" else expected="$1"
fi fi
_assert_equal_as "$kind" "$expected" "$output" assert_equal "$expected" "$output"
} }
assert_line() { assert_line() {
@ -175,7 +135,7 @@ path_without() {
if [ "$found" != "${PYENV_ROOT}/shims" ]; then if [ "$found" != "${PYENV_ROOT}/shims" ]; then
alt="${PYENV_TEST_DIR}/$(echo "${found#/}" | tr '/' '-')" alt="${PYENV_TEST_DIR}/$(echo "${found#/}" | tr '/' '-')"
mkdir -p "$alt" mkdir -p "$alt"
for util in basename bash cut dirname find greadlink gzip head mkdir readlink sed sort tar tr xargs; do for util in bash head cut readlink greadlink tr sed xargs basename sort; do
if [ -x "${found}/$util" ]; then if [ -x "${found}/$util" ]; then
ln -s "${found}/$util" "${alt}/$util" ln -s "${found}/$util" "${alt}/$util"
fi fi
@ -233,8 +193,3 @@ create_hook() {
cat > "${PYENV_HOOK_PATH}/$1/$2" cat > "${PYENV_HOOK_PATH}/$1/$2"
fi fi
} }
skip_if_nonposix_security() {
true "${1:?}" "${2:?}"
test "$@" && skip "UNIX permission bits are being overridden" || true
}

View file

@ -90,13 +90,13 @@ IN
} }
@test "skips glob path traversal" { @test "skips glob path traversal" {
cat > my-version <<'IN' cat > my-version <<IN
../* ../*
3.9.3 3.9.3
IN IN
run pyenv-version-file-read my-version run pyenv-version-file-read my-version
assert_success <<'OUT' assert_success <<OUT
pyenv: invalid version `../*' ignored in `my-version' pyenv: invalid version \`../\*' ignored in \`my-version'
3.9.3 3.9.3
OUT OUT
} }

View file

@ -73,15 +73,3 @@ create_file() {
run pyenv-version-file "$PWD" run pyenv-version-file "$PWD"
assert_failure "" assert_failure ""
} }
@test "walks up beyond cwd for a relative path" {
create_file ".python-version"
mkdir -p project/subdir
cd project
# Run with CPU-time limit because this used to loop forever
run bash -c 'ulimit -t 5; pyenv-version-file .'
assert_success "${PYENV_TEST_DIR}/.python-version"
run pyenv-version-file ./subdir
assert_success "${PYENV_TEST_DIR}/.python-version"
}

View file

@ -83,7 +83,8 @@ OUT
create_version "3.4.0/envs/bar" create_version "3.4.0/envs/bar"
create_version "3.5.2" create_version "3.5.2"
run pyenv-versions run pyenv-versions
assert_success <<OUT assert_success
assert_output <<OUT
* system (set by ${PYENV_ROOT}/version) * system (set by ${PYENV_ROOT}/version)
2.7.6 2.7.6
3.4.0 3.4.0
@ -94,7 +95,6 @@ OUT
} }
@test "skips envs with --skip-envs" { @test "skips envs with --skip-envs" {
stub_system_python
create_version "3.3.3" create_version "3.3.3"
create_version "3.4.0" create_version "3.4.0"
create_version "3.4.0/envs/foo" create_version "3.4.0/envs/foo"
@ -188,7 +188,9 @@ OUT
create_external_version "moo" create_external_version "moo"
run pyenv-versions --bare --skip-aliases run pyenv-versions --bare --skip-aliases
assert_success <<OUT assert_success
assert_output <<OUT
1.8.7 1.8.7
moo moo
OUT OUT