mirror of
https://github.com/pyenv/pyenv.git
synced 2026-08-04 03:20:35 +09:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdf8d254fe | ||
|
|
f5915fc866 | ||
|
|
84e0f3a887 | ||
|
|
7126870320 | ||
|
|
26dd5d9dda | ||
|
|
bac39a0ef8 | ||
|
|
bec9a562a5 | ||
|
|
ae9e6594d6 | ||
|
|
16c7cc6de4 | ||
|
|
017ad4a573 | ||
|
|
6188e9efa9 | ||
|
|
286a167cf0 | ||
|
|
135adbb192 | ||
|
|
b3a3e702f1 | ||
|
|
958e3aca95 | ||
|
|
74c6efe2f4 | ||
|
|
95df7dbc7b | ||
|
|
e4c462dc70 | ||
|
|
790bedd821 | ||
|
|
19f4c7cf20 | ||
|
|
46cc3e552b | ||
|
|
8f68aab443 | ||
|
|
a27d2723d6 | ||
|
|
4c78a0dd50 | ||
|
|
91aacc5dbf | ||
|
|
3974647f0f | ||
|
|
ebd788d08c | ||
|
|
4b77d83d9d |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -4,6 +4,8 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
|
||||
364
.github/scripts/generate_release_notes_sponsors.py
vendored
Executable file
364
.github/scripts/generate_release_notes_sponsors.py
vendored
Executable file
@ -0,0 +1,364 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a "Sponsors since <date>" section for release notes.
|
||||
|
||||
Queries GitHub Sponsors and OpenCollective for new sponsors since the latest
|
||||
pyenv release or one month ago, whichever is longer. The output is Markdown
|
||||
suitable for GitHub Releases.
|
||||
|
||||
Requirements:
|
||||
* Python 3.8+
|
||||
* The ``gh`` CLI authenticated with the ``read:user`` scope.
|
||||
* Network access to https://opencollective.com.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import calendar
|
||||
import datetime
|
||||
import json
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import typing
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
GITHUB_ORG = "pyenv"
|
||||
OPENCOLLECTIVE_MEMBERS_URL = "https://opencollective.com/pyenv/members.json"
|
||||
|
||||
|
||||
class SponsorDataError(RuntimeError):
|
||||
"""Raised when a sponsors data source cannot be queried or parsed."""
|
||||
|
||||
|
||||
def parse_date(value: str) -> datetime.date:
|
||||
return datetime.datetime.fromisoformat(value).date()
|
||||
|
||||
|
||||
def one_month_ago(today: typing.Optional[datetime.date] = None) -> datetime.date:
|
||||
today = today or datetime.date.today()
|
||||
year = today.year
|
||||
month = today.month - 1
|
||||
if month == 0:
|
||||
year -= 1
|
||||
month = 12
|
||||
try:
|
||||
return datetime.date(year, month, today.day)
|
||||
except ValueError:
|
||||
last_day = calendar.monthrange(year, month)[1]
|
||||
return datetime.date(year, month, last_day)
|
||||
|
||||
|
||||
def latest_release_date() -> datetime.date:
|
||||
"""Return the publish date of the latest GitHub release."""
|
||||
latest_release_error = None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["gh", "api", f"repos/{GITHUB_ORG}/{GITHUB_ORG}/releases/latest"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
data = json.loads(result.stdout)
|
||||
published = data["published_at"].replace("Z", "+00:00")
|
||||
return datetime.datetime.fromisoformat(published).date()
|
||||
except (
|
||||
subprocess.CalledProcessError,
|
||||
OSError,
|
||||
json.JSONDecodeError,
|
||||
KeyError,
|
||||
) as exc:
|
||||
latest_release_error = exc
|
||||
|
||||
try:
|
||||
tag = subprocess.run(
|
||||
["git", "describe", "--tags", "--abbrev=0"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout.strip()
|
||||
date_str = subprocess.run(
|
||||
["git", "log", "-1", "--format=%cI", tag],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout.strip()
|
||||
return datetime.datetime.fromisoformat(date_str).date()
|
||||
except (subprocess.CalledProcessError, OSError) as exc:
|
||||
detail = ""
|
||||
if latest_release_error is not None:
|
||||
detail = f" GitHub release lookup failed first: {latest_release_error}."
|
||||
raise SponsorDataError(
|
||||
"Could not determine the latest release date. "
|
||||
"Pass --since explicitly or run from a clone with release tags."
|
||||
f"{detail}"
|
||||
) from exc
|
||||
|
||||
|
||||
def compute_since_date(explicit_since: typing.Optional[datetime.date]) -> datetime.date:
|
||||
if explicit_since is not None:
|
||||
return explicit_since
|
||||
return min(latest_release_date(), one_month_ago())
|
||||
|
||||
|
||||
def github_sponsors(since: datetime.date) -> typing.List[typing.Dict]:
|
||||
"""Return GitHub Sponsors created on or after *since*."""
|
||||
query = """
|
||||
query($org: String!, $after: String) {
|
||||
organization(login: $org) {
|
||||
sponsorshipsAsMaintainer(
|
||||
first: 100,
|
||||
after: $after,
|
||||
activeOnly: false,
|
||||
orderBy: {field: CREATED_AT, direction: DESC}
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
createdAt
|
||||
sponsorEntity {
|
||||
... on User { login, name }
|
||||
... on Organization { login, name }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
since_dt = datetime.datetime.combine(
|
||||
since, datetime.time.min, tzinfo=datetime.timezone.utc
|
||||
)
|
||||
sponsors = []
|
||||
cursor = None
|
||||
while True:
|
||||
command = [
|
||||
"gh",
|
||||
"api",
|
||||
"graphql",
|
||||
"-F",
|
||||
f"org={GITHUB_ORG}",
|
||||
"-f",
|
||||
f"query={query}",
|
||||
]
|
||||
if cursor is not None:
|
||||
command.extend(["-F", f"after={cursor}"])
|
||||
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
detail = result.stderr.strip() or result.stdout.strip() or "no output"
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query failed. "
|
||||
"Check that `gh auth status` shows access to the pyenv org "
|
||||
"and that the token has the scopes required to read sponsorships. "
|
||||
f"`gh api graphql` exited {result.returncode}: {detail}"
|
||||
)
|
||||
|
||||
try:
|
||||
data = json.loads(result.stdout)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned invalid JSON."
|
||||
) from exc
|
||||
|
||||
if "errors" in data:
|
||||
raise SponsorDataError(
|
||||
f"GitHub Sponsors query returned errors: {data['errors']}"
|
||||
)
|
||||
|
||||
try:
|
||||
sponsorships = data["data"]["organization"]["sponsorshipsAsMaintainer"]
|
||||
except (TypeError, KeyError) as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned an unexpected response shape."
|
||||
) from exc
|
||||
try:
|
||||
nodes = sponsorships["nodes"]
|
||||
page_info = sponsorships["pageInfo"]
|
||||
except (TypeError, KeyError) as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned incomplete pagination data."
|
||||
) from exc
|
||||
|
||||
for node in nodes:
|
||||
try:
|
||||
created = datetime.datetime.fromisoformat(
|
||||
node["createdAt"].replace("Z", "+00:00")
|
||||
)
|
||||
entity = node["sponsorEntity"]
|
||||
login = entity["login"]
|
||||
except (AttributeError, KeyError, TypeError, ValueError) as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned an unexpected sponsor record."
|
||||
) from exc
|
||||
if created < since_dt:
|
||||
return sponsors
|
||||
sponsors.append({
|
||||
"login": login,
|
||||
"name": entity.get("name") or login,
|
||||
})
|
||||
|
||||
try:
|
||||
has_next_page = page_info["hasNextPage"]
|
||||
cursor = page_info["endCursor"]
|
||||
except (TypeError, KeyError) as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned incomplete pagination data."
|
||||
) from exc
|
||||
if not has_next_page:
|
||||
return sponsors
|
||||
|
||||
|
||||
def opencollective_sponsors(since: datetime.date, data: typing.Union[str, None]) -> typing.List[typing.Dict]:
|
||||
"""Return OpenCollective backers active on or after *since*."""
|
||||
if data is None:
|
||||
req = urllib.request.Request(
|
||||
f"{OPENCOLLECTIVE_MEMBERS_URL}?limit=1000",
|
||||
headers={"User-Agent": f"{GITHUB_ORG}/release-notes-sponsors"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
data = resp.read()
|
||||
except urllib.error.HTTPError as exc:
|
||||
raise SponsorDataError(
|
||||
f"OpenCollective sponsors query failed for {OPENCOLLECTIVE_MEMBERS_URL}: "
|
||||
f"HTTP {exc.code} {exc.reason}"
|
||||
) from exc
|
||||
except urllib.error.URLError as exc:
|
||||
raise SponsorDataError(
|
||||
f"OpenCollective sponsors query failed for {OPENCOLLECTIVE_MEMBERS_URL}: "
|
||||
f"{exc.reason}"
|
||||
) from exc
|
||||
try:
|
||||
members = json.loads(data)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise SponsorDataError(
|
||||
"OpenCollective sponsors query returned invalid JSON."
|
||||
) from exc
|
||||
|
||||
since_dt = datetime.datetime.combine(since, datetime.time.min)
|
||||
sponsors = []
|
||||
for member in members:
|
||||
if member.get("role") != "BACKER":
|
||||
continue
|
||||
try:
|
||||
last_transaction_at = datetime.datetime.strptime(member["lastTransactionAt"], "%Y-%m-%d %H:%M")
|
||||
profile = member["profile"].rstrip("/")
|
||||
except (KeyError, TypeError, ValueError) as exc:
|
||||
raise SponsorDataError(
|
||||
"OpenCollective sponsors query returned an unexpected member record."
|
||||
) from exc
|
||||
if last_transaction_at < since_dt:
|
||||
continue
|
||||
slug = profile.split("/")[-1]
|
||||
if slug == "github-sponsors":
|
||||
# Listed separately under GitHub Sponsors.
|
||||
continue
|
||||
sponsors.append({
|
||||
"name": member.get("name") or slug,
|
||||
"profile": profile,
|
||||
})
|
||||
return sponsors
|
||||
|
||||
|
||||
def render(
|
||||
since: datetime.date,
|
||||
gh_sponsors: typing.List[typing.Dict],
|
||||
oc_sponsors: typing.List[typing.Dict],
|
||||
) -> str:
|
||||
lines = [f"## Sponsors since {since.isoformat()}", ""]
|
||||
|
||||
if gh_sponsors:
|
||||
lines.append("### GitHub Sponsors")
|
||||
for sponsor in gh_sponsors:
|
||||
lines.append(
|
||||
markdown_link(
|
||||
sponsor["name"],
|
||||
f"https://github.com/{sponsor['login']}",
|
||||
)
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
if oc_sponsors:
|
||||
lines.append("### Open Collective")
|
||||
for sponsor in oc_sponsors:
|
||||
lines.append(markdown_link(sponsor["name"], sponsor["profile"]))
|
||||
lines.append("")
|
||||
|
||||
if not gh_sponsors and not oc_sponsors:
|
||||
lines.append("*No new sponsors in this period.*")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def markdown_link(text: str, url: str) -> str:
|
||||
escaped_text = escape_markdown_text(text)
|
||||
escaped_url = url.replace(")", "%29")
|
||||
return f"- [{escaped_text}]({escaped_url})"
|
||||
|
||||
|
||||
def escape_markdown_text(text: str) -> str:
|
||||
escaped = text.replace("\\", "\\\\")
|
||||
for char in r"`*_{}[]()#+-.!|>":
|
||||
escaped = escaped.replace(char, f"\\{char}")
|
||||
return escaped
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a sponsors section for GitHub release notes."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--since",
|
||||
type=parse_date,
|
||||
metavar="YYYY-MM-DD",
|
||||
help="Include sponsors created on or after this date.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
metavar="FILE",
|
||||
help="Write the section to FILE instead of stdout.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-opencollective",
|
||||
action="store_true",
|
||||
help="Skip OpenCollective backers if the members endpoint is unavailable.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--opencollective-data",
|
||||
metavar="FILE",
|
||||
help="Take OpenCollective API reply from FILE.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
since = compute_since_date(args.since)
|
||||
oc_sponsors = []
|
||||
if not args.skip_opencollective:
|
||||
manual_data = (
|
||||
pathlib.Path(args.opencollective_data).read_text()) \
|
||||
if args.opencollective_data \
|
||||
else None
|
||||
oc_sponsors = opencollective_sponsors(since, manual_data)
|
||||
section = render(since, github_sponsors(since), oc_sponsors)
|
||||
except SponsorDataError as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if args.output:
|
||||
with open(args.output, "w", encoding="utf-8") as f:
|
||||
f.write(section)
|
||||
else:
|
||||
print(section, end="")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
4
.github/workflows/add_version.yml
vendored
4
.github/workflows/add_version.yml
vendored
@ -14,8 +14,8 @@ jobs:
|
||||
add_cpython:
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3
|
||||
cache: 'pip'
|
||||
|
||||
2
.github/workflows/macos_build.yml
vendored
2
.github/workflows/macos_build.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
- "3.14"
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
brew install openssl readline sqlite3 xz tcl-tk@8 libb2 zstd
|
||||
- run: |
|
||||
|
||||
12
.github/workflows/modified_scripts_build.yml
vendored
12
.github/workflows/modified_scripts_build.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
versions_cpython_only: ${{steps.modified-versions.outputs.versions_cpython_only}}
|
||||
versions_macos_build_exclude: ${{steps.modified-versions.outputs.versions_macos_build_exclude}}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- run: git fetch origin "$GITHUB_BASE_REF"
|
||||
- shell: bash
|
||||
run: >
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
if name == 'anaconda3' and version >= packaging.version.Version('2025.12'):
|
||||
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))
|
||||
|
||||
# GraalPy dropped MacOS x64 support
|
||||
@ -94,7 +94,7 @@ jobs:
|
||||
exclude: ${{fromJson(needs.discover_modified_scripts.outputs.versions_macos_build_exclude)}}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
#envvars
|
||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||
@ -156,7 +156,7 @@ jobs:
|
||||
os: ["macos-14", "macos-15", "macos-15-intel"]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
#envvars
|
||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||
@ -205,7 +205,7 @@ jobs:
|
||||
- ubuntu-24.04
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
#envvars
|
||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||
@ -268,7 +268,7 @@ jobs:
|
||||
os: ["ubuntu-latest"]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
#envvars
|
||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||
|
||||
2
.github/workflows/pyenv_tests.yml
vendored
2
.github/workflows/pyenv_tests.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
- macos-26
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
if test "$RUNNER_OS" == "macOS"; then
|
||||
brew install coreutils fish
|
||||
|
||||
2
.github/workflows/ubuntu_build.yml
vendored
2
.github/workflows/ubuntu_build.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
- "3.14"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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 \
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@
|
||||
/default-packages
|
||||
.idea
|
||||
*.un~
|
||||
*.swp
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@ -1,5 +1,23 @@
|
||||
# Version History
|
||||
|
||||
## Release v2.8.1
|
||||
* pyenv-binary: add the `generate-installer` subcommand by @macayu17 in https://github.com/pyenv/pyenv/pull/3488
|
||||
* Add CPython 3.15.0b4 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3495
|
||||
|
||||
## Release v2.8.0
|
||||
* CI: add_version enhancements by @native-api in https://github.com/pyenv/pyenv/pull/3480
|
||||
* Add miniforge3-26.3.2-2, 26.3.2-3 by @native-api in https://github.com/pyenv/pyenv/pull/3481
|
||||
* rehash: fix race condition in landlock writability check by @Sheile in https://github.com/pyenv/pyenv/pull/3483
|
||||
* Add script to generate sponsors section for release notes by @macayu17 in https://github.com/pyenv/pyenv/pull/3478
|
||||
* install: Add an ability to install a version under an alias by @macayu17 in https://github.com/pyenv/pyenv/pull/3484
|
||||
* Add graalpy-3.12-25.1.3 by @msimacek in https://github.com/pyenv/pyenv/pull/3485
|
||||
* Bump actions/checkout from 6 to 7 in the github-actions group by @dependabot[bot] in https://github.com/pyenv/pyenv/pull/3486
|
||||
* Add an experimental pyenv-binary plugin with a save command by @macayu17 in https://github.com/pyenv/pyenv/pull/3487
|
||||
* CI: dependabot: set cooldown by @orbisai0security in https://github.com/pyenv/pyenv/pull/3489
|
||||
* Fix typos in docs, scripts, and tests by @maxtaran2010 in https://github.com/pyenv/pyenv/pull/3490
|
||||
* Add miniconda3 26.5.3-1 by @native-api in https://github.com/pyenv/pyenv/pull/3491
|
||||
* version-name: skip redundant checks by @native-api in https://github.com/pyenv/pyenv/pull/3492
|
||||
|
||||
## Release v2.7.3
|
||||
* CI: add_version enhancements by @macayu17 in https://github.com/pyenv/pyenv/pull/3475
|
||||
* Add CPython 3.15.0b3 by @pyenv-bot[bot] in https://github.com/pyenv/pyenv/pull/3479
|
||||
@ -369,7 +387,7 @@
|
||||
* Add CPython 3.13.0a4 by @saaketp in https://github.com/pyenv/pyenv/pull/2903
|
||||
* Handle the case where `pyenv-commands --sh` returns nothing by @aphedges in https://github.com/pyenv/pyenv/pull/2908
|
||||
* Document default build configuration customizations by @native-api in https://github.com/pyenv/pyenv/pull/2911
|
||||
* Use Homebrew in Linux if Pyenv is installled with Homebrew by @native-api in https://github.com/pyenv/pyenv/pull/2906
|
||||
* Use Homebrew in Linux if Pyenv is installed with Homebrew by @native-api in https://github.com/pyenv/pyenv/pull/2906
|
||||
* Add miniforge and mambaforge 22.11.1-3, 22.11.1-4, 23.1.0-0 to 23.11.0-0 by @aphedges in https://github.com/pyenv/pyenv/pull/2909
|
||||
* Add miniconda3-24.1.2 by @binbjz in https://github.com/pyenv/pyenv/pull/2915
|
||||
* Minor grammar fix in libffi backport patch in 2.5.x by @cuinix in https://github.com/pyenv/pyenv/pull/2922
|
||||
@ -1505,7 +1523,7 @@
|
||||
* pyenv: Prefer gawk over awk if both are available.
|
||||
* python-build: Add new PyPy release; pypy-2.3, pypy-2.3-src (#162)
|
||||
* python-build: Add new Anaconda release; anaconda-1.9.2 (#155)
|
||||
* python-build: Add new Miniconda releases; miniconda-3.3.0, minoconda-3.4.2, miniconda3-3.3.0, miniconda3-3.4.2
|
||||
* python-build: Add new Miniconda releases; miniconda-3.3.0, miniconda-3.4.2, miniconda3-3.3.0, miniconda3-3.4.2
|
||||
* python-build: Add new Stackless releases; stackless-2.7.3, stackless-2.7.4, stackless-2.7.5, stackless-2.7.6, stackless-3.2.5, stackless-3.3.5 (#164)
|
||||
* python-build: Add IronPython versions (setuptools and pip will work); ironpython-2.7.4, ironpython-dev
|
||||
* python-build: Add new Jython beta release; jython-2.7-beta2
|
||||
|
||||
@ -202,8 +202,8 @@ or, if you prefer 3.3.3 over 2.7.6,
|
||||
|
||||
Install a Python version (using [`python-build`](https://github.com/pyenv/pyenv/tree/master/plugins/python-build)).
|
||||
|
||||
Usage: pyenv install [-f] [-kvp] <version>
|
||||
pyenv install [-f] [-kvp] <definition-file>
|
||||
Usage: pyenv install [-f] [-kvp] <version>[:<alias>]
|
||||
pyenv install [-f] [-kvp] <definition-file>[:<alias>]
|
||||
pyenv install -l|--list
|
||||
|
||||
-l/--list List all available versions
|
||||
|
||||
@ -7,6 +7,14 @@ Release checklist:
|
||||
* Start [drafting a new release on GitHub](https://github.com/pyenv/pyenv/releases) to generate a summary of changes.
|
||||
Type the would-be tag name in the "Choose a tag" field and press "Generate release notes"
|
||||
* The summary may need editing. E.g. rephrase entries, delete/merge entries that are too minor or irrelevant to the users (e.g. typo fixes, CI)
|
||||
* Add a sponsors section to the release notes by running:
|
||||
```bash
|
||||
.github/scripts/generate_release_notes_sponsors.py
|
||||
```
|
||||
Paste the output at the end of the release notes.
|
||||
* This lists new GitHub Sponsors and OpenCollective backers since the last release or within the last month, whichever is longer.
|
||||
* The GitHub Sponsors query requires the `gh` CLI with the `read:user` scope.
|
||||
* If OpenCollective is unavailable, pass `--skip-opencollective` and add those backers manually.
|
||||
* Update `CHANGELOG.md` with the new version number and the edited summary (only the changes section)
|
||||
* Push the version number in `libexec/pyenv---version` and `plugins/python-build/bin/python-build`
|
||||
* Minor version is pushed if there are significant functional changes (not e.g. bugfixes/formula adaptations/supporting niche use cases).
|
||||
@ -14,4 +22,4 @@ Type the would-be tag name in the "Choose a tag" field and press "Generate relea
|
||||
* Commit the changes locally into `master`
|
||||
* Create a new tag locally with the same name as specified in the new release window
|
||||
* Push the changes including the tag
|
||||
* In the still open new release window, press "Publish release". The now-existing tag will be used.
|
||||
* In the still open new release window, press "Publish release". The now-existing tag will be used.
|
||||
|
||||
95
Makefile
95
Makefile
@ -1,16 +1,21 @@
|
||||
TEST_BATS_VERSION = v1.10.0
|
||||
TEST_BASH_VERSIONS = 3.2.57 4.1.17
|
||||
|
||||
TEST_UNIT_DOCKER_PREFIX = test-unit-docker
|
||||
TEST_UNIT_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_UNIT_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_UNIT_DOCKER_PREFIX)))
|
||||
TEST_PLUGIN_DOCKER_PREFIX = test-plugin-docker
|
||||
TEST_PLUGIN_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_PLUGIN_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_PLUGIN_DOCKER_PREFIX)))
|
||||
|
||||
TEST_PYTHON_BUILD_DOCKER_PREFIX = test-python-build-docker
|
||||
TEST_PYTHON_BUILD_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_PYTHON_BUILD_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_PYTHON_BUILD_DOCKER_PREFIX)))
|
||||
|
||||
TEST_BINARY_DOCKER_PREFIX = test-binary-docker
|
||||
TEST_BINARY_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BINARY_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BINARY_DOCKER_PREFIX)))
|
||||
|
||||
TEST_BATS_IMAGE_PREFIX = test-pyenv-docker-image
|
||||
TEST_BATS_IMAGE_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BATS_IMAGE_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BATS_IMAGE_PREFIX)))
|
||||
|
||||
.PHONY:
|
||||
test-docker: $(TEST_UNIT_DOCKER_PREFIX) $(TEST_PLUGIN_DOCKER_PREFIX)
|
||||
.PHONY: test-docker
|
||||
test-docker: $(TEST_UNIT_DOCKER_PREFIX) $(TEST_PYTHON_BUILD_DOCKER_PREFIX) $(TEST_BINARY_DOCKER_PREFIX)
|
||||
|
||||
# Run all unit test under bats docker
|
||||
.PHONY: $(TEST_UNIT_DOCKER_PREFIX)
|
||||
$(TEST_UNIT_DOCKER_PREFIX): $(TEST_UNIT_DOCKER_TARGETS)
|
||||
|
||||
@ -31,23 +36,22 @@ $(TEST_UNIT_DOCKER_TARGETS): $(TEST_UNIT_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PR
|
||||
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
|
||||
$${BATS_TEST_FILTER:+-e BATS_TEST_FILTER="$${BATS_TEST_FILTER}"} \
|
||||
$${BATS_FILE_FILTER:+-e BATS_FILE_FILTER="$${BATS_FILE_FILTER}"} \
|
||||
$${CI+-e CI="$${CI}"} \
|
||||
$(INTERACTIVE) \
|
||||
$${CI+-e CI="$${CI}"} \
|
||||
$(INTERACTIVE) \
|
||||
$(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
test/run
|
||||
|
||||
# Run all plugin test under bats docker
|
||||
.PHONY: $(TEST_PLUGIN_DOCKER_PREFIX)
|
||||
$(TEST_PLUGIN_DOCKER_PREFIX): $(TEST_PLUGIN_DOCKER_TARGETS)
|
||||
.PHONY: $(TEST_PYTHON_BUILD_DOCKER_PREFIX)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_PREFIX): $(TEST_PYTHON_BUILD_DOCKER_TARGETS)
|
||||
|
||||
# Run each plugin test under bats docker
|
||||
.PHONY: $(TEST_PLUGIN_DOCKER_TARGETS)
|
||||
$(TEST_PLUGIN_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_PLUGIN_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||
$(TEST_PLUGIN_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||
$(TEST_PLUGIN_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||
$(TEST_PLUGIN_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
|
||||
$(TEST_PLUGIN_DOCKER_TARGETS): $(TEST_PLUGIN_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
|
||||
.PHONY: $(TEST_PYTHON_BUILD_DOCKER_TARGETS)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): $(TEST_PYTHON_BUILD_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
|
||||
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
|
||||
docker run \
|
||||
--init \
|
||||
@ -55,11 +59,33 @@ $(TEST_PLUGIN_DOCKER_TARGETS): $(TEST_PLUGIN_DOCKER_PREFIX)-% : $(TEST_BATS_IMAG
|
||||
-v /etc/passwd:/etc/passwd:ro \
|
||||
-v /etc/group:/etc/group:ro \
|
||||
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
|
||||
$${CI+-e CI="$${CI}"} \
|
||||
$(INTERACTIVE) \
|
||||
$${CI+-e CI="$${CI}"} \
|
||||
$(INTERACTIVE) \
|
||||
$(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
bats $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/python-build/test/$${BATS_FILE_FILTER}
|
||||
|
||||
.PHONY: $(TEST_BINARY_DOCKER_PREFIX)
|
||||
$(TEST_BINARY_DOCKER_PREFIX): $(TEST_BINARY_DOCKER_TARGETS)
|
||||
|
||||
.PHONY: $(TEST_BINARY_DOCKER_TARGETS)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||
$(TEST_BINARY_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): $(TEST_BINARY_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
|
||||
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
|
||||
docker run \
|
||||
--init \
|
||||
-v $(PWD):/code:ro \
|
||||
-v /etc/passwd:/etc/passwd:ro \
|
||||
-v /etc/group:/etc/group:ro \
|
||||
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
|
||||
$${CI+-e CI="$${CI}"} \
|
||||
$(INTERACTIVE) \
|
||||
$(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
bats $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/pyenv-binary/test/$${BATS_FILE_FILTER}
|
||||
|
||||
# Build all images needed for bats under docker
|
||||
.PHONY: $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_BATS_IMAGE_PREFIX): $(TEST_BATS_IMAGE_TARGETS)
|
||||
@ -71,7 +97,7 @@ $(TEST_BATS_IMAGE_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||
$(TEST_BATS_IMAGE_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||
$(TEST_BATS_IMAGE_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||
$(TEST_BATS_IMAGE_TARGETS):
|
||||
$(info Building docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
|
||||
if [ -z "$$(docker images -q '$(DOCKER_IMAGE):$(DOCKER_TAG)')" ]]; then \
|
||||
docker build \
|
||||
--quiet \
|
||||
-f "$(PWD)/test/Dockerfile" \
|
||||
@ -79,41 +105,32 @@ $(TEST_BATS_IMAGE_TARGETS):
|
||||
--build-arg BASH="$(BASH)" \
|
||||
--build-arg BATS_VERSION="$(TEST_BATS_VERSION)" \
|
||||
-t $(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
./
|
||||
./ ; \
|
||||
fi
|
||||
|
||||
.PHONY: test test-build test-unit test-plugin
|
||||
.PHONY: test test-unit test-python-build test-binary
|
||||
|
||||
# Do not pass in user flags to build tests.
|
||||
unexport PYTHON_CFLAGS
|
||||
unexport PYTHON_CONFIGURE_OPTS
|
||||
|
||||
test: test-unit test-plugin
|
||||
test: test-unit test-python-build test-binary
|
||||
|
||||
test-unit: bats
|
||||
PATH="./bats/bin:$$PATH" test/run
|
||||
|
||||
test-plugin: bats
|
||||
test-python-build: bats
|
||||
cd plugins/python-build && $(PWD)/bats/bin/bats $${CI:+--tap} $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
|
||||
|
||||
PYTHON_BUILD_ROOT := $(CURDIR)/plugins/python-build
|
||||
PYTHON_BUILD_OPTS ?= --verbose
|
||||
PYTHON_BUILD_VERSION ?= 3.8-dev
|
||||
PYTHON_BUILD_TEST_PREFIX ?= $(PYTHON_BUILD_ROOT)/test/build/tmp/dist
|
||||
|
||||
test-build:
|
||||
$(RM) -r $(PYTHON_BUILD_TEST_PREFIX)
|
||||
$(PYTHON_BUILD_ROOT)/bin/python-build $(PYTHON_BUILD_OPTS) $(PYTHON_BUILD_VERSION) $(PYTHON_BUILD_TEST_PREFIX)
|
||||
[ -e $(PYTHON_BUILD_TEST_PREFIX)/bin/python ]
|
||||
$(PYTHON_BUILD_TEST_PREFIX)/bin/python -V
|
||||
[ -e $(PYTHON_BUILD_TEST_PREFIX)/bin/pip ]
|
||||
$(PYTHON_BUILD_TEST_PREFIX)/bin/pip -V
|
||||
test-binary: bats
|
||||
cd plugins/pyenv-binary && $(PWD)/bats/bin/bats $${CI:+--tap} $${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
|
||||
|
||||
.SECONDARY: bats-$(TEST_BATS_VERSION)
|
||||
bats-$(TEST_BATS_VERSION):
|
||||
rm -rf bats
|
||||
ln -sf bats-$(TEST_BATS_VERSION) bats
|
||||
git clone --depth 1 --branch $(TEST_BATS_VERSION) https://github.com/bats-core/bats-core.git bats-$(TEST_BATS_VERSION)
|
||||
|
||||
.PHONY: bats
|
||||
bats: bats-$(TEST_BATS_VERSION)
|
||||
ln -sf bats-$(TEST_BATS_VERSION) bats
|
||||
if [ \( ! -L bats \) -o \( "x$$(readlink bats)" != "xbats-$(TEST_BATS_VERSION)" \) ]; then \
|
||||
rm -rf bats; ln -s bats-$(TEST_BATS_VERSION) bats; \
|
||||
fi
|
||||
|
||||
11
README.md
11
README.md
@ -672,7 +672,7 @@ to `PATH` in the `<command>`'s environment, the same as what e.g. RVM does.
|
||||
### Running nested shells from Python-based programs
|
||||
|
||||
In addition to altering `PATH`, `pyenv exec` sets `PYENV_VERSION` in the
|
||||
executed program's environment to ensure that it won't spontaneouly switch to
|
||||
executed program's environment to ensure that it won't spontaneously switch to
|
||||
using a different Python version.
|
||||
|
||||
Some Python-based programs (e.g. Jupyter) can spawn nested shell sessions.
|
||||
@ -837,13 +837,10 @@ e.g. `~/.pyenv --install bash`.
|
||||
## Development
|
||||
|
||||
The pyenv source code is [hosted on
|
||||
GitHub](https://github.com/pyenv/pyenv). It's clean, modular,
|
||||
and easy to understand, even if you're not a shell hacker.
|
||||
GitHub](https://github.com/pyenv/pyenv).
|
||||
|
||||
Tests are executed using [Bats](https://github.com/bats-core/bats-core):
|
||||
|
||||
bats test
|
||||
bats/test/<file>.bats
|
||||
Tests are executed using [Bats](https://github.com/bats-core/bats-core).
|
||||
See the [tests README](test/README.md) for details.
|
||||
|
||||
|
||||
### Contributing
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
version="2.7.3"
|
||||
version="2.8.1"
|
||||
git_revision=""
|
||||
|
||||
if cd "${BASH_SOURCE%/*}" 2>/dev/null && git remote -v 2>/dev/null | grep -q pyenv; then
|
||||
|
||||
@ -36,12 +36,16 @@ if [ -n "$versions" ]; then
|
||||
pyenv-version-file-write "$PYENV_VERSION_FILE" "${versions[@]}"
|
||||
else
|
||||
OLDIFS="$IFS"
|
||||
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
|
||||
# which is undesired
|
||||
set -f
|
||||
IFS=: versions=($(
|
||||
pyenv-version-file-read "$PYENV_VERSION_FILE" ||
|
||||
pyenv-version-file-read "${PYENV_ROOT}/global" ||
|
||||
pyenv-version-file-read "${PYENV_ROOT}/default" ||
|
||||
echo system
|
||||
))
|
||||
set +f
|
||||
IFS="$OLDIFS"
|
||||
for version in "${versions[@]}"; do
|
||||
echo "$version"
|
||||
|
||||
@ -59,7 +59,11 @@ elif [ -n "$versions" ]; then
|
||||
pyenv-version-file-write ${FORCE:+-f }.python-version "${versions[@]}"
|
||||
else
|
||||
if version_file="$(pyenv-version-file "$PWD")"; then
|
||||
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
|
||||
# which is undesired
|
||||
set -f
|
||||
IFS=: versions=($(pyenv-version-file-read "$version_file"))
|
||||
set +f
|
||||
for version in "${versions[@]}"; do
|
||||
echo "$version"
|
||||
done
|
||||
|
||||
@ -28,6 +28,9 @@ fi
|
||||
PYENV_PREFIX_PATHS=()
|
||||
OLDIFS="$IFS"
|
||||
{ IFS=:
|
||||
# Unquoted $VAR does glob expansion (against the current dir's contents) after IFS-splitting
|
||||
# which is undesired
|
||||
set -f
|
||||
for version in ${PYENV_VERSION}; do
|
||||
if [ "$version" = "system" ]; then
|
||||
if PYTHON_PATH="$(PYENV_VERSION="${version}" pyenv-which python --skip-advice 2>/dev/null)" || \
|
||||
@ -52,6 +55,7 @@ OLDIFS="$IFS"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
set +f
|
||||
}
|
||||
IFS="$OLDIFS"
|
||||
|
||||
|
||||
@ -28,7 +28,9 @@ acquire_lock() {
|
||||
# So check for writablity by trying to write to a different file,
|
||||
# in a way that taxes the usual use case as little as possible.
|
||||
if [[ -z $tested_for_other_write_errors ]]; then
|
||||
( t="$(TMPDIR="$SHIM_PATH" mktemp)" && rm "$t" ) \
|
||||
# if lots of (50+) rehashes run concurrently, another concurrent rehash
|
||||
# may delete the temporary file in remove_*_shims() before we do so
|
||||
( t="$(TMPDIR="$SHIM_PATH" mktemp)" && rm -f "$t" ) \
|
||||
&& tested_for_other_write_errors=1 \
|
||||
|| { echo "pyenv: cannot rehash: $SHIM_PATH isn't writable" >&2
|
||||
set +o noclobber
|
||||
|
||||
@ -9,7 +9,11 @@ set -e
|
||||
|
||||
exitcode=0
|
||||
OLDIFS="$IFS"
|
||||
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
|
||||
# which is undesired
|
||||
set -f
|
||||
IFS=: PYENV_VERSION_NAMES=($(pyenv-version-name)) || exitcode=$?
|
||||
set +f
|
||||
IFS="$OLDIFS"
|
||||
|
||||
unset bare
|
||||
|
||||
@ -46,17 +46,22 @@ versions=()
|
||||
OLDIFS="$IFS"
|
||||
{ IFS=:
|
||||
any_not_installed=0
|
||||
normalization_done=
|
||||
# Unquoted $VAR does glob expansion (against the current dir's contents) after IFS-splitting
|
||||
# which is undesired
|
||||
set -f
|
||||
for version in ${PYENV_VERSION}; do
|
||||
# Remove the explicit 'python-' prefix from versions like 'python-3.12'.
|
||||
normalised_version="${version#python-}"
|
||||
if version_exists "${version}" || [ "$version" = "system" ]; then
|
||||
versions+=("${version}")
|
||||
elif version_exists "${normalised_version}"; then
|
||||
[[ $version != "${normalised_version}" ]] && normalization_done=1
|
||||
if [[ $version == "system" ]] || version_exists "${normalised_version}" ; then
|
||||
versions+=("${normalised_version}")
|
||||
elif resolved_version="$(pyenv-latest -b "${version}")"; then
|
||||
versions+=("${resolved_version}")
|
||||
elif [[ -n $normalization_done ]] && version_exists "${version}"; then
|
||||
versions+=("${version}")
|
||||
elif resolved_version="$(pyenv-latest -b "${normalised_version}")"; then
|
||||
versions+=("${resolved_version}")
|
||||
elif [[ -n $normalization_done ]] && resolved_version="$(pyenv-latest -b "${version}")"; then
|
||||
versions+=("${resolved_version}")
|
||||
else
|
||||
if [[ -n $FORCE ]]; then
|
||||
versions+=("${normalised_version}")
|
||||
@ -66,6 +71,7 @@ OLDIFS="$IFS"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
set +f
|
||||
}
|
||||
IFS="$OLDIFS"
|
||||
|
||||
|
||||
@ -99,6 +99,9 @@ else
|
||||
miss_prefix=" "
|
||||
OLDIFS="$IFS"
|
||||
IFS=:
|
||||
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
|
||||
# which is undesired
|
||||
set -f
|
||||
if ((${BASH_VERSINFO[0]} > 3)); then
|
||||
for i in $(pyenv-version-name || true); do
|
||||
current_versions["$i"]="1"
|
||||
@ -106,6 +109,7 @@ else
|
||||
else
|
||||
current_versions=($(pyenv-version-name || true))
|
||||
fi
|
||||
set +f
|
||||
IFS="$OLDIFS"
|
||||
include_system="1"
|
||||
fi
|
||||
@ -161,11 +165,15 @@ versions_dir_entries=("$versions_dir"/*)
|
||||
if sort --version-sort </dev/null >/dev/null 2>&1; then
|
||||
# system sort supports version sorting
|
||||
OLDIFS="$IFS"
|
||||
# VAR=() of unquoted value does glob expansion (against the current dir's contents) after IFS-splitting
|
||||
# which is undesired
|
||||
set -f
|
||||
IFS=$'\n'
|
||||
versions_dir_entries=($(
|
||||
printf "%s\n" "${versions_dir_entries[@]}" |
|
||||
sort --version-sort
|
||||
))
|
||||
set +f
|
||||
IFS="$OLDIFS"
|
||||
fi
|
||||
|
||||
|
||||
@ -63,7 +63,11 @@ if [ -z "$PYENV_COMMAND" ]; then
|
||||
fi
|
||||
|
||||
OLDIFS="$IFS"
|
||||
# Unquoted $VAR or VAR=() of unquoted value does glob expansion (against the current dir's contents)
|
||||
# after IFS-splitting which is undesired
|
||||
set -f
|
||||
IFS=: versions=(${PYENV_VERSION:-$(pyenv-version-name -f)})
|
||||
set +f
|
||||
IFS="$OLDIFS"
|
||||
|
||||
declare -a nonexistent_versions
|
||||
|
||||
1
plugins/.gitignore
vendored
1
plugins/.gitignore
vendored
@ -2,4 +2,5 @@
|
||||
!/.gitignore
|
||||
!/version-ext-compat
|
||||
!/python-build
|
||||
!/pyenv-binary
|
||||
/python-build/test/build
|
||||
|
||||
75
plugins/pyenv-binary/README.md
Normal file
75
plugins/pyenv-binary/README.md
Normal file
@ -0,0 +1,75 @@
|
||||
# pyenv-binary (experimental)
|
||||
|
||||
Package an installed Python version into a relocatable archive that can be
|
||||
installed on another machine.
|
||||
|
||||
This is experimental and intentionally decoupled: it does not change
|
||||
`pyenv install` or any other command. You drive it explicitly through
|
||||
`pyenv binary`. Run `pyenv binary <command> --help` for details on a command.
|
||||
|
||||
## Portability
|
||||
|
||||
An archive is portable across machines that share its build platform (OS,
|
||||
architecture and a compatible libc) and have the recorded system libraries. It
|
||||
is not portable across, say, glibc and musl, or to an older glibc; the platform
|
||||
and dependency metadata exist to catch that.
|
||||
|
||||
## Commands
|
||||
|
||||
### `pyenv binary package <version>:<entry> --archive-base-url <url>`
|
||||
|
||||
Installs `<version>` from source under the separate name `<entry>`, packages
|
||||
that install with `save`, then emits a python-build definition for it with
|
||||
`generate-installer`. The archive, metadata and definition land in the current
|
||||
directory, named after the entry; host the archive under `<url>` and drop the
|
||||
definition into python-build's definition directory. Keeping the entry name
|
||||
distinct from the version lets the binary sit alongside a normal source
|
||||
install of the same version.
|
||||
|
||||
```sh
|
||||
pyenv binary package 3.12.7:3.12.7-debian-12 \
|
||||
--archive-base-url https://example.com/binaries
|
||||
# writes 3.12.7-debian-12.tar.gz, its .meta file and
|
||||
# a `3.12.7-debian-12' definition
|
||||
```
|
||||
|
||||
### `pyenv binary save <version> [<output-dir>] [--name <name>]`
|
||||
|
||||
Packs an installed version into `<version>-<platform>.tar.gz` (relative paths)
|
||||
and writes `<version>-<platform>.meta` describing the build platform (OS, arch,
|
||||
distro and libc version) and the system libraries the build links against. Use
|
||||
`--name` to set a different base name for both files.
|
||||
|
||||
```sh
|
||||
pyenv binary save 3.12.7 ./dist
|
||||
```
|
||||
|
||||
### `pyenv binary generate-installer <metadata-file> --archive-url <url> [-o <output>]`
|
||||
|
||||
Reads a `.meta` file and emits a python-build definition. Drop it into
|
||||
python-build's definition directory and `pyenv install <name>` installs the
|
||||
archive like any other version. The archive location is a parameter, so you can
|
||||
host it anywhere (it does not have to be a pyenv location); the archive itself
|
||||
must sit next to the `.meta` file so its checksum can be baked into the
|
||||
definition.
|
||||
|
||||
The definition refuses to install on a different OS/architecture, or an older
|
||||
glibc, than the archive was built for, and checks that the system libraries it
|
||||
needs are present.
|
||||
|
||||
```sh
|
||||
pyenv binary generate-installer ./dist/3.12.7-linux-x86_64.meta \
|
||||
--archive-url https://example.com/3.12.7-linux-x86_64.tar.gz \
|
||||
-o "$(pyenv root)/plugins/python-build/share/python-build/3.12.7-linux-x86_64"
|
||||
|
||||
pyenv install 3.12.7-linux-x86_64
|
||||
```
|
||||
|
||||
### `pyenv binary relocate <prefix>`
|
||||
|
||||
Rewrites the rpaths of a Python tree unpacked into `<prefix>` so the interpreter
|
||||
and its extension modules load the bundled libraries from there rather than from
|
||||
the path the archive was built at. Uses `patchelf`. The generated definition
|
||||
calls this; you rarely run it by hand.
|
||||
|
||||
Relocation is implemented for Linux; macOS is not wired up yet.
|
||||
65
plugins/pyenv-binary/bin/pyenv-binary
Executable file
65
plugins/pyenv-binary/bin/pyenv-binary
Executable file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Package an installed Python version as a relocatable binary (experimental)
|
||||
#
|
||||
# Usage: pyenv binary <command> [<args>]
|
||||
#
|
||||
# `pyenv binary` packages an already-installed Python version into a relocatable
|
||||
# archive that can be installed on another machine. It is experimental and does
|
||||
# not touch `pyenv install' or any other command.
|
||||
#
|
||||
# Run `pyenv binary' to list its commands, or `pyenv binary <command> --help'
|
||||
# for command-specific help.
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
libexec="${BASH_SOURCE%/*}/../libexec"
|
||||
|
||||
# The pyenv launcher only puts a plugin's bin on PATH, but `pyenv-help' looks a
|
||||
# command up there. Add our libexec so the subcommands, and the help text they
|
||||
# print, can be found.
|
||||
export PATH="${libexec}:${PATH}"
|
||||
|
||||
list_commands() {
|
||||
local path
|
||||
for path in "$libexec"/pyenv-binary-*; do
|
||||
[ -e "$path" ] && echo "${path##*/pyenv-binary-}"
|
||||
done
|
||||
}
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
shift
|
||||
if [ -z "$1" ]; then
|
||||
list_commands
|
||||
else
|
||||
command_path="${libexec}/pyenv-binary-$1"
|
||||
shift
|
||||
[ -x "$command_path" ] && exec "$command_path" --complete "$@"
|
||||
fi
|
||||
exit
|
||||
fi
|
||||
|
||||
subcommand="$1"
|
||||
if [ -z "$subcommand" ]; then
|
||||
{ pyenv-help binary
|
||||
echo
|
||||
echo "Commands:"
|
||||
list_commands | sed 's/^/ /'
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
|
||||
command_path="${libexec}/pyenv-binary-${subcommand}"
|
||||
if [ ! -x "$command_path" ]; then
|
||||
echo "pyenv-binary: no such command \`${subcommand}'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||
exec pyenv-help "binary-${subcommand}"
|
||||
fi
|
||||
|
||||
exec "$command_path" "$@"
|
||||
189
plugins/pyenv-binary/libexec/pyenv-binary-generate-installer
Executable file
189
plugins/pyenv-binary/libexec/pyenv-binary-generate-installer
Executable file
@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Generate a python-build definition for a saved binary archive
|
||||
#
|
||||
# Usage: pyenv binary generate-installer <metadata-file> --archive-url <url> [-o <output>]
|
||||
#
|
||||
# Reads a metadata file written by `pyenv binary save' and emits a python-build
|
||||
# definition. Dropped into python-build's definition directory, it installs the
|
||||
# archive like any other version: `pyenv install' downloads it from <url>,
|
||||
# checks the platform and the required system libraries, unpacks it, then
|
||||
# rewrites rpaths so the copy runs from its prefix.
|
||||
#
|
||||
# The archive must sit next to the metadata file so its checksum can be baked
|
||||
# into the definition; `pyenv binary save' writes the two together.
|
||||
#
|
||||
# <metadata-file> A .meta file written by `pyenv binary save'.
|
||||
# The corresponsing binary package written by `pyenv binary save'
|
||||
# needs to be present alongside it.
|
||||
# --archive-url <url> The URL for the resulting installation script to download
|
||||
# the package from.
|
||||
# -o <output> Write the definition here (default: stdout).
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --archive-url
|
||||
echo -o
|
||||
exit
|
||||
fi
|
||||
|
||||
metadata=""
|
||||
archive_url=""
|
||||
output=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--archive-url )
|
||||
[ $# -ge 2 ] || { echo "pyenv-binary: --archive-url needs a value" >&2; exit 1; }
|
||||
archive_url="$2"; shift 2 ;;
|
||||
-o )
|
||||
[ $# -ge 2 ] || { echo "pyenv-binary: -o needs a value" >&2; exit 1; }
|
||||
output="$2"; shift 2 ;;
|
||||
-* )
|
||||
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
|
||||
* )
|
||||
[ -z "$metadata" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; }
|
||||
metadata="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$metadata" ] || [ -z "$archive_url" ]; then
|
||||
pyenv-help --usage binary-generate-installer >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="" os="" arch="" distro="" libc="" archive=""
|
||||
deps=""
|
||||
while IFS='=' read -r key value; do
|
||||
case "$key" in
|
||||
version ) version="$value" ;;
|
||||
os ) os="$value" ;;
|
||||
arch ) arch="$value" ;;
|
||||
distro ) distro="$value" ;;
|
||||
libc ) libc="$value" ;;
|
||||
archive ) archive="$value" ;;
|
||||
dep ) deps="${deps:+$deps }$value" ;;
|
||||
esac
|
||||
done < "$metadata"
|
||||
|
||||
# Fail here if the metadata is incomplete, rather than bake a blank into the
|
||||
# definition where a missing arch would give a confusing platform error.
|
||||
for field in version os arch archive; do
|
||||
if [ -z "${!field}" ]; then
|
||||
echo "pyenv-binary: metadata is missing \`${field}'" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# macOS relocation needs install_name_tool and a different rpath scheme, so it is
|
||||
# not supported yet. Other systems relocate with patchelf.
|
||||
if [ "$os" = "Darwin" ]; then
|
||||
echo "pyenv-binary: macOS archives are not supported yet" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The glibc floor only applies on Linux; require it there so the definition can
|
||||
# tell whether the target is new enough to run the binaries.
|
||||
if [ "$os" = "Linux" ] && [ -z "$libc" ]; then
|
||||
echo "pyenv-binary: metadata is missing \`libc'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# python-build downloads the archive itself, so the checksum has to be baked in.
|
||||
# The archive sits next to the metadata `save' wrote it alongside.
|
||||
archive_path="$(dirname "$metadata")/${archive}"
|
||||
if [ ! -r "$archive_path" ]; then
|
||||
echo "pyenv-binary: cannot read the archive \`${archive_path}' to checksum it" >&2
|
||||
exit 1
|
||||
fi
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256="$(sha256sum "$archive_path")"; sha256="${sha256%% *}"
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
sha256="$(shasum -a 256 "$archive_path")"; sha256="${sha256%% *}"
|
||||
elif command -v openssl >/dev/null 2>&1; then
|
||||
sha256="$(openssl dgst -sha256 "$archive_path")"; sha256="${sha256##* }"
|
||||
else
|
||||
echo "pyenv-binary: need sha256sum, shasum or openssl to checksum the archive" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
emit() {
|
||||
# The values known now go in as %q-quoted assignments so a stray quote or space
|
||||
# in the URL or metadata cannot break the definition or inject into it. The body
|
||||
# below is a quoted here-doc, verbatim.
|
||||
{
|
||||
printf '# python-build definition for prebuilt Python %s (%s/%s%s).\n' \
|
||||
"$version" "$os" "$arch" "${distro:+, $distro}"
|
||||
echo "# Generated by \`pyenv binary generate-installer'."
|
||||
echo
|
||||
printf 'EXPECT_OS=%q\n' "$os"
|
||||
printf 'EXPECT_ARCH=%q\n' "$arch"
|
||||
printf 'EXPECT_LIBC=%q\n' "$libc"
|
||||
printf 'VERSION=%q\n' "$version"
|
||||
printf 'ARCHIVE_URL=%q\n' "$archive_url"
|
||||
printf 'SHA256=%q\n' "$sha256"
|
||||
printf 'DEPS=%q\n' "$deps"
|
||||
}
|
||||
cat <<'EOF'
|
||||
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
if [ "$os" != "$EXPECT_OS" ] || [ "$arch" != "$EXPECT_ARCH" ]; then
|
||||
echo "pyenv-binary: this archive is for ${EXPECT_OS}/${EXPECT_ARCH}, not ${os}/${arch}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Refuse a target whose glibc is older than the one the archive was built on;
|
||||
# the dynamic loader would reject the binaries.
|
||||
case "$EXPECT_LIBC" in
|
||||
"glibc "* )
|
||||
build_libc="${EXPECT_LIBC#glibc }"
|
||||
target_libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
|
||||
case "$target_libc" in
|
||||
"glibc "* )
|
||||
target_libc="${target_libc#glibc }"
|
||||
older="$(printf '%s\n%s\n' "$build_libc" "$target_libc" | sort -V | head -n1)"
|
||||
if [ "$older" = "$target_libc" ] && [ "$target_libc" != "$build_libc" ]; then
|
||||
echo "pyenv-binary: archive needs glibc ${build_libc} or newer, but this system has ${target_libc}" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
# Required system libraries must already be present on the target. ldconfig -p
|
||||
# lists each library as "<soname> (...) => <path>"; match the soname column
|
||||
# exactly so a longer name like libc.so.6.1 does not satisfy libc.so.6.
|
||||
if command -v ldconfig &>/dev/null && cache="$(ldconfig -p)"; then
|
||||
missing=""
|
||||
for dep in $DEPS; do
|
||||
printf '%s\n' "$cache" | awk -v d="$dep" '$1 == d { found = 1 } END { exit !found }' \
|
||||
|| missing="${missing} ${dep}"
|
||||
done
|
||||
if [ -n "$missing" ]; then
|
||||
echo "pyenv-binary: missing required system libraries:${missing}" >&2
|
||||
exit 1
|
||||
fi
|
||||
elif [ -n "$DEPS" ]; then
|
||||
echo "pyenv-binary: cannot check required system libraries (ldconfig with -p not found)" >&2
|
||||
fi
|
||||
|
||||
build_package_relocate() {
|
||||
pyenv binary relocate "$PREFIX_PATH"
|
||||
}
|
||||
|
||||
install_package "Python-${VERSION}-binary" "${ARCHIVE_URL}#${SHA256}" copy relocate
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
if [ -n "$output" ]; then
|
||||
emit > "$output"
|
||||
echo "Wrote definition to ${output}"
|
||||
else
|
||||
emit
|
||||
fi
|
||||
90
plugins/pyenv-binary/libexec/pyenv-binary-package
Executable file
90
plugins/pyenv-binary/libexec/pyenv-binary-package
Executable file
@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Create an installable binary package from a Python version
|
||||
#
|
||||
# Usage: pyenv binary package <version>:<entry> --archive-base-url <url>
|
||||
#
|
||||
# Installs <version> from source under the separate name <entry>, saves it as
|
||||
# a binary package, then emits a python-build definition for that package.
|
||||
# The archive, metadata and definition are written to the current directory
|
||||
# as <entry>.tar.gz, <entry>.meta and <entry>.
|
||||
#
|
||||
# <version> A version `pyenv install' knows how to build.
|
||||
# <entry> The name to build under and to install the binary
|
||||
# as, e.g. `3.13.14-debian-12'. Keeping it distinct
|
||||
# from <version> lets the binary sit alongside a
|
||||
# normal source install of the same version.
|
||||
# --archive-base-url <url>
|
||||
# Where the archive will be hosted; the definition
|
||||
# downloads it from <url>/<entry>.tar.gz.
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --archive-base-url
|
||||
exec pyenv-install --list --bare
|
||||
fi
|
||||
|
||||
spec=""
|
||||
archive_base_url=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--archive-base-url )
|
||||
[ $# -ge 2 ] || { echo "pyenv-binary: --archive-base-url needs a value" >&2; exit 1; }
|
||||
archive_base_url="$2"; shift 2 ;;
|
||||
-* )
|
||||
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
|
||||
* )
|
||||
[ -z "$spec" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; }
|
||||
spec="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then
|
||||
pyenv-help --usage binary-package >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$spec" in
|
||||
*?:?* ) ;;
|
||||
* )
|
||||
echo "pyenv-binary: expected <version>:<entry>, e.g. \`3.13.14:3.13.14-debian-12'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
entry="${spec##*:}"
|
||||
|
||||
case "$entry" in
|
||||
# `pyenv install' reads a trailing `:latest' as part of the version rather than
|
||||
# as an alias, so nothing would end up installed under that name.
|
||||
latest )
|
||||
echo "pyenv-binary: \`latest' cannot be used as an entry name" >&2
|
||||
exit 1
|
||||
;;
|
||||
# The entry becomes a directory name under versions/. `pyenv install' does not
|
||||
# validate the alias, and `save' only checks once the build is done, so refuse
|
||||
# a name that could point elsewhere before compiling anything.
|
||||
*/* | .. | . )
|
||||
echo "pyenv-binary: invalid entry name \`${entry}'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
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 "$spec"
|
||||
pyenv-binary-save "$entry" "$PWD" --name "$entry"
|
||||
|
||||
pyenv-binary-generate-installer "${entry}.meta" \
|
||||
--archive-url "${archive_base_url%/}/${entry}.tar.gz" -o "$entry"
|
||||
58
plugins/pyenv-binary/libexec/pyenv-binary-relocate
Executable file
58
plugins/pyenv-binary/libexec/pyenv-binary-relocate
Executable file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Rewrite an unpacked Python's rpaths so it runs from its prefix
|
||||
#
|
||||
# Usage: pyenv binary relocate <prefix>
|
||||
#
|
||||
# Rewrites the rpaths of a Python tree that was unpacked into <prefix> so the
|
||||
# interpreter and its extension modules load the bundled libraries from their
|
||||
# new location rather than the path it was built at. Requires patchelf.
|
||||
#
|
||||
# The definition that `pyenv binary generate-installer' produces calls this
|
||||
# after `install_package ... copy' has laid the tree down.
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
if [ "$1" = "--complete" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
prefix="$1"
|
||||
if [ -z "$prefix" ]; then
|
||||
pyenv-help --usage binary-relocate >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# patchelf can add an rpath and write one of any length; chrpath can only shorten
|
||||
# an existing one, which is not enough to relocate every build, so require it.
|
||||
if ! command -v patchelf >/dev/null 2>&1; then
|
||||
echo "pyenv-binary: need patchelf to relocate the binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The interpreter loads libpython from ../lib. Patch every ELF binary in bin/ so
|
||||
# this holds whatever the interpreter is named (python3, python2.7, ...); the
|
||||
# scripts alongside it (pip, idle) are not ELF, so `--print-rpath' fails on them
|
||||
# and they are skipped. A tree with no interpreter at all did not unpack the way
|
||||
# we expect, so treat that as an error rather than quietly relocating nothing.
|
||||
found=0
|
||||
for file in "$prefix"/bin/*; do
|
||||
[[ -f $file && -x $file && ! -L $file ]] || continue
|
||||
patchelf --print-rpath "$file" >/dev/null 2>&1 || continue
|
||||
patchelf --set-rpath "$prefix/lib" "$file"
|
||||
found=1
|
||||
done
|
||||
if [ "$found" -eq 0 ]; then
|
||||
echo "pyenv-binary: found no interpreter to relocate under \`${prefix}/bin'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The extension modules CPython built are the only other objects with an rpath
|
||||
# into the old prefix, so point them at lib/ as well. Anything a wheel installed
|
||||
# carries an rpath of its own, often into a bundled library directory beside it,
|
||||
# and would stop loading if we overwrote it. Each match is an ELF object we expect
|
||||
# to patch, so let a failure stop us rather than swallow it.
|
||||
while IFS= read -r so; do
|
||||
patchelf --set-rpath "$prefix/lib" "$so"
|
||||
done < <(find "$prefix" -path '*/lib-dynload/*.so')
|
||||
141
plugins/pyenv-binary/libexec/pyenv-binary-save
Executable file
141
plugins/pyenv-binary/libexec/pyenv-binary-save
Executable file
@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Save an installed Python version as a relocatable archive
|
||||
#
|
||||
# Usage: pyenv binary save <version> [<output-dir>] [--name <name>]
|
||||
#
|
||||
# Packs an installed version into a relocatable .tar.gz (relative paths) and
|
||||
# writes a metadata file listing the build platform and the system libraries
|
||||
# it links against, so an installer can check compatibility before unpacking.
|
||||
#
|
||||
# <version> An installed version, as listed by `pyenv versions --bare'.
|
||||
# <output-dir> Where to write the archive and metadata (default: `.').
|
||||
# --name <name> Use <name> as the archive and metadata base name instead of
|
||||
# <version>-<platform>.
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --name
|
||||
exec pyenv-versions --bare
|
||||
fi
|
||||
|
||||
version=""
|
||||
output_dir=""
|
||||
package_name=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--name )
|
||||
[ $# -ge 2 ] && [ -n "$2" ] || { echo "pyenv-binary: --name needs a value" >&2; exit 1; }
|
||||
package_name="$2"; shift 2 ;;
|
||||
-* )
|
||||
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
|
||||
* )
|
||||
if [ -z "$version" ]; then
|
||||
version="$1"
|
||||
elif [ -z "$output_dir" ]; then
|
||||
output_dir="$1"
|
||||
else
|
||||
echo "pyenv-binary: unexpected argument \`$1'" >&2
|
||||
exit 1
|
||||
fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
output_dir="${output_dir:-$PWD}"
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
echo "Usage: pyenv binary save <version> [<output-dir>] [--name <name>]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A version is a single directory name under versions/. With no slash allowed,
|
||||
# the only remaining names that could point elsewhere are `.' and `..'.
|
||||
case "$version" in
|
||||
*/* | .. | . )
|
||||
echo "pyenv-binary: invalid version name \`${version}'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$package_name" in
|
||||
*/* | .. | . | *[[:cntrl:]]* )
|
||||
echo "pyenv-binary: invalid package name \`${package_name}'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
prefix="${PYENV_ROOT}/versions/${version}"
|
||||
if [ ! -d "${prefix}/bin" ]; then
|
||||
echo "pyenv-binary: version \`${version}' is not installed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
platform="$(printf '%s' "$os" | tr '[:upper:]' '[:lower:]')-${arch}"
|
||||
|
||||
# Record the distro and libc version. Platform and arch alone are too coarse to
|
||||
# judge compatibility: a build is only portable to a matching libc (e.g. a
|
||||
# glibc 2.36 build will not load on an older glibc, nor on musl at all).
|
||||
distro=""
|
||||
libc=""
|
||||
if [ -r /etc/os-release ]; then
|
||||
distro="$( . /etc/os-release && printf '%s %s' "${ID:-}" "${VERSION_ID:-}" )"
|
||||
elif [ "$os" = "Darwin" ]; then
|
||||
distro="macos $(sw_vers -productVersion 2>/dev/null)"
|
||||
fi
|
||||
if [ "$os" = "Linux" ]; then
|
||||
libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
# List the external shared libraries the install links against: those that
|
||||
# resolve outside its own prefix, so they must already exist on the target.
|
||||
# The interpreter plus every bundled shared object are inspected.
|
||||
system_deps() {
|
||||
local f
|
||||
{
|
||||
for f in "${prefix}"/bin/python*; do
|
||||
[ -e "$f" ] && printf '%s\n' "$f"
|
||||
done
|
||||
# CPython ships its extension modules as *.so (and *.dylib on macOS). A
|
||||
# bare *.so.* is unusual for CPython itself, but the odd build carries a
|
||||
# versioned copy alongside, so match it too rather than miss a dependency.
|
||||
find "${prefix}" -type f \( -name '*.so' -o -name '*.so.*' -o -name '*.dylib' \)
|
||||
} | sort -u | while IFS= read -r f; do
|
||||
if [ "$os" = "Darwin" ]; then
|
||||
otool -L "$f" 2>/dev/null | tail -n +2 | awk -v pfx="${prefix}/" \
|
||||
'$1 !~ /^@/ && substr($1, 1, length(pfx)) != pfx { print $1 }'
|
||||
else
|
||||
ldd "$f" 2>/dev/null | awk -v pfx="${prefix}/" \
|
||||
'$2 == "=>" && $3 ~ /^\// && substr($3, 1, length(pfx)) != pfx { print $1 }'
|
||||
fi
|
||||
done | sort -u
|
||||
}
|
||||
|
||||
mkdir -p "$output_dir"
|
||||
package_name="${package_name:-${version}-${platform}}"
|
||||
archive="${package_name}.tar.gz"
|
||||
metadata="${package_name}.meta"
|
||||
|
||||
tar -C "$(dirname "$prefix")" -czf "${output_dir}/${archive}" "$(basename "$prefix")"
|
||||
|
||||
{
|
||||
echo "# pyenv-binary metadata"
|
||||
echo "version=${version}"
|
||||
echo "os=${os}"
|
||||
echo "arch=${arch}"
|
||||
echo "platform=${platform}"
|
||||
[ -n "$distro" ] && echo "distro=${distro}"
|
||||
[ -n "$libc" ] && echo "libc=${libc}"
|
||||
echo "archive=${archive}"
|
||||
system_deps | while IFS= read -r dep; do
|
||||
[ -n "$dep" ] && echo "dep=${dep}"
|
||||
done
|
||||
} > "${output_dir}/${metadata}"
|
||||
|
||||
echo "Saved ${archive} and ${metadata} to ${output_dir}"
|
||||
123
plugins/pyenv-binary/test/generate-installer.bats
Normal file
123
plugins/pyenv-binary/test/generate-installer.bats
Normal file
@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
create_meta() {
|
||||
local os="${1-Linux}"
|
||||
local arch="${2-x86_64}"
|
||||
local libc="${3-glibc 2.17}"
|
||||
local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
|
||||
local meta="${BATS_TEST_TMPDIR}/sample.meta"
|
||||
|
||||
rm -rf "${BATS_TEST_TMPDIR}/archive"
|
||||
mkdir -p "${BATS_TEST_TMPDIR}/archive/3.12.7/bin"
|
||||
printf '#!/bin/sh\n' > "${BATS_TEST_TMPDIR}/archive/3.12.7/bin/python"
|
||||
chmod +x "${BATS_TEST_TMPDIR}/archive/3.12.7/bin/python"
|
||||
tar -C "${BATS_TEST_TMPDIR}/archive" -czf "$archive" 3.12.7
|
||||
|
||||
{
|
||||
echo "version=3.12.7"
|
||||
echo "os=${os}"
|
||||
echo "arch=${arch}"
|
||||
[ -z "$libc" ] || echo "libc=${libc}"
|
||||
echo "archive=${archive##*/}"
|
||||
} > "$meta"
|
||||
echo "$meta"
|
||||
}
|
||||
|
||||
@test "completion lists the options" {
|
||||
run pyenv-binary-generate-installer --complete
|
||||
assert_success "--archive-url
|
||||
-o"
|
||||
}
|
||||
|
||||
@test "fails with no arguments" {
|
||||
create_stub pyenv-help "echo usage"
|
||||
run pyenv-binary-generate-installer
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails without an archive url" {
|
||||
create_stub pyenv-help "echo usage"
|
||||
run pyenv-binary-generate-installer "$(create_meta)"
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails when --archive-url has no value" {
|
||||
run pyenv-binary-generate-installer "$(create_meta)" --archive-url
|
||||
assert_failure "pyenv-binary: --archive-url needs a value"
|
||||
}
|
||||
|
||||
@test "rejects a second positional argument" {
|
||||
run pyenv-binary-generate-installer "$(create_meta)" extra --archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: unexpected argument \`extra'"
|
||||
}
|
||||
|
||||
@test "fails for a metadata file that cannot be read" {
|
||||
run pyenv-binary-generate-installer /no/such.meta --archive-url http://x/a.tar.gz
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "refuses macOS metadata" {
|
||||
run pyenv-binary-generate-installer "$(create_meta Darwin arm64 '')" \
|
||||
--archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: macOS archives are not supported yet"
|
||||
}
|
||||
|
||||
@test "fails when required metadata is missing" {
|
||||
local field meta
|
||||
for field in version os arch archive; do
|
||||
meta="$(create_meta)"
|
||||
#cannot use -i: GNU sed requires -i[suf], BSD sed required -i <suf>
|
||||
sed "/^${field}=/d" "$meta" > "${meta}.tmp"; mv "${meta}"{.tmp,}
|
||||
|
||||
run pyenv-binary-generate-installer "$meta" --archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: metadata is missing \`${field}'"
|
||||
done
|
||||
}
|
||||
|
||||
@test "fails when Linux metadata is missing libc" {
|
||||
run pyenv-binary-generate-installer "$(create_meta Linux x86_64 '')" \
|
||||
--archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: metadata is missing \`libc'"
|
||||
}
|
||||
|
||||
@test "refuses a host whose glibc is older than the archive" {
|
||||
local out="${BATS_TEST_TMPDIR}/definition"
|
||||
pyenv-binary-generate-installer "$(create_meta Linux x86_64 'glibc 99.0')" \
|
||||
--archive-url http://example.com/a.tar.gz -o "$out"
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
create_stub getconf 'echo "glibc 2.31"'
|
||||
|
||||
run bash "$out"
|
||||
assert_failure "pyenv-binary: archive needs glibc 99.0 or newer, but this system has 2.31"
|
||||
}
|
||||
|
||||
@test "fails when the archive is not beside the metadata" {
|
||||
local meta="$(create_meta)"
|
||||
rm "${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
|
||||
|
||||
run pyenv-binary-generate-installer "$meta" --archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: cannot read the archive \`${BATS_TEST_TMPDIR}/3.12.7.tar.gz' to checksum it"
|
||||
}
|
||||
|
||||
@test "the generated definition is installable with python-build" {
|
||||
ldconfig -p &>/dev/null || skip "ldconfig with -p is not present"
|
||||
local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
|
||||
local cache="${BATS_TEST_TMPDIR}/cache"
|
||||
local definition="${BATS_TEST_TMPDIR}/definition"
|
||||
local prefix="${BATS_TEST_TMPDIR}/install"
|
||||
pyenv-binary-generate-installer "$(create_meta)" \
|
||||
--archive-url http://example.com/3.12.7.tar.gz -o "$definition"
|
||||
mkdir -p "$cache"
|
||||
cp "$archive" "${cache}/Python-3.12.7-binary.tar.gz"
|
||||
create_stub pyenv 'echo "pyenv $*"'
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
|
||||
PYTHON_BUILD_CACHE_PATH="$cache" run \
|
||||
"${BATS_TEST_DIRNAME}/../../python-build/bin/python-build" "$definition" "$prefix"
|
||||
assert_success
|
||||
assert_line "pyenv binary relocate ${prefix}"
|
||||
assert_line "Installed Python-3.12.7-binary to ${prefix}"
|
||||
assert [ -x "${prefix}/bin/python" ]
|
||||
}
|
||||
92
plugins/pyenv-binary/test/package.bats
Normal file
92
plugins/pyenv-binary/test/package.bats
Normal file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
# Make the build deterministic: `pyenv-install' just creates the prefix, and
|
||||
# the platform tools report a fixed Linux target so the real `save' and
|
||||
# `generate-installer' behave the same on any test host.
|
||||
stub_build_environment() {
|
||||
create_stub pyenv-install 'mkdir -p "${PYENV_ROOT}/versions/${1##*:}/bin"'
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
create_stub getconf 'echo "glibc 2.17"'
|
||||
}
|
||||
|
||||
@test "completion lists the option and definitions provided by another plugin" {
|
||||
mkdir -p "${PYENV_ROOT}/plugins/example/share/python-build"
|
||||
touch "${PYENV_ROOT}/plugins/example/share/python-build/3.12.7-example"
|
||||
PATH="${BATS_TEST_DIRNAME}/../../python-build/bin:${PATH}"
|
||||
|
||||
run pyenv-binary-package --complete
|
||||
assert_success
|
||||
assert_line "--archive-base-url"
|
||||
assert_line "3.12.7-example"
|
||||
refute_line "Available versions:"
|
||||
}
|
||||
|
||||
@test "fails with no arguments" {
|
||||
create_stub pyenv-help "echo usage"
|
||||
run pyenv-binary-package
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails without an archive base url" {
|
||||
create_stub pyenv-help "echo usage"
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails when --archive-base-url has no value" {
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test --archive-base-url
|
||||
assert_failure "pyenv-binary: --archive-base-url needs a value"
|
||||
}
|
||||
|
||||
@test "rejects a second positional argument" {
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test extra --archive-base-url http://x/b
|
||||
assert_failure "pyenv-binary: unexpected argument \`extra'"
|
||||
}
|
||||
|
||||
@test "rejects a bare version without an entry name" {
|
||||
run pyenv-binary-package 3.12.7 --archive-base-url http://x/b
|
||||
assert_failure "pyenv-binary: expected <version>:<entry>, e.g. \`3.13.14:3.13.14-debian-12'"
|
||||
}
|
||||
|
||||
@test "rejects an entry name containing a slash" {
|
||||
run pyenv-binary-package "3.12.7:foo/bar" --archive-base-url http://x/b
|
||||
assert_failure "pyenv-binary: invalid entry name \`foo/bar'"
|
||||
}
|
||||
|
||||
@test "rejects \`latest' as an entry name" {
|
||||
run pyenv-binary-package 3.12:latest --archive-base-url http://x/b
|
||||
assert_failure "pyenv-binary: \`latest' cannot be used as an entry name"
|
||||
}
|
||||
|
||||
@test "refuses to package on macOS before compiling anything" {
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test --archive-base-url http://x/b
|
||||
assert_failure "pyenv-binary: macOS archives are not supported yet"
|
||||
}
|
||||
|
||||
@test "writes the archive, metadata and definition under the entry name" {
|
||||
stub_build_environment
|
||||
cd "${BATS_TEST_TMPDIR}"
|
||||
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test \
|
||||
--archive-base-url http://example.com/binaries
|
||||
assert_success
|
||||
assert [ -d "${PYENV_ROOT}/versions/3.12.7-test" ]
|
||||
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test.tar.gz" ]
|
||||
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test.meta" ]
|
||||
run grep '^ARCHIVE_URL=' "${BATS_TEST_TMPDIR}/3.12.7-test"
|
||||
assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz"
|
||||
}
|
||||
|
||||
@test "strips a trailing slash from the archive base url" {
|
||||
stub_build_environment
|
||||
cd "${BATS_TEST_TMPDIR}"
|
||||
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test \
|
||||
--archive-base-url http://example.com/binaries/
|
||||
assert_success
|
||||
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"
|
||||
}
|
||||
72
plugins/pyenv-binary/test/relocate.bats
Normal file
72
plugins/pyenv-binary/test/relocate.bats
Normal file
@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
_setup() {
|
||||
create_stub pyenv-help "echo usage"
|
||||
}
|
||||
|
||||
stub_patchelf() {
|
||||
create_path_executable patchelf <<STUB
|
||||
if [ "\$1" = "--print-rpath" ]; then
|
||||
exit 0
|
||||
fi
|
||||
echo "\$*" >> "${BATS_TEST_TMPDIR}/patchelf.log"
|
||||
STUB
|
||||
}
|
||||
|
||||
create_interpreter() {
|
||||
mkdir -p "${BATS_TEST_TMPDIR}/prefix/bin"
|
||||
printf '#!/bin/sh\n' > "${BATS_TEST_TMPDIR}/prefix/bin/python2.7"
|
||||
chmod +x "${BATS_TEST_TMPDIR}/prefix/bin/python2.7"
|
||||
}
|
||||
|
||||
@test "completion produces nothing" {
|
||||
run pyenv-binary-relocate --complete
|
||||
assert_success ""
|
||||
}
|
||||
|
||||
@test "fails without a prefix" {
|
||||
run pyenv-binary-relocate
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails when patchelf is not available" {
|
||||
PATH="$(path_without patchelf)" run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
|
||||
assert_failure "pyenv-binary: need patchelf to relocate the binary"
|
||||
}
|
||||
|
||||
@test "fails when the prefix has no interpreter" {
|
||||
create_path_executable patchelf "exit 0"
|
||||
mkdir -p "${BATS_TEST_TMPDIR}/prefix"
|
||||
|
||||
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
|
||||
assert_failure "pyenv-binary: found no interpreter to relocate under \`${BATS_TEST_TMPDIR}/prefix/bin'"
|
||||
}
|
||||
|
||||
@test "relocates an executable interpreter" {
|
||||
stub_patchelf
|
||||
create_interpreter
|
||||
|
||||
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
|
||||
assert_success
|
||||
run cat "${BATS_TEST_TMPDIR}/patchelf.log"
|
||||
assert_output "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${BATS_TEST_TMPDIR}/prefix/bin/python2.7"
|
||||
}
|
||||
|
||||
@test "relocates the extension modules but not what a wheel installed" {
|
||||
local lib="${BATS_TEST_TMPDIR}/prefix/lib/python3.12"
|
||||
stub_patchelf
|
||||
create_interpreter
|
||||
mkdir -p "${lib}/lib-dynload" "${lib}/site-packages/numpy"
|
||||
touch "${lib}/lib-dynload/_ssl.cpython-312-x86_64-linux-gnu.so"
|
||||
# A wheel points its extensions at the libraries it bundles alongside them, so
|
||||
# its rpath is its own business and must survive relocation.
|
||||
touch "${lib}/site-packages/numpy/_multiarray.so"
|
||||
|
||||
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
|
||||
assert_success
|
||||
run cat "${BATS_TEST_TMPDIR}/patchelf.log"
|
||||
assert_line "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${lib}/lib-dynload/_ssl.cpython-312-x86_64-linux-gnu.so"
|
||||
refute_line "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${lib}/site-packages/numpy/_multiarray.so"
|
||||
}
|
||||
132
plugins/pyenv-binary/test/save.bats
Normal file
132
plugins/pyenv-binary/test/save.bats
Normal file
@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
create_version() {
|
||||
mkdir -p "${PYENV_ROOT}/versions/$1/bin"
|
||||
}
|
||||
|
||||
platform() {
|
||||
echo "$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)"
|
||||
}
|
||||
|
||||
@test "fails with no version given" {
|
||||
run pyenv-binary-save
|
||||
assert_failure "Usage: pyenv binary save <version> [<output-dir>] [--name <name>]"
|
||||
}
|
||||
|
||||
@test "fails for a version that is not installed" {
|
||||
run pyenv-binary-save 9.9.9
|
||||
assert_failure "pyenv-binary: version \`9.9.9' is not installed"
|
||||
}
|
||||
|
||||
@test "rejects a version name containing a slash" {
|
||||
run pyenv-binary-save "foo/bar"
|
||||
assert_failure "pyenv-binary: invalid version name \`foo/bar'"
|
||||
}
|
||||
|
||||
@test "rejects the parent directory reference" {
|
||||
run pyenv-binary-save ".."
|
||||
assert_failure "pyenv-binary: invalid version name \`..'"
|
||||
}
|
||||
|
||||
@test "packages an installed version" {
|
||||
create_version "3.12.7"
|
||||
local out="${BATS_TEST_TMPDIR}/dist"
|
||||
local archive="${out}/3.12.7-$(platform).tar.gz"
|
||||
|
||||
run pyenv-binary-save "3.12.7" "$out"
|
||||
assert_success "Saved 3.12.7-$(platform).tar.gz and 3.12.7-$(platform).meta to $out"
|
||||
assert [ -f "$archive" ]
|
||||
assert [ -f "${out}/3.12.7-$(platform).meta" ]
|
||||
run tar -tzf "$archive"
|
||||
assert_success
|
||||
assert_line 0 "3.12.7/"
|
||||
}
|
||||
|
||||
@test "uses an explicit package name" {
|
||||
create_version "3.12.7"
|
||||
local out="${BATS_TEST_TMPDIR}/dist"
|
||||
|
||||
run pyenv-binary-save "3.12.7" "$out" --name "custom"
|
||||
assert_success "Saved custom.tar.gz and custom.meta to $out"
|
||||
assert [ -f "${out}/custom.tar.gz" ]
|
||||
run grep '^archive=' "${out}/custom.meta"
|
||||
assert_success "archive=custom.tar.gz"
|
||||
}
|
||||
|
||||
@test "fails when --name has no value" {
|
||||
run pyenv-binary-save "3.12.7" --name
|
||||
assert_failure "pyenv-binary: --name needs a value"
|
||||
}
|
||||
|
||||
@test "rejects an invalid package name" {
|
||||
create_version "3.12.7"
|
||||
|
||||
run pyenv-binary-save "3.12.7" --name "foo/bar"
|
||||
assert_failure "pyenv-binary: invalid package name \`foo/bar'"
|
||||
}
|
||||
|
||||
@test "records the platform in the metadata" {
|
||||
create_version "3.12.7"
|
||||
local out="${BATS_TEST_TMPDIR}/dist"
|
||||
pyenv-binary-save "3.12.7" "$out" >/dev/null
|
||||
|
||||
run cat "${out}/3.12.7-$(platform).meta"
|
||||
assert_success
|
||||
assert_line "version=3.12.7"
|
||||
assert_line "platform=$(platform)"
|
||||
assert_line "archive=3.12.7-$(platform).tar.gz"
|
||||
}
|
||||
|
||||
@test "records only the libraries ldd resolves outside the prefix" {
|
||||
create_version "3.12.7"
|
||||
touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12"
|
||||
create_path_executable uname <<'STUB'
|
||||
case "$1" in
|
||||
-s) echo Linux ;;
|
||||
-m) echo x86_64 ;;
|
||||
esac
|
||||
STUB
|
||||
create_path_executable ldd <<'STUB'
|
||||
prefix="${PYENV_ROOT}/versions/3.12.7"
|
||||
cat <<EOF
|
||||
linux-vdso.so.1 (0x00007ffd1adfe000)
|
||||
libpython3.12.so.1.0 => ${prefix}/lib/libpython3.12.so.1.0 (0x00007f4a3c000000)
|
||||
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4a3bc00000)
|
||||
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4a3b800000)
|
||||
/lib64/ld-linux-x86-64.so.2 (0x00007f4a3c200000)
|
||||
EOF
|
||||
STUB
|
||||
|
||||
run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist"
|
||||
assert_success
|
||||
run grep '^dep=' "${BATS_TEST_TMPDIR}/dist/"*.meta
|
||||
assert_output "dep=libc.so.6
|
||||
dep=libm.so.6"
|
||||
}
|
||||
|
||||
@test "records only the libraries otool resolves outside the prefix" {
|
||||
create_version "3.12.7"
|
||||
touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12"
|
||||
create_path_executable uname <<'STUB'
|
||||
case "$1" in
|
||||
-s) echo Darwin ;;
|
||||
-m) echo arm64 ;;
|
||||
esac
|
||||
STUB
|
||||
create_path_executable otool <<'STUB'
|
||||
prefix="${PYENV_ROOT}/versions/3.12.7"
|
||||
cat <<EOF
|
||||
${2}:
|
||||
@rpath/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)
|
||||
${prefix}/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)
|
||||
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.0.0)
|
||||
EOF
|
||||
STUB
|
||||
|
||||
run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist"
|
||||
assert_success
|
||||
run grep '^dep=' "${BATS_TEST_TMPDIR}/dist/"*.meta
|
||||
assert_output "dep=/usr/lib/libSystem.B.dylib"
|
||||
}
|
||||
1
plugins/pyenv-binary/test/test_helper.bash
Symbolic link
1
plugins/pyenv-binary/test/test_helper.bash
Symbolic link
@ -0,0 +1 @@
|
||||
../../../test/test_helper.bash
|
||||
@ -67,6 +67,13 @@ exact name of the version you want to install. For example,
|
||||
Python versions will be installed into a directory of the same name under
|
||||
`~/.pyenv/versions`.
|
||||
|
||||
To install a version under a different name -- for instance, to keep several
|
||||
builds of the same version side by side -- append `:<alias>` to the version:
|
||||
|
||||
pyenv install 3.12.0:3.12-custom
|
||||
|
||||
This installs into `~/.pyenv/versions/3.12-custom`.
|
||||
|
||||
To see a list of all available Python versions, run `pyenv install --list`. You
|
||||
may also tab-complete available Python versions if your pyenv installation is
|
||||
properly configured.
|
||||
@ -262,7 +269,7 @@ would be to make symlinks at the mirror's root:
|
||||
```
|
||||
|
||||
The rationale is to abstract away difference between directory structures of sites
|
||||
of various Python flavors and their occasional changes as well as to accomodate
|
||||
of various Python flavors and their occasional changes as well as to accommodate
|
||||
people who only wish to cache some select downloads. This also allows to mirror multiple sites at once.
|
||||
|
||||
If the mirror being used does not have the same checksum (*e.g.* with a
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
#
|
||||
# Summary: Install a Python version using python-build
|
||||
#
|
||||
# Usage: pyenv install [-f] [-kvp] <version>...
|
||||
# pyenv install [-f] [-kvp] <definition-file>
|
||||
# pyenv install -l|--list
|
||||
# Usage: pyenv install [-f] [-kvp] <version>[:<alias>]...
|
||||
# pyenv install [-f] [-kvp] <definition-file>[:<alias>]
|
||||
# pyenv install -l|--list [--bare]
|
||||
# pyenv install --version
|
||||
#
|
||||
# -l/--list List all available versions
|
||||
@ -20,6 +20,13 @@
|
||||
# --version Show version of python-build
|
||||
# -g/--debug Build a debug version
|
||||
#
|
||||
# Append `:<alias>' to a version to install it under a custom name, so that
|
||||
# several builds of the same version can coexist:
|
||||
#
|
||||
# pyenv install 3.12.0:my-3.12
|
||||
#
|
||||
# This installs into $PYENV_ROOT/versions/my-3.12.
|
||||
#
|
||||
# For detailed information on installing Python versions with
|
||||
# python-build, including a list of environment variables for adjusting
|
||||
# compilation, see: https://github.com/pyenv/pyenv#readme
|
||||
@ -38,6 +45,7 @@ shopt -u nullglob
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --bare
|
||||
echo --list
|
||||
echo --force
|
||||
echo --skip-existing
|
||||
@ -72,6 +80,8 @@ unset KEEP
|
||||
unset VERBOSE
|
||||
unset HAS_PATCH
|
||||
unset DEBUG
|
||||
unset BARE
|
||||
unset LIST
|
||||
|
||||
[ -n "$PYENV_DEBUG" ] && VERBOSE="-v"
|
||||
|
||||
@ -81,10 +91,11 @@ for option in "${OPTIONS[@]}"; do
|
||||
"h" | "help" )
|
||||
usage 0
|
||||
;;
|
||||
"bare" )
|
||||
BARE=1
|
||||
;;
|
||||
"l" | "list" )
|
||||
echo "Available versions:"
|
||||
definitions | indent
|
||||
exit
|
||||
LIST=1
|
||||
;;
|
||||
"f" | "force" )
|
||||
FORCE=true
|
||||
@ -113,6 +124,16 @@ for option in "${OPTIONS[@]}"; do
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n $LIST ]]; then
|
||||
if [[ -n $BARE ]]; then
|
||||
definitions
|
||||
else
|
||||
echo "Available versions:"
|
||||
definitions | indent
|
||||
fi
|
||||
exit
|
||||
fi
|
||||
|
||||
unset VERSION_NAME
|
||||
|
||||
# The first argument contains the definition to install. If the
|
||||
@ -123,6 +144,20 @@ DEFINITIONS=("${ARGUMENTS[@]}")
|
||||
[[ "${#DEFINITIONS[*]}" -eq 0 ]] && DEFINITIONS=($(pyenv-local 2>/dev/null || true))
|
||||
[[ "${#DEFINITIONS[*]}" -eq 0 ]] && usage 1 >&2
|
||||
|
||||
# A `<version>:<alias>` argument installs <version> under the custom name
|
||||
# <alias>, so that several builds of the same version can coexist. The `latest`
|
||||
# suffix is reserved for latest-version resolution (see the `install` hook), so
|
||||
# it is left untouched here.
|
||||
declare -a ALIASES
|
||||
for i in "${!DEFINITIONS[@]}"; do
|
||||
definition="${DEFINITIONS[$i]}"
|
||||
alias="${definition##*:}"
|
||||
if [[ "$definition" == *:* ]] && [ "$alias" != "latest" ]; then
|
||||
DEFINITIONS[$i]="${definition%:*}"
|
||||
ALIASES[$i]="$alias"
|
||||
fi
|
||||
done
|
||||
|
||||
# Define `before_install` and `after_install` functions that allow
|
||||
# plugin hooks to register a string of code for execution before or
|
||||
# after the installation process.
|
||||
@ -152,7 +187,9 @@ IFS="$OLDIFS"
|
||||
for script in "${scripts[@]}"; do source "$script"; done
|
||||
|
||||
COMBINED_STATUS=0
|
||||
for DEFINITION in "${DEFINITIONS[@]}"; do
|
||||
for i in "${!DEFINITIONS[@]}"; do
|
||||
DEFINITION="${DEFINITIONS[$i]}"
|
||||
VERSION_ALIAS="${ALIASES[$i]}"
|
||||
STATUS=0
|
||||
|
||||
# Try to resolve a prefix if user indeed gave a prefix.
|
||||
@ -161,9 +198,12 @@ for DEFINITION in "${DEFINITIONS[@]}"; do
|
||||
DEFINITION="$(pyenv-latest -f -k "$DEFINITION")"
|
||||
|
||||
# Set VERSION_NAME from $DEFINITION. Then compute the installation prefix.
|
||||
# With a `<version>:<alias>` argument, install under the alias instead;
|
||||
# VERSION_NAME still reflects the real version so version-specific build logic
|
||||
# (e.g. the bootstrap version detection below) keeps working.
|
||||
VERSION_NAME="${DEFINITION##*/}"
|
||||
[ -n "$DEBUG" ] && VERSION_NAME="${VERSION_NAME}-debug"
|
||||
PREFIX="${PYENV_ROOT}/versions/${VERSION_NAME}"
|
||||
PREFIX="${PYENV_ROOT}/versions/${VERSION_ALIAS:-$VERSION_NAME}"
|
||||
|
||||
[ -d "${PREFIX}" ] && PREFIX_EXISTS=1
|
||||
|
||||
@ -188,7 +228,7 @@ for DEFINITION in "${DEFINITIONS[@]}"; do
|
||||
|
||||
# If PYENV_BUILD_ROOT is set, always pass keep options to python-build.
|
||||
if [ -n "${PYENV_BUILD_ROOT}" ]; then
|
||||
export PYTHON_BUILD_BUILD_PATH="${PYENV_BUILD_ROOT}/${VERSION_NAME}"
|
||||
export PYTHON_BUILD_BUILD_PATH="${PYENV_BUILD_ROOT}/${VERSION_ALIAS:-$VERSION_NAME}"
|
||||
KEEP="-k"
|
||||
fi
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
# -g/--debug Build a debug version
|
||||
#
|
||||
|
||||
PYTHON_BUILD_VERSION="2.7.3"
|
||||
PYTHON_BUILD_VERSION="2.8.1"
|
||||
|
||||
OLDIFS="$IFS"
|
||||
|
||||
|
||||
@ -31,13 +31,6 @@ import requests_html
|
||||
import sortedcontainers
|
||||
import tqdm
|
||||
|
||||
#CI uses exit code 1 as a signal that no new version is found
|
||||
#so have to produce a different exit code on an exception
|
||||
def _excepthook(type,value,traceback):
|
||||
logging.error("Unhandled exception occured",exc_info=(type,value,traceback))
|
||||
sys.exit(2)
|
||||
sys.excepthook = _excepthook
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CUTOFF_VERSION=packaging.version.Version('3.10')
|
||||
@ -176,6 +169,8 @@ def handle_version_patches(
|
||||
logger.info(f"Copying patches from {previous_version} to {version}")
|
||||
shutil.copytree(previous_patches, new_patches)
|
||||
|
||||
# Subdir rename as a separate step from upper dir copying/moving
|
||||
# in case there are patches for dependency packages as well
|
||||
previous_package_patches = new_patches / f"Python-{previous_version}"
|
||||
new_package_patches = new_patches / f"Python-{version}"
|
||||
if is_prerelease_upgrade:
|
||||
@ -187,10 +182,9 @@ def handle_version_patches(
|
||||
else:
|
||||
previous_package_patches.rename(new_package_patches)
|
||||
|
||||
previous_t_patches = patches_dir / f"{previous_version}t"
|
||||
if previous_t_patches.exists() or previous_t_patches.is_symlink():
|
||||
if is_prerelease_upgrade:
|
||||
previous_t_patches.unlink()
|
||||
if uses_t_thunks(previous_version) and is_prerelease_upgrade:
|
||||
(patches_dir / f"{previous_version}t").unlink(missing_ok=True)
|
||||
if uses_t_thunks(version):
|
||||
(patches_dir / f"{version}t").symlink_to(
|
||||
str(version), target_is_directory=True
|
||||
)
|
||||
@ -225,7 +219,7 @@ def cleanup_prerelease_upgrade(
|
||||
|
||||
|
||||
def handle_t_thunks(version, previous_version, is_prerelease_upgrade):
|
||||
if (version.major, version.minor) < (3, 13):
|
||||
if not uses_t_thunks(version):
|
||||
return
|
||||
|
||||
# an old thunk may have older version-specific code
|
||||
@ -244,6 +238,10 @@ def handle_t_thunks(version, previous_version, is_prerelease_upgrade):
|
||||
thunk_path.write_text(T_THUNK, encoding='utf-8')
|
||||
|
||||
|
||||
def uses_t_thunks(version: packaging.version.Version) -> bool:
|
||||
return (version.major, version.minor) >= (3, 13)
|
||||
|
||||
|
||||
Arguments: argparse.Namespace
|
||||
|
||||
def main():
|
||||
@ -262,14 +260,20 @@ def main():
|
||||
# So until we know the release is out, its directory is a potential prerelease directory.
|
||||
# Normally, prereleases are only made for initial releases (x.y.0) --
|
||||
# but rarely, they may make them for other releases (e.g. 3.14.5).
|
||||
for release in (v for v in frozenset(VersionDirectory.available.keys()) #refining changes the
|
||||
for release in (v for v in frozenset(VersionDirectory.available.keys()) #refining alters the
|
||||
#corresponding directory key
|
||||
#which breaks iteration
|
||||
#over the directory --
|
||||
#so have to iterate over a copy
|
||||
if v not in VersionDirectory.existing):
|
||||
VersionDirectory.available.get_store_available_source_downloads(release, True)
|
||||
del release
|
||||
|
||||
# Excluding versions for which there already are PRs.
|
||||
# This will prevent us from using advanced features of
|
||||
# peter-evans/create-pull-request Github Action
|
||||
# like updating a PR and closing a superseded PR
|
||||
# but we don't really need them as of this writing.
|
||||
versions_to_add = sorted(
|
||||
VersionDirectory.available.keys()
|
||||
- VersionDirectory.existing.keys()
|
||||
@ -293,9 +297,8 @@ def get_pending_versions() -> typing.Set[packaging.version.Version]:
|
||||
|
||||
pending_versions = set()
|
||||
for line in ls_remote.splitlines():
|
||||
match = AUTO_ADD_VERSION_REF_RE.fullmatch(line)
|
||||
if not match:
|
||||
raise ValueError(f"Unexpected git ls-remote output: {line!r}")
|
||||
if not (match := AUTO_ADD_VERSION_REF_RE.fullmatch(line)):
|
||||
raise ValueError(f"Unexpected git ls-remote output line: {line!r}")
|
||||
pending_versions.update(
|
||||
packaging.version.Version(version)
|
||||
for version in match.group("versions").split("_")
|
||||
@ -453,8 +456,9 @@ class CPythonAvailableVersionsDirectory(KeyedList[_CPythonAvailableVersionInfo,
|
||||
download_version = packaging.version.Version(m.group("version"))
|
||||
if download_version != version:
|
||||
if not refine_mode:
|
||||
raise ValueError(f"Unexpectedly found a download {name} ({download_version}) "
|
||||
logger.warning(f"Ignoring download {name} ({download_version}) "
|
||||
f"for {version} at page {entry.download_page_url}")
|
||||
continue
|
||||
entry_to_fill = additional_versions_found.get_or_create(
|
||||
download_version,
|
||||
download_page_url=entry.download_page_url
|
||||
@ -467,7 +471,10 @@ class CPythonAvailableVersionsDirectory(KeyedList[_CPythonAvailableVersionInfo,
|
||||
m.group("extension"), m.group('package'), url
|
||||
))
|
||||
|
||||
if not exact_download_found:
|
||||
# XXX: Exact download not found in non-refine mode never happens now
|
||||
# 'cuz we first call the function in refine mode.
|
||||
# Decide what's best to do if it starts to after a logic change.
|
||||
if not exact_download_found and refine_mode:
|
||||
actual_version = max(additional_versions_found.keys())
|
||||
logger.debug(f"Refining available version {version} to {actual_version}")
|
||||
del self[version]
|
||||
@ -499,7 +506,7 @@ class CPythonExistingScriptsDirectory(KeyedList[_CPythonExistingScriptInfo, pack
|
||||
v = packaging.version.Version(entry_name)
|
||||
if v < CUTOFF_VERSION:
|
||||
continue
|
||||
# branch tip scrpts are different from release scripts and thus unusable as a pattern
|
||||
# branch tip scripts are different from release scripts and thus unusable as a pattern
|
||||
if v.dev is not None:
|
||||
continue
|
||||
logger.debug(f"Existing version {v}")
|
||||
@ -533,7 +540,7 @@ class OpenSSLVersionsDirectory(KeyedList[_OpenSSLVersionInfo, packaging.version.
|
||||
if matching:
|
||||
return max(matching, key=lambda release: release.version)
|
||||
|
||||
url = "https://api.github.com/repos/openssl/openssl/releases?per_page=100"
|
||||
url = "https://api.github.com/repos/openssl/openssl/releases"
|
||||
while url:
|
||||
response = requests.get(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
@ -657,6 +664,7 @@ class DownloadPage:
|
||||
if session is None:
|
||||
session = requests_html.HTMLSession()
|
||||
response = session.get(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
page = response.html
|
||||
table = page.find("pre", first=True)
|
||||
# some GNU mirrors format entries as a table
|
||||
@ -736,4 +744,9 @@ class Url:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
#sys.excepthook seems to have no effect in Github Actions
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception:
|
||||
logging.exception("Unhandled exception occurred")
|
||||
sys.exit(2)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
more_itertools
|
||||
requests-html
|
||||
fake_useragent<2
|
||||
fake_useragent<2; python_version < "3.9"
|
||||
lxml[html_clean]
|
||||
packaging
|
||||
requests
|
||||
|
||||
@ -5,7 +5,7 @@ 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 "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.0b3" "https://www.python.org/ftp/python/3.15.0/Python-3.15.0b3.tar.xz#6a935ae234a67e6549894373b0cfeb8361182d03b21442328ae9598ab7422127" standard verify_py315 copy_python_gdb ensurepip
|
||||
install_package "Python-3.15.0b4" "https://www.python.org/ftp/python/3.15.0/Python-3.15.0b4.tar.xz#93efb9c88d7b6633368e7f7b8f8db6e98988f7f761c09b77849447262841ce3a" standard verify_py315 copy_python_gdb ensurepip
|
||||
else
|
||||
install_package "Python-3.15.0b3" "https://www.python.org/ftp/python/3.15.0/Python-3.15.0b3.tgz#e5a18806817f912f2c9a1091ac77703f9a969b129c878d436f0f7025bf2f9e97" standard verify_py315 copy_python_gdb ensurepip
|
||||
install_package "Python-3.15.0b4" "https://www.python.org/ftp/python/3.15.0/Python-3.15.0b4.tgz#4825af9329834306ad4a9cb56b94455381b52bcde4b653604a39f387cc559fc9" standard verify_py315 copy_python_gdb ensurepip
|
||||
fi
|
||||
61
plugins/python-build/share/python-build/graalpy3.12-25.1.3
Normal file
61
plugins/python-build/share/python-build/graalpy3.12-25.1.3
Normal file
@ -0,0 +1,61 @@
|
||||
# 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.1.3'
|
||||
BUILD=''
|
||||
|
||||
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.12-community-${VERSION}" && echo
|
||||
|
||||
|
||||
graalpy_arch="$(graalpy_architecture 2>/dev/null || true)"
|
||||
|
||||
case "$graalpy_arch" in
|
||||
"linux-amd64" )
|
||||
checksum="53d46f3ad78229699eb00677f4cb5e3cfe451b58c46970e4d4272a719b53ce82"
|
||||
;;
|
||||
"linux-aarch64" )
|
||||
checksum="947170f0896c8acb02b364caad3e0c01439c12cbdad1b7605cff589b7dd5b26e"
|
||||
;;
|
||||
"macos-aarch64" )
|
||||
checksum="adb17eb3aa3c5701cfc518af26bbaf4e5755ecb62a6a22d490af06523ea02913"
|
||||
;;
|
||||
* )
|
||||
{ echo
|
||||
colorize 1 "ERROR"
|
||||
echo ": No binary distribution of GraalPy is available for $(uname -sm)."
|
||||
echo
|
||||
} >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "${BUILD}" ]; then
|
||||
{ echo
|
||||
colorize 1 "ERROR"
|
||||
echo "Oracle GraalPy currently doesn't provide snapshot builds. Use graalpy3.12-community if you need snapshots."
|
||||
echo
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
url="https://github.com/oracle/graalpython/releases/download/graal-${VERSION}/graalpy3.12-${VERSION}-${graalpy_arch}.tar.gz#${checksum}"
|
||||
|
||||
install_package "graalpy3.12-${VERSION}" "${url}" "copy" ensurepip
|
||||
49
plugins/python-build/share/python-build/graalpy3.12-25.2.4
Normal file
49
plugins/python-build/share/python-build/graalpy3.12-25.2.4
Normal file
@ -0,0 +1,49 @@
|
||||
# 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.2.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.12-community-${VERSION}" && echo
|
||||
|
||||
|
||||
graalpy_arch="$(graalpy_architecture 2>/dev/null || true)"
|
||||
|
||||
case "$graalpy_arch" in
|
||||
"linux-amd64" )
|
||||
checksum="b3d0766ae6d55daa15f0db1f6c884383d7eec506b186d0ba2f702523a78ff28d"
|
||||
;;
|
||||
"linux-aarch64" )
|
||||
checksum="e57472272b1b659ae6ac972117723b0515a49cc9577688ed5563919793170d67"
|
||||
;;
|
||||
"macos-aarch64" )
|
||||
checksum="79f0e1fefb42bb484122ae9e50cb21ce14541d18e6bd66c498e44b459ce652df"
|
||||
;;
|
||||
* )
|
||||
{ echo
|
||||
colorize 1 "ERROR"
|
||||
echo ": No binary distribution of GraalPy is available for $(uname -sm)."
|
||||
echo
|
||||
} >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
install_package "graalpy3.12-${VERSION}" "https://github.com/oracle/graalpython/releases/download/graal-${VERSION}/graalpy3.12-${VERSION}-${graalpy_arch}.tar.gz#${checksum}" "copy" ensurepip
|
||||
@ -0,0 +1,51 @@
|
||||
# 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.1.3'
|
||||
BUILD=''
|
||||
|
||||
graalpy_arch="$(graalpy_architecture 2>/dev/null || true)"
|
||||
|
||||
case "$graalpy_arch" in
|
||||
"linux-amd64" )
|
||||
checksum="1705b59e5a3d04364b1fa4cbb31ad6c273b487a87dc67e336fc5476b599c8d3e"
|
||||
;;
|
||||
"linux-aarch64" )
|
||||
checksum="2558dec612e02b1ce5d7807fdd338f2a65dd9a0e62913458a32202289cc211ac"
|
||||
;;
|
||||
"macos-aarch64" )
|
||||
checksum="76c0ccde939b94e6669a0e7eb01df612a070a6dbb966ed70989a851a9b1066e8"
|
||||
;;
|
||||
* )
|
||||
{ echo
|
||||
colorize 1 "ERROR"
|
||||
echo ": No binary distribution of GraalPy is available for $(uname -sm)."
|
||||
echo
|
||||
} >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "${BUILD}" ]; then
|
||||
url="https://github.com/graalvm/graalvm-ce-dev-builds/releases/download/${VERSION}-dev-${BUILD}/graalpy3.12-community-dev-${graalpy_arch}.tar.gz"
|
||||
else
|
||||
url="https://github.com/oracle/graalpython/releases/download/graal-${VERSION}/graalpy3.12-community-${VERSION}-${graalpy_arch}.tar.gz#${checksum}"
|
||||
fi
|
||||
|
||||
install_package "graalpy3.12-community-${VERSION}${BUILD}" "${url}" "copy" ensurepip
|
||||
@ -0,0 +1,44 @@
|
||||
# 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.2.4'
|
||||
|
||||
graalpy_arch="$(graalpy_architecture 2>/dev/null || true)"
|
||||
|
||||
case "$graalpy_arch" in
|
||||
"linux-amd64" )
|
||||
checksum="98af303a6b714bde39077354733254f78023ddef06d61e14cb66a3a01f10ae82"
|
||||
;;
|
||||
"linux-aarch64" )
|
||||
checksum="dda5e97c8b38158184dec189addaece57a37207a0dad38be162621d605f964f5"
|
||||
;;
|
||||
"macos-aarch64" )
|
||||
checksum="76a182713660ce69f640b9666c2861ec8a5839637a972eaa80c6b5cf38f1357b"
|
||||
;;
|
||||
* )
|
||||
{ echo
|
||||
colorize 1 "ERROR"
|
||||
echo ": No binary distribution of GraalPy is available for $(uname -sm)."
|
||||
echo
|
||||
} >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
install_package "graalpy3.12-community-${VERSION}" "https://github.com/oracle/graalpython/releases/download/graal-${VERSION}/graalpy3.12-community-${VERSION}-${graalpy_arch}.tar.gz#${checksum}" "copy" ensurepip
|
||||
@ -0,0 +1,20 @@
|
||||
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
|
||||
case "$(anaconda_architecture 2>/dev/null || true)" in
|
||||
"Linux-aarch64" )
|
||||
install_script "Miniconda3-py310_26.5.3-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_26.5.3-1-Linux-aarch64.sh#d2988632a30ea71e158874b9c5386d016660908fec38ee58125af7783b1a0bf6" "miniconda" verify_py310
|
||||
;;
|
||||
"Linux-x86_64" )
|
||||
install_script "Miniconda3-py310_26.5.3-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_26.5.3-1-Linux-x86_64.sh#4a82fe0a50a28e8a9406b3ed8e465b7009aa7d0225566802c3370df96b10d834" "miniconda" verify_py310
|
||||
;;
|
||||
"MacOSX-arm64" )
|
||||
install_script "Miniconda3-py310_26.5.3-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_26.5.3-1-MacOSX-arm64.sh#7868e87c7e1ecaa6ff25f5041e0abdf766e5e2d082788b26bcf57554d0036a2d" "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
|
||||
@ -0,0 +1,20 @@
|
||||
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
|
||||
case "$(anaconda_architecture 2>/dev/null || true)" in
|
||||
"Linux-aarch64" )
|
||||
install_script "Miniconda3-py311_26.5.3-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_26.5.3-1-Linux-aarch64.sh#4a2e39642fcd0b25cf199f5ada4725120a9a40fa10e864d67b8f93e7f2f44dad" "miniconda" verify_py311
|
||||
;;
|
||||
"Linux-x86_64" )
|
||||
install_script "Miniconda3-py311_26.5.3-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_26.5.3-1-Linux-x86_64.sh#f1d308a450763ce617f4e4f1609521358f663b2d1c097cfcc11a1c1f09baf680" "miniconda" verify_py311
|
||||
;;
|
||||
"MacOSX-arm64" )
|
||||
install_script "Miniconda3-py311_26.5.3-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_26.5.3-1-MacOSX-arm64.sh#721bccd83c53ff56e364faf883c5da9abfdf61daecb4863c533818851b33edc3" "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
|
||||
@ -0,0 +1,20 @@
|
||||
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
|
||||
case "$(anaconda_architecture 2>/dev/null || true)" in
|
||||
"Linux-aarch64" )
|
||||
install_script "Miniconda3-py312_26.5.3-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_26.5.3-1-Linux-aarch64.sh#9e48caecbad9cc43d8a483584f0a5e1da2754189416a8811aaada3f58aceeb67" "miniconda" verify_py312
|
||||
;;
|
||||
"Linux-x86_64" )
|
||||
install_script "Miniconda3-py312_26.5.3-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_26.5.3-1-Linux-x86_64.sh#ecb43ee4ae30a7a5af87737e9548ceb21f0a10ec55b8dc40d247aa925b80bfec" "miniconda" verify_py312
|
||||
;;
|
||||
"MacOSX-arm64" )
|
||||
install_script "Miniconda3-py312_26.5.3-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_26.5.3-1-MacOSX-arm64.sh#f5767f038d5aa8299254b96b7e0db4f086c29e4b3a620fc38fea51974f66abdd" "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
|
||||
@ -0,0 +1,20 @@
|
||||
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
|
||||
case "$(anaconda_architecture 2>/dev/null || true)" in
|
||||
"Linux-aarch64" )
|
||||
install_script "Miniconda3-py313_26.5.3-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_26.5.3-1-Linux-aarch64.sh#4eaf1f2d83ede3ad010afa6ad19bef69893ca4667ba5996f51efb3080c08a70d" "miniconda" verify_py313
|
||||
;;
|
||||
"Linux-x86_64" )
|
||||
install_script "Miniconda3-py313_26.5.3-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_26.5.3-1-Linux-x86_64.sh#7358a5961dc6a4941d087281cd70313728fcc68695735e18a337321bc31c7f51" "miniconda" verify_py313
|
||||
;;
|
||||
"MacOSX-arm64" )
|
||||
install_script "Miniconda3-py313_26.5.3-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_26.5.3-1-MacOSX-arm64.sh#c73b91d59c872f472d7a21dadaf0cc70dcff1fadf5bd98200bd15341be2bcbd0" "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
|
||||
@ -0,0 +1,20 @@
|
||||
export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
|
||||
case "$(anaconda_architecture 2>/dev/null || true)" in
|
||||
"Linux-aarch64" )
|
||||
install_script "Miniconda3-py314_26.5.3-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py314_26.5.3-1-Linux-aarch64.sh#781abf4cec9b842f5ed508e7c364ed9951380ba11187b80fe5d9c3dbf9ffccea" "miniconda" verify_py314
|
||||
;;
|
||||
"Linux-x86_64" )
|
||||
install_script "Miniconda3-py314_26.5.3-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py314_26.5.3-1-Linux-x86_64.sh#42cfece170da342364a78d629e06b94dfd81b0f2717d7655729100d888d606b4" "miniconda" verify_py314
|
||||
;;
|
||||
"MacOSX-arm64" )
|
||||
install_script "Miniconda3-py314_26.5.3-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py314_26.5.3-1-MacOSX-arm64.sh#0cb1e1d43810d3118f7b6cd0095aff48dbde8312a19cb8c44e9a79c38bb48be3" "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
|
||||
25
plugins/python-build/share/python-build/miniforge3-26.3.2-2
Normal file
25
plugins/python-build/share/python-build/miniforge3-26.3.2-2
Normal file
@ -0,0 +1,25 @@
|
||||
case "$(anaconda_architecture 2>/dev/null || true)" in
|
||||
"Linux-aarch64" )
|
||||
install_script "Miniforge3-26.3.2-2-Linux-aarch64.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-2/Miniforge3-26.3.2-2-Linux-aarch64.sh#f4096a92482b30f04534cddb63d8bc929118318deffac71d90fb89dc52359d22" "miniconda" verify_py313
|
||||
;;
|
||||
"Linux-ppc64le" )
|
||||
install_script "Miniforge3-26.3.2-2-Linux-ppc64le.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-2/Miniforge3-26.3.2-2-Linux-ppc64le.sh#8b3527cd4c70e6eb4a6c2c4c41de34c2480f0a5200de2ecb7fe385d186af0869" "miniconda" verify_py313
|
||||
;;
|
||||
"Linux-x86_64" )
|
||||
install_script "Miniforge3-26.3.2-2-Linux-x86_64.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-2/Miniforge3-26.3.2-2-Linux-x86_64.sh#42260ffe3830fb953d5eee1bbb32229ff06aa7c3833c1ed7a9a0420a95685d94" "miniconda" verify_py313
|
||||
;;
|
||||
"MacOSX-arm64" )
|
||||
install_script "Miniforge3-26.3.2-2-MacOSX-arm64.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-2/Miniforge3-26.3.2-2-MacOSX-arm64.sh#2657d94152343cff7c06159ac9fc09624d7879fa9575c5a0a324c571c4df0ade" "miniconda" verify_py313
|
||||
;;
|
||||
"MacOSX-x86_64" )
|
||||
install_script "Miniforge3-26.3.2-2-MacOSX-x86_64.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-2/Miniforge3-26.3.2-2-MacOSX-x86_64.sh#a755192103de19bb2782685ac78820c2e00702e5f33e6e4f0a3bf3c214f45d69" "miniconda" verify_py313
|
||||
;;
|
||||
* )
|
||||
{ echo
|
||||
colorize 1 "ERROR"
|
||||
echo ": The binary distribution of Miniforge is not available for $(anaconda_architecture 2>/dev/null || true)."
|
||||
echo
|
||||
} >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
25
plugins/python-build/share/python-build/miniforge3-26.3.2-3
Normal file
25
plugins/python-build/share/python-build/miniforge3-26.3.2-3
Normal file
@ -0,0 +1,25 @@
|
||||
case "$(anaconda_architecture 2>/dev/null || true)" in
|
||||
"Linux-aarch64" )
|
||||
install_script "Miniforge3-26.3.2-3-Linux-aarch64.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-3/Miniforge3-26.3.2-3-Linux-aarch64.sh#2c113a69297e612b01ca0f320c22a3107a11f2ab9b573d79ac868a175945ce29" "miniconda" verify_py313
|
||||
;;
|
||||
"Linux-ppc64le" )
|
||||
install_script "Miniforge3-26.3.2-3-Linux-ppc64le.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-3/Miniforge3-26.3.2-3-Linux-ppc64le.sh#df7e80ee070ccc6031e2710eb4cf81ee0012264b587306b5aa3890d3d89edd97" "miniconda" verify_py313
|
||||
;;
|
||||
"Linux-x86_64" )
|
||||
install_script "Miniforge3-26.3.2-3-Linux-x86_64.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-3/Miniforge3-26.3.2-3-Linux-x86_64.sh#848194851a98903134187fbb4ab50efe87b003e0c0f808f97644b7524a62bf2c" "miniconda" verify_py313
|
||||
;;
|
||||
"MacOSX-arm64" )
|
||||
install_script "Miniforge3-26.3.2-3-MacOSX-arm64.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-3/Miniforge3-26.3.2-3-MacOSX-arm64.sh#59168f1e24d0a4ad9932021170809fca836cd240e183eeeb331d5bcfc0098168" "miniconda" verify_py313
|
||||
;;
|
||||
"MacOSX-x86_64" )
|
||||
install_script "Miniforge3-26.3.2-3-MacOSX-x86_64.sh" "https://github.com/conda-forge/miniforge/releases/download/26.3.2-3/Miniforge3-26.3.2-3-MacOSX-x86_64.sh#39273e4c89a0a1af4538010615d44ae8f44e1af41007e02def593d20f316b003" "miniconda" verify_py313
|
||||
;;
|
||||
* )
|
||||
{ echo
|
||||
colorize 1 "ERROR"
|
||||
echo ": The binary distribution of Miniforge is not available for $(anaconda_architecture 2>/dev/null || true)."
|
||||
echo
|
||||
} >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@ -31,6 +31,32 @@ stub_python_build() {
|
||||
unstub python-build
|
||||
}
|
||||
|
||||
@test "install a single version under an alias" {
|
||||
stub_python_build_lib
|
||||
stub_python_build
|
||||
|
||||
run pyenv-install 3.4.2:3.4.2-custom
|
||||
assert_success "python-build 3.4.2 ${PYENV_ROOT}/versions/3.4.2-custom"
|
||||
|
||||
unstub python-build
|
||||
}
|
||||
|
||||
@test "install multiple versions, each under its own alias" {
|
||||
stub_python_build_lib
|
||||
stub_python_build
|
||||
stub_python_build
|
||||
|
||||
run pyenv-install 3.4.1:custom1 3.4.2:custom2
|
||||
assert_success
|
||||
assert_output <<OUT
|
||||
python-build 3.4.1 ${PYENV_ROOT}/versions/custom1
|
||||
python-build 3.4.2 ${PYENV_ROOT}/versions/custom2
|
||||
OUT
|
||||
|
||||
unstub python-build
|
||||
unstub pyenv-latest
|
||||
}
|
||||
|
||||
@test "install multiple versions" {
|
||||
stub_python_build_lib
|
||||
stub_python_build
|
||||
@ -134,6 +160,22 @@ OUT
|
||||
unstub python-build
|
||||
}
|
||||
|
||||
@test "list available versions with --bare" {
|
||||
stub_python_build_lib
|
||||
stub_python_build "--definitions : echo 2.6.9 2.7.9-rc1 2.7.9-rc2 3.4.2 | tr ' ' $'\\n'"
|
||||
|
||||
run pyenv-install --list --bare
|
||||
assert_success
|
||||
assert_output <<OUT
|
||||
2.6.9
|
||||
2.7.9-rc1
|
||||
2.7.9-rc2
|
||||
3.4.2
|
||||
OUT
|
||||
|
||||
unstub python-build
|
||||
}
|
||||
|
||||
@test "upgrade instructions given for a nonexistent version" {
|
||||
stub brew false
|
||||
stub_python_build_lib
|
||||
@ -239,6 +281,7 @@ OUT
|
||||
run pyenv-install --complete
|
||||
assert_success
|
||||
assert_output <<OUT
|
||||
--bare
|
||||
--list
|
||||
--force
|
||||
--skip-existing
|
||||
|
||||
@ -15,62 +15,25 @@ Under the hood, `pyenv` test suites use `bats` as a test framework and are run o
|
||||
- `test`
|
||||
- Run the whole test suite on the local host
|
||||
- `test-docker`
|
||||
- Run the whole test suite on docker
|
||||
- Some volumes are used in read-only mode
|
||||
- Run the whole test suite in Docker (in all environments)
|
||||
- `test-unit`
|
||||
- Run the unit test
|
||||
- `test-plugin`
|
||||
- Run the plugin test
|
||||
- `test-unit-docker-[BASH_VERSION]`
|
||||
- Run the unit test under **official** bash docker container (alpine/busybox) with the specified bash version if present is in the `Makefile`
|
||||
- Some volumes are used in read-only mode
|
||||
- `test-unit-docker-gnu-[BASH_VERSION]`
|
||||
- Run the unit test under **official** bash docker container (alpine/busybox), completed by **GNU Tools**, with the specified bash version if present is in the `Makefile`
|
||||
- Some volumes are used in read-only mode
|
||||
- `test-plugin-docker-[BASH_VERSION]`
|
||||
- Run the plugin test under **official** bash docker container (alpine/busybox), completed by **GNU Tools**, with the specified bash version if present is in the `Makefile`
|
||||
- Some volumes are used in read-only mode
|
||||
- `test-plugin-docker-gnu-[BASH_VERSION]`
|
||||
- Run the plugin test under **official** bash docker container (alpine/busybox), completed by **GNU Tools**, with the specified bash version if present is in the `Makefile`
|
||||
- Some volumes are used in read-only mode
|
||||
- Run core tests
|
||||
- `test-python-build`
|
||||
- Run Python-Build tests
|
||||
- `test-binary`
|
||||
- Run Pyenv-Binary tests
|
||||
- `test-*-docker`
|
||||
- Run the corresponding test suite in Docker
|
||||
- `test-*-docker-[BASH_VERSION]`, `test-*-docker-gnu-[BASH_VERSION]`
|
||||
- Run the corresponding test suite in the official Bash Docker container (alpine/busybox)
|
||||
against either Busybox tools or GNU tools, with the specified Bash version
|
||||
among those listed in the `Makefile`
|
||||
|
||||
## Targeting specific test / test file
|
||||
|
||||
By setting some environment variables, it is possible to filtering which test and/or test file who will be tested with bats
|
||||
By setting some environment variables, it is possible to filter which test and/or test file will be run
|
||||
|
||||
- `BATS_FILE_FILTER`
|
||||
|
||||
- Run test only with the specified file
|
||||
|
||||
- Run tests from the specified file
|
||||
- `BATS_TEST_FILTER`
|
||||
- Run test only who corresponding to the filter provided
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
$ BATS_TEST_FILTER=".*installed.*" BATS_FILE_FILTER="build.bats" make test-plugin-docker-gnu-3.2.57
|
||||
build.bats
|
||||
✓ yaml is installed for python
|
||||
✓ homebrew is used in Linux if Pyenv is installed with Homebrew
|
||||
✓ homebrew is not used in Linux if Pyenv is not installed with Homebrew
|
||||
|
||||
3 tests, 0 failures
|
||||
|
||||
$ BATS_TEST_FILTER=".*installed.*" BATS_FILE_FILTER="build.bats" make test-plugin
|
||||
build.bats
|
||||
✓ yaml is installed for python
|
||||
✓ homebrew is used in Linux if Pyenv is installed with Homebrew
|
||||
✓ homebrew is not used in Linux if Pyenv is not installed with Homebrew
|
||||
|
||||
3 tests, 0 failures
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Writing test
|
||||
|
||||
To be reproducible, each test use/should use its own `TMPDIR` .
|
||||
It's achieved by using the environment variable `BATS_TEST_TMPDIR` provided by bats that is automatically deleted at the end of each test. More info [here](https://bats-core.readthedocs.io/en/stable/writing-tests.html#special-variables)
|
||||
|
||||
Another variable who could be used to source some file who need to be tested is `BATS_TEST_DIRNAME` who point to the directory in which the bats test file is located.
|
||||
- Run tests with the names corresponding to the filter
|
||||
|
||||
@ -16,6 +16,14 @@ load test_helper
|
||||
assert_output "1.2.3"
|
||||
}
|
||||
|
||||
@test "version with glob characters is handled correctly" {
|
||||
cd "$BATS_TEST_TMPDIR"; touch 1.1
|
||||
mkdir -p "$PYENV_ROOT"
|
||||
echo "[1-9].?*" > "$PYENV_ROOT/version"
|
||||
run pyenv-global
|
||||
assert_success "[1-9].?*"
|
||||
}
|
||||
|
||||
@test "set PYENV_ROOT/version" {
|
||||
mkdir -p "$PYENV_ROOT/versions/1.2.3"
|
||||
run pyenv-global "1.2.3"
|
||||
|
||||
@ -19,6 +19,13 @@ _setup() {
|
||||
assert_success "1.2.3"
|
||||
}
|
||||
|
||||
@test "version with glob characters is handled correctly" {
|
||||
cd "$BATS_TEST_TMPDIR"; touch 1.1
|
||||
echo "[1-9].?*" > .python-version
|
||||
run pyenv-local
|
||||
assert_success "[1-9].?*"
|
||||
}
|
||||
|
||||
@test "discovers version file in parent directory" {
|
||||
echo "1.2.3" > .python-version
|
||||
mkdir -p "subdir" && cd "subdir"
|
||||
|
||||
@ -16,6 +16,12 @@ load test_helper
|
||||
assert_failure "pyenv: version \`1.2.3' not installed"
|
||||
}
|
||||
|
||||
@test "version with glob characters is handled correctly" {
|
||||
cd "$BATS_TEST_TMPDIR"; touch 1.1
|
||||
PYENV_VERSION="[1-9].?*" run pyenv-prefix
|
||||
assert_failure "pyenv: version \`[1-9].?*' not installed"
|
||||
}
|
||||
|
||||
@test "prefix for system" {
|
||||
mkdir -p "${PYENV_TEST_DIR}/bin"
|
||||
touch "${PYENV_TEST_DIR}/bin/python"
|
||||
|
||||
@ -35,7 +35,7 @@ setup() {
|
||||
unset xdg_var
|
||||
|
||||
# Workaround for Powershell. When tests are run from a terminal,
|
||||
# and running a script fron a here-document,
|
||||
# and running a script from a here-document,
|
||||
# Powershell 7.5.4 erroneously prints ANSI escape sequences
|
||||
# even if its output is redirected, breaking the comparison logic
|
||||
export NO_COLOR=1
|
||||
|
||||
@ -22,6 +22,14 @@ _setup() {
|
||||
assert_success "system"
|
||||
}
|
||||
|
||||
@test "version with glob characters is handled correctly" {
|
||||
cd "$BATS_TEST_TMPDIR"; touch 1.1
|
||||
PYENV_VERSION="[1-9].?*" run pyenv-version-name
|
||||
assert_failure <<'!'
|
||||
pyenv: version `[1-9].?*' is not installed (set by PYENV_VERSION environment variable)
|
||||
!
|
||||
}
|
||||
|
||||
@test "PYENV_VERSION can be overridden by hook" {
|
||||
create_version "2.7.11"
|
||||
create_version "3.5.1"
|
||||
|
||||
@ -79,3 +79,12 @@ OUT
|
||||
3.3.3
|
||||
OUT
|
||||
}
|
||||
|
||||
@test "version with glob characters is handled correctly" {
|
||||
cd "$BATS_TEST_TMPDIR"; touch 1.1
|
||||
PYENV_VERSION="[1-9].?*" run pyenv-version
|
||||
assert_failure
|
||||
assert_output <<OUT
|
||||
pyenv: version \`[1-9].?*' is not installed (set by PYENV_VERSION environment variable)
|
||||
OUT
|
||||
}
|
||||
|
||||
@ -63,6 +63,18 @@ OUT
|
||||
assert_success "3.3"
|
||||
}
|
||||
|
||||
@test "version with glob characters is handled correctly" {
|
||||
stub_system_python
|
||||
create_version "[1-9].?*"
|
||||
cd "$BATS_TEST_TMPDIR"; touch 1.1
|
||||
run pyenv-versions
|
||||
assert_success
|
||||
assert_output <<OUT
|
||||
* system (set by ${PYENV_ROOT}/version)
|
||||
[1-9].?*
|
||||
OUT
|
||||
}
|
||||
|
||||
@test "multiple versions and envs" {
|
||||
stub_system_python
|
||||
create_version "2.7.6"
|
||||
|
||||
@ -90,6 +90,17 @@ Note: See 'pyenv help global' for tips on allowing multiple
|
||||
OUT
|
||||
}
|
||||
|
||||
@test "version with glob characters is handled correctly" {
|
||||
bats_require_minimum_version 1.5.0
|
||||
cd "$BATS_TEST_TMPDIR"; touch 1.1
|
||||
PATH="$(path_without foo)" PYENV_VERSION="[1-9].?*" run -127 pyenv-which foo
|
||||
assert_failure
|
||||
assert_output <<!
|
||||
pyenv: version \`[1-9].?*' is not installed (set by PYENV_VERSION environment variable)
|
||||
pyenv: foo: command not found
|
||||
!
|
||||
}
|
||||
|
||||
@test "no executable found" {
|
||||
bats_require_minimum_version 1.5.0
|
||||
create_alt_executable_in_version "2.7" "py.test"
|
||||
@ -181,7 +192,7 @@ exit
|
||||
assert_success "version=3.4.2"
|
||||
}
|
||||
|
||||
@test "skip advice supresses error messages" {
|
||||
@test "skip advice suppresses error messages" {
|
||||
bats_require_minimum_version 1.5.0
|
||||
create_alt_executable_in_version "2.7" "python"
|
||||
create_alt_executable_in_version "3.3" "py.test"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user