mirror of
https://github.com/pyenv/pyenv.git
synced 2026-09-11 16:16:17 -04:00
Compare commits
No commits in common. "master" and "v1.2.0" have entirely different histories.
|
|
@ -1,4 +0,0 @@
|
|||
.git/
|
||||
.python-version
|
||||
.vscode/
|
||||
.idea/
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
|
||||
# Makefiles always use tabs for indentation
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
indent_size = unset # Allow user-defined tab width
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
|
@ -1 +0,0 @@
|
|||
* @pyenv/pyenv-core-maintainers @pyenv/pyenv-core-committers
|
||||
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
|
|
@ -1,12 +0,0 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [pyenv] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: pyenv # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
54
.github/ISSUE_TEMPLATE/bug_report.md
vendored
54
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,54 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Prerequisites
|
||||
* [ ] Make sure your problem is not listed in [the common build problems](https://github.com/pyenv/pyenv/wiki/Common-build-problems).
|
||||
* [ ] Make sure no duplicated issue has already been reported in [the pyenv issues](https://github.com/pyenv/pyenv/issues?q=is%3Aissue). For build errors, a reported issue typically mentions a key error message. This key error message is often not in the 10 last build log lines reported to the console but is rather earlier in the build log -- typically, it's the first error message encountered in the log.
|
||||
* [ ] Make sure you are reporting a problem in Pyenv and not seeking consultation with Pyenv usage.
|
||||
* GitHub issues are intended mainly for Pyenv development purposes. If you are seeking help with Pyenv usage, check [Pyenv documentation](https://github.com/pyenv/pyenv?tab=readme-ov-file#simple-python-version-management-pyenv), go to a user community site like [Gitter](https://gitter.im/yyuu/pyenv), [StackOverflow](https://stackoverflow.com/questions/tagged/pyenv), etc, or to [Discussions](https://github.com/orgs/pyenv/discussions).
|
||||
* [ ] Make sure your problem is not derived from packaging (e.g. [Homebrew](https://brew.sh)).
|
||||
* Please refer to the package documentation for the installation issues, etc.
|
||||
* [ ] Make sure your problem is not derived from plugins.
|
||||
* This repository is maintaining `pyenv` and the default `python-build` plugin only. Please refrain from reporting issues of other plugins here.
|
||||
|
||||
### Describe the bug
|
||||
A clear and concise description of what the bug is.
|
||||
Do specify what the expected behaviour is if that's not obvious from the bug's nature.
|
||||
|
||||
#### Reproduction steps
|
||||
Listing the commands to run in a new console session and their output is usually sufficient.
|
||||
Please use a Markdown code block (three backticks on a line by themselves before and after the text) to denote a console output excerpt.
|
||||
Usually not needed for build errors (since the arguments can already be seen in the debug trace) unless you are using an unusual invocation (e.g. setting environment variables that affect the build).
|
||||
|
||||
#### Diagnostic details
|
||||
- [ ] Platform information (e.g. Ubuntu Linux 24.04):
|
||||
- [ ] OS architecture (e.g. amd64):
|
||||
- [ ] pyenv version:
|
||||
- [ ] Python version:
|
||||
- [ ] C Compiler information (e.g. gcc 7.3):
|
||||
- [ ] Please attach the debug trace of the failing command as a [gist](https://gist.github.com/):
|
||||
* Run `env PYENV_DEBUG=1 <faulty command> 2>&1 | tee trace.log` and attach `trace.log`. E.g. if you have a problem with installing Python, run `env PYENV_DEBUG=1 pyenv install -v <version> 2>&1 | tee trace.log` (note the `-v` option to `pyenv install`).
|
||||
- [ ] If you have a problem with installing Python, please also attach `config.log` from the build directory
|
||||
* The build directory is reported after the "BUILD FAILED" message and is usually under `/tmp`.
|
||||
- [ ] If the build succeeds but the problem is still with the build process (e.g. the resulting Python is missing a feature), please attach
|
||||
* the debug trace from reinstalling the faulty version with `env PYENV_DEBUG=1 pyenv install -f -k -v <version> 2>&1 | tee trace.log`
|
||||
* `config.log` from the build directory. When using `pyenv install` with `-k` as per above, the build directory will be under `$PYENV_ROOT/sources`.
|
||||
- [ ] If the problem happens in another Pyenv invocation, turn on debug logging by setting `PYENV_DEBUG=1`, e.g. `env PYENV_DEBUG=1 pyenv local 3.6.4`, and attach the resulting trace as a gist
|
||||
- [ ] If the problem happens outside of a Pyenv invocation, get the debug trace like this:
|
||||
```
|
||||
export PYENV_DEBUG=1
|
||||
# for Bash
|
||||
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
||||
# for Zsh
|
||||
export PS4='+(%x:%I): %N(%i): '
|
||||
|
||||
set -x
|
||||
<reproduce the problem>
|
||||
set +x
|
||||
```
|
||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the intended use case**
|
||||
* What your general environment is if it's relevant to the feature request and is not a generic console with a typical Pyenv installation (CI, server with a custom setup, cloud environment, IDE)
|
||||
* What you are trying to achieve
|
||||
* What specifically you are doing for that regarding Pyenv
|
||||
* Where you are stuck
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
[ ] In particular, did you consider [writing a plugin](https://github.com/pyenv/pyenv/blob/master/README.md#pyenv-plugins)? Note that if your plugin has general applicability, you can publish it in the 3rd-party plugin catalog on the Pyenv Wiki as per the link above.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,13 +0,0 @@
|
|||
Make sure you have checked all steps below.
|
||||
|
||||
### Prerequisite
|
||||
* [ ] Please consider implementing the feature as a hook script or plugin as a first step.
|
||||
* pyenv has some powerful support for plugins and hook scripts. Please refer to [Authoring plugins](https://github.com/pyenv/pyenv/wiki/Authoring-plugins) for details and try to implement it as a plugin if possible.
|
||||
* [ ] My PR addresses the following pyenv issue (if any)
|
||||
- Closes https://github.com/pyenv/pyenv/issues/XXXX
|
||||
|
||||
### Description
|
||||
- [ ] Here are some details about my PR
|
||||
|
||||
### Tests
|
||||
- [ ] My PR adds the following unit tests (if any)
|
||||
44
.github/actions/build-python/action.yml
vendored
44
.github/actions/build-python/action.yml
vendored
|
|
@ -1,44 +0,0 @@
|
|||
name: Build a Python version
|
||||
description: >
|
||||
Install python-build's system dependencies for the current runner's OS, build
|
||||
and check the requested version.
|
||||
inputs:
|
||||
python-version:
|
||||
description: A version as accepted by `pyenv install`.
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
#envvars
|
||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||
echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV
|
||||
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
|
||||
# The two OSes differ only in this step.
|
||||
- if: runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
#prerequisites
|
||||
brew install openssl readline sqlite3 xz tcl-tk@8 libb2 zstd
|
||||
- if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
#prerequisites
|
||||
pyenv install-prerequisites
|
||||
- shell: bash
|
||||
run: |
|
||||
#build
|
||||
pyenv --debug install ${{ inputs.python-version }} && rc=$? || rc=$?
|
||||
if [[ $rc -ne 0 ]]; then echo config.log:; cat "${TMPDIR:-/tmp}"/python-build*/*/config.log; false; fi
|
||||
pyenv global ${{ inputs.python-version }}
|
||||
pyenv rehash
|
||||
- shell: bash
|
||||
run: |
|
||||
#print version
|
||||
python --version
|
||||
python -m pip --version
|
||||
- shell: python # Prove that actual Python == expected Python
|
||||
env:
|
||||
EXPECTED_PYTHON: ${{ inputs.python-version }}
|
||||
run: import os, sys ; assert sys.version.startswith(os.getenv("EXPECTED_PYTHON"))
|
||||
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
|
|
@ -1,12 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
364
.github/scripts/generate_release_notes_sponsors.py
vendored
364
.github/scripts/generate_release_notes_sponsors.py
vendored
|
|
@ -1,364 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate a "Sponsors since <date>" section for release notes.
|
||||
|
||||
Queries GitHub Sponsors and OpenCollective for new sponsors since the latest
|
||||
pyenv release or one month ago, whichever is longer. The output is Markdown
|
||||
suitable for GitHub Releases.
|
||||
|
||||
Requirements:
|
||||
* Python 3.8+
|
||||
* The ``gh`` CLI authenticated with the ``read:user`` scope.
|
||||
* Network access to https://opencollective.com.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import calendar
|
||||
import datetime
|
||||
import json
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import typing
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
GITHUB_ORG = "pyenv"
|
||||
OPENCOLLECTIVE_MEMBERS_URL = "https://opencollective.com/pyenv/members.json"
|
||||
|
||||
|
||||
class SponsorDataError(RuntimeError):
|
||||
"""Raised when a sponsors data source cannot be queried or parsed."""
|
||||
|
||||
|
||||
def parse_date(value: str) -> datetime.date:
|
||||
return datetime.datetime.fromisoformat(value).date()
|
||||
|
||||
|
||||
def one_month_ago(today: typing.Optional[datetime.date] = None) -> datetime.date:
|
||||
today = today or datetime.date.today()
|
||||
year = today.year
|
||||
month = today.month - 1
|
||||
if month == 0:
|
||||
year -= 1
|
||||
month = 12
|
||||
try:
|
||||
return datetime.date(year, month, today.day)
|
||||
except ValueError:
|
||||
last_day = calendar.monthrange(year, month)[1]
|
||||
return datetime.date(year, month, last_day)
|
||||
|
||||
|
||||
def latest_release_date() -> datetime.date:
|
||||
"""Return the publish date of the latest GitHub release."""
|
||||
latest_release_error = None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["gh", "api", f"repos/{GITHUB_ORG}/{GITHUB_ORG}/releases/latest"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
data = json.loads(result.stdout)
|
||||
published = data["published_at"].replace("Z", "+00:00")
|
||||
return datetime.datetime.fromisoformat(published).date()
|
||||
except (
|
||||
subprocess.CalledProcessError,
|
||||
OSError,
|
||||
json.JSONDecodeError,
|
||||
KeyError,
|
||||
) as exc:
|
||||
latest_release_error = exc
|
||||
|
||||
try:
|
||||
tag = subprocess.run(
|
||||
["git", "describe", "--tags", "--abbrev=0"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout.strip()
|
||||
date_str = subprocess.run(
|
||||
["git", "log", "-1", "--format=%cI", tag],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout.strip()
|
||||
return datetime.datetime.fromisoformat(date_str).date()
|
||||
except (subprocess.CalledProcessError, OSError) as exc:
|
||||
detail = ""
|
||||
if latest_release_error is not None:
|
||||
detail = f" GitHub release lookup failed first: {latest_release_error}."
|
||||
raise SponsorDataError(
|
||||
"Could not determine the latest release date. "
|
||||
"Pass --since explicitly or run from a clone with release tags."
|
||||
f"{detail}"
|
||||
) from exc
|
||||
|
||||
|
||||
def compute_since_date(explicit_since: typing.Optional[datetime.date]) -> datetime.date:
|
||||
if explicit_since is not None:
|
||||
return explicit_since
|
||||
return min(latest_release_date(), one_month_ago())
|
||||
|
||||
|
||||
def github_sponsors(since: datetime.date) -> typing.List[typing.Dict]:
|
||||
"""Return GitHub Sponsors created on or after *since*."""
|
||||
query = """
|
||||
query($org: String!, $after: String) {
|
||||
organization(login: $org) {
|
||||
sponsorshipsAsMaintainer(
|
||||
first: 100,
|
||||
after: $after,
|
||||
activeOnly: false,
|
||||
orderBy: {field: CREATED_AT, direction: DESC}
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
createdAt
|
||||
sponsorEntity {
|
||||
... on User { login, name }
|
||||
... on Organization { login, name }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
since_dt = datetime.datetime.combine(
|
||||
since, datetime.time.min, tzinfo=datetime.timezone.utc
|
||||
)
|
||||
sponsors = []
|
||||
cursor = None
|
||||
while True:
|
||||
command = [
|
||||
"gh",
|
||||
"api",
|
||||
"graphql",
|
||||
"-F",
|
||||
f"org={GITHUB_ORG}",
|
||||
"-f",
|
||||
f"query={query}",
|
||||
]
|
||||
if cursor is not None:
|
||||
command.extend(["-F", f"after={cursor}"])
|
||||
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
detail = result.stderr.strip() or result.stdout.strip() or "no output"
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query failed. "
|
||||
"Check that `gh auth status` shows access to the pyenv org "
|
||||
"and that the token has the scopes required to read sponsorships. "
|
||||
f"`gh api graphql` exited {result.returncode}: {detail}"
|
||||
)
|
||||
|
||||
try:
|
||||
data = json.loads(result.stdout)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned invalid JSON."
|
||||
) from exc
|
||||
|
||||
if "errors" in data:
|
||||
raise SponsorDataError(
|
||||
f"GitHub Sponsors query returned errors: {data['errors']}"
|
||||
)
|
||||
|
||||
try:
|
||||
sponsorships = data["data"]["organization"]["sponsorshipsAsMaintainer"]
|
||||
except (TypeError, KeyError) as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned an unexpected response shape."
|
||||
) from exc
|
||||
try:
|
||||
nodes = sponsorships["nodes"]
|
||||
page_info = sponsorships["pageInfo"]
|
||||
except (TypeError, KeyError) as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned incomplete pagination data."
|
||||
) from exc
|
||||
|
||||
for node in nodes:
|
||||
try:
|
||||
created = datetime.datetime.fromisoformat(
|
||||
node["createdAt"].replace("Z", "+00:00")
|
||||
)
|
||||
entity = node["sponsorEntity"]
|
||||
login = entity["login"]
|
||||
except (AttributeError, KeyError, TypeError, ValueError) as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned an unexpected sponsor record."
|
||||
) from exc
|
||||
if created < since_dt:
|
||||
return sponsors
|
||||
sponsors.append({
|
||||
"login": login,
|
||||
"name": entity.get("name") or login,
|
||||
})
|
||||
|
||||
try:
|
||||
has_next_page = page_info["hasNextPage"]
|
||||
cursor = page_info["endCursor"]
|
||||
except (TypeError, KeyError) as exc:
|
||||
raise SponsorDataError(
|
||||
"GitHub Sponsors query returned incomplete pagination data."
|
||||
) from exc
|
||||
if not has_next_page:
|
||||
return sponsors
|
||||
|
||||
|
||||
def opencollective_sponsors(since: datetime.date, data: typing.Union[str, None]) -> typing.List[typing.Dict]:
|
||||
"""Return OpenCollective backers active on or after *since*."""
|
||||
if data is None:
|
||||
req = urllib.request.Request(
|
||||
f"{OPENCOLLECTIVE_MEMBERS_URL}?limit=1000",
|
||||
headers={"User-Agent": f"{GITHUB_ORG}/release-notes-sponsors"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
data = resp.read()
|
||||
except urllib.error.HTTPError as exc:
|
||||
raise SponsorDataError(
|
||||
f"OpenCollective sponsors query failed for {OPENCOLLECTIVE_MEMBERS_URL}: "
|
||||
f"HTTP {exc.code} {exc.reason}"
|
||||
) from exc
|
||||
except urllib.error.URLError as exc:
|
||||
raise SponsorDataError(
|
||||
f"OpenCollective sponsors query failed for {OPENCOLLECTIVE_MEMBERS_URL}: "
|
||||
f"{exc.reason}"
|
||||
) from exc
|
||||
try:
|
||||
members = json.loads(data)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise SponsorDataError(
|
||||
"OpenCollective sponsors query returned invalid JSON."
|
||||
) from exc
|
||||
|
||||
since_dt = datetime.datetime.combine(since, datetime.time.min)
|
||||
sponsors = []
|
||||
for member in members:
|
||||
if member.get("role") != "BACKER":
|
||||
continue
|
||||
try:
|
||||
last_transaction_at = datetime.datetime.strptime(member["lastTransactionAt"], "%Y-%m-%d %H:%M")
|
||||
profile = member["profile"].rstrip("/")
|
||||
except (KeyError, TypeError, ValueError) as exc:
|
||||
raise SponsorDataError(
|
||||
"OpenCollective sponsors query returned an unexpected member record."
|
||||
) from exc
|
||||
if last_transaction_at < since_dt:
|
||||
continue
|
||||
slug = profile.split("/")[-1]
|
||||
if slug == "github-sponsors":
|
||||
# Listed separately under GitHub Sponsors.
|
||||
continue
|
||||
sponsors.append({
|
||||
"name": member.get("name") or slug,
|
||||
"profile": profile,
|
||||
})
|
||||
return sponsors
|
||||
|
||||
|
||||
def render(
|
||||
since: datetime.date,
|
||||
gh_sponsors: typing.List[typing.Dict],
|
||||
oc_sponsors: typing.List[typing.Dict],
|
||||
) -> str:
|
||||
lines = [f"## Sponsors since {since.isoformat()}", ""]
|
||||
|
||||
if gh_sponsors:
|
||||
lines.append("### GitHub Sponsors")
|
||||
for sponsor in gh_sponsors:
|
||||
lines.append(
|
||||
markdown_link(
|
||||
sponsor["name"],
|
||||
f"https://github.com/{sponsor['login']}",
|
||||
)
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
if oc_sponsors:
|
||||
lines.append("### Open Collective")
|
||||
for sponsor in oc_sponsors:
|
||||
lines.append(markdown_link(sponsor["name"], sponsor["profile"]))
|
||||
lines.append("")
|
||||
|
||||
if not gh_sponsors and not oc_sponsors:
|
||||
lines.append("*No new sponsors in this period.*")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def markdown_link(text: str, url: str) -> str:
|
||||
escaped_text = escape_markdown_text(text)
|
||||
escaped_url = url.replace(")", "%29")
|
||||
return f"- [{escaped_text}]({escaped_url})"
|
||||
|
||||
|
||||
def escape_markdown_text(text: str) -> str:
|
||||
escaped = text.replace("\\", "\\\\")
|
||||
for char in r"`*_{}[]()#+-.!|>":
|
||||
escaped = escaped.replace(char, f"\\{char}")
|
||||
return escaped
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a sponsors section for GitHub release notes."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--since",
|
||||
type=parse_date,
|
||||
metavar="YYYY-MM-DD",
|
||||
help="Include sponsors created on or after this date.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
metavar="FILE",
|
||||
help="Write the section to FILE instead of stdout.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-opencollective",
|
||||
action="store_true",
|
||||
help="Skip OpenCollective backers if the members endpoint is unavailable.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--opencollective-data",
|
||||
metavar="FILE",
|
||||
help="Take OpenCollective API reply from FILE.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
since = compute_since_date(args.since)
|
||||
oc_sponsors = []
|
||||
if not args.skip_opencollective:
|
||||
manual_data = (
|
||||
pathlib.Path(args.opencollective_data).read_text()) \
|
||||
if args.opencollective_data \
|
||||
else None
|
||||
oc_sponsors = opencollective_sponsors(since, manual_data)
|
||||
section = render(since, github_sponsors(since), oc_sponsors)
|
||||
except SponsorDataError as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if args.output:
|
||||
with open(args.output, "w", encoding="utf-8") as f:
|
||||
f.write(section)
|
||||
else:
|
||||
print(section, end="")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
58
.github/workflows/add_version.yml
vendored
58
.github/workflows/add_version.yml
vendored
|
|
@ -1,58 +0,0 @@
|
|||
name: Add versions
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
schedule:
|
||||
# Every N hours
|
||||
- cron: '25 */4 * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
add_cpython:
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3
|
||||
cache: 'pip'
|
||||
cache-dependency-path: plugins/python-build/scripts/requirements.txt
|
||||
- run: pip install -r plugins/python-build/scripts/requirements.txt
|
||||
|
||||
- name: check for a release
|
||||
run: |
|
||||
python plugins/python-build/scripts/add_cpython.py --verbose >added_versions.lst && rc=$? || rc=$?
|
||||
#0 means new version found, 1 not found, 2 another error
|
||||
[[ $rc -gt 1 ]] && false
|
||||
echo "rc=$rc" >> $GITHUB_ENV
|
||||
- name: set PR properties
|
||||
if: env.rc == 0
|
||||
shell: python
|
||||
run: |
|
||||
import os
|
||||
import sys
|
||||
versions=[l.rstrip() for l in open("added_versions.lst")]
|
||||
with open(os.environ['GITHUB_ENV'],'a') as f:
|
||||
f.write(f'branch_name=auto_add_version/{"_".join(versions)}\n')
|
||||
f.write(f'pr_name=Add CPython {", ".join(versions)}\n')
|
||||
os.remove("added_versions.lst")
|
||||
|
||||
# https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens
|
||||
- name: Generate Github token
|
||||
if: env.rc == 0
|
||||
uses: actions/create-github-app-token@v3
|
||||
id: generate-token
|
||||
with:
|
||||
app-id: ${{ vars.PYENV_BOT_APP_ID }}
|
||||
private-key: ${{ secrets.PYENV_BOT_PRIVATE_KEY }}
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
if: env.rc == 0
|
||||
with:
|
||||
branch: ${{ env.branch_name }}
|
||||
title: ${{ env.pr_name }}
|
||||
commit-message: ${{ env.pr_name }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
62
.github/workflows/build.yml
vendored
62
.github/workflows/build.yml
vendored
|
|
@ -1,62 +0,0 @@
|
|||
name: build
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
discover_build_logic_change:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-slim
|
||||
env:
|
||||
FULL_OS_MATRIX: '[
|
||||
"macos-14",
|
||||
"macos-15",
|
||||
"macos-15-intel",
|
||||
"macos-26",
|
||||
"macos-26-intel",
|
||||
"ubuntu-22.04",
|
||||
"ubuntu-22.04-arm",
|
||||
"ubuntu-24.04",
|
||||
"ubuntu-24.04-arm"
|
||||
]'
|
||||
outputs:
|
||||
os: ${{ steps.detect.outputs.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: git fetch origin "$GITHUB_BASE_REF"
|
||||
- id: detect
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -n $(git diff --name-only "origin/$GITHUB_BASE_REF" -- \
|
||||
plugins/python-build/bin/python-build \
|
||||
.github/actions/build-python/action.yml \
|
||||
.github/workflows/build.yml) ]]
|
||||
then
|
||||
printf "%s\n" "os<<!" "$FULL_OS_MATRIX" "!" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
build:
|
||||
needs: discover_build_logic_change
|
||||
# Run when the detection job succeeded *or was skipped* -- but not failed
|
||||
if: ${{ !cancelled() && !failure() }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ${{ fromJson(needs.discover_build_logic_change.outputs.os || '["macos-latest","ubuntu-latest"]') }}
|
||||
python-version:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: ./.github/actions/build-python
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
304
.github/workflows/modified_scripts_build.yml
vendored
304
.github/workflows/modified_scripts_build.yml
vendored
|
|
@ -1,304 +0,0 @@
|
|||
name: modified_scripts
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
discover_modified_scripts:
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
versions: ${{steps.modified-versions.outputs.versions}}
|
||||
versions_cpython_only: ${{steps.modified-versions.outputs.versions_cpython_only}}
|
||||
versions_macos_build_exclude: ${{steps.modified-versions.outputs.versions_macos_build_exclude}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: git fetch origin "$GITHUB_BASE_REF"
|
||||
- shell: bash
|
||||
run: >
|
||||
versions=$(git diff "origin/$GITHUB_BASE_REF" --name-only -z
|
||||
| perl -ne 'BEGIN {$\="\n";$/="\0";} chomp;
|
||||
if (/^(plugins\/python-build\/share\/python-build\/)(?:([^\/]+)|patches\/([^\/]+)\/.*)$/ and -e $& )
|
||||
{
|
||||
print $2.$3;
|
||||
if ( -e $1.$2.$3.t ) { print $2.$3.t; }
|
||||
}' \
|
||||
| sort -u);
|
||||
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64);
|
||||
echo "versions<<$EOF" >> $GITHUB_ENV;
|
||||
echo "$versions" >> $GITHUB_ENV;
|
||||
echo "$EOF" >> $GITHUB_ENV;
|
||||
|
||||
versions_cpython_only=$(grep -Ee '^[[:digit:]]' <<<"$versions")
|
||||
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64);
|
||||
echo "versions_cpython_only<<$EOF" >> $GITHUB_ENV;
|
||||
echo "$versions_cpython_only" >> $GITHUB_ENV;
|
||||
echo "$EOF" >> $GITHUB_ENV;
|
||||
- run: pip install packaging
|
||||
- name: Build exclusions JSON
|
||||
shell: python
|
||||
run: |
|
||||
import os
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import packaging.version
|
||||
|
||||
result=[]
|
||||
def exclude_macos_intel(result, python_version):
|
||||
result.append({'os':'macos-15-intel','python-version':python_version})
|
||||
result.append({'os':'macos-26-intel','python-version':python_version})
|
||||
|
||||
|
||||
for line in os.environ['versions'].splitlines():
|
||||
if m:=re.match(r'miniconda3-\d+\.\d+-(\d+\.\d+\.\d+)', line):
|
||||
version = packaging.version.Version(m.group(1))
|
||||
|
||||
# Miniconda dropped MacOS x64 support
|
||||
if version >= packaging.version.Version('25.9.1'):
|
||||
exclude_macos_intel(result, line)
|
||||
|
||||
if m:=re.match(r'anaconda3-(\d+\.\d+)', line):
|
||||
version = packaging.version.Version(m.group(1))
|
||||
|
||||
# Anaconda dropped MacOS x64 support
|
||||
if version >= packaging.version.Version('2025.12'):
|
||||
exclude_macos_intel(result, line)
|
||||
|
||||
if m:=re.match(r'graalpy[^-]*-(community-)?(\d+\.\d+\.\d+)', line):
|
||||
version = packaging.version.Version(m.group(2))
|
||||
|
||||
# GraalPy dropped MacOS x64 support
|
||||
if version >= packaging.version.Version('25.0.2'):
|
||||
exclude_macos_intel(result, line)
|
||||
|
||||
|
||||
EOF = str(random.getrandbits(15*8))
|
||||
|
||||
with open(os.environ['GITHUB_ENV'],'w') as f:
|
||||
f.write(f"versions_macos_build_exclude_json<<{EOF}\n")
|
||||
json.dump(result, f)
|
||||
f.write(f'\n{EOF}\n')
|
||||
|
||||
- id: modified-versions
|
||||
run: |
|
||||
echo "versions=`echo "${{ env.versions }}" | jq -R . | jq -sc .`" >> $GITHUB_OUTPUT
|
||||
echo "versions_cpython_only=`echo "${{ env.versions_cpython_only }}" | jq -R . | jq -sc .`" >> $GITHUB_OUTPUT
|
||||
echo 'versions_macos_build_exclude=${{ env.versions_macos_build_exclude_json }}' >> $GITHUB_OUTPUT
|
||||
macos_build:
|
||||
needs: discover_modified_scripts
|
||||
if: needs.discover_modified_scripts.outputs.versions != '[""]'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions)}}
|
||||
os:
|
||||
- macos-14
|
||||
- macos-15
|
||||
- macos-15-intel
|
||||
- macos-26
|
||||
- macos-26-intel
|
||||
exclude: ${{fromJson(needs.discover_modified_scripts.outputs.versions_macos_build_exclude)}}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
#envvars
|
||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||
echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV
|
||||
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
|
||||
- run: |
|
||||
#prerequisites
|
||||
brew install openssl readline sqlite3 xz tcl-tk@8 libb2 zstd
|
||||
if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then
|
||||
export PYENV_BOOTSTRAP_VERSION=pypy2.7-7
|
||||
echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV
|
||||
pyenv install $PYENV_BOOTSTRAP_VERSION
|
||||
fi
|
||||
- run: |
|
||||
#build
|
||||
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
|
||||
if [[ $rc -ne 0 ]]; then echo config.log:; cat $TMPDIR/python-build*/*/config.log; false; fi
|
||||
pyenv global ${{ matrix.python-version }}
|
||||
# Micropython doesn't support --version
|
||||
- run: |
|
||||
#print version
|
||||
if [[ "${{ matrix.python-version }}" == "micropython-"* ]]; then
|
||||
python -c 'import sys; print(sys.version)'
|
||||
else
|
||||
python --version
|
||||
python -m pip --version
|
||||
fi
|
||||
# Micropython doesn't support sys.executable, os.path, older versions even os
|
||||
- env:
|
||||
EXPECTED_PYTHON: ${{ matrix.python-version }}
|
||||
run: |
|
||||
#check
|
||||
if [[ "${{ matrix.python-version }}" == "micropython-"* ]]; then
|
||||
[[ $(pyenv which python) == "${{ env.PYENV_ROOT }}/versions/${{ matrix.python-version }}/bin/python" ]] || exit 1
|
||||
python -c 'import sys; assert sys.implementation.name == "micropython"'
|
||||
else
|
||||
python -c 'if True:
|
||||
import os, sys, os.path
|
||||
correct_dir = os.path.join(
|
||||
os.environ["PYENV_ROOT"],
|
||||
"versions",
|
||||
os.environ["EXPECTED_PYTHON"],
|
||||
"bin")
|
||||
assert os.path.dirname(sys.executable) == correct_dir'
|
||||
fi
|
||||
# bundled executables in some Anaconda releases cause the post-run step to hang in MacOS
|
||||
- run: |
|
||||
pyenv global system
|
||||
rm -f "$(pyenv root)"/shims/*
|
||||
|
||||
|
||||
macos_build_bundled_dependencies:
|
||||
needs: discover_modified_scripts
|
||||
if: needs.discover_modified_scripts.outputs.versions_cpython_only != '[""]'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions_cpython_only)}}
|
||||
os:
|
||||
- macos-14
|
||||
- macos-15
|
||||
- macos-15-intel
|
||||
- macos-26
|
||||
- macos-26-intel
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
#envvars
|
||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||
echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV
|
||||
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
|
||||
- run: |
|
||||
#prerequisites
|
||||
brew install sqlite3 xz tcl-tk@8 libb2 zstd
|
||||
"$GITHUB_WORKSPACE/.github/workflows/scripts/brew-uninstall-cascade.sh" $(brew list | grep -E '^openssl(@|$)') readline
|
||||
- run: |
|
||||
#build
|
||||
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
|
||||
if [[ $rc -ne 0 ]]; then echo config.log:; cat $TMPDIR/python-build*/*/config.log; false; fi
|
||||
pyenv global ${{ matrix.python-version }}
|
||||
- run: |
|
||||
#print version
|
||||
python --version
|
||||
python -m pip --version
|
||||
- env:
|
||||
EXPECTED_PYTHON: ${{ matrix.python-version }}
|
||||
run: |
|
||||
#check
|
||||
python -c '
|
||||
import os, sys, os.path
|
||||
correct_dir = os.path.join(
|
||||
os.environ["PYENV_ROOT"],
|
||||
"versions",
|
||||
os.environ["EXPECTED_PYTHON"],
|
||||
"bin")
|
||||
assert os.path.dirname(sys.executable) == correct_dir'
|
||||
|
||||
ubuntu_build:
|
||||
needs: discover_modified_scripts
|
||||
if: needs.discover_modified_scripts.outputs.versions != '[""]'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions)}}
|
||||
os:
|
||||
- ubuntu-22.04
|
||||
- ubuntu-22.04-arm
|
||||
- ubuntu-24.04
|
||||
- ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
#envvars
|
||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||
echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV
|
||||
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
|
||||
- run: |
|
||||
#prerequisites
|
||||
pyenv install-prerequisites
|
||||
if [[ "${{ matrix.python-version }}" =~ pypy.*-(src|dev) ]]; then
|
||||
export PYENV_BOOTSTRAP_VERSION=pypy2.7-7
|
||||
echo "PYENV_BOOTSTRAP_VERSION=$PYENV_BOOTSTRAP_VERSION" >> $GITHUB_ENV
|
||||
pyenv install $PYENV_BOOTSTRAP_VERSION
|
||||
fi
|
||||
- run: |
|
||||
#build
|
||||
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
|
||||
if [[ $rc -ne 0 ]]; then echo config.log:; cat $TMPDIR/python-build*/*/config.log; false; fi
|
||||
pyenv global ${{ matrix.python-version }}
|
||||
# Micropython doesn't support --version
|
||||
- run: |
|
||||
#print version
|
||||
if [[ "${{ matrix.python-version }}" == "micropython-"* ]]; then
|
||||
python -c 'import sys; print(sys.version)'
|
||||
else
|
||||
python --version
|
||||
python -m pip --version
|
||||
fi
|
||||
# Micropython doesn't support sys.executable, os.path, older versions even os
|
||||
- env:
|
||||
EXPECTED_PYTHON: ${{ matrix.python-version }}
|
||||
run: |
|
||||
#check
|
||||
if [[ "${{ matrix.python-version }}" == "micropython-"* ]]; then
|
||||
[[ $(pyenv which python) == "${{ env.PYENV_ROOT }}/versions/${{ matrix.python-version }}/bin/python" ]] || exit 1
|
||||
python -c 'import sys; assert sys.implementation.name == "micropython"'
|
||||
else
|
||||
python -c 'if True:
|
||||
import os, sys, os.path
|
||||
correct_dir = os.path.join(
|
||||
os.environ["PYENV_ROOT"],
|
||||
"versions",
|
||||
os.environ["EXPECTED_PYTHON"],
|
||||
"bin")
|
||||
assert os.path.dirname(sys.executable) == correct_dir'
|
||||
fi
|
||||
# bundled executables in some Anaconda releases cause the post-run step to break
|
||||
- run: |
|
||||
pyenv global system
|
||||
rm -f "$(pyenv root)"/shims/*
|
||||
ubuntu_build_tar_gz:
|
||||
needs: discover_modified_scripts
|
||||
if: needs.discover_modified_scripts.outputs.versions_cpython_only != '[""]'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ${{fromJson(needs.discover_modified_scripts.outputs.versions_cpython_only)}}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
#envvars
|
||||
export PYENV_ROOT="$GITHUB_WORKSPACE"
|
||||
echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV
|
||||
echo "$PYENV_ROOT/shims:$PYENV_ROOT/bin" >> $GITHUB_PATH
|
||||
echo "_PYTHON_BUILD_FORCE_SKIP_XZ=1" >> $GITHUB_ENV
|
||||
- run: |
|
||||
#prerequisites
|
||||
pyenv install-prerequisites
|
||||
- run: |
|
||||
#build
|
||||
pyenv --debug install ${{ matrix.python-version }} && rc=$? || rc=$?
|
||||
if [[ $rc -ne 0 ]]; then echo config.log:; cat $TMPDIR/python-build*/*/config.log; false; fi
|
||||
pyenv global ${{ matrix.python-version }}
|
||||
- run: |
|
||||
#print version
|
||||
python --version
|
||||
python -m pip --version
|
||||
- env:
|
||||
EXPECTED_PYTHON: ${{ matrix.python-version }}
|
||||
run: |
|
||||
#check
|
||||
python -c '
|
||||
import os, sys, os.path
|
||||
correct_dir = os.path.join(
|
||||
os.environ["PYENV_ROOT"],
|
||||
"versions",
|
||||
os.environ["EXPECTED_PYTHON"],
|
||||
"bin")
|
||||
assert os.path.dirname(sys.executable) == correct_dir'
|
||||
30
.github/workflows/no-response.yml
vendored
30
.github/workflows/no-response.yml
vendored
|
|
@ -1,30 +0,0 @@
|
|||
name: No Response
|
||||
|
||||
# Both `issue_comment` and `scheduled` event types are required for this Action
|
||||
# to work properly.
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
schedule:
|
||||
# Schedule for ten minutes after the hour, every 2 hours
|
||||
- cron: '10 */2 * * *'
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
noResponse:
|
||||
permissions:
|
||||
issues: write # to update issues (lee-dohm/no-response)
|
||||
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: lee-dohm/no-response@v0.5.0
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
daysUntilClose: 30
|
||||
responseRequiredLabel: need-feedback
|
||||
closeComment: >
|
||||
This issue has been automatically closed because there has been no response
|
||||
to our request for more information from the original author. With only the
|
||||
information that is currently in the issue, we don't have enough information
|
||||
to take action. Please reach out if you have or find the answers we need so
|
||||
that we can investigate further.
|
||||
70
.github/workflows/publish_binary.yml
vendored
70
.github/workflows/publish_binary.yml
vendored
|
|
@ -1,70 +0,0 @@
|
|||
name: Publish a binary package
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: A CPython version accepted by `pyenv install`
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish_ubuntu:
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
PYENV_ROOT: ${{ github.workspace }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Install build prerequisites
|
||||
run: ./bin/pyenv install-prerequisites
|
||||
- name: Build package
|
||||
env:
|
||||
PYTHON_CONFIGURE_OPTS: --enable-optimizations
|
||||
run: |
|
||||
VERSION="${VERSION%%:*}"
|
||||
entry="$(./bin/pyenv binary package-name "$VERSION")"
|
||||
echo "entry=$entry" >> "$GITHUB_ENV"
|
||||
mkdir dist
|
||||
cd dist
|
||||
"$GITHUB_WORKSPACE/bin/pyenv" binary package --verbose \
|
||||
"$VERSION:$entry" \
|
||||
--archive-base-url https://pyenv.github.io/pythons/binaries
|
||||
|
||||
- name: Generate GitHub token
|
||||
uses: actions/create-github-app-token@v3
|
||||
id: generate-token
|
||||
with:
|
||||
app-id: ${{ vars.PYENV_BOT_APP_ID }}
|
||||
private-key: ${{ secrets.PYENV_BOT_PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: pyenv.github.io
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: pyenv/pyenv.github.io
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
path: site
|
||||
- name: Update downloads
|
||||
run: |
|
||||
mkdir -p site/pythons/binaries
|
||||
cp "dist/$entry.tar.gz" "dist/$entry.meta" "dist/$entry" \
|
||||
site/pythons/binaries/
|
||||
cd site/pythons
|
||||
./update.sh
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
path: site
|
||||
branch: auto_add_binary/${{ env.entry }}
|
||||
title: Add CPython ${{ env.entry }}
|
||||
commit-message: Add CPython ${{ env.entry }}
|
||||
body: |
|
||||
Built from pyenv/pyenv@${{ github.sha }} with profile-guided optimization enabled.
|
||||
|
||||
Part of pyenv/pyenv#2334.
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
45
.github/workflows/pyenv_tests.yml
vendored
45
.github/workflows/pyenv_tests.yml
vendored
|
|
@ -1,45 +0,0 @@
|
|||
name: pyenv_tests
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request: {}
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
pyenv_tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-22.04
|
||||
- ubuntu-22.04-arm
|
||||
- ubuntu-24.04
|
||||
- ubuntu-24.04-arm
|
||||
- macos-14
|
||||
- macos-15
|
||||
- macos-15-intel
|
||||
- macos-26
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Install prerequisites
|
||||
run: |
|
||||
if test "$RUNNER_OS" == "macOS"; then
|
||||
brew install coreutils fish
|
||||
fi
|
||||
- name: Run tests
|
||||
run: |
|
||||
make test
|
||||
- env:
|
||||
PYENV_NATIVE_EXT: 1
|
||||
run: |
|
||||
(cd src; ./configure; make)
|
||||
bats/bin/bats test/{pyenv,hooks,versions}.bats
|
||||
pyenv_tests_docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
make test-docker
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
declare -a packages rdepends
|
||||
packages=("$@")
|
||||
|
||||
# have to try one by one, otherwise `brew uses` would only print
|
||||
# packages that require them all rather than any of them
|
||||
for package in "${packages[@]}"; do
|
||||
rdepends+=($(brew uses --installed --include-build --include-test --include-optional --recursive "$package"))
|
||||
done
|
||||
brew uninstall "${packages[@]}" "${rdepends[@]}"
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -6,9 +6,5 @@
|
|||
/libexec/*.dylib
|
||||
/src/Makefile
|
||||
/src/*.o
|
||||
/bats
|
||||
/bats-*/
|
||||
/bats/
|
||||
/default-packages
|
||||
.idea
|
||||
*.un~
|
||||
*.swp
|
||||
|
|
|
|||
17
.travis.yml
Normal file
17
.travis.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
sudo: false
|
||||
install: git clone --depth 1 https://github.com/sstephenson/bats.git
|
||||
script: make test
|
||||
language: c
|
||||
env:
|
||||
- PYENV_NATIVE_EXT=
|
||||
- PYENV_NATIVE_EXT=1
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: FLWevL09KYp7V1SjJUNEdWzuomuocXwNvPr1DSAFH7mmrjKTtjzwrjINAthSqzjlDrs5B//P47l1VLyHp5byEzy673W+bOmEg8swmqc7E9FrHLRyEByd/yca3DzkZgXEXgGdY/cl7tHhM4V2fYKEgAIWbFV+takmTFMK4WkEtNg=
|
||||
on:
|
||||
repo: pyenv/pyenv
|
||||
tags: true
|
||||
1147
CHANGELOG.md
1147
CHANGELOG.md
File diff suppressed because it is too large
Load diff
153
COMMANDS.md
153
COMMANDS.md
|
|
@ -5,7 +5,6 @@ first argument.
|
|||
|
||||
The most common subcommands are:
|
||||
|
||||
* [`pyenv help`](#pyenv-help)
|
||||
* [`pyenv commands`](#pyenv-commands)
|
||||
* [`pyenv local`](#pyenv-local)
|
||||
* [`pyenv global`](#pyenv-global)
|
||||
|
|
@ -17,18 +16,6 @@ The most common subcommands are:
|
|||
* [`pyenv versions`](#pyenv-versions)
|
||||
* [`pyenv which`](#pyenv-which)
|
||||
* [`pyenv whence`](#pyenv-whence)
|
||||
* [`pyenv exec`](#pyenv-exec)
|
||||
* [`pyenv root`](#pyenv-root)
|
||||
* [`pyenv prefix`](#pyenv-prefix)
|
||||
* [`pyenv latest`](#pyenv-latest)
|
||||
* [`pyenv hooks`](#pyenv-hooks)
|
||||
* [`pyenv shims`](#pyenv-shims)
|
||||
* [`pyenv init`](#pyenv-init)
|
||||
* [`pyenv completions`](#pyenv-completions)
|
||||
|
||||
## `pyenv help`
|
||||
|
||||
List all available pyenv commands along with a brief description of what they do. Run `pyenv help <command>` for information on a specific command. For full documentation, see: https://github.com/pyenv/pyenv#readme
|
||||
|
||||
|
||||
## `pyenv commands`
|
||||
|
|
@ -40,8 +27,9 @@ Lists all available pyenv commands.
|
|||
|
||||
Sets a local application-specific Python version by writing the version
|
||||
name to a `.python-version` file in the current directory. This version
|
||||
overrides the [global version](#pyenv-global), and can be overridden
|
||||
itself with the [`pyenv shell`](#pyenv-shell) command.
|
||||
overrides the global version, and can be overridden itself by setting
|
||||
the `PYENV_VERSION` environment variable or with the `pyenv shell`
|
||||
command.
|
||||
|
||||
$ pyenv local 2.7.6
|
||||
|
||||
|
|
@ -90,15 +78,12 @@ or, if you prefer 3.3.3 over 2.7.6,
|
|||
Python 3.3.3
|
||||
|
||||
|
||||
You can use the `-f/--force` flag to force setting versions even if some aren't installed.
|
||||
This is mainly useful in special cases like provisioning scripts.
|
||||
|
||||
|
||||
## `pyenv global`
|
||||
|
||||
Sets the global version of Python to be used in all shells by writing
|
||||
the version name to the `$PYENV_ROOT/version` file. This version can be
|
||||
overridden with [`pyenv local`](#pyenv-local) or [`pyenv shell`](#pyenv-shell).
|
||||
the version name to the `~/.pyenv/version` file. This version can be
|
||||
overridden by an application-specific `.python-version` file, or by
|
||||
setting the `PYENV_VERSION` environment variable.
|
||||
|
||||
$ pyenv global 2.7.6
|
||||
|
||||
|
|
@ -202,8 +187,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>[:<alias>]
|
||||
pyenv install [-f] [-kvp] <definition-file>[:<alias>]
|
||||
Usage: pyenv install [-f] [-kvp] <version>
|
||||
pyenv install [-f] [-kvp] <definition-file>
|
||||
pyenv install -l|--list
|
||||
|
||||
-l/--list List all available versions
|
||||
|
|
@ -231,23 +216,11 @@ Then install the desired versions:
|
|||
2.6.8
|
||||
* 2.7.6 (set by /home/yyuu/.pyenv/version)
|
||||
|
||||
You can also install the latest version of Python in a specific version line by supplying a prefix instead of a complete name:
|
||||
|
||||
$ pyenv install 3.10
|
||||
$ pyenv install 3
|
||||
|
||||
See the [`pyenv latest` documentation](#pyenv-latest) for details on prefix resolution.
|
||||
|
||||
An older option is to use the `:latest` syntax. For example, to install the latest patch version for Python 3.8 you could do:
|
||||
|
||||
pyenv install 3.8:latest
|
||||
|
||||
|
||||
## `pyenv uninstall`
|
||||
|
||||
Uninstall Python versions.
|
||||
Uninstall a specific Python version.
|
||||
|
||||
Usage: pyenv uninstall [-f|--force] <version> ...
|
||||
Usage: pyenv uninstall [-f|--force] <version>
|
||||
|
||||
-f Attempt to remove the specified version without prompting
|
||||
for confirmation. If the version does not exist, do not
|
||||
|
|
@ -268,15 +241,8 @@ version of Python, or install a package that provides binaries.
|
|||
Displays the currently active Python version, along with information on
|
||||
how it was set.
|
||||
|
||||
Usage: pyenv version [--bare]
|
||||
|
||||
--bare show just the version name. An alias to `pyenv version-name'
|
||||
|
||||
|
||||
$ pyenv version
|
||||
2.7.6 (set by /home/yyuu/.pyenv/version)
|
||||
$ pyenv version --bare
|
||||
2.7.6
|
||||
|
||||
|
||||
## `pyenv versions`
|
||||
|
|
@ -301,8 +267,6 @@ you run the given command.
|
|||
$ pyenv which python3.3
|
||||
/home/yyuu/.pyenv/versions/3.3.3/bin/python3.3
|
||||
|
||||
Use --nosystem argument in case when you don't need to search command in the
|
||||
system environment.
|
||||
|
||||
## `pyenv whence`
|
||||
|
||||
|
|
@ -312,100 +276,3 @@ Lists all Python versions with the given command installed.
|
|||
2.6.8
|
||||
2.7.6
|
||||
3.3.3
|
||||
|
||||
## `pyenv exec`
|
||||
|
||||
Usage: pyenv exec <command> [arg1 arg2...]
|
||||
|
||||
Runs an executable by first preparing PATH so that the selected Python
|
||||
version's `bin` directory is at the front.
|
||||
|
||||
For example, if the currently selected Python version is 3.9.7:
|
||||
|
||||
pyenv exec pip install -r requirements.txt
|
||||
|
||||
is equivalent to:
|
||||
|
||||
PATH="$PYENV_ROOT/versions/3.9.7/bin:$PATH" pip install -r requirements.txt
|
||||
|
||||
## `pyenv root`
|
||||
|
||||
Displays the root directory where versions and shims are kept.
|
||||
|
||||
$ pyenv root
|
||||
/home/user/.pyenv
|
||||
|
||||
## `pyenv prefix`
|
||||
|
||||
Displays the directories where the given Python versions are installed,
|
||||
separated by colons. If no version is given, `pyenv prefix` displays the
|
||||
locations of the currently selected versions.
|
||||
|
||||
$ pyenv prefix 3.9.7
|
||||
/home/user/.pyenv/versions/3.9.7
|
||||
|
||||
## `pyenv latest`
|
||||
|
||||
Displays the latest installed or known version with the given prefix
|
||||
|
||||
Usage: pyenv latest [-k|--known] <prefix>
|
||||
|
||||
-k/--known Select from all known versions instead of installed
|
||||
|
||||
Only full prefixes are searched: in the actual name, the given prefix must be followed by a dot or a dash.
|
||||
|
||||
Prereleases and versions with specific suffixes (e.g. `-src`) are ignored.
|
||||
|
||||
## `pyenv hooks`
|
||||
|
||||
Lists installed hook scripts for a given pyenv command.
|
||||
|
||||
Usage: pyenv hooks <command>
|
||||
|
||||
## `pyenv shims`
|
||||
|
||||
List existing pyenv shims.
|
||||
|
||||
Usage: pyenv shims [--short]
|
||||
|
||||
$ pyenv shims
|
||||
/home/user/.pyenv/shims/2to3
|
||||
/home/user/.pyenv/shims/2to3-3.9
|
||||
/home/user/.pyenv/shims/idle
|
||||
/home/user/.pyenv/shims/idle3
|
||||
/home/user/.pyenv/shims/idle3.9
|
||||
/home/user/.pyenv/shims/pip
|
||||
/home/user/.pyenv/shims/pip3
|
||||
/home/user/.pyenv/shims/pip3.9
|
||||
/home/user/.pyenv/shims/pydoc
|
||||
/home/user/.pyenv/shims/pydoc3
|
||||
/home/user/.pyenv/shims/pydoc3.9
|
||||
/home/user/.pyenv/shims/python
|
||||
/home/user/.pyenv/shims/python3
|
||||
/home/user/.pyenv/shims/python3.9
|
||||
/home/user/.pyenv/shims/python3.9-config
|
||||
/home/user/.pyenv/shims/python3.9-gdb.py
|
||||
/home/user/.pyenv/shims/python3-config
|
||||
/home/user/.pyenv/shims/python-config
|
||||
|
||||
## `pyenv init`
|
||||
|
||||
Configure the shell environment for pyenv
|
||||
|
||||
Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [<shell>])"
|
||||
pyenv init --install [<shell>]
|
||||
pyenv init --detect-shell [<shell>]
|
||||
|
||||
- Initialize shims directory, print PYENV_SHELL variable, completions path
|
||||
and shell function
|
||||
--path Print shims path
|
||||
--install Configure detected shell startup files
|
||||
--no-push-path Do not push shim to the start of PATH if they're already there
|
||||
--detect-shell Print shell startup files detected for the current shell
|
||||
--no-rehash Add no rehash command to output
|
||||
|
||||
## `pyenv completions`
|
||||
|
||||
Lists available completions for a given pyenv command.
|
||||
|
||||
Usage: pyenv completions <command> [arg1 arg2...]
|
||||
|
|
|
|||
|
|
@ -67,9 +67,9 @@ members of the project's leadership.
|
|||
|
||||
## Project Maintainers
|
||||
|
||||
* Yamashita, Yuu <<peek824545201@gmail.com>>
|
||||
* Daniel Hahler <<git@thequod.de>>
|
||||
* Josh Friend <<josh@fueledbycaffeine.com>>
|
||||
* Sam Stephenson <<sstephenson@gmail.com>>
|
||||
* Mislav Marohnić <<mislav.marohnic@gmail.com>>
|
||||
* Erik Michaels-Ober <<sferik@gmail.com>>
|
||||
|
||||
## Attribution
|
||||
|
||||
|
|
|
|||
103
CONTRIBUTING.md
103
CONTRIBUTING.md
|
|
@ -1,103 +0,0 @@
|
|||
General guidance
|
||||
================
|
||||
|
||||
* The usual principles of respecting existing conventions and making sure that your changes
|
||||
are in line with the overall product design apply when contributing code to Pyenv.
|
||||
|
||||
* We are limited to Bash 3.2 features
|
||||
|
||||
That's because that's the version shipped with MacOS.
|
||||
(They didn't upgrade past it and switched to Zsh because later versions
|
||||
are covered by GPLv3 which has additional restrictions unacceptable for Apple.)
|
||||
|
||||
You can still add performance optimizations etc that take advantage of newer Bash features
|
||||
as long as there is a fallback execution route for Bash 3.
|
||||
|
||||
Formatting PRs
|
||||
==============
|
||||
|
||||
We strive to keep commit history one-concern-per-commit to keep it meaningful and easy to follow.
|
||||
If a pull request (PR) addresses a single concern (the typical case), we usually squash commits
|
||||
from it together when merging so its commit history doesn't matter.
|
||||
If however a PR addresses multiple separate concerns, each of them should be presented as a separate commit.
|
||||
Adding multiple new Python releases of the same flavor is okay with either a single or multiple commits.
|
||||
|
||||
|
||||
Authoring installation scripts
|
||||
==============================
|
||||
|
||||
Adding new Python release support
|
||||
---------------------------------
|
||||
|
||||
The easiest way to add support for a new Python release is to copy the script from the previous one
|
||||
and adjust it as necessary. In many cases, just changing version numbers, URLs and hashes is enough.
|
||||
Do pay attention to other "magic numbers" that may be present in a script --
|
||||
e.g. the set of architectures and OS versions supported by a release -- since those change from time to time, too.
|
||||
|
||||
Make sure to also copy any patches for the previous release that still apply to the new one.
|
||||
Typically, a patch no longer applies if it addresses a problem that's already fixed in the new release.
|
||||
|
||||
For prereleases, we only create an entry for the latest prerelease in a specific version line.
|
||||
When submitting a newer prerelease, replace the older one.
|
||||
|
||||
|
||||
Adding version-specific fixes/patches
|
||||
-------------------------------------
|
||||
|
||||
We accept fixes to issues in specific Python releases that prevent users from using them with Pyenv.
|
||||
|
||||
In the default configuration for a Python release, we strive to provide as close to vanilla experience as practical,
|
||||
to maintain [the principle of the least surprise](https://en.wikipedia.org/wiki/Principle_of_least_astonishment).
|
||||
As such, any such fixes:
|
||||
|
||||
* Must not break or degrade (e.g. disable features) the build in any of the environments that the release officially supports
|
||||
* Must not introduce incompatibilities with the vanilla release (including binary incompatibilities)
|
||||
* Should not patch things unnecessarily, to minimize the risk of the aforementioned undesirable side effects.
|
||||
* E.g. if the fix is for a specific environment, its logic ought to only fire in this specific environment and not touch execution paths for other environments.
|
||||
* As such, it's advisable to briefly explain in the PR what each added patch does and why it is necessary to fix the declared problem
|
||||
|
||||
Generally, version-specific fixes belong in the scripts for the affected releases and/or patches for them -- this guarantees that their effect is limited to only those releases.
|
||||
|
||||
<h3>Backporting upstream patches</h3>
|
||||
|
||||
Usually, this is the easiest way to backport a fix for a problem that is fixed in a newer release.
|
||||
|
||||
* Clone Python, check out the tag for the appropriate release and create a branch
|
||||
* Apply existing patches if there are any (with either `patch` or `git am`) and commit
|
||||
* Cherry-pick the upstream commit that fixes the problem in a newer release
|
||||
* Commit and `git format-patch`
|
||||
* Commit the generated patch file into Pyenv, test your changes and submit a PR
|
||||
|
||||
|
||||
Deprecation policy
|
||||
------------------
|
||||
|
||||
We do not provide official support for EOL releases and environments or otherwise provide any kind of extended support for old Python releases.
|
||||
|
||||
We do however accept fixes from interested parties that would allow running older, including EOL, releases in environments that they do not officially support.
|
||||
In addition to the above requirements for release-specific fixes,
|
||||
|
||||
* Such a fix must not add maintenance burden (e.g. add new logic to `python-build` that has to be kept there indefinitely)
|
||||
* Unless the added logic is useful for both EOL and non-EOL releases. In this case, it will be considered as being primarily an improvement for non-EOL releases.
|
||||
* Support is provided on a "best effort" basis: we do not actively maintain these fixes but won't actively break them, either, and will accept any corrections.
|
||||
Since old releases never change, it's pretty safe to assume that the fixes will continue to work until a later version
|
||||
of an environment introduces further incompatible changes.
|
||||
|
||||
|
||||
Advanced changes / adding new Python flavor support
|
||||
---------------------------------------------------
|
||||
|
||||
An installation script is sourced from `python-build`. All installation scripts are based on the same logic:
|
||||
|
||||
1. Select the source to download and other variable parameters as needed.
|
||||
|
||||
This includes showing an error if the user's environment (OS, architecture) is not supported by the release.
|
||||
Binary releases that only officially support specific distro(s) typically show a warning in other distros instead.
|
||||
|
||||
2. Run one of the `install_*` shell functions
|
||||
|
||||
`install_*` shell functions defined in `python-build` install Python from different kinds of sources -- compressed package (binary or source), upstream installation script, VCS checkout. Pick one that's the most appropriate for your packaging.
|
||||
|
||||
Each of them accepts a couple of function-specific arguments which are followed by arguments that constitute the build sequence. Each `<argument>` in the build sequence corresponds to the `install_*_<argument>` function in `python-build`. Check what's available and add any functions with logic specific to your flavor if needed.
|
||||
|
||||
We strive to keep out of `python-build` parts of build logic that are release-specific and/or tend to change abruptly between releases -- e.g. sets of supported architectures and other software's versions. This results in logic duplication between installation scripts -- but since old releases never change once released, this doesn't really add to the maintenance burden. As a rule of thumb, `python-build` can host parts of logic that are expected to stay the same for an indefinite amount of time -- for an entire Python flavor or release line.
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
Creating a release
|
||||
==================
|
||||
|
||||
The release of the new version of Pyenv is done via GitHub Releases.
|
||||
|
||||
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).
|
||||
* Major version is pushed if there are breaking changes
|
||||
* 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.
|
||||
168
Makefile
168
Makefile
|
|
@ -1,170 +1,12 @@
|
|||
TEST_BATS_VERSION = v1.10.0
|
||||
TEST_BASH_VERSIONS = 3.2.57 4.1.17
|
||||
|
||||
TEST_UNIT_DOCKER_PREFIX = test-unit-docker
|
||||
TEST_UNIT_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_UNIT_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_UNIT_DOCKER_PREFIX)))
|
||||
|
||||
TEST_PYTHON_BUILD_DOCKER_PREFIX = test-python-build-docker
|
||||
TEST_PYTHON_BUILD_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_PYTHON_BUILD_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_PYTHON_BUILD_DOCKER_PREFIX)))
|
||||
|
||||
TEST_BINARY_DOCKER_PREFIX = test-binary-docker
|
||||
TEST_BINARY_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BINARY_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BINARY_DOCKER_PREFIX)))
|
||||
|
||||
TEST_LINK_DOCKER_PREFIX = test-link-docker
|
||||
TEST_LINK_DOCKER_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_LINK_DOCKER_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_LINK_DOCKER_PREFIX)))
|
||||
|
||||
TEST_BATS_IMAGE_PREFIX = test-pyenv-docker-image
|
||||
TEST_BATS_IMAGE_TARGETS = $(foreach bash,$(TEST_BASH_VERSIONS),$(addsuffix -$(bash),$(TEST_BATS_IMAGE_PREFIX)) $(addsuffix -gnu-$(bash),$(TEST_BATS_IMAGE_PREFIX)))
|
||||
|
||||
.PHONY: test-docker
|
||||
test-docker: $(TEST_UNIT_DOCKER_PREFIX) $(TEST_PYTHON_BUILD_DOCKER_PREFIX) $(TEST_BINARY_DOCKER_PREFIX) $(TEST_LINK_DOCKER_PREFIX)
|
||||
|
||||
.PHONY: $(TEST_UNIT_DOCKER_PREFIX)
|
||||
$(TEST_UNIT_DOCKER_PREFIX): $(TEST_UNIT_DOCKER_TARGETS)
|
||||
|
||||
# Run each unit test under bats docker
|
||||
.PHONY: $(TEST_UNIT_DOCKER_TARGETS)
|
||||
$(TEST_UNIT_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_UNIT_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||
$(TEST_UNIT_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||
$(TEST_UNIT_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||
$(TEST_UNIT_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
|
||||
$(TEST_UNIT_DOCKER_TARGETS): $(TEST_UNIT_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
|
||||
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
|
||||
docker run \
|
||||
--init \
|
||||
-v $(PWD):/code:ro \
|
||||
-v /etc/passwd:/etc/passwd:ro \
|
||||
-v /etc/group:/etc/group:ro \
|
||||
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
|
||||
$${BATS_TEST_FILTER:+-e BATS_TEST_FILTER="$${BATS_TEST_FILTER}"} \
|
||||
$${BATS_FILE_FILTER:+-e BATS_FILE_FILTER="$${BATS_FILE_FILTER}"} \
|
||||
$${CI+-e CI="$${CI}"} \
|
||||
$(INTERACTIVE) \
|
||||
$(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
test/run
|
||||
|
||||
.PHONY: $(TEST_PYTHON_BUILD_DOCKER_PREFIX)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_PREFIX): $(TEST_PYTHON_BUILD_DOCKER_TARGETS)
|
||||
|
||||
# Run each plugin test under bats docker
|
||||
.PHONY: $(TEST_PYTHON_BUILD_DOCKER_TARGETS)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
|
||||
$(TEST_PYTHON_BUILD_DOCKER_TARGETS): $(TEST_PYTHON_BUILD_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
|
||||
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
|
||||
docker run \
|
||||
--init \
|
||||
-v $(PWD):/code:ro \
|
||||
-v /etc/passwd:/etc/passwd:ro \
|
||||
-v /etc/group:/etc/group:ro \
|
||||
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
|
||||
$${CI+-e CI="$${CI}"} \
|
||||
$(INTERACTIVE) \
|
||||
$(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
bats $${CI:+-F "/code/test/libexec/bats-format-tap-suite"} \
|
||||
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/python-build/test/$${BATS_FILE_FILTER}
|
||||
|
||||
.PHONY: $(TEST_BINARY_DOCKER_PREFIX)
|
||||
$(TEST_BINARY_DOCKER_PREFIX): $(TEST_BINARY_DOCKER_TARGETS)
|
||||
|
||||
.PHONY: $(TEST_BINARY_DOCKER_TARGETS)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||
$(TEST_BINARY_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
|
||||
$(TEST_BINARY_DOCKER_TARGETS): $(TEST_BINARY_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
|
||||
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
|
||||
docker run \
|
||||
--init \
|
||||
-v $(PWD):/code:ro \
|
||||
-v /etc/passwd:/etc/passwd:ro \
|
||||
-v /etc/group:/etc/group:ro \
|
||||
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
|
||||
$${CI+-e CI="$${CI}"} \
|
||||
$(INTERACTIVE) \
|
||||
$(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
bats $${CI:+-F "/code/test/libexec/bats-format-tap-suite"} \
|
||||
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/pyenv-binary/test/$${BATS_FILE_FILTER}
|
||||
|
||||
.PHONY: $(TEST_LINK_DOCKER_PREFIX)
|
||||
$(TEST_LINK_DOCKER_PREFIX): $(TEST_LINK_DOCKER_TARGETS)
|
||||
|
||||
.PHONY: $(TEST_LINK_DOCKER_TARGETS)
|
||||
$(TEST_LINK_DOCKER_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_LINK_DOCKER_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||
$(TEST_LINK_DOCKER_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||
$(TEST_LINK_DOCKER_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||
$(TEST_LINK_DOCKER_TARGETS): INTERACTIVE = $(if $(findstring true,$(CI)),,-ti)
|
||||
$(TEST_LINK_DOCKER_TARGETS): $(TEST_LINK_DOCKER_PREFIX)-% : $(TEST_BATS_IMAGE_PREFIX)-%
|
||||
$(info Running test with docker image '$(DOCKER_IMAGE):$(DOCKER_TAG)')
|
||||
docker run \
|
||||
--init \
|
||||
-v $(PWD):/code:ro \
|
||||
-v /etc/passwd:/etc/passwd:ro \
|
||||
-v /etc/group:/etc/group:ro \
|
||||
-u "$$(id -u $$(whoami)):$$(id -g $$(whoami))" \
|
||||
$${CI+-e CI="$${CI}"} \
|
||||
$(INTERACTIVE) \
|
||||
$(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
bats $${CI:+-F "/code/test/libexec/bats-format-tap-suite"} \
|
||||
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} plugins/pyenv-link/test/$${BATS_FILE_FILTER}
|
||||
|
||||
# Build all images needed for bats under docker
|
||||
.PHONY: $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_BATS_IMAGE_PREFIX): $(TEST_BATS_IMAGE_TARGETS)
|
||||
|
||||
# Build each image needed for bats under docker
|
||||
.PHONY: $(TEST_BATS_IMAGE_TARGETS)
|
||||
$(TEST_BATS_IMAGE_TARGETS): DOCKER_IMAGE = $(TEST_BATS_IMAGE_PREFIX)
|
||||
$(TEST_BATS_IMAGE_TARGETS): GNU = $(if $(findstring -gnu-,$@),True,False)
|
||||
$(TEST_BATS_IMAGE_TARGETS): BASH = $(filter $(TEST_BASH_VERSIONS),$(subst -, ,$@))
|
||||
$(TEST_BATS_IMAGE_TARGETS): DOCKER_TAG = bash-$(BASH)-gnu-$(GNU)
|
||||
$(TEST_BATS_IMAGE_TARGETS):
|
||||
if [ -z "$$(docker images -q '$(DOCKER_IMAGE):$(DOCKER_TAG)')" ]]; then \
|
||||
docker build \
|
||||
--quiet \
|
||||
-f "$(PWD)/test/Dockerfile" \
|
||||
--build-arg GNU="$(GNU)" \
|
||||
--build-arg BASH="$(BASH)" \
|
||||
--build-arg BATS_VERSION="$(TEST_BATS_VERSION)" \
|
||||
-t $(DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
./ ; \
|
||||
fi
|
||||
|
||||
.PHONY: test test-unit test-python-build test-binary test-link
|
||||
.PHONY: test
|
||||
|
||||
# Do not pass in user flags to build tests.
|
||||
unexport PYTHON_CFLAGS
|
||||
unexport PYTHON_CONFIGURE_OPTS
|
||||
|
||||
test: test-unit test-python-build test-binary test-link
|
||||
|
||||
test-unit: bats
|
||||
test: bats
|
||||
PATH="./bats/bin:$$PATH" test/run
|
||||
|
||||
test-python-build: bats
|
||||
cd plugins/python-build && $(PWD)/bats/bin/bats $${CI:+-F "$(PWD)/test/libexec/bats-format-tap-suite"} \
|
||||
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
|
||||
cd plugins/python-build && $(PWD)/bats/bin/bats $${CI:+--tap} test
|
||||
|
||||
test-binary: bats
|
||||
cd plugins/pyenv-binary && $(PWD)/bats/bin/bats $${CI:+-F "$(PWD)/test/libexec/bats-format-tap-suite"} \
|
||||
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
|
||||
|
||||
test-link: bats
|
||||
cd plugins/pyenv-link && $(PWD)/bats/bin/bats $${CI:+-F "$(PWD)/test/libexec/bats-format-tap-suite"} \
|
||||
$${BATS_TEST_FILTER:+--filter "$${BATS_TEST_FILTER}"} test/$${BATS_FILE_FILTER}
|
||||
|
||||
.SECONDARY: bats-$(TEST_BATS_VERSION)
|
||||
bats-$(TEST_BATS_VERSION):
|
||||
git clone --depth 1 --branch $(TEST_BATS_VERSION) https://github.com/bats-core/bats-core.git bats-$(TEST_BATS_VERSION)
|
||||
|
||||
.PHONY: bats
|
||||
bats: bats-$(TEST_BATS_VERSION)
|
||||
if [ \( ! -L bats \) -o \( "x$$(readlink bats)" != "xbats-$(TEST_BATS_VERSION)" \) ]; then \
|
||||
rm -rf bats; ln -s bats-$(TEST_BATS_VERSION) bats; \
|
||||
fi
|
||||
bats:
|
||||
git clone --depth 1 https://github.com/sstephenson/bats.git
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
$scriptblock = {
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
$words = $commandAst.ToString()
|
||||
if ( $wordToComplete ) {
|
||||
$matches = (($words[0..$cursorPosition] -join '') | Select-String -Pattern "\s+" -AllMatches).Matches
|
||||
if ( $matches ) {
|
||||
$cursorPosition = $matches[-1].Index - 1
|
||||
}
|
||||
}
|
||||
$words = $words[0..$cursorPosition] -join '' -split "\s+"
|
||||
if ( $words.Count -ge 2 ) {
|
||||
pyenv completions $words[1] | where { $_ -match $wordToComplete }
|
||||
} else {
|
||||
pyenv commands | where { $_ -match $wordToComplete }
|
||||
}
|
||||
}
|
||||
Register-ArgumentCompleter -Native -CommandName pyenv -ScriptBlock $scriptblock
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 531 KiB |
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
unset CDPATH
|
||||
|
||||
if [ "$1" = "--debug" ]; then
|
||||
export PYENV_DEBUG=1
|
||||
|
|
@ -7,8 +8,7 @@ if [ "$1" = "--debug" ]; then
|
|||
fi
|
||||
|
||||
if [ -n "$PYENV_DEBUG" ]; then
|
||||
# https://wiki-dev.bash-hackers.org/scripting/debuggingtips#making_xtrace_more_useful
|
||||
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
||||
export PS4='+ [${BASH_SOURCE##*/}:${LINENO}] '
|
||||
set -x
|
||||
fi
|
||||
|
||||
|
|
@ -22,14 +22,13 @@ abort() {
|
|||
|
||||
if enable -f "${BASH_SOURCE%/*}"/../libexec/pyenv-realpath.dylib realpath 2>/dev/null; then
|
||||
abs_dirname() {
|
||||
local path
|
||||
path="$(realpath "$1")"
|
||||
local path="$(realpath "$1")"
|
||||
echo "${path%/*}"
|
||||
}
|
||||
else
|
||||
[ -z "$PYENV_NATIVE_EXT" ] || abort "failed to load \`realpath' builtin"
|
||||
|
||||
READLINK=$(type -P readlink)
|
||||
READLINK=$(type -p greadlink readlink | head -1)
|
||||
[ -n "$READLINK" ] || abort "cannot find readlink - are you missing GNU coreutils?"
|
||||
|
||||
resolve_link() {
|
||||
|
|
@ -42,15 +41,12 @@ else
|
|||
# Use a subshell to avoid changing the current path
|
||||
(
|
||||
while [ -n "$path" ]; do
|
||||
cd_path="${path%/*}"
|
||||
if [[ "$cd_path" != "$path" ]]; then
|
||||
cd "$cd_path"
|
||||
fi
|
||||
name="${path##*/}"
|
||||
cd "${path%/*}"
|
||||
local name="${path##*/}"
|
||||
path="$(resolve_link "$name" || true)"
|
||||
done
|
||||
|
||||
echo "$PWD"
|
||||
pwd
|
||||
)
|
||||
}
|
||||
fi
|
||||
|
|
@ -62,6 +58,19 @@ else
|
|||
fi
|
||||
export PYENV_ROOT
|
||||
|
||||
# Transfer PYENV_FILE_ARG (from shims) into PYENV_DIR.
|
||||
if [ -z "${PYENV_DIR}" ]; then
|
||||
if [ -n "${PYENV_FILE_ARG}" ]; then
|
||||
if [ -L "${PYENV_FILE_ARG}" ]; then
|
||||
PYENV_DIR="$(abs_dirname "${PYENV_FILE_ARG}")"
|
||||
else
|
||||
PYENV_DIR="${PYENV_FILE_ARG%/*}"
|
||||
fi
|
||||
export PYENV_DIR
|
||||
unset PYENV_FILE_ARG
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${PYENV_DIR}" ]; then
|
||||
PYENV_DIR="$PWD"
|
||||
fi
|
||||
|
|
@ -76,29 +85,18 @@ export PYENV_DIR
|
|||
|
||||
shopt -s nullglob
|
||||
|
||||
_PYENV_INSTALL_PREFIX="$(abs_dirname "$0")"
|
||||
_PYENV_INSTALL_PREFIX="${_PYENV_INSTALL_PREFIX%/*}"
|
||||
|
||||
export _PYENV_INSTALL_PREFIX
|
||||
|
||||
for plugin_bin in "${_PYENV_INSTALL_PREFIX}"/plugins/*/bin; do
|
||||
bin_path="$(abs_dirname "$0")"
|
||||
for plugin_bin in "${PYENV_ROOT}/plugins/"*/bin; do
|
||||
PATH="${plugin_bin}:${PATH}"
|
||||
done
|
||||
# PYENV_ROOT can be set to anything, so it may happen to be equal to the base path above,
|
||||
# resulting in duplicate PATH entries
|
||||
if [ "${_PYENV_INSTALL_PREFIX}" != "$PYENV_ROOT" ]; then
|
||||
for plugin_bin in "${PYENV_ROOT}"/plugins/*/bin; do
|
||||
PATH="${plugin_bin}:${PATH}"
|
||||
done
|
||||
fi
|
||||
export PATH="${_PYENV_INSTALL_PREFIX}/libexec:${PATH}"
|
||||
export PATH="${bin_path}:${PATH}"
|
||||
|
||||
PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:${PYENV_ROOT}/pyenv.d"
|
||||
if [ "${_PYENV_INSTALL_PREFIX}" != "$PYENV_ROOT" ]; then
|
||||
if [ "${bin_path%/*}" != "$PYENV_ROOT" ]; then
|
||||
# Add pyenv's own `pyenv.d` unless pyenv was cloned to PYENV_ROOT
|
||||
PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:${_PYENV_INSTALL_PREFIX}/pyenv.d"
|
||||
PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:${bin_path%/*}/pyenv.d"
|
||||
fi
|
||||
PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:/usr/etc/pyenv.d:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks"
|
||||
PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks"
|
||||
for plugin_hook in "${PYENV_ROOT}/plugins/"*/etc/pyenv.d; do
|
||||
PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:${plugin_hook}"
|
||||
done
|
||||
|
|
@ -123,13 +121,7 @@ case "$command" in
|
|||
;;
|
||||
* )
|
||||
command_path="$(command -v "pyenv-$command" || true)"
|
||||
if [ -z "$command_path" ]; then
|
||||
if [ "$command" == "shell" ]; then
|
||||
abort "shell integration not enabled. Run \`pyenv init' for instructions."
|
||||
else
|
||||
abort "no such command \`$command'"
|
||||
fi
|
||||
fi
|
||||
[ -n "$command_path" ] || abort "no such command \`$command'"
|
||||
|
||||
shift 1
|
||||
if [ "$1" = --help ]; then
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
version="2.8.5"
|
||||
version="1.2.0"
|
||||
git_revision=""
|
||||
|
||||
if cd "${BASH_SOURCE%/*}" 2>/dev/null && git remote -v 2>/dev/null | grep -q pyenv; then
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
# Usage: pyenv commands [--sh|--no-sh]
|
||||
|
||||
set -e
|
||||
[[ -n $PYENV_DEBUG ]] && set -x
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
if [[ $1 = "--complete" ]]; then
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --sh
|
||||
echo --no-sh
|
||||
exit
|
||||
fi
|
||||
|
||||
if [[ $1 = "--sh" ]]; then
|
||||
if [ "$1" = "--sh" ]; then
|
||||
sh=1
|
||||
shift
|
||||
elif [[ $1 = "--no-sh" ]]; then
|
||||
elif [ "$1" = "--no-sh" ]; then
|
||||
nosh=1
|
||||
shift
|
||||
fi
|
||||
|
|
@ -24,25 +24,20 @@ IFS=: paths=($PATH)
|
|||
|
||||
shopt -s nullglob
|
||||
|
||||
{
|
||||
if [[ -n $sh ]]; then
|
||||
for path in "${paths[@]}"; do
|
||||
for command in "${path}"/pyenv-sh-*; do
|
||||
echo "${command##*/pyenv-sh-}"
|
||||
done
|
||||
done
|
||||
else
|
||||
for path in "${paths[@]}"; do
|
||||
for command in "${path}"/pyenv-*; do
|
||||
command="${command##*/pyenv-}"
|
||||
if [[ -n $nosh ]]; then
|
||||
if [[ ${command:0:3} != "sh-" ]]; then
|
||||
echo "$command"
|
||||
fi
|
||||
else
|
||||
echo "${command##sh-}"
|
||||
{ for path in "${paths[@]}"; do
|
||||
for command in "${path}/pyenv-"*; do
|
||||
command="${command##*pyenv-}"
|
||||
if [ -n "$sh" ]; then
|
||||
if [ ${command:0:3} = "sh-" ]; then
|
||||
echo ${command##sh-}
|
||||
fi
|
||||
done
|
||||
elif [ -n "$nosh" ]; then
|
||||
if [ ${command:0:3} != "sh-" ]; then
|
||||
echo ${command##sh-}
|
||||
fi
|
||||
else
|
||||
echo ${command##sh-}
|
||||
fi
|
||||
done
|
||||
fi
|
||||
} | sort -u
|
||||
done
|
||||
} | sort | uniq
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@
|
|||
# version's `bin' directory is at the front.
|
||||
#
|
||||
# For example, if the currently selected Python version is 2.7.6:
|
||||
# pyenv exec pip install -r requirements.txt
|
||||
# pyenv exec pip install -rrequirements.txt
|
||||
#
|
||||
# is equivalent to:
|
||||
# PATH="$PYENV_ROOT/versions/2.7.6/bin:$PATH" pip install -r requirements.txt
|
||||
# PATH="$PYENV_ROOT/versions/2.7.6/bin:$PATH" pip install -rrequirements.txt
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
|
@ -21,7 +21,7 @@ if [ "$1" = "--complete" ]; then
|
|||
exec pyenv-shims --short
|
||||
fi
|
||||
|
||||
PYENV_VERSION="$(pyenv-version-name -f)"
|
||||
PYENV_VERSION="$(pyenv-version-name)"
|
||||
PYENV_COMMAND="$1"
|
||||
|
||||
if [ -z "$PYENV_COMMAND" ]; then
|
||||
|
|
@ -29,19 +29,10 @@ if [ -z "$PYENV_COMMAND" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n $_PYENV_SHIM_PATH ]]; then
|
||||
PROGRAM="$(echo "$PYENV_COMMAND" | tr a-z- A-Z_ | sed 's/[^A-Z0-9_]/_/g')"
|
||||
NR_PYENV_SHIM_PATHS_PROGRAM="_PYENV_SHIM_PATHS_${PROGRAM}"
|
||||
export -- "$NR_PYENV_SHIM_PATHS_PROGRAM=$_PYENV_SHIM_PATH${!NR_PYENV_SHIM_PATHS_PROGRAM:+:${!NR_PYENV_SHIM_PATHS_PROGRAM}}"
|
||||
unset PROGRAM NR_PYENV_SHIM_PATHS_PROGRAM
|
||||
unset _PYENV_SHIM_PATH
|
||||
fi
|
||||
|
||||
export PYENV_VERSION
|
||||
PYENV_COMMAND_PATH="$(pyenv-which "$PYENV_COMMAND")"
|
||||
PYENV_BIN_PATH="${PYENV_COMMAND_PATH%/*}"
|
||||
|
||||
export PYENV_VERSION
|
||||
|
||||
OLDIFS="$IFS"
|
||||
IFS=$'\n' scripts=(`pyenv-hooks exec`)
|
||||
IFS="$OLDIFS"
|
||||
|
|
@ -50,8 +41,7 @@ for script in "${scripts[@]}"; do
|
|||
done
|
||||
|
||||
shift 1
|
||||
if [ "${PYENV_BIN_PATH#${PYENV_ROOT}}" != "${PYENV_BIN_PATH}" ]; then
|
||||
# Only add to $PATH for non-system version.
|
||||
export PATH="${PYENV_BIN_PATH}:${PATH}"
|
||||
fi
|
||||
exec "$PYENV_COMMAND_PATH" "$@"
|
||||
# CPython's `sys.executable` requires the `PYENV_BIN_PATH` to be at the top of the `PATH`.
|
||||
# https://github.com/pyenv/pyenv/issues/98
|
||||
export PATH="${PYENV_BIN_PATH}:${PATH}"
|
||||
exec -a "$PYENV_COMMAND" "$PYENV_COMMAND_PATH" "$@"
|
||||
|
|
|
|||
|
|
@ -1,24 +1,16 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Set or show the global Python version(s)
|
||||
# Summary: Set or show the global Python version
|
||||
#
|
||||
# Usage: pyenv global <version> <version2> <..>
|
||||
# Usage: pyenv global <version>
|
||||
#
|
||||
# Sets the global Python version(s). You can override the global version at
|
||||
# Sets the global Python version. You can override the global version at
|
||||
# any time by setting a directory-specific version with `pyenv local'
|
||||
# or by setting the `PYENV_VERSION' environment variable.
|
||||
#
|
||||
# <version> can be specified multiple times and should be a version
|
||||
# tag known to pyenv. The special version string `system' will use
|
||||
# your default system Python. Run `pyenv versions' for a list of
|
||||
# available Python versions.
|
||||
#
|
||||
# Example: To enable the python2.7 and python3.7 shims to find their
|
||||
# respective executables you could set both versions with:
|
||||
#
|
||||
# 'pyenv global 3.7.0 2.7.15'
|
||||
#
|
||||
|
||||
# <version> should be a string matching a Python version known to pyenv.
|
||||
# The special version string `system' will use your default system Python.
|
||||
# Run `pyenv versions' for a list of available Python versions.
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
|
@ -36,16 +28,12 @@ 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"
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ command_path() {
|
|||
}
|
||||
|
||||
extract_initial_comment_block() {
|
||||
LC_ALL= \
|
||||
LC_CTYPE=C \
|
||||
sed -ne "
|
||||
/^#/ !{
|
||||
q
|
||||
|
|
@ -44,9 +42,7 @@ extract_initial_comment_block() {
|
|||
}
|
||||
|
||||
collect_documentation() {
|
||||
# `tail` prevents "broken pipe" errors due to `head` closing the pipe without reading everything
|
||||
# https://superuser.com/questions/554855/how-can-i-fix-a-broken-pipe-error/642932#642932
|
||||
$(type -P gawk awk | tail -n +1 | head -n1) '
|
||||
$(type -p gawk awk | head -1) '
|
||||
/^Summary:/ {
|
||||
summary = substr($0, 10)
|
||||
next
|
||||
|
|
@ -90,8 +86,7 @@ collect_documentation() {
|
|||
}
|
||||
|
||||
documentation_for() {
|
||||
local filename
|
||||
filename="$(command_path "$1")"
|
||||
local filename="$(command_path "$1")"
|
||||
if [ -n "$filename" ]; then
|
||||
extract_initial_comment_block < "$filename" | collect_documentation
|
||||
fi
|
||||
|
|
@ -119,7 +114,7 @@ print_help() {
|
|||
eval "$(documentation_for "$command")"
|
||||
[ -n "$help" ] || help="$summary"
|
||||
|
||||
if [ -n "$usage" ] || [ -n "$summary" ]; then
|
||||
if [ -n "$usage" -o -n "$summary" ]; then
|
||||
if [ -n "$usage" ]; then
|
||||
echo "$usage"
|
||||
else
|
||||
|
|
@ -154,7 +149,7 @@ if [ -z "$1" ] || [ "$1" == "pyenv" ]; then
|
|||
[ -z "$usage" ] || exit
|
||||
echo
|
||||
echo "Some useful pyenv commands are:"
|
||||
print_summaries $(exec pyenv-commands | sort -u)
|
||||
print_summaries commands local global shell install uninstall rehash version versions which whence
|
||||
echo
|
||||
echo "See \`pyenv help <command>' for information on a specific command."
|
||||
echo "For full documentation, see: https://github.com/pyenv/pyenv#readme"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ if ! enable -f "${BASH_SOURCE%/*}"/pyenv-realpath.dylib realpath 2>/dev/null; th
|
|||
echo "pyenv: failed to load \`realpath' builtin" >&2
|
||||
exit 1
|
||||
fi
|
||||
READLINK=$(type -P readlink)
|
||||
READLINK=$(type -p greadlink readlink | head -1)
|
||||
if [ -z "$READLINK" ]; then
|
||||
echo "pyenv: cannot find readlink - are you missing GNU coreutils?" >&2
|
||||
exit 1
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
# Summary: Configure the shell environment for pyenv
|
||||
# Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [<shell>])"
|
||||
# pyenv init --install [<shell>]
|
||||
# pyenv init --detect-shell [<shell>]
|
||||
# Usage: eval "$(pyenv init - [--no-rehash] [<shell>])"
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
|
@ -10,505 +8,147 @@ set -e
|
|||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo -
|
||||
echo --path
|
||||
echo --install
|
||||
echo --no-push-path
|
||||
echo --no-rehash
|
||||
echo --detect-shell
|
||||
echo bash
|
||||
echo fish
|
||||
echo ksh
|
||||
echo pwsh
|
||||
echo zsh
|
||||
exit
|
||||
fi
|
||||
|
||||
mode="help"
|
||||
print=""
|
||||
no_rehash=""
|
||||
for args in "$@"
|
||||
do
|
||||
if [ "$args" = "-" ]; then
|
||||
print=1
|
||||
shift
|
||||
fi
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-)
|
||||
mode="print"
|
||||
;;
|
||||
--path)
|
||||
mode="path"
|
||||
;;
|
||||
--install)
|
||||
mode="install"
|
||||
;;
|
||||
--detect-shell)
|
||||
mode="detect-shell"
|
||||
;;
|
||||
--no-push-path)
|
||||
no_push_path=1
|
||||
;;
|
||||
--no-rehash)
|
||||
no_rehash=1
|
||||
;;
|
||||
*)
|
||||
shell="$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
if [ "$args" = "--no-rehash" ]; then
|
||||
no_rehash=1
|
||||
shift
|
||||
fi
|
||||
done
|
||||
|
||||
# If shell is not provided, detect it.
|
||||
shell="$1"
|
||||
if [ -z "$shell" ]; then
|
||||
if shell=$(tr '\0' ' ' 2>/dev/null </proc/"$PPID"/cmdline);then
|
||||
:
|
||||
else
|
||||
shell=$(ps p "$PPID" -o 'args=' 2>/dev/null || true)
|
||||
fi
|
||||
shell="$(ps -p "$PPID" -o 'args=' 2>/dev/null || true)"
|
||||
shell="${shell%% *}"
|
||||
shell="${shell##-}"
|
||||
shell="${shell:-$SHELL}"
|
||||
shell="${shell##*/}"
|
||||
shell="${shell%%-*}"
|
||||
fi
|
||||
|
||||
function main() {
|
||||
case "$mode" in
|
||||
"help")
|
||||
help_
|
||||
exit 1
|
||||
;;
|
||||
"path")
|
||||
print_path
|
||||
print_rehash
|
||||
exit 0
|
||||
;;
|
||||
"print")
|
||||
init_dirs
|
||||
print_path
|
||||
print_env
|
||||
print_completion
|
||||
print_rehash
|
||||
print_shell_function
|
||||
exit 0
|
||||
;;
|
||||
"detect-shell")
|
||||
detect_profile
|
||||
print_detect_shell
|
||||
exit 0
|
||||
;;
|
||||
"install")
|
||||
install_shell_startup_files
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
# should never get here
|
||||
exit 2
|
||||
}
|
||||
root="${0%/*}/.."
|
||||
|
||||
function detect_profile() {
|
||||
if [ -z "$print" ]; then
|
||||
case "$shell" in
|
||||
bash )
|
||||
if [ -e "${HOME}/.bash_profile" ]; then
|
||||
profile='~/.bash_profile'
|
||||
if [ -f "${HOME}/.bashrc" ] && [ ! -f "${HOME}/.bash_profile" ]; then
|
||||
profile='~/.bashrc'
|
||||
else
|
||||
profile='~/.profile'
|
||||
profile='~/.bash_profile'
|
||||
fi
|
||||
profile_explain="~/.bash_profile if it exists, otherwise ~/.profile"
|
||||
rc='~/.bashrc'
|
||||
;;
|
||||
zsh )
|
||||
profile='~/.zshrc'
|
||||
;;
|
||||
ksh )
|
||||
profile='~/.profile'
|
||||
;;
|
||||
fish )
|
||||
profile='~/.config/fish/config.fish'
|
||||
rc='~/.config/fish/config.fish'
|
||||
;;
|
||||
pwsh )
|
||||
profile='~/.config/powershell/profile.ps1'
|
||||
rc='~/.config/powershell/profile.ps1'
|
||||
;;
|
||||
zsh )
|
||||
profile='~/.zprofile'
|
||||
rc='~/.zshrc'
|
||||
;;
|
||||
ksh | ksh93 | mksh )
|
||||
# There are two implementations of Korn shell: AT&T (ksh93) and Mir (mksh).
|
||||
# Systems may have them installed under those names, or as ksh, so those
|
||||
# are recognized here. The obsolete ksh88 (subsumed by ksh93) and pdksh
|
||||
# (subsumed by mksh) are not included, since they are unlikely to still
|
||||
# be in use as interactive shells anywhere.
|
||||
profile='~/.profile'
|
||||
rc='~/.profile'
|
||||
;;
|
||||
* )
|
||||
profile=
|
||||
rc=
|
||||
profile_explain='your shell'\''s login startup file'
|
||||
rc_explain='your shell'\''s interactive startup file'
|
||||
profile='your profile'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function print_detect_shell() {
|
||||
echo "PYENV_SHELL_DETECT=$shell"
|
||||
echo "PYENV_PROFILE_DETECT=$profile"
|
||||
echo "PYENV_RC_DETECT=$rc"
|
||||
}
|
||||
|
||||
function help_() {
|
||||
detect_profile
|
||||
{
|
||||
{ echo "# Load pyenv automatically by appending"
|
||||
echo "# the following to ${profile}:"
|
||||
echo
|
||||
case "$shell" in
|
||||
fish )
|
||||
echo "# Add pyenv executable to PATH by running"
|
||||
echo "# the following interactively:"
|
||||
echo
|
||||
print_fish_user_path_setup
|
||||
echo
|
||||
echo "# Load pyenv automatically by appending"
|
||||
echo "# the following to ~/.config/fish/config.fish:"
|
||||
echo
|
||||
print_fish_shell_setup
|
||||
echo
|
||||
;;
|
||||
pwsh )
|
||||
echo '# Load pyenv automatically by appending'
|
||||
echo "# the following to $profile :"
|
||||
echo
|
||||
print_pwsh_shell_setup
|
||||
echo 'status --is-interactive; and source (pyenv init -|psub)'
|
||||
;;
|
||||
* )
|
||||
echo '# Load pyenv automatically by appending'
|
||||
echo -n "# the following to "
|
||||
if [[ "$profile" == "$rc" && -z $rc_explain ]]; then
|
||||
echo "${profile_explain:-$profile} :"
|
||||
else
|
||||
echo
|
||||
echo "# ${profile_explain:-$profile} (for login shells)"
|
||||
echo "# and ${rc_explain:-$rc} (for interactive shells) :"
|
||||
fi
|
||||
echo
|
||||
print_posix_shell_setup
|
||||
echo 'eval "$(pyenv init -)"'
|
||||
;;
|
||||
esac
|
||||
echo
|
||||
echo '# Restart your shell for the changes to take effect.'
|
||||
echo
|
||||
} >&2
|
||||
}
|
||||
|
||||
function print_posix_shell_setup() {
|
||||
echo 'export PYENV_ROOT="$HOME/.pyenv"'
|
||||
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"'
|
||||
echo 'eval "$(pyenv init - '$shell')"'
|
||||
}
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function print_fish_shell_setup() {
|
||||
echo 'pyenv init - fish | source'
|
||||
}
|
||||
mkdir -p "${PYENV_ROOT}/"{shims,versions}
|
||||
|
||||
function print_fish_user_path_setup() {
|
||||
echo 'set -Ux PYENV_ROOT $HOME/.pyenv'
|
||||
echo 'if functions -q fish_add_path'
|
||||
echo ' test -d $PYENV_ROOT/bin; and fish_add_path $PYENV_ROOT/bin'
|
||||
echo 'else'
|
||||
echo ' test -d $PYENV_ROOT/bin; and set -U fish_user_paths $PYENV_ROOT/bin $fish_user_paths'
|
||||
echo 'end'
|
||||
}
|
||||
case "$shell" in
|
||||
fish )
|
||||
echo "set -gx PATH '${PYENV_ROOT}/shims' \$PATH"
|
||||
echo "set -gx PYENV_SHELL $shell"
|
||||
;;
|
||||
* )
|
||||
echo 'export PATH="'${PYENV_ROOT}'/shims:${PATH}"'
|
||||
echo "export PYENV_SHELL=$shell"
|
||||
;;
|
||||
esac
|
||||
|
||||
function print_pwsh_shell_setup() {
|
||||
echo '$Env:PYENV_ROOT="$Env:HOME/.pyenv"'
|
||||
echo 'if (Test-Path -LP "$Env:PYENV_ROOT/bin" -PathType Container) {'
|
||||
echo ' $Env:PATH="$Env:PYENV_ROOT/bin:$Env:PATH" }'
|
||||
echo 'iex ((pyenv init -) -join "`n")'
|
||||
}
|
||||
completion="${root}/completions/pyenv.${shell}"
|
||||
if [ -r "$completion" ]; then
|
||||
echo "source '$completion'"
|
||||
fi
|
||||
|
||||
function expand_home_path() {
|
||||
local path="$1"
|
||||
printf '%s\n' "${path/#\~/$HOME}"
|
||||
}
|
||||
if [ -z "$no_rehash" ]; then
|
||||
echo 'command pyenv rehash 2>/dev/null'
|
||||
fi
|
||||
|
||||
function install_shell_startup_files() {
|
||||
if [[ -z $HOME ]]; then
|
||||
echo "pyenv: HOME must be set to configure shell startup files" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
detect_profile
|
||||
|
||||
local files=()
|
||||
local lines=()
|
||||
local profile_path rc_path setup
|
||||
|
||||
case "$shell" in
|
||||
bash | zsh | ksh | ksh93 | mksh )
|
||||
rc_path="$(expand_home_path "$rc")"
|
||||
profile_path="$(expand_home_path "$profile")"
|
||||
setup="$(print_posix_shell_setup)"
|
||||
files=("$rc_path")
|
||||
lines=("$setup")
|
||||
if [[ $profile_path != "$rc_path" ]]; then
|
||||
files+=("$profile_path")
|
||||
lines+=("$setup")
|
||||
fi
|
||||
;;
|
||||
fish )
|
||||
rc_path="$(expand_home_path "$rc")"
|
||||
files=("$rc_path")
|
||||
lines=("$(print_fish_shell_setup)")
|
||||
;;
|
||||
pwsh )
|
||||
rc_path="$(expand_home_path "$rc")"
|
||||
files=("$rc_path")
|
||||
lines=("$(print_pwsh_shell_setup)")
|
||||
;;
|
||||
* )
|
||||
echo "pyenv: cannot automatically configure startup files for $shell" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
local index
|
||||
for ((index = 0; index < ${#files[@]}; index++)); do
|
||||
check_startup_file "${files[$index]}" || return 1
|
||||
done
|
||||
|
||||
if [[ $shell == fish ]]; then
|
||||
install_fish_user_paths || return 1
|
||||
fi
|
||||
|
||||
for ((index = 0; index < ${#files[@]}; index++)); do
|
||||
append_lines "${files[$index]}" "${lines[$index]}"
|
||||
done
|
||||
}
|
||||
|
||||
function check_startup_file() {
|
||||
local file="$1"
|
||||
local grep_status
|
||||
|
||||
if [[ ! -e $file ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -f $file || ! -r $file ]]; then
|
||||
echo "pyenv: failed to inspect $file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if grep -Fi pyenv "$file" >/dev/null; then
|
||||
echo "pyenv: cannot automatically apply changes to $file: it appears to already contain Pyenv-related code." >&2
|
||||
echo "pyenv: review the file's contents and apply changes manually if necessary." >&2
|
||||
echo "pyenv: run \`pyenv init $shell\` to see the suggested setup." >&2
|
||||
return 1
|
||||
else
|
||||
grep_status=$?
|
||||
if [[ $grep_status == 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
echo "pyenv: failed to inspect $file" >&2
|
||||
return "$grep_status"
|
||||
fi
|
||||
}
|
||||
|
||||
function install_fish_user_paths() {
|
||||
local fish_setup
|
||||
|
||||
if ! command -v fish >/dev/null; then
|
||||
echo "pyenv: fish is not available to configure fish universal variables" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
fish_setup="$(print_fish_user_path_setup)"
|
||||
if ! fish -c "$fish_setup"; then
|
||||
echo "pyenv: failed to configure fish universal variables" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function append_lines() {
|
||||
local file="$1"
|
||||
local lines="$2"
|
||||
local dir last_char
|
||||
|
||||
dir="${file%/*}"
|
||||
if [[ $dir != "$file" ]]; then
|
||||
mkdir -p "$dir"
|
||||
fi
|
||||
|
||||
if [[ -s $file ]]; then
|
||||
last_char="$(tail -c 1 "$file")" || return 1
|
||||
if [[ -n $last_char ]]; then
|
||||
echo >> "$file"
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '%s\n' "$lines" >> "$file"
|
||||
}
|
||||
|
||||
function init_dirs() {
|
||||
mkdir -p "${PYENV_ROOT}/"{shims,versions}
|
||||
}
|
||||
|
||||
function print_path() {
|
||||
# if no_push_path is set, guard the PATH manipulation with a check on whether
|
||||
# the shim is already in the PATH.
|
||||
if [ -n "$no_push_path" ]; then
|
||||
case "$shell" in
|
||||
fish )
|
||||
echo 'if not contains -- "'"${PYENV_ROOT}/shims"'" $PATH'
|
||||
print_path_prepend_shims
|
||||
echo 'end'
|
||||
;;
|
||||
pwsh )
|
||||
echo 'if ( $Env:PATH -notmatch "'"${PYENV_ROOT}/shims"'" ) {'
|
||||
print_path_prepend_shims
|
||||
echo '}'
|
||||
;;
|
||||
* )
|
||||
echo 'if [[ ":$PATH:" != *'\':"${PYENV_ROOT}"/shims:\''* ]]; then'
|
||||
print_path_prepend_shims
|
||||
echo 'fi'
|
||||
;;
|
||||
esac
|
||||
else
|
||||
case "$shell" in
|
||||
fish )
|
||||
echo 'while set pyenv_index (contains -i -- "'"${PYENV_ROOT}/shims"'" $PATH)'
|
||||
echo 'set -eg PATH[$pyenv_index]; end; set -e pyenv_index'
|
||||
print_path_prepend_shims
|
||||
;;
|
||||
pwsh )
|
||||
echo '$Env:PATH="$(($Env:PATH -split '"':'"' | where { -not ($_ -match '"'${PYENV_ROOT}/shims'"') }) -join '"':'"')"'
|
||||
print_path_prepend_shims
|
||||
;;
|
||||
* )
|
||||
# Some distros (notably Debian-based) set Bash's SSH_SOURCE_BASHRC compilation option
|
||||
# that makes it source `bashrc` under SSH even when not interactive.
|
||||
# This is inhibited by a guard in Debian's stock `bashrc` but some people remove it
|
||||
# in order to get proper environment for noninteractive remote commands
|
||||
# (SSH provides /etc/ssh/sshrc and ~/.ssh/rc for that but no-one seems to use them for some reason).
|
||||
# This has caused an infinite `bashrc` execution loop for those people in the below nested Bash invocation (#2367).
|
||||
# --norc negates this behavior of such a customized Bash.
|
||||
echo 'PATH="$(bash --norc -ec '\''IFS=:; paths=($PATH); '
|
||||
echo 'for i in ${!paths[@]}; do '
|
||||
echo 'if [[ ${paths[i]} == "'\'\'"${PYENV_ROOT}/shims"\'\''" ]]; then unset '\'\\\'\''paths[i]'\'\\\'\''; '
|
||||
echo 'fi; done; '
|
||||
echo 'echo "${paths[*]}"'\'')"'
|
||||
print_path_prepend_shims
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
function print_path_prepend_shims() {
|
||||
case "$shell" in
|
||||
fish )
|
||||
echo 'set -gx PATH '\'"${PYENV_ROOT}/shims"\'' $PATH'
|
||||
;;
|
||||
pwsh )
|
||||
echo '$Env:PATH="'"${PYENV_ROOT}"'/shims:$Env:PATH"'
|
||||
;;
|
||||
* )
|
||||
echo 'export PATH="'"${PYENV_ROOT}"'/shims:${PATH}"'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function print_env() {
|
||||
case "$shell" in
|
||||
fish )
|
||||
echo "set -gx PYENV_SHELL $shell"
|
||||
;;
|
||||
pwsh )
|
||||
echo '$Env:PYENV_SHELL="'"$shell"'"'
|
||||
;;
|
||||
* )
|
||||
echo "export PYENV_SHELL=$shell"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function print_completion() {
|
||||
completion="${_PYENV_INSTALL_PREFIX}/completions/pyenv.${shell}"
|
||||
if [ -r "$completion" ]; then
|
||||
case "$shell" in
|
||||
pwsh )
|
||||
echo "iex (gc $completion -Raw)"
|
||||
;;
|
||||
* )
|
||||
echo "source '$completion'"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
function print_rehash() {
|
||||
if [ -z "$no_rehash" ]; then
|
||||
case "$shell" in
|
||||
pwsh )
|
||||
echo '& pyenv rehash'
|
||||
;;
|
||||
* )
|
||||
echo 'command pyenv rehash'
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
function print_shell_function() {
|
||||
commands=(`pyenv-commands --sh`)
|
||||
case "$shell" in
|
||||
fish )
|
||||
echo \
|
||||
'function pyenv
|
||||
set command $argv[1]
|
||||
commands=(`pyenv-commands --sh`)
|
||||
case "$shell" in
|
||||
fish )
|
||||
cat <<EOS
|
||||
function pyenv
|
||||
set command \$argv[1]
|
||||
set -e argv[1]
|
||||
|
||||
switch "$command"
|
||||
case '"${commands[*]}"'
|
||||
source (pyenv "sh-$command" $argv|psub)
|
||||
case "*"
|
||||
command pyenv "$command" $argv
|
||||
switch "\$command"
|
||||
case ${commands[*]}
|
||||
source (pyenv "sh-\$command" \$argv|psub)
|
||||
case '*'
|
||||
command pyenv "\$command" \$argv
|
||||
end
|
||||
end'
|
||||
;;
|
||||
pwsh )
|
||||
cat <<EOS
|
||||
end
|
||||
EOS
|
||||
;;
|
||||
ksh )
|
||||
cat <<EOS
|
||||
function pyenv {
|
||||
\$command=""
|
||||
if ( \$args.Count -gt 0 ) {
|
||||
\$command, \$args = \$args
|
||||
}
|
||||
typeset command
|
||||
EOS
|
||||
;;
|
||||
* )
|
||||
cat <<EOS
|
||||
pyenv() {
|
||||
local command
|
||||
EOS
|
||||
;;
|
||||
esac
|
||||
|
||||
if ( ("${commands[*]}" -split ' ') -contains \$command ) {
|
||||
\$shell_cmds = (& (get-command -commandtype application pyenv -totalcount 1) sh-\$command \$args)
|
||||
if ( \$shell_cmds.Count -gt 0 ) {
|
||||
iex (\$shell_cmds -join "\`n")
|
||||
}
|
||||
} else {
|
||||
& (get-command -commandtype application pyenv -totalcount 1) \$command \$args
|
||||
}
|
||||
if [ "$shell" != "fish" ]; then
|
||||
IFS="|"
|
||||
cat <<EOS
|
||||
command="\$1"
|
||||
if [ "\$#" -gt 0 ]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
case "\$command" in
|
||||
${commands[*]})
|
||||
eval "\$(pyenv "sh-\$command" "\$@")";;
|
||||
*)
|
||||
command pyenv "\$command" "\$@";;
|
||||
esac
|
||||
}
|
||||
EOS
|
||||
;;
|
||||
ksh | ksh93 | mksh )
|
||||
echo \
|
||||
'function pyenv {
|
||||
typeset command=${1:-}'
|
||||
;;
|
||||
* )
|
||||
echo \
|
||||
'pyenv() {
|
||||
local command=${1:-}'
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$shell" != "fish" ] && [ "$shell" != "pwsh" ]; then
|
||||
IFS="|"
|
||||
echo \
|
||||
' [ "$#" -gt 0 ] && shift
|
||||
case "$command" in
|
||||
'"${commands[*]:-/}"')
|
||||
eval "$(pyenv "sh-$command" "$@")"
|
||||
;;
|
||||
*)
|
||||
command pyenv "$command" "$@"
|
||||
;;
|
||||
esac
|
||||
}'
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Summary: Print the latest installed or known version with the given prefix
|
||||
# Usage: pyenv latest [-k|--known] <prefix>
|
||||
#
|
||||
# -k/--known Select from all known versions instead of installed
|
||||
# -b/--bypass (internal) On a resolution failure, do not print an error message
|
||||
# but rather print the argument unchanged
|
||||
# -f/--force (internal) Same as -b but also do not return a failure exit code
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
case "$1" in
|
||||
-k|--known)
|
||||
FROM_KNOWN=1
|
||||
shift
|
||||
;;
|
||||
-b|--bypass)
|
||||
BYPASS=1
|
||||
shift
|
||||
;;
|
||||
-f|--force)
|
||||
FORCE=1
|
||||
BYPASS=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
prefix=$1
|
||||
exitcode=0
|
||||
|
||||
IFS=$'\n'
|
||||
|
||||
if [[ -z $FROM_KNOWN ]]; then
|
||||
if [[ -d $PYENV_ROOT/versions/$prefix ]]; then
|
||||
echo "$prefix"
|
||||
exit $exitcode;
|
||||
fi
|
||||
DEFINITION_CANDIDATES=( $(pyenv-versions --bare --skip-envs) )
|
||||
else
|
||||
DEFINITION_CANDIDATES=( $(python-build --definitions ) )
|
||||
fi
|
||||
|
||||
if printf '%s\n' "${DEFINITION_CANDIDATES[@]}" 2>/dev/null | grep -qxFe "$prefix"; then
|
||||
echo "$prefix"
|
||||
exit $exitcode;
|
||||
fi
|
||||
|
||||
suffix=""
|
||||
if [[ $prefix =~ ^(.*[0-9])t$ ]]; then
|
||||
suffix="t"
|
||||
prefix="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
# https://stackoverflow.com/questions/11856054/is-there-an-easy-way-to-pass-a-raw-string-to-grep/63483807#63483807
|
||||
prefix_re="$(sed 's/[^\^]/[&]/g;s/[\^]/\\&/g' <<< "$prefix")"
|
||||
suffix_re="$(sed 's/[^\^]/[&]/g;s/[\^]/\\&/g' <<< "$suffix")"
|
||||
# FIXME: more reliable and readable would probably be to loop over them and transform in pure Bash
|
||||
DEFINITION_CANDIDATES=(\
|
||||
$(printf '%s\n' "${DEFINITION_CANDIDATES[@]}" | \
|
||||
grep -Ee "^$prefix_re[-.].*$suffix_re\$" || true))
|
||||
|
||||
DEFINITION_CANDIDATES=(\
|
||||
$(printf '%s\n' "${DEFINITION_CANDIDATES[@]}" | \
|
||||
sed -E -e '/-dev$/d' -e '/-src$/d' -e '/-latest$/d' -e '/(a|b|rc)[0-9]+$/d' \
|
||||
$(if [[ -z $suffix ]]; then echo "-e /[0-9]t\$/d"; fi)
|
||||
));
|
||||
|
||||
# Compose a sorting key, followed by | and original value
|
||||
DEFINITION_CANDIDATES=(\
|
||||
$(printf '%s\n' "${DEFINITION_CANDIDATES[@]}" | \
|
||||
awk \
|
||||
'{ if (match($0,"^[[:alnum:]]+-"))
|
||||
{ print substr($0,0,RLENGTH-1) "." substr($0,RLENGTH+1) "..|" $0; }
|
||||
else
|
||||
{ print $0 "...|" $0; }
|
||||
}'))
|
||||
DEFINITION_CANDIDATES=(\
|
||||
$(printf '%s\n' "${DEFINITION_CANDIDATES[@]}" \
|
||||
| sort -t. -k1,1r -k 2,2nr -k 3,3nr -k4,4nr \
|
||||
| cut -f2 -d $'|' \
|
||||
|| true))
|
||||
DEFINITION="${DEFINITION_CANDIDATES[0]}"
|
||||
|
||||
if [[ -n "$DEFINITION" ]]; then
|
||||
echo "$DEFINITION"
|
||||
else
|
||||
if [[ -z $BYPASS ]]; then
|
||||
echo "pyenv: no $([[ -z $FROM_KNOWN ]] && echo installed || echo known) versions match the prefix \`$prefix'" >&2
|
||||
else
|
||||
echo "$prefix"
|
||||
fi
|
||||
if [[ -z $FORCE ]]; then
|
||||
exitcode=1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit $exitcode
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Set or show the local application-specific Python version(s)
|
||||
# Summary: Set or show the local application-specific Python version
|
||||
#
|
||||
# Usage: pyenv local [-f|--force] [<version> [...]]
|
||||
# Usage: pyenv local <version>
|
||||
# pyenv local --unset
|
||||
#
|
||||
# -f/--force Do not verify that the versions being set exist
|
||||
#
|
||||
# Sets the local application-specific Python version(s) by writing the
|
||||
# Sets the local application-specific Python version by writing the
|
||||
# version name to a file named `.python-version'.
|
||||
#
|
||||
# When you run a Python command, pyenv will look for a `.python-version'
|
||||
|
|
@ -17,16 +15,9 @@
|
|||
# `PYENV_VERSION' environment variable takes precedence over local
|
||||
# and global versions.
|
||||
#
|
||||
# <version> can be specified multiple times and should be a version
|
||||
# tag known to pyenv. The special version string `system' will use
|
||||
# your default system Python. Run `pyenv versions' for a list of
|
||||
# available Python versions.
|
||||
#
|
||||
# Example: To enable the python2.7 and python3.7 shims to find their
|
||||
# respective executables you could set both versions with:
|
||||
#
|
||||
# 'pyenv local 3.7.0 2.7.15'
|
||||
|
||||
# <version> should be a string matching a Python version known to pyenv.
|
||||
# The special version string `system' will use your default system Python.
|
||||
# Run `pyenv versions' for a list of available Python versions.
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
|
@ -38,32 +29,15 @@ if [ "$1" = "--complete" ]; then
|
|||
exec pyenv-versions --bare
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
case "$1" in
|
||||
-f|--force)
|
||||
FORCE=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
versions=("$@")
|
||||
|
||||
if [ "$versions" = "--unset" ]; then
|
||||
rm -f .python-version
|
||||
elif [ -n "$versions" ]; then
|
||||
pyenv-version-file-write ${FORCE:+-f }.python-version "${versions[@]}"
|
||||
pyenv-version-file-write .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
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
# Summary: Display prefixes for Python versions
|
||||
# Usage: pyenv prefix [<version>...]
|
||||
# Summary: Display prefix for a Python version
|
||||
# Usage: pyenv prefix [<version>]
|
||||
#
|
||||
# Displays the directories where the given Python versions are installed,
|
||||
# separated by colons. If no version is given, `pyenv prefix' displays the
|
||||
# locations of the currently selected versions.
|
||||
# Displays the directory where a Python version is installed. If no
|
||||
# version is given, `pyenv prefix' displays the location of the
|
||||
# currently selected version.
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
|
@ -28,24 +28,16 @@ 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)" || \
|
||||
PYTHON_PATH="$(PYENV_VERSION="${version}" pyenv-which python3 --skip-advice 2>/dev/null)" || \
|
||||
PYTHON_PATH="$(PYENV_VERSION="${version}" pyenv-which python2 --skip-advice 2>/dev/null)"; then
|
||||
shopt -s extglob
|
||||
# In some distros (Arch), Python can be found in sbin as well as bin
|
||||
PYENV_PREFIX_PATH="${PYTHON_PATH%/?(s)bin/*}"
|
||||
if PYTHON_PATH="$(PYENV_VERSION="${version}" pyenv-which python 2>/dev/null)"; then
|
||||
PYENV_PREFIX_PATH="${PYTHON_PATH%/bin/*}"
|
||||
PYENV_PREFIX_PATH="${PYENV_PREFIX_PATH:-/}"
|
||||
else
|
||||
echo "pyenv: system version not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
version="$(pyenv-latest -f "$version")"
|
||||
PYENV_PREFIX_PATH="${PYENV_ROOT}/versions/${version}"
|
||||
fi
|
||||
if [ -d "$PYENV_PREFIX_PATH" ]; then
|
||||
|
|
@ -55,7 +47,6 @@ OLDIFS="$IFS"
|
|||
exit 1
|
||||
fi
|
||||
done
|
||||
set +f
|
||||
}
|
||||
IFS="$OLDIFS"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# Summary: Rehash pyenv shims (run this after installing executables)
|
||||
|
||||
set -e
|
||||
[[ -n "$PYENV_DEBUG" ]] && set -x
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
SHIM_PATH="${PYENV_ROOT}/shims"
|
||||
PROTOTYPE_SHIM_PATH="${SHIM_PATH}/.pyenv-shim"
|
||||
|
|
@ -10,75 +10,30 @@ PROTOTYPE_SHIM_PATH="${SHIM_PATH}/.pyenv-shim"
|
|||
# Create the shims directory if it doesn't already exist.
|
||||
mkdir -p "$SHIM_PATH"
|
||||
|
||||
declare last_acquire_error
|
||||
# Ensure only one instance of pyenv-rehash is running at a time by
|
||||
# setting the shell's `noclobber` option and attempting to write to
|
||||
# the prototype shim file. If the file already exists, print a warning
|
||||
# to stderr and exit with a non-zero status.
|
||||
set -o noclobber
|
||||
{ echo > "$PROTOTYPE_SHIM_PATH"
|
||||
} 2>/dev/null ||
|
||||
{ if [ -w "$SHIM_PATH" ]; then
|
||||
echo "pyenv: cannot rehash: $PROTOTYPE_SHIM_PATH exists"
|
||||
else
|
||||
echo "pyenv: cannot rehash: $SHIM_PATH isn't writable"
|
||||
fi
|
||||
exit 1
|
||||
} >&2
|
||||
set +o noclobber
|
||||
|
||||
acquire_lock() {
|
||||
local ret
|
||||
# An old lock file is presumed stale. We assume no healthy rehash takes this long.
|
||||
# The time is picked very small so that a killed rehash holds up new shell sessions
|
||||
# for as little as possible
|
||||
find "$PROTOTYPE_SHIM_PATH" -mmin +2 -exec rm -f {} \; 2>/dev/null || true
|
||||
set -o noclobber
|
||||
last_acquire_error="$( { ( echo -n > "$PROTOTYPE_SHIM_PATH"; ) 2>&1 1>&3 3>&1-; } 3>&1)" \
|
||||
&& trap release_lock EXIT \
|
||||
|| {
|
||||
# Linux Landlock and MacOS Seatbelt sandbox subsystems return false information in access(),
|
||||
# making -w "$SHIM_PATH" not catch the fact that the shims dir is not writable in this case.
|
||||
# Bash doesn't provide access to errno to check for non-EEXIST error code in `echo >'.
|
||||
# So check for writablity by trying to write to a different file,
|
||||
# in a way that taxes the usual use case as little as possible.
|
||||
if [[ -z $tested_for_other_write_errors ]]; then
|
||||
# if lots of (50+) rehashes run concurrently, another concurrent rehash
|
||||
# may delete the temporary file in remove_*_shims() before we do so
|
||||
( t="$(TMPDIR="$SHIM_PATH" mktemp)" && rm -f "$t" ) \
|
||||
&& tested_for_other_write_errors=1 \
|
||||
|| { echo "pyenv: cannot rehash: $SHIM_PATH isn't writable" >&2
|
||||
set +o noclobber
|
||||
exit 1; }
|
||||
fi
|
||||
ret=1
|
||||
}
|
||||
set +o noclobber
|
||||
[[ -z "${ret}" ]]
|
||||
}
|
||||
# If we were able to obtain a lock, register a trap to clean up the
|
||||
# prototype shim when the process exits.
|
||||
trap remove_prototype_shim EXIT
|
||||
|
||||
remove_prototype_shim() {
|
||||
rm -f "$PROTOTYPE_SHIM_PATH"
|
||||
}
|
||||
|
||||
release_lock() {
|
||||
remove_prototype_shim
|
||||
trap - EXIT
|
||||
}
|
||||
|
||||
if [ ! -w "$SHIM_PATH" ]; then
|
||||
echo "pyenv: cannot rehash: $SHIM_PATH isn't writable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
declare acquired tested_for_other_write_errors
|
||||
declare start=$SECONDS
|
||||
PYENV_REHASH_TIMEOUT=${PYENV_REHASH_TIMEOUT:-60}
|
||||
while (( SECONDS <= start + PYENV_REHASH_TIMEOUT )); do
|
||||
if acquire_lock; then
|
||||
acquired=1
|
||||
break
|
||||
else
|
||||
# POSIX sleep(1) doesn't provide subsecond precision, but many others do
|
||||
sleep 0.1 2>/dev/null || sleep 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "${acquired}" ]; then
|
||||
if [[ -n $tested_for_other_write_errors ]]; then
|
||||
echo "pyenv: cannot rehash: couldn't acquire lock"\
|
||||
"$PROTOTYPE_SHIM_PATH for $PYENV_REHASH_TIMEOUT seconds. Last error message:" >&2
|
||||
echo "$last_acquire_error" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
unset tested_for_other_write_errors
|
||||
|
||||
# The prototype shim file is a script that re-execs itself, passing
|
||||
# its filename and any arguments to `pyenv exec`. This file is
|
||||
# hard-linked for every executable and then removed. The linking
|
||||
|
|
@ -91,12 +46,21 @@ set -e
|
|||
[ -n "\$PYENV_DEBUG" ] && set -x
|
||||
|
||||
program="\${0##*/}"
|
||||
if [[ "\$program" = "python"* ]]; then
|
||||
for arg; do
|
||||
case "\$arg" in
|
||||
-c* | -- ) break ;;
|
||||
*/* )
|
||||
if [ -f "\$arg" ]; then
|
||||
export PYENV_FILE_ARG="\$arg"
|
||||
break
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
export PYENV_ROOT="$PYENV_ROOT"
|
||||
SHIM_PATH=\${0%/*}
|
||||
if [[ \$SHIM_PATH != "$PYENV_ROOT/shims" ]]; then
|
||||
export _PYENV_SHIM_PATH="\$SHIM_PATH"
|
||||
fi
|
||||
exec "$(command -v pyenv)" exec "\$program" "\$@"
|
||||
SH
|
||||
chmod +x "$PROTOTYPE_SHIM_PATH"
|
||||
|
|
@ -115,6 +79,17 @@ remove_outdated_shims() {
|
|||
done
|
||||
}
|
||||
|
||||
# List basenames of executables for every Python version
|
||||
list_executable_names() {
|
||||
local version file
|
||||
pyenv-versions --bare --skip-aliases | \
|
||||
while read version; do
|
||||
for file in "${PYENV_ROOT}/versions/${version}/bin/"*; do
|
||||
echo "${file##*/}"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
# The basename of each argument passed to `make_shims` will be
|
||||
# registered for installation as a shim. In this way, plugins may call
|
||||
# `make_shims` with a glob to register many shims at once.
|
||||
|
|
@ -126,62 +101,34 @@ make_shims() {
|
|||
done
|
||||
}
|
||||
|
||||
if ((${BASH_VERSINFO[0]} > 3)); then
|
||||
registered_shims=" "
|
||||
|
||||
declare -A registered_shims
|
||||
# Registers the name of a shim to be generated.
|
||||
register_shim() {
|
||||
registered_shims="${registered_shims}${1} "
|
||||
}
|
||||
|
||||
# Registers the name of a shim to be generated.
|
||||
register_shim() {
|
||||
registered_shims["$1"]=1
|
||||
}
|
||||
# Install all the shims registered via `make_shims` or `register_shim` directly.
|
||||
install_registered_shims() {
|
||||
local shim file
|
||||
for shim in $registered_shims; do
|
||||
file="${SHIM_PATH}/${shim}"
|
||||
[ -e "$file" ] || cp "$PROTOTYPE_SHIM_PATH" "$file"
|
||||
done
|
||||
}
|
||||
|
||||
# Install all shims registered via `make_shims` or `register_shim` directly.
|
||||
install_registered_shims() {
|
||||
local shim file
|
||||
for shim in "${!registered_shims[@]}"; do
|
||||
file="${SHIM_PATH}/${shim}"
|
||||
[ -e "$file" ] || cp "$PROTOTYPE_SHIM_PATH" "$file"
|
||||
done
|
||||
}
|
||||
|
||||
# Once the registered shims have been installed, we make a second pass
|
||||
# over the contents of the shims directory. Any file that is present
|
||||
# in the directory but has not been registered as a shim should be
|
||||
# removed.
|
||||
remove_stale_shims() {
|
||||
local shim
|
||||
for shim in "$SHIM_PATH"/*; do
|
||||
if [[ ! ${registered_shims["${shim##*/}"]} ]]; then
|
||||
rm -f "$shim"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
else # Same for bash < 4.
|
||||
|
||||
registered_shims=" "
|
||||
|
||||
register_shim() {
|
||||
registered_shims="${registered_shims}${1} "
|
||||
}
|
||||
|
||||
install_registered_shims() {
|
||||
local shim file
|
||||
for shim in $registered_shims; do
|
||||
file="${SHIM_PATH}/${shim}"
|
||||
[ -e "$file" ] || cp "$PROTOTYPE_SHIM_PATH" "$file"
|
||||
done
|
||||
}
|
||||
|
||||
remove_stale_shims() {
|
||||
local shim
|
||||
for shim in "$SHIM_PATH"/*; do
|
||||
if [[ "$registered_shims" != *" ${shim##*/} "* ]]; then
|
||||
rm -f "$shim"
|
||||
fi
|
||||
done
|
||||
}
|
||||
fi
|
||||
# Once the registered shims have been installed, we make a second pass
|
||||
# over the contents of the shims directory. Any file that is present
|
||||
# in the directory but has not been registered as a shim should be
|
||||
# removed.
|
||||
remove_stale_shims() {
|
||||
local shim
|
||||
for shim in "$SHIM_PATH"/*; do
|
||||
if [[ "$registered_shims" != *" ${shim##*/} "* ]]; then
|
||||
rm -f "$shim"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
|
|
@ -189,8 +136,7 @@ shopt -s nullglob
|
|||
# executables.
|
||||
create_prototype_shim
|
||||
remove_outdated_shims
|
||||
# shellcheck disable=SC2046
|
||||
make_shims $(pyenv-versions --executables)
|
||||
make_shims $(list_executable_names | sort -u)
|
||||
|
||||
|
||||
# Allow plugins to register shims.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
# Summary: Display the root directory where versions and shims are kept
|
||||
echo "$PYENV_ROOT"
|
||||
echo $PYENV_ROOT
|
||||
|
|
|
|||
|
|
@ -11,17 +11,11 @@ shell="$(basename "${PYENV_SHELL:-$SHELL}")"
|
|||
|
||||
# When pyenv shell integration is enabled, delegate to pyenv-rehash,
|
||||
# then tell the shell to empty its command lookup cache.
|
||||
case "$shell" in
|
||||
pwsh)
|
||||
echo "& (get-command pyenv -commandtype application) rehash"
|
||||
;;
|
||||
*)
|
||||
echo "command pyenv rehash"
|
||||
esac
|
||||
pyenv-rehash
|
||||
|
||||
case "$shell" in
|
||||
fish | pwsh )
|
||||
# no executable cache
|
||||
fish )
|
||||
# no rehash support
|
||||
;;
|
||||
* )
|
||||
echo "hash -r 2>/dev/null || true"
|
||||
|
|
|
|||
|
|
@ -48,11 +48,8 @@ if [ "$versions" = "--unset" ]; then
|
|||
echo 'set -gu PYENV_VERSION_OLD "$PYENV_VERSION"'
|
||||
echo "set -e PYENV_VERSION"
|
||||
;;
|
||||
pwsh )
|
||||
echo '$Env:PYENV_VERSION, $Env:PYENV_VERSION_OLD = $null, $Env:PYENV_VERSION'
|
||||
;;
|
||||
* )
|
||||
echo 'PYENV_VERSION_OLD="${PYENV_VERSION-}"'
|
||||
echo 'PYENV_VERSION_OLD="$PYENV_VERSION"'
|
||||
echo "unset PYENV_VERSION"
|
||||
;;
|
||||
esac
|
||||
|
|
@ -77,16 +74,6 @@ else
|
|||
echo "pyenv: PYENV_VERSION_OLD is not set" >&2
|
||||
false
|
||||
end
|
||||
EOS
|
||||
;;
|
||||
pwsh )
|
||||
cat <<EOS
|
||||
if ( Get-Item -Path Env:\PYENV_VERSION* ) {
|
||||
\$Env:PYENV_VERSION, \$Env:PYENV_VERSION_OLD = \$Env:PYENV_VERSION_OLD, \$Env:PYENV_VERSION
|
||||
} else {
|
||||
Write-Error "pyenv: Env:PYENV_VERSION_OLD is not set"
|
||||
return \$false
|
||||
}
|
||||
EOS
|
||||
;;
|
||||
* )
|
||||
|
|
@ -122,11 +109,8 @@ if pyenv-prefix "${versions[@]}" >/dev/null; then
|
|||
echo 'set -gu PYENV_VERSION_OLD "$PYENV_VERSION"'
|
||||
echo "set -gx PYENV_VERSION \"$version\""
|
||||
;;
|
||||
pwsh )
|
||||
echo '$Env:PYENV_VERSION, $Env:PYENV_VERSION_OLD = "'"${version}"'", $Env:PYENV_VERSION'
|
||||
;;
|
||||
* )
|
||||
echo 'PYENV_VERSION_OLD="${PYENV_VERSION-}"'
|
||||
echo 'PYENV_VERSION_OLD="$PYENV_VERSION"'
|
||||
echo "export PYENV_VERSION=\"${version}\""
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -1,40 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
# Summary: Show the current Python version(s) and its origin
|
||||
# Usage: pyenv version [--bare]
|
||||
# Summary: Show the current Python version and its origin
|
||||
#
|
||||
# --bare show just the version name. An alias to `pyenv version-name'
|
||||
# Shows the currently selected Python version and how it was
|
||||
# selected. To obtain only the version string, use `pyenv
|
||||
# version-name'.
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
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
|
||||
for arg; do
|
||||
case "$arg" in
|
||||
--complete )
|
||||
echo --bare
|
||||
exit ;;
|
||||
--bare ) bare=1 ;;
|
||||
* )
|
||||
pyenv-help --usage version >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
for PYENV_VERSION_NAME in "${PYENV_VERSION_NAMES[@]}"; do
|
||||
if [[ -n $bare ]]; then
|
||||
echo "$PYENV_VERSION_NAME"
|
||||
else
|
||||
echo "$PYENV_VERSION_NAME (set by $(pyenv-version-origin))"
|
||||
fi
|
||||
echo "$PYENV_VERSION_NAME (set by $(pyenv-version-origin))"
|
||||
done
|
||||
|
||||
exit $exitcode
|
||||
|
|
|
|||
|
|
@ -8,28 +8,12 @@ target_dir="$1"
|
|||
|
||||
find_local_version_file() {
|
||||
local root="$1"
|
||||
# Nonexistent paths are UB as of this writing.
|
||||
# Relative ones cause a failure but absolute ones don't
|
||||
[[ $root != /* ]] && root=$(CDPATH= cd -- "$root" && pwd)
|
||||
# Original Rbenv code supports UNC notation for Cygwin/MinGW
|
||||
# (https://github.com/rbenv/rbenv/pull/529)
|
||||
# POSIX.1-2024 still allows to treat //<name> in implementation-specific manner
|
||||
# (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap04.html#tag_04_16)
|
||||
# even though few UNIX variants do that
|
||||
local unc; [[ $root =~ ^//[^/] && ! / -ef // ]] && unc=1
|
||||
root="${root%/}"
|
||||
# when testing root, $root is ""
|
||||
while true; do
|
||||
# don't test //.python-version if // is special
|
||||
# as it's pointless and possibly very slow
|
||||
# if it e.g. leads to a network search
|
||||
[[ $unc && $root == / ]] && break
|
||||
|
||||
if [[ -f $root/.python-version ]]; then
|
||||
echo "$root/.python-version"
|
||||
while ! [[ "$root" =~ ^//[^/]*$ ]]; do
|
||||
if [ -f "${root}/.python-version" ]; then
|
||||
echo "${root}/.python-version"
|
||||
return 0
|
||||
fi
|
||||
[[ -n $root ]] || break
|
||||
[ -n "$root" ] || break
|
||||
root="${root%/*}"
|
||||
done
|
||||
return 1
|
||||
|
|
@ -38,9 +22,7 @@ find_local_version_file() {
|
|||
if [ -n "$target_dir" ]; then
|
||||
find_local_version_file "$target_dir"
|
||||
else
|
||||
# In tests, PYENV_DIR is not set, ultimately leading to "cd: null directory" in OpenSUSE
|
||||
# While most `cd` implementations treat an empty argument the same as missing, that's still wrong
|
||||
find_local_version_file "${PYENV_DIR:=$PWD}" || {
|
||||
find_local_version_file "$PYENV_DIR" || {
|
||||
[ "$PYENV_DIR" != "$PWD" ] && find_local_version_file "$PWD"
|
||||
} || echo "${PYENV_ROOT}/version"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -5,39 +5,18 @@ set -e
|
|||
|
||||
VERSION_FILE="$1"
|
||||
|
||||
function is_version_safe() {
|
||||
# As needed, check that the constructed path exists as a child path of PYENV_ROOT/versions
|
||||
version="$1"
|
||||
if [[ "$version" == ".." || "$version" == */* ]]; then
|
||||
# Sanity check the value of version to prevent malicious path-traversal
|
||||
(
|
||||
cd "$PYENV_ROOT/versions/$version" &>/dev/null || exit 1
|
||||
[[ "$PWD" == "$PYENV_ROOT/versions/"* ]]
|
||||
)
|
||||
return $?
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -s "$VERSION_FILE" ]; then
|
||||
if [ -e "$VERSION_FILE" ]; then
|
||||
# Read the first non-whitespace word from the specified version file.
|
||||
# Be careful not to load it whole in case there's something crazy in it.
|
||||
IFS="$IFS"$'\r'
|
||||
sep=
|
||||
while read -n 1024 -r version _ || [[ $version ]]; do
|
||||
if [[ -z "$version" || "$version" == \#* ]]; then
|
||||
# Skip empty lines and comments
|
||||
continue
|
||||
elif ! is_version_safe "$version"; then
|
||||
# CVE-2022-35861 allowed arbitrary code execution in some contexts and is mitigated by is_version_safe.
|
||||
echo "pyenv: invalid version \`$version' ignored in \`$VERSION_FILE'" >&2
|
||||
continue
|
||||
fi
|
||||
printf "%s%s" "$sep" "$version"
|
||||
sep=:
|
||||
done <"$VERSION_FILE"
|
||||
[[ $sep ]] && { echo; exit; }
|
||||
IFS="${IFS}"$'\r'
|
||||
words=($(cut -b 1-1024 "$VERSION_FILE" | sed 's/^\s*\(\S\+\).*/\1/'))
|
||||
versions=("${words[@]}")
|
||||
|
||||
if [ -n "$versions" ]; then
|
||||
IFS=":"
|
||||
echo "${versions[*]}"
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 1
|
||||
|
|
|
|||
|
|
@ -1,25 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# Usage: pyenv version-file-write [-f|--force] <file> <version> [...]
|
||||
#
|
||||
# -f/--force Don't verify that the versions exist
|
||||
# Usage: pyenv version-file-write <file> <version>
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
case "$1" in
|
||||
-f|--force)
|
||||
FORCE=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
PYENV_VERSION_FILE="$1"
|
||||
shift || true
|
||||
versions=("$@")
|
||||
|
|
@ -30,7 +14,7 @@ if [ -z "$versions" ] || [ -z "$PYENV_VERSION_FILE" ]; then
|
|||
fi
|
||||
|
||||
# Make sure the specified version is installed.
|
||||
[[ -z $FORCE ]] && pyenv-prefix "${versions[@]}" >/dev/null
|
||||
pyenv-prefix "${versions[@]}" >/dev/null
|
||||
|
||||
# Write the version out to disk.
|
||||
# Create an empty file. Using "rm" might cause a permission error.
|
||||
|
|
|
|||
|
|
@ -1,25 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
# Summary: Show the current Python version
|
||||
#
|
||||
# -f/--force (Internal) If a version doesn't exist, print it as is rather than produce an error
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
case "$1" in
|
||||
-f|--force)
|
||||
FORCE=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
if [ -z "$PYENV_VERSION" ]; then
|
||||
PYENV_VERSION_FILE="$(pyenv-version-file)"
|
||||
PYENV_VERSION="$(pyenv-version-file-read "$PYENV_VERSION_FILE" || true)"
|
||||
|
|
@ -46,32 +29,16 @@ 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-}"
|
||||
[[ $version != "${normalised_version}" ]] && normalization_done=1
|
||||
if [[ $version == "system" ]] || version_exists "${normalised_version}" ; then
|
||||
versions+=("${normalised_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}")
|
||||
if version_exists "$version" || [ "$version" = "system" ]; then
|
||||
versions=("${versions[@]}" "${version}")
|
||||
elif version_exists "${version#python-}"; then
|
||||
versions=("${versions[@]}" "${version#python-}")
|
||||
else
|
||||
if [[ -n $FORCE ]]; then
|
||||
versions+=("${normalised_version}")
|
||||
else
|
||||
echo "pyenv: version \`$version' is not installed (set by $(pyenv-version-origin))" >&2
|
||||
any_not_installed=1
|
||||
fi
|
||||
echo "pyenv: version \`$version' is not installed (set by $(pyenv-version-origin))" >&2
|
||||
any_not_installed=1
|
||||
fi
|
||||
done
|
||||
set +f
|
||||
}
|
||||
IFS="$OLDIFS"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,23 @@
|
|||
#!/usr/bin/env bash
|
||||
# Summary: List all Python versions available to pyenv
|
||||
# Usage: pyenv versions [--bare] [--skip-aliases] [--skip-envs] [--executables]
|
||||
# Usage: pyenv versions [--bare] [--skip-aliases]
|
||||
#
|
||||
# Lists all Python versions found in `$PYENV_ROOT/versions/*'.
|
||||
#
|
||||
# --bare List just the names, omit `system'
|
||||
# --skip-aliases Skip symlinks to other versions and to virtual environments
|
||||
# --skip-envs Skip virtual environments (under <version>/envs)
|
||||
# --executables Internal. Overrides other options.
|
||||
# Optimally get a deduplicated list of all executable names in Pyenv-managed
|
||||
# versions and environments for `pyenv rehash'
|
||||
#
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
unset bare skip_aliases skip_envs executables
|
||||
unset bare
|
||||
unset skip_aliases
|
||||
# Provide pyenv completions
|
||||
for arg; do
|
||||
case "$arg" in
|
||||
--complete )
|
||||
echo --bare
|
||||
echo --skip-aliases
|
||||
echo --skip-envs
|
||||
exit ;;
|
||||
--executables ) executables=1; break ;;
|
||||
--bare ) bare=1 ;;
|
||||
--skip-aliases ) skip_aliases=1 ;;
|
||||
--skip-envs ) skip_envs=1 ;;
|
||||
* )
|
||||
pyenv-help --usage versions >&2
|
||||
exit 1
|
||||
|
|
@ -35,28 +25,15 @@ for arg; do
|
|||
esac
|
||||
done
|
||||
|
||||
|
||||
versions_dir="${PYENV_ROOT}/versions"
|
||||
|
||||
# Fast path for rehash: skip filtering and link resolution
|
||||
if [[ -n "$executables" ]]; then
|
||||
if [ -d "$versions_dir" ]; then
|
||||
shopt -s dotglob nullglob
|
||||
# MacOS 12+ and FreeBSD 15 support `xargs -r -0' and `basename -a'
|
||||
# `sort -u` is simpler and a bit faster than `awk '!seen[$0]++'`, with the same result for rehash purposes
|
||||
printf '%s\0' "$versions_dir"/*/bin/* "$versions_dir"/*/envs/*/bin/* | xargs -0 -r basename -a | sort -u
|
||||
shopt -u dotglob nullglob
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! enable -f "${BASH_SOURCE%/*}"/pyenv-realpath.dylib realpath 2>/dev/null; then
|
||||
if [ -n "$PYENV_NATIVE_EXT" ]; then
|
||||
echo "pyenv: failed to load \`realpath' builtin" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
READLINK=$(type -P readlink)
|
||||
READLINK=$(type -p greadlink readlink | head -1)
|
||||
if [ -z "$READLINK" ]; then
|
||||
echo "pyenv: cannot find readlink - are you missing GNU coreutils?" >&2
|
||||
exit 1
|
||||
|
|
@ -87,29 +64,16 @@ if [ -d "$versions_dir" ]; then
|
|||
versions_dir="$(realpath "$versions_dir")"
|
||||
fi
|
||||
|
||||
if ((${BASH_VERSINFO[0]} > 3)); then
|
||||
declare -A current_versions
|
||||
else
|
||||
current_versions=()
|
||||
fi
|
||||
if [ -n "$bare" ]; then
|
||||
hit_prefix=""
|
||||
miss_prefix=""
|
||||
current_versions=()
|
||||
include_system=""
|
||||
else
|
||||
hit_prefix="* "
|
||||
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"
|
||||
done
|
||||
else
|
||||
current_versions=($(pyenv-version-name || true))
|
||||
fi
|
||||
set +f
|
||||
IFS=: current_versions=($(pyenv-version-name || true))
|
||||
IFS="$OLDIFS"
|
||||
include_system="1"
|
||||
fi
|
||||
|
|
@ -129,73 +93,37 @@ exists() {
|
|||
}
|
||||
|
||||
print_version() {
|
||||
local version="${1:?}"
|
||||
if [[ -n $bare ]]; then
|
||||
echo "$version"
|
||||
return
|
||||
fi
|
||||
local path="${2:?}"
|
||||
if [[ -L "$path" ]]; then
|
||||
# Only resolve the link itself for printing, do not resolve further.
|
||||
# Doing otherwise would misinform the user of what the link contains.
|
||||
version_repr="$version --> $(readlink "$path")"
|
||||
if exists "$1" "${current_versions[@]}"; then
|
||||
echo "${hit_prefix}$1 (set by $(pyenv-version-origin))"
|
||||
else
|
||||
version_repr="$version"
|
||||
fi
|
||||
if [[ ${BASH_VERSINFO[0]} -ge 4 && ${current_versions["$1"]} ]]; then
|
||||
echo "${hit_prefix}${version_repr} (set by $(pyenv-version-origin))"
|
||||
elif (( ${BASH_VERSINFO[0]} <= 3 )) && exists "$1" "${current_versions[@]}"; then
|
||||
echo "${hit_prefix}${version_repr} (set by $(pyenv-version-origin))"
|
||||
else
|
||||
echo "${miss_prefix}${version_repr}"
|
||||
echo "${miss_prefix}$1"
|
||||
fi
|
||||
num_versions=$((num_versions + 1))
|
||||
}
|
||||
|
||||
# Include "system" in the non-bare output, if it exists
|
||||
if [ -n "$include_system" ] && \
|
||||
(PYENV_VERSION=system pyenv-which python --skip-advice >/dev/null 2>&1 || \
|
||||
PYENV_VERSION=system pyenv-which python3 --skip-advice >/dev/null 2>&1 || \
|
||||
PYENV_VERSION=system pyenv-which python2 --skip-advice >/dev/null 2>&1) ; then
|
||||
print_version system "/"
|
||||
if [ -n "$include_system" ] && PYENV_VERSION=system pyenv-which python >/dev/null 2>&1; then
|
||||
print_version system
|
||||
fi
|
||||
|
||||
shopt -s dotglob nullglob
|
||||
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
|
||||
|
||||
for path in "${versions_dir_entries[@]}"; do
|
||||
shopt -s nullglob
|
||||
for path in "$versions_dir"/*; do
|
||||
if [ -d "$path" ]; then
|
||||
if [ -n "$skip_aliases" ] && [ -L "$path" ]; then
|
||||
target="$(realpath "$path")"
|
||||
[ "${target%/*}" == "$versions_dir" ] && continue
|
||||
[ "${target%/*/envs/*}" == "$versions_dir" ] && continue
|
||||
fi
|
||||
print_version "${path##*/}" "$path"
|
||||
# virtual environments created by anaconda/miniconda/pyenv-virtualenv
|
||||
if [[ -z $skip_envs ]]; then
|
||||
for env_path in "${path}/envs/"*; do
|
||||
if [ -d "${env_path}" ]; then
|
||||
print_version "${env_path#${PYENV_ROOT}/versions/}" "${env_path}"
|
||||
fi
|
||||
done
|
||||
[ "${target%/*}" != "$versions_dir" ] || continue
|
||||
[ "${target%/*/envs/*}" != "$versions_dir" ] || continue
|
||||
fi
|
||||
print_version "${path##*/}"
|
||||
# virtual environments created by anaconda/miniconda
|
||||
for env_path in "${path}/envs/"*; do
|
||||
if [ -d "${env_path}" ]; then
|
||||
print_version "${env_path#${PYENV_ROOT}/versions/}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
shopt -u dotglob nullglob
|
||||
shopt -u nullglob
|
||||
|
||||
if [ "$num_versions" -eq 0 ] && [ -n "$include_system" ]; then
|
||||
echo "Warning: no Python detected on the system" >&2
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ fi
|
|||
|
||||
whence() {
|
||||
local command="$1"
|
||||
pyenv-versions --bare | while read -r version; do
|
||||
pyenv-versions --bare | while read version; do
|
||||
path="$(pyenv-prefix "$version")/bin/${command}"
|
||||
if [ -x "$path" ]; then
|
||||
[ "$print_paths" ] && echo "$path" || echo "$version"
|
||||
|
|
|
|||
|
|
@ -2,14 +2,10 @@
|
|||
#
|
||||
# Summary: Display the full path to an executable
|
||||
#
|
||||
# Usage: pyenv which <command> [--nosystem] [--skip-advice]
|
||||
# Usage: pyenv which <command>
|
||||
#
|
||||
# Displays the full path to the executable that pyenv will invoke when
|
||||
# you run the given command.
|
||||
# Use --nosystem argument in case when you don't need to search command in the
|
||||
# system environment.
|
||||
# Internal switch --skip-advice used to skip printing an error message on a
|
||||
# failed search.
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
|
@ -19,73 +15,35 @@ if [ "$1" = "--complete" ]; then
|
|||
exec pyenv-shims --short
|
||||
fi
|
||||
|
||||
system="system"
|
||||
SKIP_ADVICE=""
|
||||
PYENV_COMMAND="$1"
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
case "$1" in
|
||||
--skip-advice)
|
||||
SKIP_ADVICE=1
|
||||
shift
|
||||
;;
|
||||
--nosystem)
|
||||
system=""
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
remove_from_path() {
|
||||
local -a paths_to_remove
|
||||
IFS=: paths_to_remove=($1)
|
||||
local path_to_remove path_before
|
||||
local path_to_remove="$1"
|
||||
local path_before
|
||||
local result=":${PATH//\~/$HOME}:"
|
||||
for path_to_remove in "${paths_to_remove[@]}"; do
|
||||
while true; do
|
||||
path_before="$result"
|
||||
result="${result//:$path_to_remove:/:}"
|
||||
if [[ ${#path_before} == "${#result}" ]]; then break; fi
|
||||
done
|
||||
while [ "$path_before" != "$result" ]; do
|
||||
path_before="$result"
|
||||
result="${result//:$path_to_remove:/:}"
|
||||
done
|
||||
result="${result:1:${#result}-2}"
|
||||
echo "$result"
|
||||
result="${result%:}"
|
||||
echo "${result#:}"
|
||||
}
|
||||
|
||||
PYENV_COMMAND="$1"
|
||||
|
||||
if [ -z "$PYENV_COMMAND" ]; then
|
||||
pyenv-help --usage which >&2
|
||||
exit 1
|
||||
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=: versions=(${PYENV_VERSION:-$(pyenv-version-name)})
|
||||
IFS="$OLDIFS"
|
||||
|
||||
declare -a nonexistent_versions
|
||||
|
||||
for version in "${versions[@]}" "$system"; do
|
||||
for version in "${versions[@]}"; do
|
||||
if [ "$version" = "system" ]; then
|
||||
PROGRAM="$(echo "$PYENV_COMMAND" | tr a-z- A-Z_ | sed 's/[^A-Z0-9_]/_/g')"
|
||||
NR_CUSTOM_SHIM_PATHS="_PYENV_SHIM_PATHS_$PROGRAM"
|
||||
SEARCH_PATH="$(remove_from_path "${PYENV_ROOT}/shims${!NR_CUSTOM_SHIM_PATHS:+:${!NR_CUSTOM_SHIM_PATHS}}")"
|
||||
PYENV_COMMAND_PATH="$(PATH="$SEARCH_PATH" command -v "$PYENV_COMMAND" || true)"
|
||||
PATH="$(remove_from_path "${PYENV_ROOT}/shims")"
|
||||
PYENV_COMMAND_PATH="$(command -v "$PYENV_COMMAND" || true)"
|
||||
else
|
||||
# $version may be a prefix to be resolved by pyenv-latest
|
||||
version_path="$(pyenv-prefix "${version}" 2>/dev/null)" || \
|
||||
{ nonexistent_versions+=("$version"); continue; }
|
||||
# resolve $version for hooks
|
||||
version="$(basename "$version_path")"
|
||||
PYENV_COMMAND_PATH="$version_path/bin/${PYENV_COMMAND}"
|
||||
unset version_path
|
||||
PYENV_COMMAND_PATH="${PYENV_ROOT}/versions/${version}/bin/${PYENV_COMMAND}"
|
||||
fi
|
||||
if [ -x "$PYENV_COMMAND_PATH" ]; then
|
||||
break
|
||||
|
|
@ -102,24 +60,29 @@ done
|
|||
if [ -x "$PYENV_COMMAND_PATH" ]; then
|
||||
echo "$PYENV_COMMAND_PATH"
|
||||
else
|
||||
if (( ${#nonexistent_versions[@]} )); then
|
||||
for version in "${nonexistent_versions[@]}"; do
|
||||
any_not_installed=0
|
||||
for version in "${versions[@]}"; do
|
||||
if [ "$version" = "system" ]; then
|
||||
continue
|
||||
fi
|
||||
if ! [ -d "${PYENV_ROOT}/versions/${version}" ]; then
|
||||
echo "pyenv: version \`$version' is not installed (set by $(pyenv-version-origin))" >&2
|
||||
done
|
||||
any_not_installed=1
|
||||
fi
|
||||
done
|
||||
if [ "$any_not_installed" = 1 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "pyenv: $PYENV_COMMAND: command not found" >&2
|
||||
if [ -z "$SKIP_ADVICE" ]; then
|
||||
versions="$(pyenv-whence "$PYENV_COMMAND" || true)"
|
||||
if [ -n "$versions" ]; then
|
||||
{ echo
|
||||
echo "The \`$PYENV_COMMAND' command exists in these Python versions:"
|
||||
echo "$versions" | sed 's/^/ /g'
|
||||
echo
|
||||
echo "Note: See 'pyenv help global' for tips on allowing multiple"
|
||||
echo " Python versions to be found at the same time."
|
||||
} >&2
|
||||
fi
|
||||
|
||||
versions="$(pyenv-whence "$PYENV_COMMAND" || true)"
|
||||
if [ -n "$versions" ]; then
|
||||
{ echo
|
||||
echo "The \`$1' command exists in these Python versions:"
|
||||
echo "$versions" | sed 's/^/ /g'
|
||||
echo
|
||||
} >&2
|
||||
fi
|
||||
|
||||
exit 127
|
||||
|
|
|
|||
550
man/man1/pyenv.1
550
man/man1/pyenv.1
|
|
@ -1,550 +0,0 @@
|
|||
.TH PYENV 1 "24 Apr 2023" "PYENV"
|
||||
.SH NAME
|
||||
pyenv \- Simple Python version management
|
||||
.SH SYNOPSIS
|
||||
.B pyenv
|
||||
\fI\,<command> \/\fR[\fI\,<args>\/\fR]
|
||||
.SH DESCRIPTION
|
||||
pyenv lets you easily switch between multiple versions of Python\. It's simple, unobtrusive, and follows the UNIX tradition of single\-purpose tools that do one thing well\.
|
||||
.P
|
||||
To start using pyenv
|
||||
.IP "1." 3
|
||||
\fBAppend\fR the following to \fB$HOME/.bashrc\fR
|
||||
.P
|
||||
.RS 15
|
||||
.nf
|
||||
if command -v pyenv 1>/dev/null 2>&1; then\n
|
||||
eval "$(pyenv init - bash)" \n
|
||||
fi
|
||||
.fi
|
||||
.RE
|
||||
.RS 3
|
||||
.P
|
||||
.nh
|
||||
Appending this line enables shims. Please make sure this line is placed toward the end of the shell configuration file since it manipulates \fBPATH\fR during the initialization\.
|
||||
.hy
|
||||
.TP 13
|
||||
.B Debian note:
|
||||
Modify only your \fB~/\.bashrc\fR file instead of creating
|
||||
.br
|
||||
\fB~/\.bash_profile\fR
|
||||
.P
|
||||
.RS 0
|
||||
\fBZsh note\fR: Modify your \fB~/\.zshrc\fR file instead of \fB~/\.bashrc\fR
|
||||
.P
|
||||
\fBWarning\fR: If you configured your system so that \fBBASH_ENV\fR variable points to \fB\.bashrc\fR\. You should almost certainly put the above mentioned line into \fB\.bash_profile\fR, and \fBnot\fR into \fB\.bashrc\fR. Otherwise you may observe strange behaviour, such as \fBpyenv\fR getting into an infinite loop. See #264
|
||||
.UR https://github\.com/pyenv/pyenv/issues/264
|
||||
.UE
|
||||
for details.
|
||||
.RE
|
||||
.RE
|
||||
.IP "2." 3
|
||||
\fBRestart your shell so the path changes take effect\.\fR You can now begin using pyenv\.
|
||||
.P
|
||||
.RS 15
|
||||
exec "$SHELL"\fR
|
||||
.RE
|
||||
.IP "3." 3
|
||||
\fBInstall Python versions into \fB$(pyenv root)/versions\fR\.\fR For example, to download and install Python 3\.6\.12, run:
|
||||
.P
|
||||
.RS 15
|
||||
.B pyenv install 3.6.12\fR
|
||||
.RE
|
||||
.P
|
||||
\fBNOTE:\fR If you need to pass configure option to build, please use \fBCONFIGURE_OPTS\fR environment variable. If you are having trouble installing a python version, please visit the wiki page about Common Build Problems
|
||||
.UR https://github\.com/pyenv/pyenv/wiki/Common\-build\-problems
|
||||
.UE
|
||||
.P
|
||||
\fBProxy note\fR: If you use a proxy, export \fBHTTP_PROXY\fR and \fBHTTPS_PROXY\fR environment variables.
|
||||
.P
|
||||
.SS "Stop using pyenv"
|
||||
The simplicity of pyenv makes it easy to temporarily disable it, or uninstall from the system\.
|
||||
To \fBdisable\fR pyenv managing your Python versions, simply remove the \fBpyenv init\fR line from your shell startup configuration\. This will remove pyenv shims directory from PATH, and future invocations like \fBpython\fR will execute the system Python version, as before pyenv\.
|
||||
.IP "" 0
|
||||
.P
|
||||
\fBpyenv\fR will still be accessible on the command line, but your Python apps won't be affected by version switching\.
|
||||
.IP "" 0
|
||||
.SH COMMAND LINE OPTIONS
|
||||
Like \fBgit\fR, the \fBpyenv\fR command delegates to subcommands based on its first argument\.
|
||||
.SS "Some useful pyenv commands are:"
|
||||
.TP 5
|
||||
.B commands
|
||||
List all available pyenv commands
|
||||
.TP
|
||||
.B exec
|
||||
Run an executable with the selected Python version
|
||||
.TP
|
||||
.B global
|
||||
Set or show the global Python version(s)
|
||||
.TP
|
||||
.B help
|
||||
Display help for a command
|
||||
.TP
|
||||
.B hooks
|
||||
List hook scripts for a given pyenv command
|
||||
.TP
|
||||
.B init
|
||||
Configure the shell environment for pyenv
|
||||
.TP
|
||||
.B install
|
||||
Install a Python version using python\-build
|
||||
.TP
|
||||
.B local
|
||||
Set or show the local application\-specific Python version(s)
|
||||
.TP
|
||||
.B prefix
|
||||
Display prefix for a Python version
|
||||
.TP
|
||||
.B rehash
|
||||
Rehash pyenv shims (run this after installing executables)
|
||||
.TP
|
||||
.B root
|
||||
Display the root directory where versions and shims are kept
|
||||
.TP
|
||||
.B shell
|
||||
Set or show the shell\-specific Python version
|
||||
.TP
|
||||
.B shims
|
||||
List existing pyenv shims
|
||||
.TP
|
||||
.B uninstall
|
||||
Uninstall Python versions
|
||||
.TP
|
||||
.B version
|
||||
Show the current Python version(s) and its origin
|
||||
.TP
|
||||
.B version\-file
|
||||
Detect the file that sets the current pyenv version
|
||||
.TP
|
||||
.B version\-name
|
||||
Show the current Python version
|
||||
.TP
|
||||
.B version\-origin
|
||||
Explain how the current Python version is set
|
||||
.TP
|
||||
.B versions
|
||||
List all Python versions available to pyenv
|
||||
.TP
|
||||
.B whence
|
||||
List all Python versions that contain the given executable
|
||||
.TP
|
||||
.B which
|
||||
Display the full path to an executable
|
||||
.PP
|
||||
See `pyenv help <command>' for information on a specific command.
|
||||
For full documentation, see \fBCOMMAND REFERENCE\fR section
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Show summary of options.
|
||||
.TP
|
||||
.B \-v, \-\-version
|
||||
Show version of program.
|
||||
.SH COMPARISON
|
||||
.P
|
||||
.B "pyenv does\|\.\|\.\|\.
|
||||
.P
|
||||
.IP \(bu 4
|
||||
Let you \fBchange the global Python version\fR on a per\-user basis\.
|
||||
.IP \(bu 4
|
||||
Provide support for \fBper\-project Python versions\fR\.
|
||||
.IP \(bu 4
|
||||
Allow you to \fBoverride the Python version\fR with an environment variable\.
|
||||
.IP \(bu 4
|
||||
Search commands from \fBmultiple versions of Python at a time\fR\. This may be helpful to test across Python versions with tox
|
||||
.IP "" 0
|
||||
.P
|
||||
.B "In contrast with pythonbrew and pythonz, pyenv does not\|\.\|\.\|\."
|
||||
.IP \(bu 4
|
||||
\fBDepend on Python itself\.\fR pyenv was made from pure shell scripts\. There is no bootstrap problem of Python\.
|
||||
.IP \(bu 4
|
||||
\fBNeed to be loaded into your shell\.\fR Instead, pyenv's shim approach works by adding a directory to your \fB$PATH\fR\.
|
||||
.IP \(bu 4
|
||||
\fBManage virtualenv\.\fR Of course, you can create virtualenv yourself, or pyenv\-virtualenv to automate the process\.
|
||||
.SH "How It Works"
|
||||
At a high level, pyenv intercepts Python commands using shim executables injected into your \fBPATH\fR, determines which Python version has been specified by your application, and passes your commands along to the correct Python installation\.
|
||||
.SS "Understanding PATH"
|
||||
When you run a command like \fBpython\fR or \fBpip\fR, your operating system searches through a list of directories to find an executable file with that name\. This list of directories lives in an environment variable called \fBPATH\fR, with each directory in the list separated by a colon:
|
||||
.IP "" 4
|
||||
.nf
|
||||
/usr/local/bin:/usr/bin:/bin
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Directories in \fBPATH\fR are searched from left to right, so a matching executable in a directory at the beginning of the list takes precedence over another one at the end\. In this example, the \fB/usr/local/bin\fR directory will be searched first, then \fB/usr/bin\fR, then \fB/bin\fR\.
|
||||
.SS "Understanding Shims"
|
||||
pyenv works by inserting a directory of \fIshims\fR at the front of your \fBPATH\fR:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$(pyenv root)/shims:/usr/local/bin:/usr/bin:/bin
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Through a process called \fIrehashing\fR, pyenv maintains shims in that directory to match every Python command (\fBpython\fR,\fBpip\fR,etc...) across every installed version of Python
|
||||
.P
|
||||
Shims are lightweight executables that simply pass your command along to pyenv\. So with pyenv installed, when you run, say, \fBpip\fR, your operating system will do the following:
|
||||
.IP \(bu 4
|
||||
Search your \fBPATH\fR for an executable file named \fBpip\fR
|
||||
.IP \(bu 4
|
||||
Find the pyenv shim named \fBpip\fR at the beginning of your \fBPATH\fR
|
||||
.IP \(bu 4
|
||||
Run the shim named \fBpip\fR, which in turn passes the command along to pyenv
|
||||
.IP "" 0
|
||||
.SS "Choosing the Python Version"
|
||||
When you execute a shim, pyenv determines which Python version to use by reading it from the following sources, in this order:
|
||||
.IP "1." 4
|
||||
The \fBPYENV_VERSION\fR environment variable (if specified)\. You can use the \fBpyenv shell\fR command to set this environment variable in your current shell session\.
|
||||
.IP "2." 4
|
||||
The application\-specific \fB\.python\-version\fR file in the current directory (if present)\. You can modify the current directory's \fB\.python\-version\fR file with the \fBpyenv local\fR command\.
|
||||
.IP "3." 4
|
||||
The first \fB\.python\-version\fR file found (if any) by searching each parent directory, until reaching the root of your filesystem\.
|
||||
.IP "4." 4
|
||||
The global \fB$(pyenv root)/version\fR file\. You can modify this file using the \fBpyenv global\fR command\. If the global version file is not present, pyenv assumes you want to use the "system" Python\. (In other words, whatever version would run if pyenv weren't in your \fBPATH\fR\.)
|
||||
.IP "" 0
|
||||
.P
|
||||
.nh
|
||||
\fBNOTE:\fR You can activate multiple versions at the same time, including multiple versions of Python2 or Python3 simultaneously\. This allows for parallel usage of Python2 and Python3, and is required with tools like \fBtox\fR\. For example, to set your path to first use your \fBsystem\fR Python and Python3 (set to 2\.7\.9 and 3\.4\.2 in this example), but also have Python 3\.3\.6, 3\.2, and 2\.5 available on your \fBPATH\fR, one would first \fBpyenv install\fR the missing versions, then set \fBpyenv global system 3\.3\.6 3\.2 2\.5\fR\. At this point, one should be able to find the full executable path to each of these using \fBpyenv which\fR, e\.g\. \fBpyenv which python2\.5\fR (should display \fB$(pyenv root)/versions/2\.5/bin/python2\.5\fR), or \fBpyenv which python3\.4\fR (should display path to system Python3)\. You can also specify multiple versions in a \fB\.python\-version\fR file, separated by newlines or any whitespace\.
|
||||
hy
|
||||
.SS "Locating the Python Installation"
|
||||
Once pyenv has determined which version of Python your application has specified, it passes the command along to the corresponding Python installation\.
|
||||
.P
|
||||
Each Python version is installed into its own directory under
|
||||
.nf
|
||||
\fB$(pyenv root)/versions\fR\.
|
||||
.fi
|
||||
.P
|
||||
For example, you might have these versions installed:
|
||||
.IP \(bu 4
|
||||
\fB$(pyenv root)/versions/2\.7\.8/\fR
|
||||
.IP \(bu 4
|
||||
\fB$(pyenv root)/versions/3\.4\.2/\fR
|
||||
.IP \(bu 4
|
||||
\fB$(pyenv root)/versions/pypy\-2\.4\.0/\fR
|
||||
.IP "" 0
|
||||
.P
|
||||
As far as pyenv is concerned, version names are simply the directories in \fB$(pyenv root)/versions\fR\.
|
||||
.SS "Managing Virtual Environments"
|
||||
There is a pyenv plugin named pyenv\-virtualenv which comes with various features to help pyenv users to manage virtual environments created by virtualenv or Anaconda\. Because the \fBactivate\fR script of those virtual environments are relying on mutating \fB$PATH\fR variable of user's interactive shell, it will intercept pyenv's shim style command execution hooks\. We'd recommend to install pyenv\-virtualenv as well if you have some plan to play with those virtual environments\.
|
||||
|
||||
.SH "Advanced Configuration"
|
||||
Skip this section unless you must know what every line in your shell profile is doing\.
|
||||
.P
|
||||
\fBpyenv init\fR is the only command that crosses the line of loading extra commands into your shell\. Coming from rvm, some of you might be opposed to this idea\. Here's what \fBpyenv init\fR actually does:
|
||||
.IP "1." 4
|
||||
\fBSets up your shims path\.\fR This is the only requirement for pyenv to function properly\. You can do this by hand by prepending \fB$(pyenv root)/shims\fR to your \fB$PATH\fR\.
|
||||
.IP "2." 4
|
||||
\fBRehashes shims\.\fR From time to time you'll need to rebuild your shim files\. Doing this on init makes sure everything is up to date\. You can always run \fBpyenv rehash\fR manually\.
|
||||
You can disable this functionality by adding \fB--no-rehash\fR to the end of your \fBpyenv init\fR command line.
|
||||
.IP "3." 4
|
||||
\fBInstalls the sh dispatcher\.\fR This bit is also optional, but allows pyenv and plugins to change variables in your current shell, making commands like \fBpyenv shell\fR possible\. The sh dispatcher doesn't do anything crazy like override \fBcd\fR or hack your shell prompt, but if for some reason you need \fBpyenv\fR to be a real script rather than a shell function, you can safely skip it\.
|
||||
.IP "" 0
|
||||
.P
|
||||
To see exactly what happens under the hood for yourself, run \fB"pyenv init \-"\fR\.
|
||||
.SH "Uninstalling Python Versions"
|
||||
As time goes on, you will accumulate Python versions in your \fB$(pyenv root)/versions\fR directory\.
|
||||
.P
|
||||
To remove old Python versions, \fBpyenv uninstall\fR command to automate the removal process\.
|
||||
.P
|
||||
Alternatively, simply \fBrm \-rf\fR the directory of the version you want to remove\. You can find the directory of a particular Python version with the \fBpyenv prefix\fR command,
|
||||
.P
|
||||
e\.g\. \fBpyenv prefix 2\.6\.8\fR\.
|
||||
.SH "Command Reference"
|
||||
.P
|
||||
The most common subcommands are:
|
||||
.SS "pyenv commands"
|
||||
Lists all available pyenv commands\.
|
||||
.SS "pyenv local"
|
||||
Sets a local application\-specific Python version by writing the version name to a \fB\.python\-version\fR file in the current directory\. This version overrides the global version, and can be overridden itself by setting the \fBPYENV_VERSION\fR environment variable or with the \fBpyenv shell\fR command\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv local 2\.7\.6
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
When run without a version number, \fBpyenv local\fR reports the currently configured local version\. You can also unset the local version:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv local \-\-unset
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Previous versions of pyenv stored local version specifications in a file named \fB\.pyenv\-version\fR\. For backwards compatibility, pyenv will read a local version specified in an \fB\.pyenv\-version\fR file, but a \fB\.python\-version\fR file in the same directory will take precedence\.
|
||||
.P
|
||||
You can specify multiple versions as local Python at once\.
|
||||
.P
|
||||
Let's say if you have two versions of 2\.7\.6 and 3\.3\.3\. If you prefer 2\.7\.6 over 3\.3\.3,
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv local 2\.7\.6 3\.3\.3
|
||||
$ pyenv versions
|
||||
system
|
||||
* 2\.7\.6 (set by /Users/yyuu/path/to/project/\.python\-version)
|
||||
* 3\.3\.3 (set by /Users/yyuu/path/to/project/\.python\-version)
|
||||
$ python \-\-version
|
||||
Python 2\.7\.6
|
||||
$ python2\.7 \-\-version
|
||||
Python 2\.7\.6
|
||||
$ python3\.3 \-\-version
|
||||
Python 3\.3\.3
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
or, if you prefer 3\.3\.3 over 2\.7\.6,
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv local 3\.3\.3 2\.7\.6
|
||||
$ pyenv versions
|
||||
system
|
||||
* 2\.7\.6 (set by /Users/yyuu/path/to/project/\.python\-version)
|
||||
* 3\.3\.3 (set by /Users/yyuu/path/to/project/\.python\-version)
|
||||
venv27
|
||||
$ python \-\-version
|
||||
Python 3\.3\.3
|
||||
$ python2\.7 \-\-version
|
||||
Python 2\.7\.6
|
||||
$ python3\.3 \-\-version
|
||||
Python 3\.3\.3
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SS "pyenv global"
|
||||
Sets the global version of Python to be used in all shells by writing the version name to the \fB~/\.pyenv/version\fR file\. This version can be overridden by an application\-specific \fB\.python\-version\fR file, or by setting the \fBPYENV_VERSION\fR environment variable\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv global 2\.7\.6
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
The special version name \fBsystem\fR tells pyenv to use the system Python (detected by searching your \fB$PATH\fR)\.
|
||||
.P
|
||||
When run without a version number, \fBpyenv global\fR reports the currently configured global version\.
|
||||
.P
|
||||
You can specify multiple versions as global Python at once\.
|
||||
.P
|
||||
Let's say if you have two versions of 2\.7\.6 and 3\.3\.3\. If you prefer 2\.7\.6 over 3\.3\.3,
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv global 2\.7\.6 3\.3\.3
|
||||
$ pyenv versions
|
||||
system
|
||||
* 2\.7\.6 (set by /Users/yyuu/\.pyenv/version)
|
||||
* 3\.3\.3 (set by /Users/yyuu/\.pyenv/version)
|
||||
$ python \-\-version
|
||||
Python 2\.7\.6
|
||||
$ python2\.7 \-\-version
|
||||
Python 2\.7\.6
|
||||
$ python3\.3 \-\-version
|
||||
Python 3\.3\.3
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
or, if you prefer 3\.3\.3 over 2\.7\.6,
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv global 3\.3\.3 2\.7\.6
|
||||
$ pyenv versions
|
||||
system
|
||||
* 2\.7\.6 (set by /Users/yyuu/\.pyenv/version)
|
||||
* 3\.3\.3 (set by /Users/yyuu/\.pyenv/version)
|
||||
venv27
|
||||
$ python \-\-version
|
||||
Python 3\.3\.3
|
||||
$ python2\.7 \-\-version
|
||||
Python 2\.7\.6
|
||||
$ python3\.3 \-\-version
|
||||
Python 3\.3\.3
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SS "pyenv shell"
|
||||
Sets a shell\-specific Python version by setting the \fBPYENV_VERSION\fR environment variable in your shell\. This version overrides application\-specific versions and the global version\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv shell pypy\-2\.2\.1
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
When run without a version number, \fBpyenv shell\fR reports the current value of \fBPYENV_VERSION\fR\. You can also unset the shell version:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv shell \-\-unset
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Note that you'll need pyenv's shell integration enabled (step 3 of the installation instructions) in order to use this command\. If you prefer not to use shell integration, you may simply set the \fBPYENV_VERSION\fR variable yourself:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ export PYENV_VERSION=pypy\-2\.2\.1
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
You can specify multiple versions via \fBPYENV_VERSION\fR at once\.
|
||||
.P
|
||||
Let's say if you have two versions of 2\.7\.6 and 3\.3\.3\. If you prefer 2\.7\.6 over 3\.3\.3,
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv shell 2\.7\.6 3\.3\.3
|
||||
$ pyenv versions
|
||||
system
|
||||
* 2\.7\.6 (set by PYENV_VERSION environment variable)
|
||||
* 3\.3\.3 (set by PYENV_VERSION environment variable)
|
||||
$ python \-\-version
|
||||
Python 2\.7\.6
|
||||
$ python2\.7 \-\-version
|
||||
Python 2\.7\.6
|
||||
$ python3\.3 \-\-version
|
||||
Python 3\.3\.3
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
or, if you prefer 3\.3\.3 over 2\.7\.6,
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv shell 3\.3\.3 2\.7\.6
|
||||
$ pyenv versions
|
||||
system
|
||||
* 2\.7\.6 (set by PYENV_VERSION environment variable)
|
||||
* 3\.3\.3 (set by PYENV_VERSION environment variable)
|
||||
venv27
|
||||
$ python \-\-version
|
||||
Python 3\.3\.3
|
||||
$ python2\.7 \-\-version
|
||||
Python 2\.7\.6
|
||||
$ python3\.3 \-\-version
|
||||
Python 3\.3\.3
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SS "pyenv install"
|
||||
Install a Python version
|
||||
.IP "" 4
|
||||
.nf
|
||||
Usage: pyenv install [\-f] [\-kvp] <version>
|
||||
pyenv install [\-f] [\-kvp] <definition\-file>
|
||||
pyenv install \-l|\-\-list
|
||||
|
||||
\-l, \-\-list List all available versions
|
||||
\-f, \-\-force Install even if the version appears to be installed
|
||||
already
|
||||
\-s, \-\-skip\-existing Skip the installation if the version appears to be
|
||||
installed already
|
||||
|
||||
python\-build options:
|
||||
|
||||
\-k, \-\-keep Keep source tree in $PYENV_BUILD_ROOT after installation
|
||||
(defaults to $PYENV_ROOT/sources)
|
||||
\-v, \-\-verbose Verbose mode: print compilation status to stdout
|
||||
\-p, \-\-patch Apply a patch from stdin before building
|
||||
\-g, \-\-debug Build a debug version
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
To list the all available versions of Python, including Anaconda, Jython, pypy, and stackless, use:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv install \-\-list
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Then install the desired versions:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv install 2\.7\.6
|
||||
$ pyenv install 2\.6\.8
|
||||
$ pyenv versions
|
||||
system
|
||||
2\.6\.8
|
||||
* 2\.7\.6 (set by /home/yyuu/\.pyenv/version)
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SS "pyenv uninstall"
|
||||
Uninstall Python versions\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
Usage: pyenv uninstall [\-f|\-\-force] <version> ...
|
||||
|
||||
\-f Attempt to remove the specified version without prompting
|
||||
for confirmation\. If the version does not exist, do not
|
||||
display an error message\.
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SS "pyenv rehash"
|
||||
Installs shims for all Python binaries known to pyenv (i\.e\., \fB~/\.pyenv/versions/*/bin/*\fR)\. Run this command after you install a new version of Python, or install a package that provides binaries\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv rehash
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SS "pyenv version"
|
||||
Displays the currently active Python version, along with information on how it was set\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv version
|
||||
2\.7\.6 (set by /home/yyuu/\.pyenv/version)
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SS "pyenv versions"
|
||||
Lists all Python versions known to pyenv, and shows an asterisk next to the currently active version\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv versions
|
||||
2\.5\.6
|
||||
2\.6\.8
|
||||
* 2\.7\.6 (set by /home/yyuu/\.pyenv/version)
|
||||
3\.3\.3
|
||||
jython\-2\.5\.3
|
||||
pypy\-2\.2\.1
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SS "pyenv which"
|
||||
Displays the full path to the executable that pyenv will invoke when you run the given command\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv which python3\.3
|
||||
/home/yyuu/\.pyenv/versions/3\.3\.3/bin/python3\.3
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SS "pyenv whence"
|
||||
Lists all Python versions with the given command installed\.
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ pyenv whence 2to3
|
||||
2\.6\.8
|
||||
2\.7\.6
|
||||
3\.3\.3
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SH "Environment variables"
|
||||
You can affect how pyenv operates with the following settings:
|
||||
.TP 28
|
||||
.B name (default)
|
||||
.B description
|
||||
.TP 28
|
||||
.B PYENV_VERSION
|
||||
Specifies the Python version to be used. Also see \fBpyenv shell\fR
|
||||
.TP
|
||||
.B PYENV_ROOT (\fB~/.pyenv\fR)
|
||||
Defines the directory under which Python versions and shims reside. Also see \fBpyenv root\fR
|
||||
.TP
|
||||
.B PYENV_DEBUG
|
||||
Outputs debug information.
|
||||
.br
|
||||
Also as: \fBpyenv --debug <subcommand>\fR
|
||||
.TP
|
||||
.B PYENV_HOOK_PATH
|
||||
Colon\-separated list of paths searched for pyenv hooks\.
|
||||
.TP
|
||||
.B PYENV_DIR (\fB$PWD\fR)
|
||||
Directory to start searching for \fB\.python\-version\fR files\.
|
||||
.TP
|
||||
.B HTTP_PROXY,HTTPS_PROXY
|
||||
Proxy Variables
|
||||
.TP
|
||||
.B CONFIGURE_OPTS
|
||||
Pass configure options to build.
|
||||
.TP
|
||||
.B PYTHON_BUILD_ARIA2_OPTS
|
||||
Used to pass additional parameters to \fBaria2\fR
|
||||
.UR https://aria2\.github\.io/
|
||||
.UE
|
||||
If the \fBaria2c\fR binary is available on PATH, pyenv uses \fBaria2c\fR instead of \fBcurl\fR or \fBwget\fR to download the Python Source code\. If you have an unstable internet connection, you can use this variable to instruct \fBaria2\fR to accelerate the download\.
|
||||
In most cases, you will only need to use \fB\-x 10 \-k 1M\fR as value to \fBPYTHON_BUILD_ARIA2_OPTS\fR environment variable
|
||||
.SH "License"
|
||||
The \fBMIT\fR License
|
||||
3
plugins/.gitignore
vendored
3
plugins/.gitignore
vendored
|
|
@ -2,6 +2,3 @@
|
|||
!/.gitignore
|
||||
!/version-ext-compat
|
||||
!/python-build
|
||||
!/pyenv-binary
|
||||
!/pyenv-link
|
||||
/python-build/test/build
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
# pyenv-binary (experimental)
|
||||
|
||||
Package an installed Python version into a relocatable archive that can be
|
||||
installed on another machine.
|
||||
|
||||
This is experimental and intentionally decoupled: it does not change
|
||||
`pyenv install` or any other command. You drive it explicitly through
|
||||
`pyenv binary`. Run `pyenv binary <command> --help` for details on a command.
|
||||
|
||||
## Portability
|
||||
|
||||
An archive is portable across machines that share its build platform (OS,
|
||||
architecture and a compatible libc) and have the recorded system libraries. It
|
||||
is not portable across, say, glibc and musl, or to an older glibc; the platform
|
||||
and dependency metadata exist to catch that.
|
||||
|
||||
## Commands
|
||||
|
||||
### `pyenv binary package [-v|--verbose] <version>[:<entry>] --archive-base-url <url>`
|
||||
|
||||
Installs `<version>` from source under a separate name, packages that install
|
||||
with `save`, then emits a python-build definition for it with
|
||||
`generate-installer`. With no explicit entry, the name is generated from the
|
||||
current platform, platform version and architecture. An explicit entry keeps
|
||||
the existing custom-build workflow.
|
||||
|
||||
Pass `-v` to show build progress from `pyenv install`.
|
||||
|
||||
```sh
|
||||
pyenv binary package 3.12.7 \
|
||||
--archive-base-url https://example.com/binaries
|
||||
# On Debian 12 x86_64, writes 3.12.7-debian-12-x86_64.tar.gz,
|
||||
# its .meta file and a `3.12.7-debian-12-x86_64' definition.
|
||||
|
||||
pyenv binary package 3.12.7:company-python \
|
||||
--archive-base-url https://example.com/binaries
|
||||
```
|
||||
|
||||
The archive, metadata and definition land in the current directory, named after
|
||||
the entry. Host the archive under `<url>` and drop the definition into
|
||||
python-build's definition directory.
|
||||
|
||||
### `pyenv binary package-name <version>`
|
||||
|
||||
Prints the automatically generated entry name without building anything. Linux
|
||||
uses the distribution name and version, macOS uses the macOS version, and other
|
||||
systems use the name and release reported by `uname`. All names include the
|
||||
architecture.
|
||||
|
||||
```sh
|
||||
pyenv binary package-name 3.12.7
|
||||
# 3.12.7-debian-12-x86_64
|
||||
```
|
||||
|
||||
### `pyenv binary save <version> [<output-dir>] [--name <name>]`
|
||||
|
||||
Packs an installed version into `<version>-<platform>.tar.gz` (relative paths)
|
||||
and writes `<version>-<platform>.meta` describing the build platform (OS, arch,
|
||||
distro and libc version) and the system libraries the build links against. Use
|
||||
`--name` to set a different base name for both files.
|
||||
|
||||
```sh
|
||||
pyenv binary save 3.12.7 ./dist
|
||||
```
|
||||
|
||||
### `pyenv binary generate-installer <metadata-file> --archive-url <url> [-o <output>]`
|
||||
|
||||
Reads a `.meta` file and emits a python-build definition. Drop it into
|
||||
python-build's definition directory and `pyenv install <name>` installs the
|
||||
archive like any other version. The archive location is a parameter, so you can
|
||||
host it anywhere (it does not have to be a pyenv location); the archive itself
|
||||
must sit next to the `.meta` file so its checksum can be baked into the
|
||||
definition.
|
||||
|
||||
The definition refuses to install on a different OS/architecture, or an older
|
||||
glibc, than the archive was built for, and checks that the system libraries it
|
||||
needs are present.
|
||||
|
||||
```sh
|
||||
pyenv binary generate-installer ./dist/3.12.7-linux-x86_64.meta \
|
||||
--archive-url https://example.com/3.12.7-linux-x86_64.tar.gz \
|
||||
-o "$(pyenv root)/plugins/python-build/share/python-build/3.12.7-linux-x86_64"
|
||||
|
||||
pyenv install 3.12.7-linux-x86_64
|
||||
```
|
||||
|
||||
### `pyenv binary relocate <prefix>`
|
||||
|
||||
Rewrites the rpaths of a Python tree unpacked into `<prefix>` so the interpreter
|
||||
and its extension modules load the bundled libraries from there rather than from
|
||||
the path the archive was built at. Uses `patchelf`. The generated definition
|
||||
calls this; you rarely run it by hand.
|
||||
|
||||
Relocation is implemented for Linux; macOS is not wired up yet.
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
#!/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" "$@"
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Generate a python-build definition for a saved binary archive
|
||||
#
|
||||
# Usage: pyenv binary generate-installer <metadata-file> --archive-url <url> [-o <output>]
|
||||
#
|
||||
# Reads a metadata file written by `pyenv binary save' and emits a python-build
|
||||
# definition. Dropped into python-build's definition directory, it installs the
|
||||
# archive like any other version: `pyenv install' downloads it from <url>,
|
||||
# checks the platform and the required system libraries, unpacks it, then
|
||||
# rewrites rpaths so the copy runs from its prefix.
|
||||
#
|
||||
# The archive must sit next to the metadata file so its checksum can be baked
|
||||
# into the definition; `pyenv binary save' writes the two together.
|
||||
#
|
||||
# <metadata-file> A .meta file written by `pyenv binary save'.
|
||||
# The corresponding binary package written by `pyenv binary save'
|
||||
# needs to be present alongside it.
|
||||
# --archive-url <url> The URL for the resulting installation script to download
|
||||
# the package from.
|
||||
# -o <output> Write the definition here (default: stdout).
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --archive-url
|
||||
echo -o
|
||||
exit
|
||||
fi
|
||||
|
||||
metadata=""
|
||||
archive_url=""
|
||||
output=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--archive-url )
|
||||
[ $# -ge 2 ] || { echo "pyenv-binary: --archive-url needs a value" >&2; exit 1; }
|
||||
archive_url="$2"; shift 2 ;;
|
||||
-o )
|
||||
[ $# -ge 2 ] || { echo "pyenv-binary: -o needs a value" >&2; exit 1; }
|
||||
output="$2"; shift 2 ;;
|
||||
-* )
|
||||
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
|
||||
* )
|
||||
[ -z "$metadata" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; }
|
||||
metadata="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$metadata" ] || [ -z "$archive_url" ]; then
|
||||
pyenv-help --usage binary-generate-installer >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="" os="" arch="" distro="" libc="" build_prefix="" archive=""
|
||||
deps=""
|
||||
while IFS='=' read -r key value; do
|
||||
case "$key" in
|
||||
version ) version="$value" ;;
|
||||
os ) os="$value" ;;
|
||||
arch ) arch="$value" ;;
|
||||
distro ) distro="$value" ;;
|
||||
libc ) libc="$value" ;;
|
||||
build_prefix ) build_prefix="$value" ;;
|
||||
archive ) archive="$value" ;;
|
||||
dep ) deps="${deps:+$deps }$value" ;;
|
||||
esac
|
||||
done < "$metadata"
|
||||
|
||||
# Fail here if the metadata is incomplete, rather than bake a blank into the
|
||||
# definition where a missing arch would give a confusing platform error.
|
||||
for field in version os arch archive; do
|
||||
if [ -z "${!field}" ]; then
|
||||
echo "pyenv-binary: metadata is missing \`${field}'" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Mach-O load commands contain the prefix where Python was built.
|
||||
if [ "$os" = "Darwin" ] && [ -z "$build_prefix" ]; then
|
||||
echo "pyenv-binary: metadata is missing \`build_prefix'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The glibc floor only applies on Linux; require it there so the definition can
|
||||
# tell whether the target is new enough to run the binaries.
|
||||
if [ "$os" = "Linux" ] && [ -z "$libc" ]; then
|
||||
echo "pyenv-binary: metadata is missing \`libc'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# python-build downloads the archive itself, so the checksum has to be baked in.
|
||||
# The archive sits next to the metadata `save' wrote it alongside.
|
||||
archive_path="$(dirname "$metadata")/${archive}"
|
||||
if [ ! -r "$archive_path" ]; then
|
||||
echo "pyenv-binary: cannot read the archive \`${archive_path}' to checksum it" >&2
|
||||
exit 1
|
||||
fi
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256="$(sha256sum "$archive_path")"; sha256="${sha256%% *}"
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
sha256="$(shasum -a 256 "$archive_path")"; sha256="${sha256%% *}"
|
||||
elif command -v openssl >/dev/null 2>&1; then
|
||||
sha256="$(openssl dgst -sha256 "$archive_path")"; sha256="${sha256##* }"
|
||||
else
|
||||
echo "pyenv-binary: need sha256sum, shasum or openssl to checksum the archive" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
emit() {
|
||||
# The values known now go in as %q-quoted assignments so a stray quote or space
|
||||
# in the URL or metadata cannot break the definition or inject into it. The body
|
||||
# below is a quoted here-doc, verbatim.
|
||||
{
|
||||
printf '# python-build definition for prebuilt Python %s (%s/%s%s).\n' \
|
||||
"$version" "$os" "$arch" "${distro:+, $distro}"
|
||||
echo "# Generated by \`pyenv binary generate-installer'."
|
||||
echo
|
||||
printf 'EXPECT_OS=%q\n' "$os"
|
||||
printf 'EXPECT_ARCH=%q\n' "$arch"
|
||||
printf 'EXPECT_LIBC=%q\n' "$libc"
|
||||
printf 'BUILD_PREFIX=%q\n' "$build_prefix"
|
||||
printf 'VERSION=%q\n' "$version"
|
||||
printf 'ARCHIVE_URL=%q\n' "$archive_url"
|
||||
printf 'SHA256=%q\n' "$sha256"
|
||||
printf 'DEPS=%q\n' "$deps"
|
||||
}
|
||||
cat <<'EOF'
|
||||
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
if [ "$os" != "$EXPECT_OS" ] || [ "$arch" != "$EXPECT_ARCH" ]; then
|
||||
echo "pyenv-binary: this archive is for ${EXPECT_OS}/${EXPECT_ARCH}, not ${os}/${arch}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Refuse a target whose glibc is older than the one the archive was built on;
|
||||
# the dynamic loader would reject the binaries.
|
||||
case "$EXPECT_LIBC" in
|
||||
"glibc "* )
|
||||
build_libc="${EXPECT_LIBC#glibc }"
|
||||
target_libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
|
||||
case "$target_libc" in
|
||||
"glibc "* )
|
||||
target_libc="${target_libc#glibc }"
|
||||
older="$(printf '%s\n%s\n' "$build_libc" "$target_libc" | sort -V | head -n1)"
|
||||
if [ "$older" = "$target_libc" ] && [ "$target_libc" != "$build_libc" ]; then
|
||||
echo "pyenv-binary: archive needs glibc ${build_libc} or newer, but this system has ${target_libc}" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
# Required system libraries must already be present on the target. Linux lists
|
||||
# the soname first; FreeBSD lists it in the resolved path, so normalize that
|
||||
# path before matching exact names.
|
||||
cache=""
|
||||
if command -v ldconfig &>/dev/null; then
|
||||
case "$os" in
|
||||
FreeBSD )
|
||||
cache="$(ldconfig -r 2>/dev/null)" &&
|
||||
cache="$(printf '%s\n' "$cache" | awk -F ' *=> *' '{ sub(/^.*\//, "", $2); print $2 }')" \
|
||||
|| cache=""
|
||||
;;
|
||||
* )
|
||||
cache="$(ldconfig -p 2>/dev/null)" || cache=""
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
if [ -n "$cache" ]; then
|
||||
missing=""
|
||||
for dep in $DEPS; do
|
||||
printf '%s\n' "$cache" | awk -v d="$dep" '$1 == d { found = 1 } END { exit !found }' \
|
||||
|| missing="${missing} ${dep}"
|
||||
done
|
||||
if [ -n "$missing" ]; then
|
||||
echo "pyenv-binary: missing required system libraries:${missing}" >&2
|
||||
exit 1
|
||||
fi
|
||||
elif [ -n "$DEPS" ]; then
|
||||
echo "pyenv-binary: cannot check required system libraries (ldconfig cache unavailable)" >&2
|
||||
fi
|
||||
|
||||
case "$os" in
|
||||
Darwin )
|
||||
for tool in otool install_name_tool; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
echo "pyenv-binary: need ${tool} to relocate the binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
;;
|
||||
* )
|
||||
if ! command -v patchelf >/dev/null 2>&1; then
|
||||
echo "pyenv-binary: need patchelf to relocate the binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
build_package_relocate() {
|
||||
if [ "$os" = "Darwin" ]; then
|
||||
pyenv binary relocate "$PREFIX_PATH" "$BUILD_PREFIX"
|
||||
else
|
||||
pyenv binary relocate "$PREFIX_PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
install_package "Python-${VERSION}-binary" "${ARCHIVE_URL}#${SHA256}" copy relocate
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
if [ -n "$output" ]; then
|
||||
emit > "$output"
|
||||
echo "Wrote definition to ${output}"
|
||||
else
|
||||
emit
|
||||
fi
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Create an installable binary package from a Python version
|
||||
#
|
||||
# Usage: pyenv binary package [-v|--verbose] <version>[:<entry>] --archive-base-url <url>
|
||||
#
|
||||
# Installs <version> from source under a separate entry name, saves it as a
|
||||
# binary package, then emits a python-build definition for that package.
|
||||
# The archive, metadata and definition are written to the current directory
|
||||
# as <entry>.tar.gz, <entry>.meta and <entry>.
|
||||
#
|
||||
# <version> A version `pyenv install' knows how to build.
|
||||
# <entry> The optional name to build under and install the
|
||||
# binary as. If omitted, a name is generated from the
|
||||
# current platform, platform version and architecture.
|
||||
# -v,--verbose Show build progress from `pyenv install'.
|
||||
# --archive-base-url <url>
|
||||
# Where the archive will be hosted; the definition
|
||||
# downloads it from <url>/<entry>.tar.gz.
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --archive-base-url
|
||||
echo --verbose
|
||||
exec pyenv-install --list --bare
|
||||
fi
|
||||
|
||||
spec=""
|
||||
archive_base_url=""
|
||||
verbose=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--archive-base-url )
|
||||
[ $# -lt 2 ] && { echo "pyenv-binary: --archive-base-url needs a value" >&2; exit 1; }
|
||||
archive_base_url="$2"; shift ;;
|
||||
-v|--verbose)
|
||||
verbose=1 ;;
|
||||
-* )
|
||||
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
|
||||
* )
|
||||
[ -z "$spec" ] || { echo "pyenv-binary: unexpected argument \`$1'" >&2; exit 1; }
|
||||
spec="$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "$spec" ] || [ -z "$archive_base_url" ]; then
|
||||
pyenv-help --usage binary-package >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$spec" in
|
||||
*?:?* ) entry="${spec##*:}" ;;
|
||||
* ) entry="$(pyenv-binary-package-name "$spec")"; spec="${spec}:${entry}" ;;
|
||||
esac
|
||||
|
||||
case "$entry" in
|
||||
# `pyenv install' reads a trailing `:latest' as part of the version rather than
|
||||
# as an alias, so nothing would end up installed under that name.
|
||||
latest )
|
||||
echo "pyenv-binary: \`latest' cannot be used as an entry name" >&2
|
||||
exit 1
|
||||
;;
|
||||
# The entry becomes a directory name under versions/. `pyenv install' does not
|
||||
# validate the alias, and `save' only checks once the build is done, so refuse
|
||||
# a name that could point elsewhere before compiling anything.
|
||||
*/* | .. | . )
|
||||
echo "pyenv-binary: invalid entry name \`${entry}'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# `pyenv install' puts a `<version>:<alias>' build under versions/<alias>.
|
||||
pyenv-install ${verbose:+--verbose} "$spec"
|
||||
pyenv-binary-save "$entry" "$PWD" --name "$entry"
|
||||
|
||||
pyenv-binary-generate-installer "${entry}.meta" \
|
||||
--archive-url "${archive_base_url%/}/${entry}.tar.gz" -o "$entry"
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Generate a binary package name for the current platform
|
||||
#
|
||||
# Usage: pyenv binary package-name <version>
|
||||
#
|
||||
# Prints a package name containing the Python version, platform name,
|
||||
# platform version and architecture.
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
if [ "$1" = "--complete" ]; then
|
||||
exec pyenv-install --list --bare
|
||||
fi
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
pyenv-help --usage binary-package-name >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="$1"
|
||||
|
||||
case "$version" in
|
||||
*/* | .. | . | *[[:cntrl:]]* )
|
||||
echo "pyenv-binary: invalid version name \`${version}'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
version="$(pyenv-latest -f -k "$version")"
|
||||
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
distro=""
|
||||
distro_version=""
|
||||
|
||||
case "$os" in
|
||||
Linux )
|
||||
if type -p lsb_release >/dev/null; then
|
||||
distro="$(lsb_release -si)"
|
||||
distro_version="$(lsb_release -sr)"
|
||||
elif [ -r /etc/os-release ]; then
|
||||
distro="$(. /etc/os-release && printf '%s' "${ID:-}")"
|
||||
distro_version="$(. /etc/os-release && printf '%s' "${VERSION_ID:-}")"
|
||||
fi
|
||||
;;
|
||||
Darwin )
|
||||
distro="macos"
|
||||
distro_version="$(sw_vers -productVersion)"
|
||||
;;
|
||||
* )
|
||||
distro="$os"
|
||||
distro_version="$(uname -r)"
|
||||
;;
|
||||
esac
|
||||
|
||||
slug() {
|
||||
printf '%s' "$1" |
|
||||
tr '[:upper:] ' '[:lower:]-' |
|
||||
tr -cd 'a-z0-9._-'
|
||||
}
|
||||
|
||||
distro="$(slug "$distro")"
|
||||
distro_version="$(slug "$distro_version")"
|
||||
arch="$(slug "$arch")"
|
||||
|
||||
if [ -z "$distro" ] || [ -z "$distro_version" ] || [ -z "$arch" ]; then
|
||||
echo "pyenv-binary: could not determine the package platform" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s-%s-%s-%s\n' "$version" "$distro" "$distro_version" "$arch"
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Rewrite an unpacked Python's library paths so it runs from its prefix
|
||||
#
|
||||
# Usage: pyenv binary relocate <prefix> [<build-prefix>]
|
||||
#
|
||||
# Rewrites the library paths of a Python tree that was unpacked into <prefix>
|
||||
# so the interpreter and its extension modules load the bundled libraries from
|
||||
# their new location rather than the path it was built at. Requires patchelf on
|
||||
# Linux and FreeBSD, or otool and install_name_tool on macOS.
|
||||
#
|
||||
# The definition that `pyenv binary generate-installer' produces calls this
|
||||
# after `install_package ... copy' has laid the tree down.
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
if [ "$1" = "--complete" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
prefix="$1"
|
||||
if [ -z "$prefix" ]; then
|
||||
pyenv-help --usage binary-relocate >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
build_prefix="${2%/}"
|
||||
if [ -z "$build_prefix" ]; then
|
||||
echo "pyenv-binary: need the original build prefix to relocate a macOS binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
for tool in otool install_name_tool; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
echo "pyenv-binary: need ${tool} to relocate the binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
relocate_macho() {
|
||||
local file="$1" new_rpath="$2"
|
||||
local load_commands install_ids rpaths dependency install_id rpath replacement
|
||||
|
||||
load_commands="$(otool -L "$file")"
|
||||
install_ids="$(otool -D "$file" 2>/dev/null || true)"
|
||||
rpaths="$(otool -l "$file")"
|
||||
install_id="$(printf '%s\n' "$install_ids" | sed -n '2s/^[[:space:]]*//p')"
|
||||
|
||||
while IFS= read -r dependency; do
|
||||
case "$dependency" in
|
||||
"$build_prefix"/lib/* )
|
||||
if [ "$dependency" != "$install_id" ]; then
|
||||
replacement="@rpath/${dependency#"$build_prefix"/lib/}"
|
||||
install_name_tool -change "$dependency" "$replacement" "$file"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < <(printf '%s\n' "$load_commands" | tail -n +2 | sed -E \
|
||||
's/^[[:space:]]*//; s/[[:space:]]+\(compatibility version.*$//')
|
||||
|
||||
case "$install_id" in
|
||||
"$build_prefix"/lib/* )
|
||||
replacement="@rpath/${install_id#"$build_prefix"/lib/}"
|
||||
install_name_tool -id "$replacement" "$file"
|
||||
;;
|
||||
esac
|
||||
|
||||
while IFS= read -r rpath; do
|
||||
if [ "$rpath" = "$build_prefix/lib" ]; then
|
||||
install_name_tool -rpath "$rpath" "$new_rpath" "$file"
|
||||
fi
|
||||
done < <(printf '%s\n' "$rpaths" | awk '
|
||||
/cmd LC_RPATH/ { found = 1; next }
|
||||
found && /^[[:space:]]*path / {
|
||||
sub(/^[[:space:]]*path /, "")
|
||||
sub(/ \(offset [0-9]+\)$/, "")
|
||||
print
|
||||
found = 0
|
||||
}
|
||||
')
|
||||
}
|
||||
|
||||
found=0
|
||||
for file in "$prefix"/bin/*; do
|
||||
[[ -f $file && -x $file && ! -L $file ]] || continue
|
||||
otool -L "$file" >/dev/null 2>&1 || continue
|
||||
relocate_macho "$file" '@executable_path/../lib'
|
||||
found=1
|
||||
done
|
||||
if [ "$found" -eq 0 ]; then
|
||||
echo "pyenv-binary: found no interpreter to relocate under \`${prefix}/bin'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for file in "$prefix"/lib/*.dylib; do
|
||||
[[ -f $file && ! -L $file ]] || continue
|
||||
relocate_macho "$file" '@loader_path'
|
||||
done
|
||||
while IFS= read -r so; do
|
||||
relocate_macho "$so" '@loader_path/../..'
|
||||
done < <(find "$prefix" -type f -path '*/lib/python*/lib-dynload/*.so')
|
||||
exit
|
||||
fi
|
||||
|
||||
# patchelf can add an rpath and write one of any length; chrpath can only shorten
|
||||
# an existing one, which is not enough to relocate every build, so require it.
|
||||
if ! command -v patchelf >/dev/null 2>&1; then
|
||||
echo "pyenv-binary: need patchelf to relocate the binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The interpreter loads libpython from ../lib. Patch every ELF binary in bin/ so
|
||||
# this holds whatever the interpreter is named (python3, python2.7, ...); the
|
||||
# scripts alongside it (pip, idle) are not ELF, so `--print-rpath' fails on them
|
||||
# and they are skipped. A tree with no interpreter at all did not unpack the way
|
||||
# we expect, so treat that as an error rather than quietly relocating nothing.
|
||||
found=0
|
||||
for file in "$prefix"/bin/*; do
|
||||
[[ -f $file && -x $file && ! -L $file ]] || continue
|
||||
patchelf --print-rpath "$file" >/dev/null 2>&1 || continue
|
||||
patchelf --set-rpath "$prefix/lib" "$file"
|
||||
found=1
|
||||
done
|
||||
if [ "$found" -eq 0 ]; then
|
||||
echo "pyenv-binary: found no interpreter to relocate under \`${prefix}/bin'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The extension modules CPython built are the only other objects with an rpath
|
||||
# into the old prefix, so point them at lib/ as well. Anything a wheel installed
|
||||
# carries an rpath of its own, often into a bundled library directory beside it,
|
||||
# and would stop loading if we overwrote it. Each match is an ELF object we expect
|
||||
# to patch, so let a failure stop us rather than swallow it.
|
||||
while IFS= read -r so; do
|
||||
patchelf --set-rpath "$prefix/lib" "$so"
|
||||
done < <(find "$prefix" -path '*/lib-dynload/*.so')
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Save an installed Python version as a relocatable archive
|
||||
#
|
||||
# Usage: pyenv binary save <version> [<output-dir>] [--name <name>]
|
||||
#
|
||||
# Packs an installed version into a relocatable .tar.gz (relative paths) and
|
||||
# writes a metadata file listing the build platform and the system libraries
|
||||
# it links against, so an installer can check compatibility before unpacking.
|
||||
#
|
||||
# <version> An installed version, as listed by `pyenv versions --bare'.
|
||||
# <output-dir> Where to write the archive and metadata (default: `.').
|
||||
# --name <name> Use <name> as the archive and metadata base name instead of
|
||||
# <version>-<platform>.
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --name
|
||||
exec pyenv-versions --bare
|
||||
fi
|
||||
|
||||
version=""
|
||||
output_dir=""
|
||||
package_name=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--name )
|
||||
[ $# -ge 2 ] && [ -n "$2" ] || { echo "pyenv-binary: --name needs a value" >&2; exit 1; }
|
||||
package_name="$2"; shift 2 ;;
|
||||
-* )
|
||||
echo "pyenv-binary: unknown option \`$1'" >&2; exit 1 ;;
|
||||
* )
|
||||
if [ -z "$version" ]; then
|
||||
version="$1"
|
||||
elif [ -z "$output_dir" ]; then
|
||||
output_dir="$1"
|
||||
else
|
||||
echo "pyenv-binary: unexpected argument \`$1'" >&2
|
||||
exit 1
|
||||
fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
output_dir="${output_dir:-$PWD}"
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
echo "Usage: pyenv binary save <version> [<output-dir>] [--name <name>]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A version is a single directory name under versions/. With no slash allowed,
|
||||
# the only remaining names that could point elsewhere are `.' and `..'.
|
||||
case "$version" in
|
||||
*/* | .. | . )
|
||||
echo "pyenv-binary: invalid version name \`${version}'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$package_name" in
|
||||
*/* | .. | . | *[[:cntrl:]]* )
|
||||
echo "pyenv-binary: invalid package name \`${package_name}'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
prefix="${PYENV_ROOT}/versions/${version}"
|
||||
if [ ! -d "${prefix}/bin" ]; then
|
||||
echo "pyenv-binary: version \`${version}' is not installed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
platform="$(printf '%s' "$os" | tr '[:upper:]' '[:lower:]')-${arch}"
|
||||
|
||||
# Record the distro and libc version. Platform and arch alone are too coarse to
|
||||
# judge compatibility: a build is only portable to a matching libc (e.g. a
|
||||
# glibc 2.36 build will not load on an older glibc, nor on musl at all).
|
||||
distro=""
|
||||
libc=""
|
||||
if [ -r /etc/os-release ]; then
|
||||
distro="$( . /etc/os-release && printf '%s %s' "${ID:-}" "${VERSION_ID:-}" )"
|
||||
elif [ "$os" = "Darwin" ]; then
|
||||
distro="macos $(sw_vers -productVersion 2>/dev/null)"
|
||||
fi
|
||||
if [ "$os" = "Linux" ]; then
|
||||
libc="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
|
||||
fi
|
||||
if [ "$os" != "Darwin" ] && ! LC_ALL=C readelf --version &>/dev/null; then
|
||||
echo "pyenv-binary: need readelf to inspect shared libraries" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# List the external shared libraries the install links against: those that
|
||||
# resolve outside its own prefix, so they must already exist on the target.
|
||||
# The interpreter plus every bundled shared object are inspected.
|
||||
system_deps() {
|
||||
local f needed
|
||||
{
|
||||
for f in "${prefix}"/bin/python*; do
|
||||
[ -e "$f" ] && printf '%s\n' "$f"
|
||||
done
|
||||
# CPython ships its extension modules as *.so (and *.dylib on macOS). A
|
||||
# bare *.so.* is unusual for CPython itself, but the odd build carries a
|
||||
# versioned copy alongside, so match it too rather than miss a dependency.
|
||||
find "${prefix}" -type f \( -name '*.so' -o -name '*.so.*' -o -name '*.dylib' \)
|
||||
} | sort -u | while IFS= read -r f; do
|
||||
if [ "$os" = "Darwin" ]; then
|
||||
otool -L "$f" 2>/dev/null | tail -n +2 | awk -v pfx="${prefix}/" \
|
||||
'$1 !~ /^@/ && substr($1, 1, length(pfx)) != pfx { print $1 }'
|
||||
else
|
||||
# GNU binutils 2.44 writes "(NEEDED) ... [name]"; FreeBSD 15.1 writes
|
||||
# "NEEDED ... [name]". readelf(1) documents -d, not its text format.
|
||||
needed="$(LC_ALL=C readelf -dW "$f" 2>/dev/null | awk \
|
||||
'$2 == "(NEEDED)" || $2 == "NEEDED" { sub(/^.*\[/, ""); sub(/\].*$/, ""); print }' | tr '\n' ' ')"
|
||||
# Linux and FreeBSD ldd write resolved libraries as
|
||||
# "name => /absolute/path (address)"; entries without "=>" are ignored.
|
||||
LC_ALL=C ldd "$f" 2>/dev/null | awk -v needed="$needed" -v pfx="${prefix}/" '
|
||||
BEGIN {
|
||||
split(needed, deps, " ")
|
||||
for (i in deps) direct[deps[i]] = 1
|
||||
}
|
||||
$1 in direct && $2 == "=>" && $3 ~ /^\// && substr($3, 1, length(pfx)) != pfx { print $1 }
|
||||
'
|
||||
fi
|
||||
done | sort -u
|
||||
}
|
||||
|
||||
mkdir -p "$output_dir"
|
||||
package_name="${package_name:-${version}-${platform}}"
|
||||
archive="${package_name}.tar.gz"
|
||||
metadata="${package_name}.meta"
|
||||
|
||||
tar -C "$(dirname "$prefix")" -czf "${output_dir}/${archive}" "$(basename "$prefix")"
|
||||
|
||||
{
|
||||
echo "# pyenv-binary metadata"
|
||||
echo "version=${version}"
|
||||
echo "os=${os}"
|
||||
echo "arch=${arch}"
|
||||
echo "platform=${platform}"
|
||||
[ -n "$distro" ] && echo "distro=${distro}"
|
||||
[ -n "$libc" ] && echo "libc=${libc}"
|
||||
echo "build_prefix=${prefix}"
|
||||
echo "archive=${archive}"
|
||||
system_deps | while IFS= read -r dep; do
|
||||
[ -n "$dep" ] && echo "dep=${dep}"
|
||||
done
|
||||
} > "${output_dir}/${metadata}"
|
||||
|
||||
echo "Saved ${archive} and ${metadata} to ${output_dir}"
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
create_meta() {
|
||||
local os="${1-Linux}"
|
||||
local arch="${2-x86_64}"
|
||||
local libc="${3-glibc 2.17}"
|
||||
local build_prefix="${4-}"
|
||||
local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
|
||||
local meta="${BATS_TEST_TMPDIR}/sample.meta"
|
||||
|
||||
rm -rf "${BATS_TEST_TMPDIR}/archive"
|
||||
mkdir -p "${BATS_TEST_TMPDIR}/archive/3.12.7/bin"
|
||||
printf '#!/bin/sh\n' > "${BATS_TEST_TMPDIR}/archive/3.12.7/bin/python"
|
||||
chmod +x "${BATS_TEST_TMPDIR}/archive/3.12.7/bin/python"
|
||||
tar -C "${BATS_TEST_TMPDIR}/archive" -czf "$archive" 3.12.7
|
||||
|
||||
{
|
||||
echo "version=3.12.7"
|
||||
echo "os=${os}"
|
||||
echo "arch=${arch}"
|
||||
[ -z "$libc" ] || echo "libc=${libc}"
|
||||
[ -z "$build_prefix" ] || echo "build_prefix=${build_prefix}"
|
||||
echo "archive=${archive##*/}"
|
||||
} > "$meta"
|
||||
echo "$meta"
|
||||
}
|
||||
|
||||
@test "completion lists the options" {
|
||||
run pyenv-binary-generate-installer --complete
|
||||
assert_success "--archive-url
|
||||
-o"
|
||||
}
|
||||
|
||||
@test "fails with no arguments" {
|
||||
create_stub pyenv-help "echo usage"
|
||||
run pyenv-binary-generate-installer
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails without an archive url" {
|
||||
create_stub pyenv-help "echo usage"
|
||||
run pyenv-binary-generate-installer "$(create_meta)"
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails when --archive-url has no value" {
|
||||
run pyenv-binary-generate-installer "$(create_meta)" --archive-url
|
||||
assert_failure "pyenv-binary: --archive-url needs a value"
|
||||
}
|
||||
|
||||
@test "rejects a second positional argument" {
|
||||
run pyenv-binary-generate-installer "$(create_meta)" extra --archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: unexpected argument \`extra'"
|
||||
}
|
||||
|
||||
@test "fails for a metadata file that cannot be read" {
|
||||
run pyenv-binary-generate-installer /no/such.meta --archive-url http://x/a.tar.gz
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "fails when macOS metadata is missing the build prefix" {
|
||||
run pyenv-binary-generate-installer "$(create_meta Darwin arm64 '')" \
|
||||
--archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: metadata is missing \`build_prefix'"
|
||||
}
|
||||
|
||||
@test "quotes the macOS build prefix in the generated definition" {
|
||||
local out="${BATS_TEST_TMPDIR}/definition"
|
||||
pyenv-binary-generate-installer \
|
||||
"$(create_meta Darwin arm64 '' '/build prefix/$name')" \
|
||||
--archive-url http://example.com/a.tar.gz -o "$out"
|
||||
|
||||
run grep '^BUILD_PREFIX=' "$out"
|
||||
assert_success 'BUILD_PREFIX=/build\ prefix/\$name'
|
||||
}
|
||||
|
||||
@test "fails when required metadata is missing" {
|
||||
local field meta
|
||||
for field in version os arch archive; do
|
||||
meta="$(create_meta)"
|
||||
#cannot use -i: GNU sed requires -i[suf], BSD sed required -i <suf>
|
||||
sed "/^${field}=/d" "$meta" > "${meta}.tmp"; mv "${meta}"{.tmp,}
|
||||
|
||||
run pyenv-binary-generate-installer "$meta" --archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: metadata is missing \`${field}'"
|
||||
done
|
||||
}
|
||||
|
||||
@test "fails when Linux metadata is missing libc" {
|
||||
run pyenv-binary-generate-installer "$(create_meta Linux x86_64 '')" \
|
||||
--archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: metadata is missing \`libc'"
|
||||
}
|
||||
|
||||
@test "refuses a host whose glibc is older than the archive" {
|
||||
local out="${BATS_TEST_TMPDIR}/definition"
|
||||
pyenv-binary-generate-installer "$(create_meta Linux x86_64 'glibc 99.0')" \
|
||||
--archive-url http://example.com/a.tar.gz -o "$out"
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
create_stub getconf 'echo "glibc 2.31"'
|
||||
|
||||
run bash "$out"
|
||||
assert_failure "pyenv-binary: archive needs glibc 99.0 or newer, but this system has 2.31"
|
||||
}
|
||||
|
||||
@test "checks required libraries in the FreeBSD ldconfig cache" {
|
||||
local out="${BATS_TEST_TMPDIR}/definition"
|
||||
local meta="$(create_meta FreeBSD amd64 '')"
|
||||
printf 'dep=libc.so.7\ndep=libmissing.so.1\n' >> "$meta"
|
||||
pyenv-binary-generate-installer "$meta" \
|
||||
--archive-url http://example.com/a.tar.gz -o "$out"
|
||||
create_stub uname 'case "$1" in -s) echo FreeBSD;; -m) echo amd64;; esac'
|
||||
create_stub ldconfig '[ "$1" = "-r" ] && echo "0:-lc.7=>/lib/libc.so.7"'
|
||||
|
||||
run bash "$out"
|
||||
assert_failure "pyenv-binary: missing required system libraries: libmissing.so.1"
|
||||
}
|
||||
|
||||
@test "checks for patchelf before installing the archive" {
|
||||
local out="${BATS_TEST_TMPDIR}/definition"
|
||||
pyenv-binary-generate-installer "$(create_meta)" \
|
||||
--archive-url http://example.com/a.tar.gz -o "$out"
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
create_stub getconf 'echo "glibc 2.31"'
|
||||
|
||||
PATH="$(path_without patchelf)" run bash "$out"
|
||||
assert_failure "pyenv-binary: need patchelf to relocate the binary"
|
||||
}
|
||||
|
||||
@test "checks for otool before installing a macOS archive" {
|
||||
local out="${BATS_TEST_TMPDIR}/definition"
|
||||
pyenv-binary-generate-installer \
|
||||
"$(create_meta Darwin arm64 '' /build/3.12.7)" \
|
||||
--archive-url http://example.com/a.tar.gz -o "$out"
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
create_path_executable install_name_tool true
|
||||
|
||||
PATH="$(path_without otool)" run bash "$out"
|
||||
assert_failure "pyenv-binary: need otool to relocate the binary"
|
||||
}
|
||||
|
||||
@test "checks for install_name_tool before installing a macOS archive" {
|
||||
local out="${BATS_TEST_TMPDIR}/definition"
|
||||
pyenv-binary-generate-installer \
|
||||
"$(create_meta Darwin arm64 '' /build/3.12.7)" \
|
||||
--archive-url http://example.com/a.tar.gz -o "$out"
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
create_path_executable otool true
|
||||
|
||||
PATH="$(path_without install_name_tool)" run bash "$out"
|
||||
assert_failure "pyenv-binary: need install_name_tool to relocate the binary"
|
||||
}
|
||||
|
||||
@test "the generated macOS definition preserves both relocation arguments" {
|
||||
local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
|
||||
local cache="${BATS_TEST_TMPDIR}/cache"
|
||||
local definition="${BATS_TEST_TMPDIR}/definition"
|
||||
local prefix="${BATS_TEST_TMPDIR}/install"
|
||||
pyenv-binary-generate-installer \
|
||||
"$(create_meta Darwin arm64 '' '/build prefix/$name')" \
|
||||
--archive-url http://example.com/3.12.7.tar.gz -o "$definition"
|
||||
mkdir -p "$cache"
|
||||
cp "$archive" "${cache}/Python-3.12.7-binary.tar.gz"
|
||||
create_stub pyenv 'printf "argc=%s\narg1=%s\narg2=%s\narg3=%s\narg4=%s\n" "$#" "$1" "$2" "$3" "$4"'
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
create_stub sw_vers 'echo 15.5'
|
||||
create_stub ldconfig true
|
||||
create_path_executable otool true
|
||||
create_path_executable install_name_tool true
|
||||
|
||||
PYTHON_BUILD_CACHE_PATH="$cache" run \
|
||||
"${BATS_TEST_DIRNAME}/../../python-build/bin/python-build" "$definition" "$prefix"
|
||||
assert_success
|
||||
assert_line "argc=4"
|
||||
assert_line "arg1=binary"
|
||||
assert_line "arg2=relocate"
|
||||
assert_line "arg3=${prefix}"
|
||||
assert_line 'arg4=/build prefix/$name'
|
||||
assert_line "Installed Python-3.12.7-binary to ${prefix}"
|
||||
}
|
||||
|
||||
@test "fails when the archive is not beside the metadata" {
|
||||
local meta="$(create_meta)"
|
||||
rm "${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
|
||||
|
||||
run pyenv-binary-generate-installer "$meta" --archive-url http://x/a.tar.gz
|
||||
assert_failure "pyenv-binary: cannot read the archive \`${BATS_TEST_TMPDIR}/3.12.7.tar.gz' to checksum it"
|
||||
}
|
||||
|
||||
@test "the generated definition is installable with python-build" {
|
||||
ldconfig -p &>/dev/null || skip "ldconfig with -p is not present"
|
||||
local archive="${BATS_TEST_TMPDIR}/3.12.7.tar.gz"
|
||||
local cache="${BATS_TEST_TMPDIR}/cache"
|
||||
local definition="${BATS_TEST_TMPDIR}/definition"
|
||||
local prefix="${BATS_TEST_TMPDIR}/install"
|
||||
pyenv-binary-generate-installer "$(create_meta)" \
|
||||
--archive-url http://example.com/3.12.7.tar.gz -o "$definition"
|
||||
mkdir -p "$cache"
|
||||
cp "$archive" "${cache}/Python-3.12.7-binary.tar.gz"
|
||||
create_stub pyenv 'echo "pyenv $*"'
|
||||
create_path_executable patchelf "exit 0"
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
|
||||
PYTHON_BUILD_CACHE_PATH="$cache" run \
|
||||
"${BATS_TEST_DIRNAME}/../../python-build/bin/python-build" "$definition" "$prefix"
|
||||
assert_success
|
||||
assert_line "pyenv binary relocate ${prefix}"
|
||||
assert_line "Installed Python-3.12.7-binary to ${prefix}"
|
||||
assert [ -x "${prefix}/bin/python" ]
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
_setup() {
|
||||
create_stub pyenv-latest '[ "$1" = "-f" ] && [ "$2" = "-k" ] && shift 2 && echo "$*"'
|
||||
}
|
||||
|
||||
@test "completion lists installable versions" {
|
||||
create_stub pyenv-install \
|
||||
'[ "$*" = "--list --bare" ] && echo 3.13.14'
|
||||
|
||||
run pyenv-binary-package-name --complete
|
||||
assert_success "3.13.14"
|
||||
}
|
||||
|
||||
@test "fails without a version" {
|
||||
create_stub pyenv-help 'echo usage'
|
||||
|
||||
run pyenv-binary-package-name
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "rejects a second argument" {
|
||||
create_stub pyenv-help 'echo usage'
|
||||
|
||||
run pyenv-binary-package-name 3.13.14 extra
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "generates a package name for Linux" {
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
create_stub lsb_release 'case "$1" in -si) echo Debian;; -sr) echo 12;; esac'
|
||||
|
||||
run pyenv-binary-package-name 3.13.14
|
||||
assert_success "3.13.14-debian-12-x86_64"
|
||||
}
|
||||
|
||||
@test "generates a package name for macOS" {
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
create_stub sw_vers 'echo 15.5'
|
||||
|
||||
run pyenv-binary-package-name 3.13.14
|
||||
assert_success "3.13.14-macos-15.5-arm64"
|
||||
}
|
||||
|
||||
@test "resolves a version prefix when generating a package name" {
|
||||
create_stub pyenv-latest '[ "$*" = "-f -k 3" ] && echo 3.14.7'
|
||||
create_stub uname \
|
||||
'case "$1" in -s) echo FreeBSD;; -m) echo amd64;; -r) echo 14.2-RELEASE-p3;; esac'
|
||||
|
||||
run pyenv-binary-package-name 3
|
||||
assert_success "3.14.7-freebsd-14.2-release-p3-amd64"
|
||||
}
|
||||
|
||||
@test "rejects an invalid version name" {
|
||||
run pyenv-binary-package-name ../3.13.14
|
||||
assert_failure "pyenv-binary: invalid version name \`../3.13.14'"
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
# Make the build deterministic: `pyenv-install' just creates the prefix, and
|
||||
# the platform tools report a fixed Linux target so the real `save' and
|
||||
# `generate-installer' behave the same on any test host.
|
||||
stub_build_environment() {
|
||||
create_stub pyenv-install 'echo "${0##*/} $*"; mkdir -p "${PYENV_ROOT}/versions/${1##*:}/bin"'
|
||||
create_stub pyenv-latest 'while (($#)); do case "$1" in -f|-k);; *)break;; esac; shift; done; echo "$*"'
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
create_stub getconf 'echo "glibc 2.17"'
|
||||
create_stub readelf true
|
||||
}
|
||||
|
||||
@test "-v|--verbose runs pyenv install verbosely" {
|
||||
stub_build_environment
|
||||
create_stub pyenv-binary-save true
|
||||
create_stub pyenv-binary-generate-installer true
|
||||
|
||||
for opt in "" -v --verbose; do
|
||||
run pyenv-binary-package $opt 3.12.7:3.12.7-test \
|
||||
--archive-base-url http://example.com/binaries
|
||||
assert_success "pyenv-install ${opt:+--verbose }3.12.7:3.12.7-test"
|
||||
done
|
||||
}
|
||||
|
||||
@test "completions" {
|
||||
create_stub pyenv-install 'echo "${0##*/} $*"'
|
||||
|
||||
run pyenv-binary-package --complete
|
||||
assert_success <<!
|
||||
--archive-base-url
|
||||
--verbose
|
||||
pyenv-install --list --bare
|
||||
!
|
||||
}
|
||||
|
||||
@test "fails with no arguments" {
|
||||
create_stub pyenv-help "echo usage"
|
||||
run pyenv-binary-package
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails without an archive base url" {
|
||||
create_stub pyenv-help "echo usage"
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails when --archive-base-url has no value" {
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test --archive-base-url
|
||||
assert_failure "pyenv-binary: --archive-base-url needs a value"
|
||||
}
|
||||
|
||||
@test "rejects a second positional argument" {
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test extra --archive-base-url http://x/b
|
||||
assert_failure "pyenv-binary: unexpected argument \`extra'"
|
||||
}
|
||||
|
||||
@test "generates an entry name for a bare version" {
|
||||
stub_build_environment
|
||||
create_stub lsb_release 'case "$1" in -si) echo Debian;; -sr) echo 12;; esac'
|
||||
cd "${BATS_TEST_TMPDIR}"
|
||||
|
||||
run pyenv-binary-package 3.12.7 --archive-base-url http://example.com/binaries
|
||||
assert_success
|
||||
assert [ -d "${PYENV_ROOT}/versions/3.12.7-debian-12-x86_64" ]
|
||||
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-debian-12-x86_64.tar.gz" ]
|
||||
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-debian-12-x86_64.meta" ]
|
||||
run grep '^ARCHIVE_URL=' "${BATS_TEST_TMPDIR}/3.12.7-debian-12-x86_64"
|
||||
assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-debian-12-x86_64.tar.gz"
|
||||
}
|
||||
|
||||
@test "rejects an entry name containing a slash" {
|
||||
run pyenv-binary-package "3.12.7:foo/bar" --archive-base-url http://x/b
|
||||
assert_failure "pyenv-binary: invalid entry name \`foo/bar'"
|
||||
}
|
||||
|
||||
@test "rejects \`latest' as an entry name" {
|
||||
run pyenv-binary-package 3.12:latest --archive-base-url http://x/b
|
||||
assert_failure "pyenv-binary: \`latest' cannot be used as an entry name"
|
||||
}
|
||||
|
||||
@test "packages on macOS" {
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
create_stub pyenv-install 'echo install'
|
||||
create_stub pyenv-binary-save 'echo save'
|
||||
create_stub pyenv-binary-generate-installer 'echo generate-installer'
|
||||
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test --archive-base-url http://x/b
|
||||
assert_success "install
|
||||
save
|
||||
generate-installer"
|
||||
}
|
||||
|
||||
@test "writes the archive, metadata and definition under the entry name (integration)" {
|
||||
stub_build_environment
|
||||
cd "${BATS_TEST_TMPDIR}"
|
||||
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test \
|
||||
--archive-base-url http://example.com/binaries
|
||||
assert_success
|
||||
assert [ -d "${PYENV_ROOT}/versions/3.12.7-test" ]
|
||||
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test.tar.gz" ]
|
||||
assert [ -f "${BATS_TEST_TMPDIR}/3.12.7-test.meta" ]
|
||||
run grep '^ARCHIVE_URL=' "${BATS_TEST_TMPDIR}/3.12.7-test"
|
||||
assert_success "ARCHIVE_URL=http://example.com/binaries/3.12.7-test.tar.gz"
|
||||
}
|
||||
|
||||
@test "correctly joins archive base url with a trailing slash" {
|
||||
stub_build_environment
|
||||
create_stub pyenv-binary-save true
|
||||
create_stub pyenv-binary-generate-installer <<'!'
|
||||
echo -n "${0##*/} "
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
--archive-url)
|
||||
echo "$1 ${2:?}"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
!
|
||||
|
||||
run pyenv-binary-package 3.12.7:3.12.7-test \
|
||||
--archive-base-url http://example.com/binaries/
|
||||
assert_success
|
||||
assert_line "pyenv-binary-generate-installer --archive-url http://example.com/binaries/3.12.7-test.tar.gz"
|
||||
}
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
_setup() {
|
||||
create_stub pyenv-help "echo usage"
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
}
|
||||
|
||||
stub_patchelf() {
|
||||
create_path_executable patchelf <<STUB
|
||||
if [ "\$1" = "--print-rpath" ]; then
|
||||
exit 0
|
||||
fi
|
||||
echo "\$*" >> "${BATS_TEST_TMPDIR}/patchelf.log"
|
||||
STUB
|
||||
}
|
||||
|
||||
create_interpreter() {
|
||||
mkdir -p "${BATS_TEST_TMPDIR}/prefix/bin"
|
||||
printf '#!/bin/sh\n' > "${BATS_TEST_TMPDIR}/prefix/bin/python2.7"
|
||||
chmod +x "${BATS_TEST_TMPDIR}/prefix/bin/python2.7"
|
||||
}
|
||||
|
||||
create_macos_tree() {
|
||||
local prefix="${BATS_TEST_TMPDIR}/prefix"
|
||||
local lib="${prefix}/lib/python3.12"
|
||||
mkdir -p "${prefix}/bin" "${lib}/lib-dynload" "${lib}/site-packages/numpy"
|
||||
printf '#!/bin/sh\n' > "${prefix}/bin/python3.12"
|
||||
chmod +x "${prefix}/bin/python3.12"
|
||||
touch "${prefix}/lib/libpython3.12.dylib"
|
||||
touch "${lib}/lib-dynload/_ssl.cpython-312-darwin.so"
|
||||
touch "${lib}/site-packages/numpy/_multiarray.so"
|
||||
}
|
||||
|
||||
stub_macos_tools() {
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
create_path_executable install_name_tool \
|
||||
'echo "$*" >> "${BATS_TEST_TMPDIR}/install-name-tool.log"'
|
||||
create_path_executable otool <<'STUB'
|
||||
file="${!#}"
|
||||
echo "$*" >> "${BATS_TEST_TMPDIR}/otool.log"
|
||||
old="/build prefix/3.12.7"
|
||||
case "$1" in
|
||||
-L )
|
||||
echo "${file}:"
|
||||
case "$file" in
|
||||
*/bin/python3.12 )
|
||||
if [ -n "$OTOOL_RELOCATED" ]; then
|
||||
echo " @rpath/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)"
|
||||
else
|
||||
echo " ${old}/lib/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)"
|
||||
fi
|
||||
echo " ${old}-other/lib/libother.dylib (compatibility version 1.0.0, current version 1.0.0)"
|
||||
echo " /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1.0.0)"
|
||||
echo " /opt/homebrew/lib/libintl.8.dylib (compatibility version 1.0.0, current version 1.0.0)"
|
||||
echo " @loader_path/liblocal.dylib (compatibility version 1.0.0, current version 1.0.0)"
|
||||
;;
|
||||
*/lib/libpython3.12.dylib )
|
||||
if [ -n "$OTOOL_RELOCATED" ]; then
|
||||
echo " @rpath/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)"
|
||||
else
|
||||
echo " ${old}/lib/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
-D )
|
||||
case "$file" in
|
||||
*/lib/libpython3.12.dylib )
|
||||
echo "${file}:"
|
||||
if [ -n "$OTOOL_RELOCATED" ]; then
|
||||
echo '@rpath/libpython3.12.dylib'
|
||||
else
|
||||
echo "${old}/lib/libpython3.12.dylib"
|
||||
fi
|
||||
;;
|
||||
* )
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
-l )
|
||||
if [ -n "$OTOOL_RELOCATED" ]; then
|
||||
case "$file" in
|
||||
*/bin/* ) rpath='@executable_path/../lib' ;;
|
||||
*/lib/*.dylib ) rpath='@loader_path' ;;
|
||||
* ) rpath='@loader_path/../..' ;;
|
||||
esac
|
||||
else
|
||||
rpath="${old}/lib"
|
||||
fi
|
||||
cat <<EOF
|
||||
cmd LC_RPATH
|
||||
path ${rpath} (offset 12)
|
||||
cmd LC_RPATH
|
||||
path /opt/homebrew/lib (offset 12)
|
||||
cmd LC_RPATH
|
||||
path @loader_path/vendor (offset 12)
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
STUB
|
||||
}
|
||||
|
||||
@test "completion produces nothing" {
|
||||
run pyenv-binary-relocate --complete
|
||||
assert_success ""
|
||||
}
|
||||
|
||||
@test "fails without a prefix" {
|
||||
run pyenv-binary-relocate
|
||||
assert_failure "usage"
|
||||
}
|
||||
|
||||
@test "fails when patchelf is not available" {
|
||||
PATH="$(path_without patchelf)" run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
|
||||
assert_failure "pyenv-binary: need patchelf to relocate the binary"
|
||||
}
|
||||
|
||||
@test "fails without the original build prefix on macOS" {
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
|
||||
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
|
||||
assert_failure "pyenv-binary: need the original build prefix to relocate a macOS binary"
|
||||
}
|
||||
|
||||
@test "fails when otool is not available on macOS" {
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
create_path_executable install_name_tool true
|
||||
|
||||
PATH="$(path_without otool)" run \
|
||||
pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix" /build/3.12.7
|
||||
assert_failure "pyenv-binary: need otool to relocate the binary"
|
||||
}
|
||||
|
||||
@test "fails when install_name_tool is not available on macOS" {
|
||||
create_stub uname 'case "$1" in -s) echo Darwin;; -m) echo arm64;; esac'
|
||||
create_path_executable otool true
|
||||
|
||||
PATH="$(path_without install_name_tool)" run \
|
||||
pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix" /build/3.12.7
|
||||
assert_failure "pyenv-binary: need install_name_tool to relocate the binary"
|
||||
}
|
||||
|
||||
@test "fails when the prefix has no interpreter" {
|
||||
create_path_executable patchelf "exit 0"
|
||||
mkdir -p "${BATS_TEST_TMPDIR}/prefix"
|
||||
|
||||
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
|
||||
assert_failure "pyenv-binary: found no interpreter to relocate under \`${BATS_TEST_TMPDIR}/prefix/bin'"
|
||||
}
|
||||
|
||||
@test "relocates an executable interpreter" {
|
||||
stub_patchelf
|
||||
create_interpreter
|
||||
|
||||
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
|
||||
assert_success
|
||||
run cat "${BATS_TEST_TMPDIR}/patchelf.log"
|
||||
assert_output "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${BATS_TEST_TMPDIR}/prefix/bin/python2.7"
|
||||
}
|
||||
|
||||
@test "relocates the extension modules but not what a wheel installed" {
|
||||
local lib="${BATS_TEST_TMPDIR}/prefix/lib/python3.12"
|
||||
stub_patchelf
|
||||
create_interpreter
|
||||
mkdir -p "${lib}/lib-dynload" "${lib}/site-packages/numpy"
|
||||
touch "${lib}/lib-dynload/_ssl.cpython-312-x86_64-linux-gnu.so"
|
||||
# A wheel points its extensions at the libraries it bundles alongside them, so
|
||||
# its rpath is its own business and must survive relocation.
|
||||
touch "${lib}/site-packages/numpy/_multiarray.so"
|
||||
|
||||
run pyenv-binary-relocate "${BATS_TEST_TMPDIR}/prefix"
|
||||
assert_success
|
||||
run cat "${BATS_TEST_TMPDIR}/patchelf.log"
|
||||
assert_line "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${lib}/lib-dynload/_ssl.cpython-312-x86_64-linux-gnu.so"
|
||||
refute_line "--set-rpath ${BATS_TEST_TMPDIR}/prefix/lib ${lib}/site-packages/numpy/_multiarray.so"
|
||||
}
|
||||
|
||||
@test "relocates macOS load commands without changing unrelated entries" {
|
||||
local prefix="${BATS_TEST_TMPDIR}/prefix"
|
||||
local old="/build prefix/3.12.7"
|
||||
local lib="${prefix}/lib/python3.12"
|
||||
create_macos_tree
|
||||
stub_macos_tools
|
||||
|
||||
run pyenv-binary-relocate "$prefix" "$old"
|
||||
assert_success
|
||||
run cat "${BATS_TEST_TMPDIR}/install-name-tool.log"
|
||||
assert_output <<EOF
|
||||
-change ${old}/lib/libpython3.12.dylib @rpath/libpython3.12.dylib ${prefix}/bin/python3.12
|
||||
-rpath ${old}/lib @executable_path/../lib ${prefix}/bin/python3.12
|
||||
-id @rpath/libpython3.12.dylib ${prefix}/lib/libpython3.12.dylib
|
||||
-rpath ${old}/lib @loader_path ${prefix}/lib/libpython3.12.dylib
|
||||
-rpath ${old}/lib @loader_path/../.. ${lib}/lib-dynload/_ssl.cpython-312-darwin.so
|
||||
EOF
|
||||
run grep -F "${lib}/site-packages/numpy/_multiarray.so" "${BATS_TEST_TMPDIR}/otool.log"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "skips already relocated macOS load commands" {
|
||||
local prefix="${BATS_TEST_TMPDIR}/prefix"
|
||||
create_macos_tree
|
||||
stub_macos_tools
|
||||
export OTOOL_RELOCATED=1
|
||||
|
||||
run pyenv-binary-relocate "$prefix" "/build prefix/3.12.7"
|
||||
assert_success
|
||||
assert [ ! -e "${BATS_TEST_TMPDIR}/install-name-tool.log" ]
|
||||
}
|
||||
|
||||
@test "fails when install_name_tool cannot modify a selected file" {
|
||||
local prefix="${BATS_TEST_TMPDIR}/prefix"
|
||||
create_macos_tree
|
||||
stub_macos_tools
|
||||
create_path_executable install_name_tool 'exit 1'
|
||||
|
||||
run pyenv-binary-relocate "$prefix" "/build prefix/3.12.7"
|
||||
assert_failure ""
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
create_version() {
|
||||
mkdir -p "${PYENV_ROOT}/versions/$1/bin"
|
||||
}
|
||||
|
||||
platform() {
|
||||
echo "$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)"
|
||||
}
|
||||
|
||||
@test "fails with no version given" {
|
||||
run pyenv-binary-save
|
||||
assert_failure "Usage: pyenv binary save <version> [<output-dir>] [--name <name>]"
|
||||
}
|
||||
|
||||
@test "fails for a version that is not installed" {
|
||||
run pyenv-binary-save 9.9.9
|
||||
assert_failure "pyenv-binary: version \`9.9.9' is not installed"
|
||||
}
|
||||
|
||||
@test "rejects a version name containing a slash" {
|
||||
run pyenv-binary-save "foo/bar"
|
||||
assert_failure "pyenv-binary: invalid version name \`foo/bar'"
|
||||
}
|
||||
|
||||
@test "rejects the parent directory reference" {
|
||||
run pyenv-binary-save ".."
|
||||
assert_failure "pyenv-binary: invalid version name \`..'"
|
||||
}
|
||||
|
||||
@test "packages an installed version" {
|
||||
create_version "3.12.7"
|
||||
local out="${BATS_TEST_TMPDIR}/dist"
|
||||
local archive="${out}/3.12.7-$(platform).tar.gz"
|
||||
|
||||
run pyenv-binary-save "3.12.7" "$out"
|
||||
assert_success "Saved 3.12.7-$(platform).tar.gz and 3.12.7-$(platform).meta to $out"
|
||||
assert [ -f "$archive" ]
|
||||
assert [ -f "${out}/3.12.7-$(platform).meta" ]
|
||||
run tar -tzf "$archive"
|
||||
assert_success
|
||||
assert_line 0 "3.12.7/"
|
||||
}
|
||||
|
||||
@test "uses an explicit package name" {
|
||||
create_version "3.12.7"
|
||||
local out="${BATS_TEST_TMPDIR}/dist"
|
||||
|
||||
run pyenv-binary-save "3.12.7" "$out" --name "custom"
|
||||
assert_success "Saved custom.tar.gz and custom.meta to $out"
|
||||
assert [ -f "${out}/custom.tar.gz" ]
|
||||
run grep '^archive=' "${out}/custom.meta"
|
||||
assert_success "archive=custom.tar.gz"
|
||||
}
|
||||
|
||||
@test "fails when --name has no value" {
|
||||
run pyenv-binary-save "3.12.7" --name
|
||||
assert_failure "pyenv-binary: --name needs a value"
|
||||
}
|
||||
|
||||
@test "rejects an invalid package name" {
|
||||
create_version "3.12.7"
|
||||
|
||||
run pyenv-binary-save "3.12.7" --name "foo/bar"
|
||||
assert_failure "pyenv-binary: invalid package name \`foo/bar'"
|
||||
}
|
||||
|
||||
@test "records the platform in the metadata" {
|
||||
create_version "3.12.7"
|
||||
local out="${BATS_TEST_TMPDIR}/dist"
|
||||
pyenv-binary-save "3.12.7" "$out" >/dev/null
|
||||
|
||||
run cat "${out}/3.12.7-$(platform).meta"
|
||||
assert_success
|
||||
assert_line "version=3.12.7"
|
||||
assert_line "platform=$(platform)"
|
||||
assert_line "archive=3.12.7-$(platform).tar.gz"
|
||||
}
|
||||
|
||||
@test "records the original installation prefix" {
|
||||
create_version "3.12.7"
|
||||
local out="${BATS_TEST_TMPDIR}/dist"
|
||||
pyenv-binary-save "3.12.7" "$out" >/dev/null
|
||||
|
||||
run grep '^build_prefix=' "${out}/3.12.7-$(platform).meta"
|
||||
assert_success "build_prefix=${PYENV_ROOT}/versions/3.12.7"
|
||||
}
|
||||
|
||||
@test "fails when readelf is not available" {
|
||||
create_version "3.12.7"
|
||||
create_stub uname 'case "$1" in -s) echo Linux;; -m) echo x86_64;; esac'
|
||||
|
||||
PATH="$(path_without readelf)" run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist"
|
||||
assert_failure "pyenv-binary: need readelf to inspect shared libraries"
|
||||
assert [ ! -e "${BATS_TEST_TMPDIR}/dist/3.12.7-$(platform).tar.gz" ]
|
||||
}
|
||||
|
||||
@test "records only direct libraries resolved outside the prefix" {
|
||||
create_version "3.12.7"
|
||||
touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12"
|
||||
create_path_executable uname <<'STUB'
|
||||
case "$1" in
|
||||
-s) echo Linux ;;
|
||||
-m) echo x86_64 ;;
|
||||
esac
|
||||
STUB
|
||||
create_path_executable ldd <<'STUB'
|
||||
prefix="${PYENV_ROOT}/versions/3.12.7"
|
||||
cat <<EOF
|
||||
linux-vdso.so.1 (0x00007ffd1adfe000)
|
||||
libpython3.12.so.1.0 => ${prefix}/lib/libpython3.12.so.1.0 (0x00007f4a3c000000)
|
||||
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4a3bc00000)
|
||||
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4a3b800000)
|
||||
/lib64/ld-linux-x86-64.so.2 (0x00007f4a3c200000)
|
||||
EOF
|
||||
STUB
|
||||
create_path_executable readelf <<'STUB'
|
||||
cat <<EOF
|
||||
0x0000000000000001 (NEEDED) Shared library: [libpython3.12.so.1.0]
|
||||
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
|
||||
EOF
|
||||
STUB
|
||||
|
||||
run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist"
|
||||
assert_success
|
||||
run grep '^dep=' "${BATS_TEST_TMPDIR}/dist/"*.meta
|
||||
assert_output "dep=libm.so.6"
|
||||
}
|
||||
|
||||
@test "records only the libraries otool resolves outside the prefix" {
|
||||
create_version "3.12.7"
|
||||
touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12"
|
||||
create_path_executable uname <<'STUB'
|
||||
case "$1" in
|
||||
-s) echo Darwin ;;
|
||||
-m) echo arm64 ;;
|
||||
esac
|
||||
STUB
|
||||
create_path_executable otool <<'STUB'
|
||||
prefix="${PYENV_ROOT}/versions/3.12.7"
|
||||
cat <<EOF
|
||||
${2}:
|
||||
@rpath/libpython3.12.dylib (compatibility version 3.12.0, current version 3.12.0)
|
||||
${prefix}/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)
|
||||
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.0.0)
|
||||
EOF
|
||||
STUB
|
||||
|
||||
run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist"
|
||||
assert_success
|
||||
run grep '^dep=' "${BATS_TEST_TMPDIR}/dist/"*.meta
|
||||
assert_output "dep=/usr/lib/libSystem.B.dylib"
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../test/test_helper.bash
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 yfprojects
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# pyenv-link
|
||||
A [pyenv](https://github.com/pyenv/pyenv) plugin for linking Python installations and virtual environments into your pyenv root.
|
||||
|
||||
This plugin recommends the [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv/) plugin.
|
||||
|
||||
## Usage
|
||||
|
||||
Make an arbitrary virtualenv available through pyenv. This automatically guesses a fitting name from the prompt, the directory name or the location of the venv.
|
||||
|
||||
```console
|
||||
$ pyenv link version .venv
|
||||
Linked new version named myproject
|
||||
```
|
||||
|
||||
You can also specify a name to use for the venv.
|
||||
|
||||
```console
|
||||
$ pyenv link version .venv myname
|
||||
Linked new version named myname
|
||||
```
|
||||
|
||||
The same command can give a platform-specific binary installation a shorter name:
|
||||
|
||||
```console
|
||||
$ pyenv link version "$(pyenv root)/versions/3.13.14-linux-x86_64" 3.13.14
|
||||
Linked new version named 3.13.14
|
||||
```
|
||||
|
||||
Now you can make pyenv activate/use the venv automatically:
|
||||
|
||||
```console
|
||||
$ pyenv local myproject
|
||||
```
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Link a Python environment as a pyenv version
|
||||
#
|
||||
# Usage: pyenv link version [--dry] [--quiet] <path> [<name>]
|
||||
#
|
||||
# Link a virtual python environment to pyenvs version directory
|
||||
# so that the venv can be used like one created with *pyenv-virtualenv*.
|
||||
#
|
||||
# <path> should be a path to a Python installation or virtual environment.
|
||||
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
case "$1" in
|
||||
--complete)
|
||||
if [ -z "$2" ]; then
|
||||
echo version
|
||||
else
|
||||
shift 2
|
||||
pyenv-link-version --complete "$@"
|
||||
fi
|
||||
;;
|
||||
version)
|
||||
shift
|
||||
pyenv-link-version "$@"
|
||||
exit $?
|
||||
;;
|
||||
*)
|
||||
pyenv-help --usage link >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Link a Python environment as a pyenv version
|
||||
#
|
||||
# Usage: pyenv link version [--dry] [--quiet] <path> [<name>]
|
||||
#
|
||||
# Link a virtual python environment to pyenvs version directory
|
||||
# so that the venv can be used like one created with *pyenv-virtualenv*.
|
||||
#
|
||||
# <path> should be a path to a Python installation or virtual environment.
|
||||
# <name> should be string used as a version name it may not be present
|
||||
# in the pyenv version directory yet.
|
||||
# If not specified a matching name is guessed from pyvenv.cfg
|
||||
# or directory name of the venv.
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# functions
|
||||
|
||||
if ! {
|
||||
enable -f "${BASH_SOURCE%/*}"/../../../libexec/pyenv-realpath.dylib realpath ||
|
||||
type realpath # realpath available?
|
||||
} >/dev/null 2>&1; then
|
||||
# realpath requires GNU coreutils to be installed. That's why its bundled with pyenv
|
||||
echo realpath not available >&2
|
||||
|
||||
# work-around when realpath is unavailable
|
||||
realpath() {
|
||||
(cd "$1" && pwd)
|
||||
}
|
||||
fi
|
||||
|
||||
guess_name() {
|
||||
local venv_dir=$1
|
||||
local name
|
||||
|
||||
# guess venv name from pyvenv.cfg
|
||||
local pyvenv_cfg_path=$venv_dir/pyvenv.cfg
|
||||
if [ -f "$pyvenv_cfg_path" ]; then
|
||||
# if `prompt` key wasn't found the var remains empty
|
||||
name=$(cut -b 1-1024 "$pyvenv_cfg_path" | sed -n '/^ *prompt *= */s///p')
|
||||
case "$name" in
|
||||
\"*\" | \'*\')
|
||||
name=${name:1:${#name}-2}
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# guess venv name from name of venv directory
|
||||
local venv_dir_name=${venv_dir##*/}
|
||||
if [ -z "$name" ]; then
|
||||
case "$venv_dir_name" in
|
||||
venv | env | .venv | .env | ENV | VENV) ;;
|
||||
*) name=$venv_dir_name ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# guess venv name from name of the parent directory of the venv directory
|
||||
local venv_dir_parent=${venv_dir%/*}
|
||||
local venv_dir_parent_name=${venv_dir_parent##*/}
|
||||
if [ -z "$name" ]; then
|
||||
name=$venv_dir_parent_name
|
||||
fi
|
||||
|
||||
echo "$name"
|
||||
}
|
||||
|
||||
# process args
|
||||
POSARGS=()
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --dry
|
||||
echo --quiet
|
||||
exit
|
||||
fi
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--dry)
|
||||
dry=true # any value
|
||||
;;
|
||||
--quiet)
|
||||
quiet=true # any value
|
||||
;;
|
||||
-*)
|
||||
pyenv-help --usage link-version >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
POSARGS+=("$arg")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ! ${#POSARGS[@]} -le 2 ] || [ ! ${#POSARGS[@]} -ge 1 ]; then
|
||||
pyenv-help --usage link-version >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
venv_dir=${POSARGS[0]%/}
|
||||
venv_name=${POSARGS[1]}
|
||||
|
||||
# Check venv exists
|
||||
if [ ! -d "$venv_dir" ]; then
|
||||
echo "The virtual env you specified doesn't exist"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# make venv dir absolute
|
||||
venv_dir=$(realpath "$venv_dir")
|
||||
|
||||
# determine venv name
|
||||
if [ -z "$venv_name" ]; then
|
||||
venv_name=$(guess_name "$venv_dir")
|
||||
fi
|
||||
|
||||
case "$venv_name" in
|
||||
"" | */* | . | .. | *:* | system | *[[:cntrl:]]* )
|
||||
echo "pyenv-link: invalid version name \`${venv_name}'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
versions_dir="$PYENV_ROOT/versions"
|
||||
pyenv_prefix_path="$versions_dir/$venv_name"
|
||||
|
||||
if [ -e "$pyenv_prefix_path" ] || [ -L "$pyenv_prefix_path" ]; then
|
||||
echo Version "$venv_name" already exists >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
case "$venv_dir" in
|
||||
"$versions_dir"/*)
|
||||
link_target=${venv_dir#"$versions_dir"/}
|
||||
;;
|
||||
*)
|
||||
link_target=$venv_dir
|
||||
;;
|
||||
esac
|
||||
|
||||
# link to pyenv version directory
|
||||
if [ -z "$dry" ]; then
|
||||
mkdir -p "$versions_dir"
|
||||
ln -s "$link_target" "$pyenv_prefix_path"
|
||||
fi
|
||||
|
||||
# output
|
||||
if [ -z "$quiet" ]; then
|
||||
echo "Linked new version named $venv_name"
|
||||
fi
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
@test "link completions use the dispatcher" {
|
||||
run pyenv completions link
|
||||
assert_success
|
||||
assert_output <<OUT
|
||||
--help
|
||||
version
|
||||
OUT
|
||||
run pyenv completions link version
|
||||
assert_success
|
||||
assert_output <<OUT
|
||||
--help
|
||||
--dry
|
||||
--quiet
|
||||
OUT
|
||||
}
|
||||
|
||||
@test "link requires a version path" {
|
||||
run pyenv-link version
|
||||
assert_failure "Usage: pyenv link version [--dry] [--quiet] <path> [<name>]"
|
||||
}
|
||||
|
||||
@test "links an installed binary under its plain version name" {
|
||||
create_alt_executable_in_version "3.13.14-linux-x86_64" python 'echo linked-python'
|
||||
run pyenv-link version "$PYENV_ROOT/versions/3.13.14-linux-x86_64" 3.13.14
|
||||
assert_success "Linked new version named 3.13.14"
|
||||
assert_equal "3.13.14-linux-x86_64" "$(readlink "$PYENV_ROOT/versions/3.13.14")"
|
||||
PYENV_VERSION=3.13.14 run pyenv exec python
|
||||
assert_success "linked-python"
|
||||
}
|
||||
|
||||
@test "links external paths containing spaces into a fresh root" {
|
||||
mkdir -p "$PYENV_TEST_DIR/external env/bin"
|
||||
run pyenv-link version "$PYENV_TEST_DIR/external env" custom
|
||||
assert_success "Linked new version named custom"
|
||||
assert_equal "$PYENV_TEST_DIR/external env" "$(readlink "$PYENV_ROOT/versions/custom")"
|
||||
}
|
||||
|
||||
@test "refuses a dangling destination link" {
|
||||
mkdir -p "$PYENV_ROOT/versions" "$PYENV_TEST_DIR/source"
|
||||
ln -s missing "$PYENV_ROOT/versions/dangling"
|
||||
run pyenv-link version "$PYENV_TEST_DIR/source" dangling
|
||||
assert_failure "Version dangling already exists"
|
||||
assert_equal missing "$(readlink "$PYENV_ROOT/versions/dangling")"
|
||||
}
|
||||
|
||||
@test "rejects invalid version names" {
|
||||
mkdir -p "$PYENV_TEST_DIR/source"
|
||||
for name in ../outside . .. 'foo/bar' 'foo:bar' system; do
|
||||
run pyenv-link version "$PYENV_TEST_DIR/source" "$name"
|
||||
assert_failure "pyenv-link: invalid version name \`$name'"
|
||||
assert [ ! -e "$PYENV_ROOT/outside" ]
|
||||
done
|
||||
echo "prompt = '../outside'" > "$PYENV_TEST_DIR/source/pyvenv.cfg"
|
||||
run pyenv-link version "$PYENV_TEST_DIR/source"
|
||||
assert_failure "pyenv-link: invalid version name \`../outside'"
|
||||
assert [ ! -e "$PYENV_ROOT/outside" ]
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../test/test_helper.bash
|
||||
|
|
@ -15,8 +15,8 @@ for changes in each version.
|
|||
|
||||
### Installing as a pyenv plugin (recommended)
|
||||
|
||||
Since python-build is bundled with pyenv by
|
||||
default, you do not need to do anything.
|
||||
You need nothing to do since python-build is bundled with pyenv by
|
||||
default.
|
||||
|
||||
### Installing as a standalone program (advanced)
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ Installing python-build as a standalone program will give you access to the
|
|||
`python-build` command for precise control over Python version installation. If you
|
||||
have pyenv installed, you will also be able to use the `pyenv install` command.
|
||||
|
||||
git clone https://github.com/pyenv/pyenv.git
|
||||
git clone git://github.com/pyenv/pyenv.git
|
||||
cd pyenv/plugins/python-build
|
||||
./install.sh
|
||||
|
||||
|
|
@ -54,11 +54,8 @@ Or, if you would like to install the latest development release:
|
|||
|
||||
## Usage
|
||||
|
||||
On Ubuntu, Debian, and Mint, install the recommended build dependencies with:
|
||||
|
||||
pyenv install-prerequisites
|
||||
|
||||
For other environments, see our [build environment recommendations](https://github.com/pyenv/pyenv/wiki#suggested-build-environment).
|
||||
Before you begin, you should ensure that your build environment has the proper
|
||||
system dependencies for compiling the wanted Python Version (see our [recommendations](https://github.com/pyenv/pyenv/wiki#suggested-build-environment)).
|
||||
|
||||
### Using `pyenv install` with pyenv
|
||||
|
||||
|
|
@ -70,13 +67,6 @@ 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.
|
||||
|
|
@ -106,64 +96,7 @@ versions of Python that are not yet supported by python-build.
|
|||
See the [python-build built-in definitions](https://github.com/pyenv/pyenv/tree/master/plugins/python-build/share/python-build) as a starting point for
|
||||
custom definition files.
|
||||
|
||||
#### Adding definitions with a Pyenv plugin
|
||||
|
||||
You can add your own definitions with a [Pyenv plugin](https://github.com/pyenv/pyenv?tab=readme-ov-file#pyenv-plugins) by placing them under
|
||||
`$PYENV_ROOT/plugins/your_plugin_name/share/python-build`.
|
||||
|
||||
### Default build configuration
|
||||
|
||||
Without the user customizing the build with environment variables (see below),
|
||||
`python-build` builds Python with mostly default Configure options
|
||||
to maintain the principle of the least surprise.
|
||||
|
||||
The exceptions -- non-default options that are set by default -- are listed below:
|
||||
|
||||
| Option/Behavior | Rationale |
|
||||
|-----------------|-----------|
|
||||
| `--enable-shared` is on by default. Pass `--disable-shared` to Configure options to override | The official CPython Docker image uses it. It's required to embed CPython. |
|
||||
| argument to `--enable-universalsdk` is ignored and set to `/` |
|
||||
| `--with-universal-archs` defaults to `universal2` on ARM64 architecture | the only dual-architecture Macs in use today are Apple Silicon which can only build that one |
|
||||
| argument to `--enable-framework` is ignored and set to a specific value | CPython's build logic requires a very specific argument to avoid installing the `Applications` part globally |
|
||||
| argument to `--enable-unicode` in non-MacOS is overridden to `ucs4` for 2.x-3.3 |
|
||||
| `MACOSX_DEPLOYMENT_TARGET` defaults to the running MacOS version |
|
||||
|
||||
|
||||
#### Integration with 3rd-party package ecosystems
|
||||
|
||||
##### Homebrew
|
||||
|
||||
Homebrew is used to find dependency packages if `brew` is found on `PATH`:
|
||||
* In MacOS, or
|
||||
* If the running Pyenv itself is installed with Homebrew
|
||||
|
||||
Set `PYTHON_BUILD_USE_HOMEBREW` or `PYTHON_BUILD_SKIP_HOMEBREW` to override this default.
|
||||
|
||||
When Homebrew is used, its `include` and `lib` paths are added to compiler search path (the latter is also set as `rpath`),
|
||||
and also Python dependencies that are typically keg-only are searched for in the Homebrew installation and added individually.
|
||||
|
||||
**NOTE:** Homebrew is not used in Linux by default because it's rolling-release which causes a problem.
|
||||
Upgrading a Python dependency in Homebrew to a new major version (that `brew` does without warning)
|
||||
would break all Pyenv-managed installations that depend on it.
|
||||
You can use a [community plugin `fix-version`](https://github.com/pyenv/pyenv/wiki/Plugins#community-plugins)
|
||||
to fix installations in such a case.
|
||||
|
||||
##### MacPorts
|
||||
|
||||
MacPorts Homebrew is used to find dependency packages if `port` is found on `PATH` in MacOS.
|
||||
|
||||
Set `PYTHON_BUILD_USE_MACPORTS` or `PYTHON_BUILD_SKIP_MACPORTS` to override this default.
|
||||
|
||||
###### Interaction with Homebrew
|
||||
|
||||
If both Homebrew and MacPorts are installed and allowed to be used, Homebrew takes preference.
|
||||
There first ecosystem where any of the required dependency packages is found is used.
|
||||
|
||||
##### Portage
|
||||
|
||||
In FreeBSD, if `pkg` is on PATH, Ports are searched for some dependencies that Configure is known to not search for via `pkg-config`.
|
||||
(Later versions of CPython search for more packages via `pkg-config` so this may eventually become redundant.)
|
||||
|
||||
[definitions]: https://github.com/pyenv/pyenv/tree/master/plugins/python-build/share/python-build
|
||||
|
||||
### Special environment variables
|
||||
|
||||
|
|
@ -176,32 +109,23 @@ You can set certain environment variables to control the build process.
|
|||
downloaded package files.
|
||||
* `PYTHON_BUILD_MIRROR_URL` overrides the default mirror URL root to one of your
|
||||
choosing.
|
||||
* `PYTHON_BUILD_MIRROR_URL_SKIP_CHECKSUM`, if set, does not append the SHA2
|
||||
checksum of the file to the mirror URL.
|
||||
* `PYTHON_BUILD_SKIP_MIRROR`, if set, forces python-build to download packages from
|
||||
their original source URLs instead of using a mirror.
|
||||
* `PYTHON_BUILD_HTTP_CLIENT`, explicitly specify the HTTP client type to use. `aria2`, `curl` and `wget` are the supported values and by default, are searched in that order.
|
||||
* `PYTHON_BUILD_CURL_OPTS`, `PYTHON_BUILD_WGET_OPTS`, `PYTHON_BUILD_ARIA2_OPTS` pass additional parameters to the corresponding HTTP client.
|
||||
* `PYTHON_BUILD_SKIP_HOMEBREW`, if set, will not search for libraries installed by Homebrew when it would normally will.
|
||||
* `PYTHON_BUILD_USE_HOMEBREW`, if set, will search for libraries installed by Homebrew when it would normally not.
|
||||
* `PYTHON_BUILD_HOMEBREW_OPENSSL_FORMULA`, override the Homebrew OpenSSL formula to use.
|
||||
* `PYTHON_BUILD_MACPORTS_OPENSSL_FORMULA`, override the MacPorts OpenSSL formula to use. The default is `openssl3 openssl`.
|
||||
* `PYTHON_BUILD_TCLTK_FORMULA`, override the Homebrew Tcl/Tk formula to use.
|
||||
* `PYTHON_BUILD_SKIP_MACPORTS`, if set, will not search for libraries installed by MacPorts when it would normally will.
|
||||
* `PYTHON_BUILD_USE_MACPORTS`, if set, will search for libraries installed by MacPorts when it would normally not.
|
||||
* `PYTHON_BUILD_ROOT` overrides the default location from where build definitions
|
||||
in `share/python-build/` are looked up.
|
||||
* `PYTHON_BUILD_DEFINITIONS` can be a list of colon-separated paths that get
|
||||
additionally searched when looking up build definitions.
|
||||
* `CC` sets the path to the C compiler.
|
||||
* `PYTHON_CFLAGS` lets you pass additional options to the default `CFLAGS`. Use
|
||||
this to override, for instance, the `-O3` option.
|
||||
* `CONFIGURE_OPTS` lets you pass additional options to `./configure`.
|
||||
* `MAKE` lets you override the command to use for `make`. Useful for specifying
|
||||
GNU make (`gmake`) on some systems.
|
||||
* `MAKE_OPTS` (or `MAKEOPTS`) lets you pass additional options to `make`.
|
||||
* `MAKE_INSTALL_OPTS` lets you pass additional options to `make install`.
|
||||
* `<PACKAGE>_CFLAGS`, `<PACKAGE>_CPPFLAGS`, `<PACKAGE>_LDFLAGS` let you pass additional options to `CFLAGS`/`CPPFLAGS`/`LDFLAGS` specifically for building `<package>` (Python itself or a dependency library) from source as part of the build script. `<PACKAGE>` should be a capitalized name of the package without version (technically, capitalized first argument to `install_package` without version). E.g. for CPython, it's "`PYTHON`", for Readline, "`READLINE`", for PyPy (only applies when building it from source), "`PYPY`". Check the source of the build script you're using if unsure.
|
||||
* `<PACKAGE>_CONFIGURE_OPTS`, `<PACKAGE>_MAKE_OPTS`, `<PACKAGE>_MAKE_INSTALL_OPTS`, `<PACKAGE>_MAKE_INSTALL_TARGET` allow
|
||||
you to specify configure and make options for building `<package>` (same as above). "Make install target" would replace "`install`" in the `make install` invocation.
|
||||
* `PYTHON_CONFIGURE_OPTS` and `PYTHON_MAKE_OPTS` and `PYTHON_MAKE_INSTALL_OPTS` allow
|
||||
you to specify configure and make options for building CPython. These variables
|
||||
will be passed to Python only, not any dependent packages (e.g. libyaml).
|
||||
|
||||
### Applying patches to Python before compiling
|
||||
|
||||
|
|
@ -223,21 +147,20 @@ $ cat fix1.patch fix2.patch | pyenv install --patch 2.7.10
|
|||
```
|
||||
|
||||
|
||||
### Building for maximum performance
|
||||
### Building with `--enable-shared`
|
||||
|
||||
Building CPython with `--enable-optimizations` will result in a faster
|
||||
interpreter at the cost of significantly longer build times. Most notably, this
|
||||
enables PGO (profile guided optimization). While your mileage may vary, it is
|
||||
common for performance improvement from this to be in the ballpark of 30%.
|
||||
You can build CPython with `--enable-shared` to install a version with
|
||||
shared object.
|
||||
|
||||
If `--enable-shared` was found in `PYTHON_CONFIGURE_OPTS` or `CONFIGURE_OPTS`,
|
||||
`python-build` will automatically set `RPATH` to the pyenv's prefix directory.
|
||||
This means you don't have to set `LD_LIBRARY_PATH` or `DYLD_LIBRARY_PATH` for
|
||||
the version(s) installed with `--enable-shared`.
|
||||
|
||||
```sh
|
||||
env PYTHON_CONFIGURE_OPTS='--enable-optimizations --with-lto' PYTHON_CFLAGS='-march=native -mtune=native' pyenv install --verbose 3.6.0
|
||||
$ env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 2.7.9
|
||||
```
|
||||
|
||||
You can also customize the task used for profile guided optimization by setting
|
||||
the `PROFILE_TASK` environment variable, for instance, `PROFILE_TASK='-m
|
||||
test.regrtest --pgo -j0'` will run much faster than the default task.
|
||||
|
||||
### Checksum verification
|
||||
|
||||
If you have the `shasum`, `openssl`, or `sha256sum` tool installed, python-build will
|
||||
|
|
@ -250,34 +173,14 @@ definition. (All bundled definitions include checksums.)
|
|||
### Package download mirrors
|
||||
|
||||
python-build will first attempt to download package files from a mirror hosted on
|
||||
GitHub Pages. If this fails, it will fall back to the
|
||||
GitHub Pages. If a package is not available on the mirror, if the mirror
|
||||
is down, or if the download is corrupt, python-build will fall back to the
|
||||
official URL specified in the definition file.
|
||||
|
||||
You can point python-build to another mirror by specifying the
|
||||
`PYTHON_BUILD_MIRROR_URL` environment variable.
|
||||
|
||||
Package mirror URLs are constructed by joining
|
||||
`$PYTHON_BUILD_MIRROR_URL` with the SHA2 checksum of the package file as specified in the URL
|
||||
in the installation script (the part after the hash sign). E.g.:
|
||||
|
||||
```
|
||||
https://mycache.example.com/0419e9085bf51b7a672009b3f50dbf1859acdf18ba725d0ec19aa5c8503f0ea3
|
||||
```
|
||||
|
||||
If you have replicated the directory structure of an official site, the easiest way to adapt
|
||||
would be to make symlinks at the mirror's root:
|
||||
|
||||
```
|
||||
0419e9085bf51b7a672009b3f50dbf1859acdf18ba725d0ec19aa5c8503f0ea3 -> 3.10.10/Python-3.10.10.tar.xz
|
||||
```
|
||||
|
||||
The rationale is to abstract away difference between directory structures of sites
|
||||
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
|
||||
pull-through cache like Artifactory), you can set the
|
||||
`PYTHON_BUILD_MIRROR_URL_SKIP_CHECKSUM` environment variable.
|
||||
`PYTHON_BUILD_MIRROR_URL` environment variable--useful if you'd like to run your
|
||||
own local mirror, for example. Package mirror URLs are constructed by joining
|
||||
this variable with the SHA2 checksum of the package file.
|
||||
|
||||
If you don't have an SHA2 program installed, python-build will skip the download
|
||||
mirror and use official URLs instead. You can force python-build to bypass the
|
||||
|
|
@ -286,15 +189,15 @@ mirror by setting the `PYTHON_BUILD_SKIP_MIRROR` environment variable.
|
|||
The official python-build download mirror is provided by
|
||||
[GitHub Pages](http://yyuu.github.io/pythons/).
|
||||
|
||||
### Package download cache
|
||||
### Package download caching
|
||||
|
||||
Python-build will keep a cache of downloaded package files
|
||||
at the location specified by the `PYTHON_BUILD_CACHE_PATH` environment variable
|
||||
if it exists. The default is `~/.pyenv/cache`, so you can
|
||||
enable caching by just creating that directory.
|
||||
You can instruct python-build to keep a local cache of downloaded package files
|
||||
by setting the `PYTHON_BUILD_CACHE_PATH` environment variable. When set, package
|
||||
files will be kept in this directory after the first successful download and
|
||||
reused by subsequent invocations of `python-build` and `pyenv install`.
|
||||
|
||||
The name of the would-be cached file is reported by Pyenv in the "Downloading <filename>..." message.
|
||||
It's possible to warm up the cache by manually putting the file there under an appropriate name.
|
||||
The `pyenv install` command defaults this path to `~/.pyenv/cache`, so in most
|
||||
cases you can enable download caching simply by creating that directory.
|
||||
|
||||
### Keeping the build directory after installation
|
||||
|
||||
|
|
@ -317,50 +220,3 @@ Please see the [pyenv wiki](https://github.com/pyenv/pyenv/wiki) for solutions t
|
|||
If you can't find an answer on the wiki, open an issue on the [issue
|
||||
tracker](https://github.com/pyenv/pyenv/issues). Be sure to include
|
||||
the full build log for build failures.
|
||||
|
||||
## Contributing
|
||||
|
||||
### Testing new python versions
|
||||
|
||||
If you are contributing a new python version for python-build,
|
||||
you can test the build in a [docker](https://www.docker.com/) container based on Ubuntu 18.04.
|
||||
|
||||
With docker installed:
|
||||
|
||||
```sh
|
||||
docker build -t my_container .
|
||||
docker run my_container pyenv install <my_version>
|
||||
```
|
||||
|
||||
To enter a shell which will allow you to build and then test a python version,
|
||||
replace the second line with
|
||||
|
||||
```sh
|
||||
docker run -it my_container
|
||||
```
|
||||
|
||||
The container will need to be rebuilt whenever you change the repo,
|
||||
but after the first build, this will be very fast,
|
||||
as the layer including the build dependencies will be cached.
|
||||
|
||||
Changes made inside the container will not be persisted.
|
||||
|
||||
To test *all* new versions since a particular revision (e.g. `master`), `cd` to the root of your `pyenv` repo, and run this script:
|
||||
|
||||
```sh
|
||||
set -e
|
||||
set -x
|
||||
|
||||
docker build -t pyenv-test-container .
|
||||
|
||||
git diff --name-only master \
|
||||
| grep '^plugins/python-build/share/python-build/' \
|
||||
| awk -F '/' '{print $NF}' \
|
||||
| xargs -I _ docker run pyenv-test-container pyenv install _
|
||||
```
|
||||
|
||||
- Build the docker image with the **t**ag pyenv-test-container
|
||||
- Look for the names files changed since revision `master`
|
||||
- Filter out any which don't live where python-build keeps its build scripts
|
||||
- Look only at the file name (i.e. the python version name)
|
||||
- Run a new docker container for each, building that version
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
#
|
||||
# Summary: Install a Python version using python-build
|
||||
#
|
||||
# Usage: pyenv install [-f] [-kvp] <version>[:<alias>]...
|
||||
# pyenv install [-f] [-kvp] <definition-file>[:<alias>]
|
||||
# pyenv install -l|--list [--bare]
|
||||
# Usage: pyenv install [-f] [-kvp] <version>
|
||||
# pyenv install [-f] [-kvp] <definition-file>
|
||||
# pyenv install -l|--list
|
||||
# pyenv install --version
|
||||
#
|
||||
# -l/--list List all available versions
|
||||
|
|
@ -20,13 +20,6 @@
|
|||
# --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
|
||||
|
|
@ -45,7 +38,6 @@ shopt -u nullglob
|
|||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
echo --bare
|
||||
echo --list
|
||||
echo --force
|
||||
echo --skip-existing
|
||||
|
|
@ -67,7 +59,7 @@ usage() {
|
|||
|
||||
definitions() {
|
||||
local query="$1"
|
||||
python-build --definitions | $(type -P ggrep grep | head -n1) -F "$query" || true
|
||||
python-build --definitions | $(type -p ggrep grep | head -1) -F "$query" || true
|
||||
}
|
||||
|
||||
indent() {
|
||||
|
|
@ -80,10 +72,6 @@ unset KEEP
|
|||
unset VERBOSE
|
||||
unset HAS_PATCH
|
||||
unset DEBUG
|
||||
unset BARE
|
||||
unset LIST
|
||||
|
||||
[ -n "$PYENV_DEBUG" ] && VERBOSE="-v"
|
||||
|
||||
parse_options "$@"
|
||||
for option in "${OPTIONS[@]}"; do
|
||||
|
|
@ -91,11 +79,10 @@ for option in "${OPTIONS[@]}"; do
|
|||
"h" | "help" )
|
||||
usage 0
|
||||
;;
|
||||
"bare" )
|
||||
BARE=1
|
||||
;;
|
||||
"l" | "list" )
|
||||
LIST=1
|
||||
echo "Available versions:"
|
||||
definitions | indent
|
||||
exit
|
||||
;;
|
||||
"f" | "force" )
|
||||
FORCE=true
|
||||
|
|
@ -124,15 +111,7 @@ 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
|
||||
[ "${#ARGUMENTS[@]}" -le 1 ] || usage 1 >&2
|
||||
|
||||
unset VERSION_NAME
|
||||
|
||||
|
|
@ -140,23 +119,9 @@ unset VERSION_NAME
|
|||
# argument is missing, try to install whatever local app-specific
|
||||
# version is specified by pyenv. Show usage instructions if a local
|
||||
# version is not specified.
|
||||
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
|
||||
DEFINITION="${ARGUMENTS[0]}"
|
||||
[ -n "$DEFINITION" ] || DEFINITION="$(pyenv-local 2>/dev/null || true)"
|
||||
[ -n "$DEFINITION" ] || usage 1 >&2
|
||||
|
||||
# Define `before_install` and `after_install` functions that allow
|
||||
# plugin hooks to register a string of code for execution before or
|
||||
|
|
@ -173,6 +138,102 @@ after_install() {
|
|||
after_hooks["${#after_hooks[@]}"]="$hook"
|
||||
}
|
||||
|
||||
OLDIFS="$IFS"
|
||||
IFS=$'\n' scripts=(`pyenv-hooks install`)
|
||||
IFS="$OLDIFS"
|
||||
for script in "${scripts[@]}"; do source "$script"; done
|
||||
|
||||
|
||||
# Set VERSION_NAME from $DEFINITION, if it is not already set. Then
|
||||
# compute the installation prefix.
|
||||
[ -n "$VERSION_NAME" ] || VERSION_NAME="${DEFINITION##*/}"
|
||||
[ -n "$DEBUG" ] && VERSION_NAME="${VERSION_NAME}-debug"
|
||||
PREFIX="${PYENV_ROOT}/versions/${VERSION_NAME}"
|
||||
|
||||
[ -d "${PREFIX}" ] && PREFIX_EXISTS=1
|
||||
|
||||
# If the installation prefix exists, prompt for confirmation unless
|
||||
# the --force option was specified.
|
||||
if [ -d "${PREFIX}/bin" ]; then
|
||||
if [ -z "$FORCE" ] && [ -z "$SKIP_EXISTING" ]; then
|
||||
echo "pyenv: $PREFIX already exists" >&2
|
||||
read -p "continue with installation? (y/N) "
|
||||
|
||||
case "$REPLY" in
|
||||
y* | Y* ) ;;
|
||||
* ) exit 1 ;;
|
||||
esac
|
||||
elif [ -n "$SKIP_EXISTING" ]; then
|
||||
# Since we know the python version is already installed, and are opting to
|
||||
# not force installation of existing versions, we just `exit 0` here to
|
||||
# leave things happy
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 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}"
|
||||
KEEP="-k"
|
||||
fi
|
||||
|
||||
# Set PYTHON_BUILD_CACHE_PATH to $PYENV_ROOT/cache, if the directory
|
||||
# exists and the variable is not already set.
|
||||
if [ -z "${PYTHON_BUILD_CACHE_PATH}" ] && [ -d "${PYENV_ROOT}/cache" ]; then
|
||||
export PYTHON_BUILD_CACHE_PATH="${PYENV_ROOT}/cache"
|
||||
fi
|
||||
|
||||
if [ -z "${PYENV_BOOTSTRAP_VERSION}" ]; then
|
||||
case "${VERSION_NAME}" in
|
||||
[23]"."* )
|
||||
# Default PYENV_VERSION to the friendly Python version. (The
|
||||
# CPython installer requires an existing Python installation to run. An
|
||||
# unsatisfied local .python-version file can cause the installer to
|
||||
# fail.)
|
||||
for version_info in "${VERSION_NAME%-dev}" "${VERSION_NAME%.*}" "${VERSION_NAME%%.*}"; do
|
||||
# Anaconda's `curl` doesn't work on platform where `/etc/pki/tls/certs/ca-bundle.crt` isn't available (e.g. Debian)
|
||||
for version in $(pyenv-whence "python${version_info}" 2>/dev/null || true); do
|
||||
if [[ "${version}" != "anaconda"* ]] && [[ "${version}" != "miniconda"* ]]; then
|
||||
PYENV_BOOTSTRAP_VERSION="${version}"
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
;;
|
||||
"pypy"*"-dev" | "pypy"*"-src" )
|
||||
# PyPy/PyPy3 requires existing Python 2.7 to build
|
||||
if [ -n "${PYENV_RPYTHON_VERSION}" ]; then
|
||||
PYENV_BOOTSTRAP_VERSION="${PYENV_RPYTHON_VERSION}"
|
||||
else
|
||||
for version in $(pyenv-versions --bare | sort -r); do
|
||||
if [[ "${version}" == "2.7"* ]]; then
|
||||
PYENV_BOOTSTRAP_VERSION="$version"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ -n "$PYENV_BOOTSTRAP_VERSION" ]; then
|
||||
for dep in curses genc pycparser; do
|
||||
if ! PYENV_VERSION="$PYENV_BOOTSTRAP_VERSION" pyenv-exec python -c "import ${dep}" 1>/dev/null 2>&1; then
|
||||
echo "pyenv-install: $VERSION_NAME: PyPy requires \`${dep}' in $PYENV_BOOTSTRAP_VERSION to build from source." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "pyenv-install: $VERSION_NAME: PyPy requires Python 2.7 to build from source." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -n "${PYENV_BOOTSTRAP_VERSION}" ]; then
|
||||
export PYENV_VERSION="${PYENV_BOOTSTRAP_VERSION}"
|
||||
fi
|
||||
|
||||
# Execute `before_install` hooks.
|
||||
for hook in "${before_hooks[@]}"; do eval "$hook"; done
|
||||
|
||||
# Plan cleanup on unsuccessful installation.
|
||||
cleanup() {
|
||||
[ -z "${PREFIX_EXISTS}" ] && rm -rf "$PREFIX"
|
||||
|
|
@ -180,156 +241,43 @@ cleanup() {
|
|||
|
||||
trap cleanup SIGINT
|
||||
|
||||
# Invoke `python-build` and record the exit status in $STATUS.
|
||||
STATUS=0
|
||||
python-build $KEEP $VERBOSE $HAS_PATCH $DEBUG "$DEFINITION" "$PREFIX" || STATUS="$?"
|
||||
|
||||
OLDIFS="$IFS"
|
||||
IFS=$'\n' scripts=(`pyenv-hooks install`)
|
||||
IFS="$OLDIFS"
|
||||
for script in "${scripts[@]}"; do source "$script"; done
|
||||
|
||||
COMBINED_STATUS=0
|
||||
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.
|
||||
# We install the version under the resolved name
|
||||
# and hooks also see the resolved name
|
||||
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_ALIAS:-$VERSION_NAME}"
|
||||
|
||||
[ -d "${PREFIX}" ] && PREFIX_EXISTS=1
|
||||
|
||||
# If the installation prefix exists, prompt for confirmation unless
|
||||
# the --force option was specified.
|
||||
if [ -d "${PREFIX}/bin" ]; then
|
||||
if [ -z "$FORCE" ] && [ -z "$SKIP_EXISTING" ]; then
|
||||
echo "pyenv: $PREFIX already exists" >&2
|
||||
read -p "continue with installation? (y/N) "
|
||||
|
||||
case "$REPLY" in
|
||||
y | Y | yes | YES ) ;;
|
||||
* ) { STATUS=1; [[ $STATUS -gt $COMBINED_STATUS ]] && COMBINED_STATUS=$STATUS; }; continue ;;
|
||||
esac
|
||||
elif [ -n "$SKIP_EXISTING" ]; then
|
||||
# Since we know the python version is already installed, and are opting to
|
||||
# not force installation of existing versions, we just `exit 0` here to
|
||||
# leave things happy
|
||||
continue
|
||||
# Display a more helpful message if the definition wasn't found.
|
||||
if [ "$STATUS" == "2" ]; then
|
||||
{ candidates="$(definitions "$DEFINITION")"
|
||||
here="$(dirname "${0%/*}")/../.."
|
||||
if [ -n "$candidates" ]; then
|
||||
echo
|
||||
echo "The following versions contain \`$DEFINITION' in the name:"
|
||||
echo "$candidates" | indent
|
||||
fi
|
||||
fi
|
||||
echo
|
||||
echo "See all available versions with \`pyenv install --list'."
|
||||
echo
|
||||
echo -n "If the version you need is missing, try upgrading pyenv"
|
||||
if [ "$here" != "${here#$(brew --prefix 2>/dev/null)}" ]; then
|
||||
printf ":\n\n"
|
||||
echo " brew update && brew upgrade pyenv"
|
||||
elif [ -d "${here}/.git" ]; then
|
||||
printf ":\n\n"
|
||||
echo " cd ${here} && git pull && cd -"
|
||||
else
|
||||
printf ".\n"
|
||||
fi
|
||||
} >&2
|
||||
fi
|
||||
|
||||
# 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_ALIAS:-$VERSION_NAME}"
|
||||
KEEP="-k"
|
||||
fi
|
||||
# Execute `after_install` hooks.
|
||||
for hook in "${after_hooks[@]}"; do eval "$hook"; done
|
||||
|
||||
# Set PYTHON_BUILD_CACHE_PATH to $PYENV_ROOT/cache, if the directory
|
||||
# exists and the variable is not already set.
|
||||
if [ -z "${PYTHON_BUILD_CACHE_PATH}" ] && [ -d "${PYENV_ROOT}/cache" ]; then
|
||||
export PYTHON_BUILD_CACHE_PATH="${PYENV_ROOT}/cache"
|
||||
fi
|
||||
# Run `pyenv-rehash` after a successful installation.
|
||||
if [ "$STATUS" == "0" ]; then
|
||||
pyenv-rehash
|
||||
else
|
||||
cleanup
|
||||
fi
|
||||
|
||||
if [ -z "${PYENV_BOOTSTRAP_VERSION}" ]; then
|
||||
case "${VERSION_NAME}" in
|
||||
[23]"."* )
|
||||
# Default PYENV_VERSION to the friendly Python version. (The
|
||||
# CPython installer requires an existing Python installation to run. An
|
||||
# unsatisfied local .python-version file can cause the installer to
|
||||
# fail.)
|
||||
for version_info in "${VERSION_NAME%-dev}" "${VERSION_NAME%.*}" "${VERSION_NAME%%.*}"; do
|
||||
# Anaconda's `curl` doesn't work on platform where `/etc/pki/tls/certs/ca-bundle.crt` isn't available (e.g. Debian)
|
||||
for version in $(pyenv-whence "python${version_info}" 2>/dev/null || true); do
|
||||
if [[ "${version}" != "anaconda"* ]] && [[ "${version}" != "miniconda"* ]]; then
|
||||
PYENV_BOOTSTRAP_VERSION="${version}"
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
;;
|
||||
"pypy"*"-dev" | "pypy"*"-src" )
|
||||
# PyPy/PyPy3 requires existing Python 2.7 to build
|
||||
if [ -n "${PYENV_RPYTHON_VERSION}" ]; then
|
||||
PYENV_BOOTSTRAP_VERSION="${PYENV_RPYTHON_VERSION}"
|
||||
else
|
||||
for version in $(pyenv-versions --bare | sort -r); do
|
||||
if [[ "${version}" == "2.7"* ]]; then
|
||||
PYENV_BOOTSTRAP_VERSION="$version"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ -n "$PYENV_BOOTSTRAP_VERSION" ]; then
|
||||
for dep in pycparser; do
|
||||
if ! PYENV_VERSION="$PYENV_BOOTSTRAP_VERSION" pyenv-exec python -c "import ${dep}" 1>/dev/null 2>&1; then
|
||||
echo "pyenv-install: $VERSION_NAME: PyPy requires \`${dep}' in $PYENV_BOOTSTRAP_VERSION to build from source." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "pyenv-install: $VERSION_NAME: PyPy requires Python 2.7 to build from source." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -n "${PYENV_BOOTSTRAP_VERSION}" ]; then
|
||||
export PYENV_VERSION="${PYENV_BOOTSTRAP_VERSION}"
|
||||
fi
|
||||
|
||||
# Execute `before_install` hooks.
|
||||
for hook in "${before_hooks[@]}"; do eval "$hook"; done
|
||||
|
||||
# Invoke `python-build` and record the exit status in $STATUS.
|
||||
python-build $KEEP $VERBOSE $HAS_PATCH $DEBUG "$DEFINITION" "$PREFIX" || \
|
||||
{ STATUS=$?; [[ $STATUS -gt $COMBINED_STATUS ]] && COMBINED_STATUS=$STATUS; }
|
||||
|
||||
# Display a more helpful message if the definition wasn't found.
|
||||
if [ "$STATUS" == "2" ]; then
|
||||
{ candidates="$(definitions "$DEFINITION")"
|
||||
if [ -n "$candidates" ]; then
|
||||
echo
|
||||
echo "The following versions contain \`$DEFINITION' in the name:"
|
||||
echo "$candidates" | indent
|
||||
fi
|
||||
echo
|
||||
echo "See all available versions with \`pyenv install --list'."
|
||||
echo
|
||||
echo -n "If the version you need is missing, try upgrading pyenv"
|
||||
declare brew_prefix
|
||||
if brew_prefix="$(brew --prefix 2>/dev/null)" && [[ $_PYENV_INSTALL_PREFIX/ == "$brew_prefix/"* ]]; then
|
||||
printf ":\n\n"
|
||||
echo " brew update && brew upgrade pyenv"
|
||||
elif [ -d "${_PYENV_INSTALL_PREFIX}/.git" ]; then
|
||||
printf ":\n\n"
|
||||
echo " cd ${_PYENV_INSTALL_PREFIX} && git pull && cd -"
|
||||
else
|
||||
printf ".\n"
|
||||
fi
|
||||
} >&2
|
||||
fi
|
||||
|
||||
# Execute `after_install` hooks.
|
||||
for hook in "${after_hooks[@]}"; do eval "$hook"; done
|
||||
|
||||
# Run `pyenv-rehash` after a successful installation.
|
||||
if [[ $STATUS -eq 0 ]]; then
|
||||
pyenv-rehash
|
||||
else
|
||||
cleanup
|
||||
break
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
|
||||
exit "${COMBINED_STATUS}"
|
||||
exit "$STATUS"
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Install Python build prerequisites on Debian-based systems
|
||||
#
|
||||
# Usage: pyenv install-prerequisites
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
usage() {
|
||||
pyenv-help install-prerequisites 2>/dev/null
|
||||
[ -z "$1" ] || exit "$1"
|
||||
}
|
||||
|
||||
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||
usage 0
|
||||
fi
|
||||
|
||||
[ "$#" -eq 0 ] || usage 1 >&2
|
||||
|
||||
if ! command -v apt-get >/dev/null; then
|
||||
echo "pyenv: installing build prerequisites is not supported on this system" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
prerequisites=(
|
||||
make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev
|
||||
libsqlite3-dev curl git llvm libncurses5-dev libncursesw5-dev xz-utils
|
||||
tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev libzstd-dev
|
||||
)
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
apt_get=(apt-get)
|
||||
elif command -v sudo >/dev/null; then
|
||||
apt_get=(sudo apt-get)
|
||||
else
|
||||
echo "pyenv: sudo is required to install build prerequisites" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${apt_get[@]}" update -q
|
||||
"${apt_get[@]}" install -yq "${prerequisites[@]}"
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Summary: Uninstall Python versions
|
||||
# Summary: Uninstall a specific Python version
|
||||
#
|
||||
# Usage: pyenv uninstall [-f|--force] <version> ...
|
||||
# Usage: pyenv uninstall [-f|--force] <version>
|
||||
#
|
||||
# -f Attempt to remove the specified version without prompting
|
||||
# for confirmation. If the version does not exist, do not
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
# See `pyenv versions` for a complete list of installed versions.
|
||||
#
|
||||
set -e
|
||||
[ -n "$PYENV_DEBUG" ] && set -x
|
||||
|
||||
# Provide pyenv completions
|
||||
if [ "$1" = "--complete" ]; then
|
||||
|
|
@ -34,17 +33,14 @@ if [ "$1" = "-f" ] || [ "$1" = "--force" ]; then
|
|||
shift
|
||||
fi
|
||||
|
||||
[ "$#" -gt 0 ] || usage 1 >&2
|
||||
[ "$#" -eq 1 ] || usage 1 >&2
|
||||
|
||||
versions=("$@")
|
||||
|
||||
for version in "${versions[@]}"; do
|
||||
case "$version" in
|
||||
"" | -* )
|
||||
usage 1 >&2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
DEFINITION="$1"
|
||||
case "$DEFINITION" in
|
||||
"" | -* )
|
||||
usage 1 >&2
|
||||
;;
|
||||
esac
|
||||
|
||||
declare -a before_hooks after_hooks
|
||||
|
||||
|
|
@ -63,36 +59,28 @@ IFS=$'\n' scripts=(`pyenv-hooks uninstall`)
|
|||
IFS="$OLDIFS"
|
||||
for script in "${scripts[@]}"; do source "$script"; done
|
||||
|
||||
uninstall-python() {
|
||||
local DEFINITION="$1"
|
||||
|
||||
local VERSION_NAME="${DEFINITION##*/}"
|
||||
local PREFIX="${PYENV_ROOT}/versions/${VERSION_NAME}"
|
||||
VERSION_NAME="${DEFINITION##*/}"
|
||||
PREFIX="${PYENV_ROOT}/versions/${VERSION_NAME}"
|
||||
|
||||
if [ -z "$FORCE" ]; then
|
||||
if [ ! -d "$PREFIX" ]; then
|
||||
echo "pyenv: version \`$VERSION_NAME' not installed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read -p "pyenv: remove $PREFIX? (y/N) "
|
||||
case "$REPLY" in
|
||||
y | Y | yes | YES ) ;;
|
||||
* ) exit 1 ;;
|
||||
esac
|
||||
if [ -z "$FORCE" ]; then
|
||||
if [ ! -d "$PREFIX" ]; then
|
||||
echo "pyenv: version \`$VERSION_NAME' not installed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for hook in "${before_hooks[@]}"; do eval "$hook"; done
|
||||
read -p "pyenv: remove $PREFIX? "
|
||||
case "$REPLY" in
|
||||
y* | Y* ) ;;
|
||||
* ) exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -d "$PREFIX" ]; then
|
||||
rm -rf "$PREFIX"
|
||||
pyenv-rehash
|
||||
echo "pyenv: $VERSION_NAME uninstalled"
|
||||
fi
|
||||
for hook in "${before_hooks[@]}"; do eval "$hook"; done
|
||||
|
||||
for hook in "${after_hooks[@]}"; do eval "$hook"; done
|
||||
}
|
||||
if [ -d "$PREFIX" ]; then
|
||||
rm -rf "$PREFIX"
|
||||
pyenv-rehash
|
||||
fi
|
||||
|
||||
for version in "${versions[@]}"; do
|
||||
uninstall-python "$version"
|
||||
done
|
||||
for hook in "${after_hooks[@]}"; do eval "$hook"; done
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1
plugins/python-build/scripts/.gitignore
vendored
1
plugins/python-build/scripts/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
/venv
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# Scripts for updating python-build
|
||||
|
||||
Install dependencies with `pip install -r requirements.txt`.
|
||||
|
||||
## add_miniconda.py
|
||||
|
||||
```_add_miniconda
|
||||
usage: add_miniconda.py [-h] [-d] [-v]
|
||||
|
||||
Script to add non-"latest" miniconda releases. Written for python 3.7. Checks
|
||||
the miniconda download archives for new versions, then writes a build script
|
||||
for any which do not exist locally, saving it to plugins/python-
|
||||
build/share/python-build. Ignores releases below 4.3.30. Also ignores sub-
|
||||
patch releases if that major.minor.patch already exists, but otherwise, takes
|
||||
the latest sub-patch release for given OS/arch. Assumes all miniconda3
|
||||
releases < 4.7 default to python 3.6, and anything else 3.7.
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-d, --dry-run Do not write scripts, just report them to stdout
|
||||
-v, --verbose Increase verbosity of logging
|
||||
```
|
||||
|
|
@ -1,758 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Script to add CPython releases.
|
||||
|
||||
Checks the CPython download archives for new versions,
|
||||
then writes a build script for any which do not exist locally,
|
||||
saving it to plugins/python-build/share/python-build.
|
||||
|
||||
"""
|
||||
import argparse
|
||||
import dataclasses
|
||||
import hashlib
|
||||
import io
|
||||
import itertools
|
||||
import logging
|
||||
import operator
|
||||
import os.path
|
||||
import pathlib
|
||||
import pprint
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import typing
|
||||
import urllib.parse
|
||||
|
||||
import jc
|
||||
import more_itertools
|
||||
import packaging.version
|
||||
import requests
|
||||
import requests_html
|
||||
import sortedcontainers
|
||||
import tqdm
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CUTOFF_VERSION=packaging.version.Version('3.10')
|
||||
EXCLUDED_VERSIONS= {
|
||||
}
|
||||
|
||||
here = pathlib.Path(__file__).resolve()
|
||||
OUT_DIR: pathlib.Path = here.parent.parent / "share" / "python-build"
|
||||
|
||||
AUTO_ADD_VERSION_REF_RE = re.compile(
|
||||
r"^(?P<object_id>[0-9a-f]{40,64})\t"
|
||||
r"refs/heads/auto_add_version/(?P<versions>\S+)$"
|
||||
)
|
||||
OPENSSL_RELEASE_TAG_RE = re.compile(
|
||||
r"^openssl-(?P<major>\d+)\.\d+(?:\.\d+)*(?:[a-z]+\d*)?$"
|
||||
)
|
||||
|
||||
T_THUNK=\
|
||||
'''export PYTHON_BUILD_FREE_THREADING=1
|
||||
source "${BASH_SOURCE[0]%t}"
|
||||
'''
|
||||
|
||||
|
||||
def adapt_script(version: packaging.version.Version,
|
||||
previous_version: packaging.version.Version) -> typing.Union[pathlib.Path, None]:
|
||||
|
||||
previous_version_path = OUT_DIR.joinpath(str(previous_version))
|
||||
|
||||
with previous_version_path.open("r", encoding='utf-8') as f:
|
||||
script = f.readlines()
|
||||
result = io.StringIO()
|
||||
for line in script:
|
||||
if m:=re.match(r'\s*install_package\s+"(?P<package>Python-\S+)"\s+'
|
||||
r'"(?P<url>\S+)"\s+.*\s+verify_py(?P<verify_py_suffix>\d+)\s+.*$',
|
||||
line):
|
||||
existing_url_path = urllib.parse.urlparse(m.group('url')).path
|
||||
try:
|
||||
matched_download = more_itertools.one(
|
||||
item for item in VersionDirectory.available[version].downloads
|
||||
if existing_url_path.endswith(item.extension))
|
||||
except ValueError:
|
||||
logger.error(f'Cannot match existing URL path\'s {existing_url_path} extension '
|
||||
f'to available downloads {VersionDirectory.available[version].downloads}')
|
||||
return
|
||||
new_package_name, new_package_url = matched_download.package_name, matched_download.url
|
||||
new_package_hash = Url.sha256_url(new_package_url, VersionDirectory.session)
|
||||
|
||||
verify_py_suffix = str(version.major)+str(version.minor)
|
||||
|
||||
line = Re.sub_groups(m,
|
||||
package=new_package_name,
|
||||
url=new_package_url+'#'+new_package_hash,
|
||||
verify_py_suffix=verify_py_suffix)
|
||||
|
||||
elif m:=re.match(r'\s*install_package\s+'
|
||||
r'"(?P<package>openssl-(?P<openssl_major>\d+)\.\S+)"\s+'
|
||||
r'"(?P<url>\S+)"\s.*$',
|
||||
line):
|
||||
item = VersionDirectory.openssl.get_store_latest_release(
|
||||
int(m.group('openssl_major'))
|
||||
)
|
||||
|
||||
line = Re.sub_groups(m,
|
||||
package=item.package_name,
|
||||
url=item.url + '#' + item.hash)
|
||||
|
||||
elif m:=re.match(r'\s*install_package\s+"(?P<package>readline-\S+)"\s+'
|
||||
r'"(?P<url>\S+)"\s.*$',
|
||||
line):
|
||||
item = VersionDirectory.readline.get_store_latest_release()
|
||||
|
||||
line = Re.sub_groups(m,
|
||||
package=item.package_name,
|
||||
url=item.url + '#' + item.hash)
|
||||
|
||||
result.write(line)
|
||||
|
||||
result_path = OUT_DIR.joinpath(str(version))
|
||||
logger.info(f"Writing {result_path}")
|
||||
result_path.write_text(result.getvalue(), encoding='utf-8')
|
||||
result.close()
|
||||
|
||||
return result_path
|
||||
|
||||
def add_version(version: packaging.version.Version):
|
||||
|
||||
previous_version = VersionDirectory.existing.pick_previous_version(version).version
|
||||
|
||||
is_prerelease_upgrade = previous_version.major==version.major\
|
||||
and previous_version.minor==version.minor\
|
||||
and previous_version.micro==version.micro
|
||||
|
||||
logger.info(f"Adding {version} based on {previous_version}"
|
||||
+ (" (prerelease upgrade)" if is_prerelease_upgrade else ""))
|
||||
|
||||
VersionDirectory.available.get_store_available_source_downloads(version)
|
||||
|
||||
new_path = adapt_script(version,
|
||||
previous_version)
|
||||
if not new_path:
|
||||
return False
|
||||
VersionDirectory.existing.append(_CPythonExistingScriptInfo(version,str(new_path)))
|
||||
|
||||
handle_version_patches(version, previous_version, is_prerelease_upgrade)
|
||||
|
||||
cleanup_prerelease_upgrade(is_prerelease_upgrade, previous_version, version)
|
||||
|
||||
handle_t_thunks(version, previous_version, is_prerelease_upgrade)
|
||||
|
||||
print(version)
|
||||
return True
|
||||
|
||||
|
||||
def handle_version_patches(
|
||||
version: packaging.version.Version,
|
||||
previous_version: packaging.version.Version,
|
||||
is_prerelease_upgrade: bool)\
|
||||
-> None:
|
||||
if (previous_version.major, previous_version.minor) != (version.major, version.minor):
|
||||
return
|
||||
|
||||
patches_dir = OUT_DIR / "patches"
|
||||
previous_patches = patches_dir / str(previous_version)
|
||||
if not previous_patches.exists():
|
||||
return
|
||||
|
||||
new_patches = patches_dir / str(version)
|
||||
if is_prerelease_upgrade:
|
||||
logger.info(f"Git moving patches from {previous_version} to {version}")
|
||||
subprocess.check_call((
|
||||
"git", "-C", OUT_DIR, "mv",
|
||||
f"patches/{previous_version}",
|
||||
f"patches/{version}",
|
||||
))
|
||||
else:
|
||||
logger.info(f"Copying patches from {previous_version} to {version}")
|
||||
shutil.copytree(previous_patches, new_patches)
|
||||
|
||||
# Subdir rename as a separate step from upper dir copying/moving
|
||||
# in case there are patches for dependency packages as well
|
||||
previous_package_patches = new_patches / f"Python-{previous_version}"
|
||||
new_package_patches = new_patches / f"Python-{version}"
|
||||
if is_prerelease_upgrade:
|
||||
subprocess.check_call((
|
||||
"git", "-C", OUT_DIR, "mv",
|
||||
f"patches/{version}/Python-{previous_version}",
|
||||
f"patches/{version}/Python-{version}",
|
||||
))
|
||||
else:
|
||||
previous_package_patches.rename(new_package_patches)
|
||||
|
||||
if uses_t_thunks(previous_version) and is_prerelease_upgrade:
|
||||
(patches_dir / f"{previous_version}t").unlink(missing_ok=True)
|
||||
if uses_t_thunks(version):
|
||||
(patches_dir / f"{version}t").symlink_to(
|
||||
str(version), target_is_directory=True
|
||||
)
|
||||
|
||||
|
||||
def cleanup_prerelease_upgrade(
|
||||
is_prerelease_upgrade: bool,
|
||||
previous_version: packaging.version.Version,
|
||||
new_version: packaging.version.Version)\
|
||||
-> None:
|
||||
if not is_prerelease_upgrade:
|
||||
return
|
||||
|
||||
previous_version_filename = str(previous_version)
|
||||
new_version_filename = str(new_version)
|
||||
new_version_path = OUT_DIR / new_version_filename
|
||||
|
||||
logger.info(f'Git moving {previous_version_filename} '
|
||||
f'to {new_version_filename} (preserving new data)')
|
||||
|
||||
data = new_version_path.read_text()
|
||||
new_version_path.unlink()
|
||||
|
||||
subprocess.check_call(("git","-C",OUT_DIR,
|
||||
"mv",
|
||||
previous_version_filename,
|
||||
new_version_filename))
|
||||
|
||||
new_version_path.write_text(data)
|
||||
|
||||
del VersionDirectory.existing[previous_version]
|
||||
|
||||
|
||||
def handle_t_thunks(version, previous_version, is_prerelease_upgrade):
|
||||
if not uses_t_thunks(version):
|
||||
return
|
||||
|
||||
# an old thunk may have older version-specific code
|
||||
# so it's safer to write a known version-independent template
|
||||
thunk_name = (str(version) + "t")
|
||||
thunk_path = OUT_DIR / thunk_name
|
||||
previous_thunk_name = str(previous_version) + "t"
|
||||
if is_prerelease_upgrade:
|
||||
logger.info(f"Git moving {previous_thunk_name} to {thunk_name}")
|
||||
subprocess.check_call(("git","-C",OUT_DIR,
|
||||
"mv",
|
||||
previous_thunk_name,
|
||||
thunk_name))
|
||||
|
||||
logger.info(f"Writing {thunk_path}")
|
||||
thunk_path.write_text(T_THUNK, encoding='utf-8')
|
||||
|
||||
|
||||
def uses_t_thunks(version: packaging.version.Version) -> bool:
|
||||
return (version.major, version.minor) >= (3, 13)
|
||||
|
||||
|
||||
Arguments: argparse.Namespace
|
||||
|
||||
def main():
|
||||
global Arguments
|
||||
Arguments = parse_args()
|
||||
logging.basicConfig(level=logging.DEBUG if Arguments.verbose else logging.INFO)
|
||||
|
||||
cached_session=requests_html.HTMLSession()
|
||||
global VersionDirectory
|
||||
VersionDirectory = _VersionDirectory(cached_session)
|
||||
|
||||
VersionDirectory.existing.populate()
|
||||
VersionDirectory.available.populate()
|
||||
|
||||
# Prereleases are placed under the same directory as the corresponding release.
|
||||
# So until we know the release is out, its directory is a potential prerelease directory.
|
||||
# Normally, prereleases are only made for initial releases (x.y.0) --
|
||||
# but rarely, they may make them for other releases (e.g. 3.14.5).
|
||||
for release in (v for v in frozenset(VersionDirectory.available.keys()) #refining alters the
|
||||
#corresponding directory key
|
||||
#which breaks iteration
|
||||
#over the directory --
|
||||
#so have to iterate over a copy
|
||||
if v not in VersionDirectory.existing):
|
||||
VersionDirectory.available.get_store_available_source_downloads(release, True)
|
||||
del release
|
||||
|
||||
# Excluding versions for which there already are PRs.
|
||||
# This will prevent us from using advanced features of
|
||||
# peter-evans/create-pull-request Github Action
|
||||
# like updating a PR and closing a superseded PR
|
||||
# but we don't really need them as of this writing.
|
||||
versions_to_add = sorted(
|
||||
VersionDirectory.available.keys()
|
||||
- VersionDirectory.existing.keys()
|
||||
- get_pending_versions()
|
||||
)
|
||||
|
||||
logger.info("Versions to add:\n"+pprint.pformat(versions_to_add))
|
||||
result = False
|
||||
for version_to_add in versions_to_add:
|
||||
result = add_version(version_to_add) or result
|
||||
return int(not result)
|
||||
|
||||
|
||||
def get_pending_versions() -> typing.Set[packaging.version.Version]:
|
||||
ls_remote = subprocess.check_output(
|
||||
("git", "-C", OUT_DIR, "ls-remote", "origin",
|
||||
"refs/heads/auto_add_version/*"),
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
pending_versions = set()
|
||||
for line in ls_remote.splitlines():
|
||||
if not (match := AUTO_ADD_VERSION_REF_RE.fullmatch(line)):
|
||||
raise ValueError(f"Unexpected git ls-remote output line: {line!r}")
|
||||
pending_versions.update(
|
||||
packaging.version.Version(version)
|
||||
for version in match.group("versions").split("_")
|
||||
)
|
||||
return pending_versions
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"-d", "--dry-run", action="store_true",
|
||||
help="Do not write scripts, just report them to stdout",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="store_true", default=0,
|
||||
help="Increase verbosity of logging",
|
||||
)
|
||||
parsed = parser.parse_args()
|
||||
return parsed
|
||||
|
||||
|
||||
T = typing.TypeVar('T', bound=object)
|
||||
|
||||
K = typing.TypeVar('K', bound=typing.Hashable)
|
||||
|
||||
class KeyedList(typing.List[T], typing.Mapping[K, T]):
|
||||
key_field: str
|
||||
item_init: typing.Callable[..., T] = None
|
||||
|
||||
def __init__(self, seq: typing.Union[typing.Iterable[T], None] = None):
|
||||
super().__init__()
|
||||
self._map = {}
|
||||
if seq is not None:
|
||||
self.__iadd__(seq)
|
||||
|
||||
# read
|
||||
|
||||
def __getitem__(self, key: K) -> T:
|
||||
return self._map[key]
|
||||
|
||||
def __contains__(self, key: K):
|
||||
return key in self._map
|
||||
|
||||
def keys(self) -> typing.AbstractSet[K]:
|
||||
return self._map.keys()
|
||||
|
||||
# write
|
||||
|
||||
def append(self, item: T) -> None:
|
||||
key = self._getkey(item)
|
||||
if key in self:
|
||||
raise ValueError(f"Key '{key:r}' already present")
|
||||
super().append(item)
|
||||
self._map[key] = item
|
||||
|
||||
def __iadd__(self, other: typing.Iterable[T]):
|
||||
for item in other:
|
||||
self.append(item)
|
||||
return self
|
||||
|
||||
def __delitem__(self, key: K):
|
||||
super().remove(self[key])
|
||||
del self._map[key]
|
||||
|
||||
def clear(self):
|
||||
super().__delitem__(slice(None,None))
|
||||
self._map.clear()
|
||||
|
||||
# read-write
|
||||
|
||||
def get_or_create(self, key: K, **kwargs):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError as e:
|
||||
if self.item_init is None:
|
||||
raise AttributeError("'item_init' must be set to use automatic item creation") from e
|
||||
kwargs[self.key_field] = key
|
||||
item = self.item_init(**kwargs)
|
||||
self.append(item)
|
||||
return item
|
||||
|
||||
# info
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + "([" + ", ".join(repr(i) for i in self) + "])"
|
||||
|
||||
# private
|
||||
|
||||
def _getkey(self, item: T) -> K:
|
||||
return getattr(item, self.key_field)
|
||||
|
||||
del T, K
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class _CPythonAvailableVersionDownloadInfo:
|
||||
extension: str
|
||||
package_name: str
|
||||
url: str
|
||||
|
||||
class _CPythonAvailableVersionDownloadsDirectory(KeyedList[_CPythonAvailableVersionDownloadInfo, str]):
|
||||
key_field = "extension"
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class _CPythonAvailableVersionInfo:
|
||||
version: packaging.version.Version
|
||||
download_page_url: str
|
||||
downloads: _CPythonAvailableVersionDownloadsDirectory = dataclasses.field(
|
||||
default_factory=lambda:_CPythonAvailableVersionDownloadsDirectory()
|
||||
)
|
||||
|
||||
|
||||
class CPythonAvailableVersionsDirectory(KeyedList[_CPythonAvailableVersionInfo, packaging.version.Version]):
|
||||
key_field = "version"
|
||||
_session: requests.Session
|
||||
item_init = _CPythonAvailableVersionInfo
|
||||
|
||||
def __init__(self, session: requests.Session, seq=None):
|
||||
super().__init__(seq)
|
||||
self._session = session
|
||||
|
||||
def populate(self):
|
||||
"""
|
||||
Fetch remote versions
|
||||
"""
|
||||
logger.info("Fetching available CPython versions")
|
||||
for name, url in DownloadPage.enum_download_entries(
|
||||
"https://www.python.org/ftp/python/",
|
||||
r'^(\d+.*)/$', self._session,
|
||||
make_name= lambda m: m.group(1)
|
||||
):
|
||||
v = packaging.version.Version(name)
|
||||
if v < CUTOFF_VERSION or v in EXCLUDED_VERSIONS:
|
||||
continue
|
||||
logger.debug(f'Available version: {name} ({v}), {url}')
|
||||
self.append(_CPythonAvailableVersionInfo(
|
||||
v,
|
||||
url
|
||||
))
|
||||
|
||||
def get_store_available_source_downloads(self, version, refine_mode=False):
|
||||
entry = self[version]
|
||||
if entry.downloads:
|
||||
#already retrieved
|
||||
return
|
||||
additional_versions_found =\
|
||||
CPythonAvailableVersionsDirectory(self._session) if refine_mode else None
|
||||
exact_download_found = False
|
||||
for name, url in DownloadPage.enum_download_entries(
|
||||
entry.download_page_url,
|
||||
r'Python-.*\.(tar\.xz|tgz)$',
|
||||
self._session):
|
||||
m = re.match(r'(?P<package>Python-(?P<version>.*))\.(?P<extension>tar\.xz|tgz)$', name)
|
||||
|
||||
download_version = packaging.version.Version(m.group("version"))
|
||||
if download_version != version:
|
||||
if not refine_mode:
|
||||
logger.warning(f"Ignoring download {name} ({download_version}) "
|
||||
f"for {version} at page {entry.download_page_url}")
|
||||
continue
|
||||
entry_to_fill = additional_versions_found.get_or_create(
|
||||
download_version,
|
||||
download_page_url=entry.download_page_url
|
||||
)
|
||||
else:
|
||||
exact_download_found = True
|
||||
entry_to_fill = entry
|
||||
|
||||
entry_to_fill.downloads.append(_CPythonAvailableVersionDownloadInfo(
|
||||
m.group("extension"), m.group('package'), url
|
||||
))
|
||||
|
||||
# XXX: Exact download not found in non-refine mode never happens now
|
||||
# 'cuz we first call the function in refine mode.
|
||||
# Decide what's best to do if it starts to after a logic change.
|
||||
if not exact_download_found and refine_mode:
|
||||
actual_version = max(additional_versions_found.keys())
|
||||
logger.debug(f"Refining available version {version} to {actual_version}")
|
||||
del self[version]
|
||||
|
||||
self.append(
|
||||
additional_versions_found[
|
||||
actual_version
|
||||
])
|
||||
|
||||
|
||||
class _CPythonExistingScriptInfo(typing.NamedTuple):
|
||||
version: packaging.version.Version
|
||||
filename: str
|
||||
|
||||
class CPythonExistingScriptsDirectory(KeyedList[_CPythonExistingScriptInfo, packaging.version.Version]):
|
||||
key_field = "version"
|
||||
_filename_pattern = r'^\d+\.\d+(?:(t?)(-\w+)|(.\d+((?:a|b|rc)\d)?(t?)))$'
|
||||
|
||||
def populate(self):
|
||||
"""
|
||||
Enumerate existing installation scripts in share/python-build/ by pattern
|
||||
"""
|
||||
logger.info(f"Enumerating existing versions in {OUT_DIR}")
|
||||
for entry_name in (p.name for p in OUT_DIR.iterdir() if p.is_file()):
|
||||
if (not (m := re.match(self._filename_pattern, entry_name))
|
||||
or m.group(1) == 't' or m.group(5) == 't'):
|
||||
continue
|
||||
try:
|
||||
v = packaging.version.Version(entry_name)
|
||||
if v < CUTOFF_VERSION:
|
||||
continue
|
||||
# branch tip scripts are different from release scripts and thus unusable as a pattern
|
||||
if v.dev is not None:
|
||||
continue
|
||||
logger.debug(f"Existing version {v}")
|
||||
|
||||
self.append(_CPythonExistingScriptInfo(v, entry_name))
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"Unable to parse existing version {entry_name}: {e}")
|
||||
|
||||
def pick_previous_version(self,
|
||||
version: packaging.version.Version) \
|
||||
-> _CPythonExistingScriptInfo:
|
||||
return max(v for v in self if v.version < version)
|
||||
|
||||
|
||||
class _OpenSSLVersionInfo(typing.NamedTuple):
|
||||
version: packaging.version.Version
|
||||
package_name: str
|
||||
url: str
|
||||
hash: str
|
||||
|
||||
class OpenSSLVersionsDirectory(KeyedList[_OpenSSLVersionInfo, packaging.version.Version]):
|
||||
key_field = "version"
|
||||
|
||||
def get_store_latest_release(self, major: int) \
|
||||
-> _OpenSSLVersionInfo:
|
||||
matching = [
|
||||
release for release in self
|
||||
if release.version.major == major
|
||||
]
|
||||
if matching:
|
||||
return max(matching, key=lambda release: release.version)
|
||||
|
||||
url = "https://api.github.com/repos/openssl/openssl/releases"
|
||||
while url:
|
||||
response = Requests.get(url)
|
||||
matching = [
|
||||
release
|
||||
for release in response.json()
|
||||
if not release['draft']
|
||||
and not release['prerelease']
|
||||
and (match := OPENSSL_RELEASE_TAG_RE.fullmatch(
|
||||
release['tag_name']
|
||||
))
|
||||
and int(match.group('major')) == major
|
||||
]
|
||||
if matching:
|
||||
j_release = matching[0]
|
||||
break
|
||||
url = response.links.get('next', {}).get('url')
|
||||
else:
|
||||
raise ValueError(f"No OpenSSL {major}.x release found")
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
# urlparse can parse str as well as bytes
|
||||
shasum_url = more_itertools.one(
|
||||
asset['browser_download_url']
|
||||
for asset in j_release['assets']
|
||||
if urllib.parse.urlparse(asset['browser_download_url']).path.split('/')[-1].endswith('.sha256')
|
||||
)
|
||||
shasum_text = Requests.get(shasum_url).text
|
||||
shasum_data = jc.parse("hashsum", shasum_text, quiet=True)[0]
|
||||
package_hash, package_filename = shasum_data["hash"], shasum_data["filename"]
|
||||
del shasum_data, shasum_text, shasum_url
|
||||
|
||||
# OpenSSL Github repo has tag names "openssl-<version>" as of this writing like we need
|
||||
# but let's not rely on that
|
||||
# splitext doesn't work with a chained extension, it only splits off the last one
|
||||
package_name, package_version_str = re.match(r"([^-]+-(.*?))\.\D", package_filename).groups()
|
||||
package_version = packaging.version.Version(package_version_str)
|
||||
|
||||
package_url = more_itertools.one(
|
||||
asset['browser_download_url']
|
||||
for asset in j_release['assets']
|
||||
if urllib.parse.urlparse(asset['browser_download_url']).path.split('/')[-1] == package_filename
|
||||
)
|
||||
|
||||
result = _OpenSSLVersionInfo(package_version, package_name, package_url, package_hash)
|
||||
self.append(result)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class _ReadlineVersionInfo(typing.NamedTuple):
|
||||
version : packaging.version.Version
|
||||
package_name : str
|
||||
url : str
|
||||
hash : str
|
||||
|
||||
class ReadlineVersionsDirectory(KeyedList[_ReadlineVersionInfo, packaging.version.Version]):
|
||||
key_field = "version"
|
||||
|
||||
def get_store_latest_release(self):
|
||||
if not self:
|
||||
self._store_latest_release()
|
||||
return self._latest_release()
|
||||
|
||||
def _store_latest_release(self):
|
||||
candidates = ReadlineVersionsDirectory()
|
||||
|
||||
pattern = r'(?P<package_name>readline-(?P<version>\d+(?:\.\d+)+)).tar\.gz$'
|
||||
for name, url in DownloadPage.enum_download_entries(
|
||||
'https://ftpmirror.gnu.org/readline/', pattern, VersionDirectory.session):
|
||||
m = re.match(pattern, name)
|
||||
version = packaging.version.Version(m.group('version'))
|
||||
candidates.append(_ReadlineVersionInfo(
|
||||
version,
|
||||
m.group('package_name'),
|
||||
url,
|
||||
""
|
||||
))
|
||||
max_item = candidates._latest_release()
|
||||
hash_ = Url.sha256_url(max_item.url, VersionDirectory.session)
|
||||
|
||||
permalink = 'https://ftpmirror.gnu.org/readline/' +\
|
||||
os.path.basename(urllib.parse.urlparse(max_item.url).path)
|
||||
|
||||
result = _ReadlineVersionInfo(
|
||||
max_item.version,
|
||||
max_item.package_name,
|
||||
permalink,
|
||||
hash_)
|
||||
self.append(result)
|
||||
|
||||
return result
|
||||
|
||||
def _latest_release(self):
|
||||
return self[max(self.keys())]
|
||||
|
||||
class _VersionDirectory:
|
||||
def __init__(self, session):
|
||||
self.existing = CPythonExistingScriptsDirectory()
|
||||
self.available = CPythonAvailableVersionsDirectory(session)
|
||||
self.openssl = OpenSSLVersionsDirectory()
|
||||
self.readline = ReadlineVersionsDirectory()
|
||||
self.session = session
|
||||
VersionDirectory : _VersionDirectory
|
||||
|
||||
class DownloadPage:
|
||||
class _DownloadPageEntry(typing.NamedTuple):
|
||||
name: str
|
||||
url: str
|
||||
|
||||
@classmethod
|
||||
def enum_download_entries(cls, url, pattern, session=None,
|
||||
make_name = lambda m: m.string ) \
|
||||
-> typing.Generator[_DownloadPageEntry, None, None]:
|
||||
"""
|
||||
Enum download entries in a standard Apache directory page
|
||||
(incl. CPython download page https://www.python.org/ftp/python/)
|
||||
or a GNU mirror directory page
|
||||
(https://ftpmirror.gnu.org/<package>/ destinations)
|
||||
"""
|
||||
if session is None:
|
||||
session = requests_html.HTMLSession()
|
||||
response = session.get(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
page = response.html
|
||||
table = page.find("pre", first=True)
|
||||
# some GNU mirrors format entries as a table
|
||||
# (e.g. https://mirrors.ibiblio.org/gnu/readline/)
|
||||
if table is None:
|
||||
table = page.find("table", first=True)
|
||||
links = table.find("a")
|
||||
for link in links:
|
||||
href = link.attrs['href']
|
||||
# CPython entries are directories
|
||||
name = link.text
|
||||
# skip directory entries
|
||||
if not (m:=re.match(pattern, name)):
|
||||
continue
|
||||
name = make_name(m)
|
||||
yield cls._DownloadPageEntry(name, urllib.parse.urljoin(response.url, href))
|
||||
|
||||
|
||||
class Re:
|
||||
@dataclasses.dataclass
|
||||
class _interval:
|
||||
group: typing.Union[int, str, None]
|
||||
start: int
|
||||
end: int
|
||||
@staticmethod
|
||||
def sub_groups(match: re.Match,
|
||||
/, *args: [typing.AnyStr],
|
||||
**kwargs: [typing.AnyStr])\
|
||||
-> typing.AnyStr:
|
||||
repls={i:repl for i,repl in enumerate(args) if repl is not None}
|
||||
repls.update({n:repl for n,repl in kwargs.items() if repl is not None})
|
||||
|
||||
intervals: sortedcontainers.SortedList[Re._interval]=\
|
||||
sortedcontainers.SortedKeyList(key=operator.attrgetter("start","end"))
|
||||
|
||||
for group_id in itertools.chain(range(1,len(match.groups())), match.groupdict().keys()):
|
||||
if group_id not in repls:
|
||||
continue
|
||||
if match.start(group_id) == -1:
|
||||
continue
|
||||
intervals.add(Re._interval(group_id,match.start(group_id),match.end(group_id)))
|
||||
del group_id
|
||||
|
||||
last_interval=Re._interval(None,0,0)
|
||||
result=""
|
||||
for interval in intervals:
|
||||
if interval.start < last_interval.end:
|
||||
raise ValueError(f"Cannot replace intersecting matches "
|
||||
f"for groups {last_interval.group} and {interval.group} "
|
||||
f"(position {interval.start})")
|
||||
if interval.end == interval.start and \
|
||||
last_interval.start == last_interval.end == interval.start:
|
||||
raise ValueError(f"Cannot replace consecutive zero-length matches "
|
||||
f"for groups {last_interval.group} and {interval.group} "
|
||||
f"(position {interval.start})")
|
||||
|
||||
result+=match.string[last_interval.end:interval.start]+repls[interval.group]
|
||||
last_interval = interval
|
||||
result+=match.string[last_interval.end:]
|
||||
|
||||
return result
|
||||
|
||||
class Url:
|
||||
@staticmethod
|
||||
def sha256_url(url, session=None):
|
||||
if session is None:
|
||||
session = requests_html.HTMLSession()
|
||||
logger.info(f"Downloading and computing hash of {url}")
|
||||
h=hashlib.sha256()
|
||||
r=session.get(url,stream=True,timeout=30)
|
||||
total_bytes=int(r.headers.get('content-length',0)) or float('inf')
|
||||
with tqdm.tqdm(total=total_bytes, unit='B', unit_scale=True, unit_divisor=1024) as t:
|
||||
for c in r.iter_content(1024):
|
||||
t.update(len(c))
|
||||
h.update(c)
|
||||
return h.hexdigest()
|
||||
|
||||
class Requests:
|
||||
@staticmethod
|
||||
def get(url: str) -> requests.Response:
|
||||
response = requests.get(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#sys.excepthook seems to have no effect in Github Actions
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception:
|
||||
logging.exception("Unhandled exception occurred")
|
||||
sys.exit(2)
|
||||
|
|
@ -1,440 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Script to add non-"latest" miniconda releases.
|
||||
Written for python 3.7.
|
||||
|
||||
Checks the miniconda download archives for new versions,
|
||||
then writes a build script for any which do not exist locally,
|
||||
saving it to plugins/python-build/share/python-build.
|
||||
|
||||
Ignores releases below 4.3.30.
|
||||
Also ignores sub-patch releases if that major.minor.patch already exists,
|
||||
but otherwise, takes the latest sub-patch release for given OS/arch.
|
||||
Assumes all miniconda3 releases < 4.7 default to python 3.6, and anything else 3.7.
|
||||
"""
|
||||
import logging
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
import textwrap
|
||||
from argparse import ArgumentParser
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from functools import total_ordering
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple, List, Optional, DefaultDict, Dict
|
||||
|
||||
import requests_html
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CONDA_REPO = "https://repo.anaconda.com"
|
||||
MINICONDA_REPO = CONDA_REPO + "/miniconda"
|
||||
ANACONDA_REPO = CONDA_REPO + "/archive"
|
||||
|
||||
auto_accept_tos_fmt="""export CONDA_PLUGINS_AUTO_ACCEPT_TOS=true
|
||||
""".strip()
|
||||
|
||||
install_script_fmt = """
|
||||
{auto_accept_tos}
|
||||
case "$(anaconda_architecture 2>/dev/null || true)" in
|
||||
{install_lines}
|
||||
* )
|
||||
{{ echo
|
||||
colorize 1 "ERROR"
|
||||
echo ": The binary distribution of {tflavor} is not available for $(anaconda_architecture 2>/dev/null || true)."
|
||||
echo
|
||||
}} >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
""".lstrip()
|
||||
|
||||
install_line_fmt = """
|
||||
"{os}-{arch}" )
|
||||
install_script "{tflavor}{suffix}-{version_py_version}{version_str}-{os}-{arch}" "{repo}/{tflavor}{suffix}-{version_py_version}{version_str}-{os}-{arch}.sh#{md5}" "{flavor}" verify_{py_version}
|
||||
;;
|
||||
""".strip()
|
||||
|
||||
here = Path(__file__).resolve()
|
||||
out_dir: Path = here.parent.parent / "share" / "python-build"
|
||||
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
"""Enum subclass whose members are also instances of str
|
||||
and directly comparable to strings. str type is forced at declaration.
|
||||
|
||||
Adapted from https://github.com/kissgyorgy/enum34-custom/blob/dbc89596761c970398701d26c6a5bbcfcf70f548/enum_custom.py#L100
|
||||
(MIT license)
|
||||
"""
|
||||
|
||||
def __new__(cls, *args):
|
||||
for arg in args:
|
||||
if not isinstance(arg, str):
|
||||
raise TypeError("Not text %s:" % arg)
|
||||
|
||||
return super(StrEnum, cls).__new__(cls, *args)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
|
||||
class SupportedOS(StrEnum):
|
||||
LINUX = "Linux"
|
||||
MACOSX = "MacOSX"
|
||||
|
||||
|
||||
class SupportedArch(StrEnum):
|
||||
AARCH64 = "aarch64"
|
||||
ARM64 = "arm64"
|
||||
PPC64LE = "ppc64le"
|
||||
S390X = "s390x"
|
||||
X86_64 = "x86_64"
|
||||
X86 = "x86"
|
||||
|
||||
|
||||
class Flavor(StrEnum):
|
||||
ANACONDA = "anaconda"
|
||||
MINICONDA = "miniconda"
|
||||
|
||||
|
||||
class TFlavor(StrEnum):
|
||||
ANACONDA = "Anaconda"
|
||||
MINICONDA = "Miniconda"
|
||||
|
||||
|
||||
class Suffix(StrEnum):
|
||||
TWO = "2"
|
||||
THREE = "3"
|
||||
NONE = ""
|
||||
|
||||
|
||||
PyVersion = None
|
||||
class PyVersionMeta(type):
|
||||
def __getattr__(self, name):
|
||||
"""Generate PyVersion.PYXXX on demand to future-proof it"""
|
||||
if PyVersion is not None:
|
||||
return PyVersion(name.lower())
|
||||
return super(PyVersionMeta,self).__getattr__(self, name)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PyVersion(metaclass=PyVersionMeta):
|
||||
major: str
|
||||
minor: str
|
||||
|
||||
def __init__(self, value):
|
||||
(major, minor) = re.match(r"py(\d)(\d+)", value).groups()
|
||||
object.__setattr__(self, "major", major)
|
||||
object.__setattr__(self, "minor", minor)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return f"py{self.major}{self.minor}"
|
||||
|
||||
def version(self):
|
||||
return f"{self.major}.{self.minor}"
|
||||
|
||||
def version_info(self):
|
||||
return (self.major, self.minor)
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
@total_ordering
|
||||
class VersionStr(str):
|
||||
def info(self):
|
||||
return tuple(int(n) for n in self.replace("-", ".").split("."))
|
||||
|
||||
def __eq__(self, other):
|
||||
return str(self) == str(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, VersionStr):
|
||||
return self.info() < other.info()
|
||||
raise ValueError("VersionStr can only be compared to other VersionStr")
|
||||
|
||||
@classmethod
|
||||
def from_info(cls, version_info):
|
||||
return VersionStr(".".join(str(n) for n in version_info))
|
||||
|
||||
def __hash__(self):
|
||||
return hash(str(self))
|
||||
|
||||
|
||||
class CondaVersion(NamedTuple):
|
||||
flavor: Flavor
|
||||
suffix: Suffix
|
||||
version_str: VersionStr
|
||||
py_version: Optional[PyVersion]
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, s):
|
||||
"""
|
||||
Convert a string of the form "miniconda_n-ver" or "miniconda_n-py_ver-ver" to a :class:`CondaVersion` object.
|
||||
"""
|
||||
miniconda_n, _, remainder = s.partition("-")
|
||||
suffix = miniconda_n[-1]
|
||||
if suffix in string.digits:
|
||||
flavor = miniconda_n[:-1]
|
||||
else:
|
||||
flavor = miniconda_n
|
||||
suffix = ""
|
||||
|
||||
components = remainder.split("-")
|
||||
if flavor == Flavor.MINICONDA and len(components) >= 2:
|
||||
py_ver, *ver_parts = components
|
||||
py_ver = PyVersion(f"py{py_ver.replace('.', '')}")
|
||||
ver = "-".join(ver_parts)
|
||||
else:
|
||||
ver = "-".join(components)
|
||||
py_ver = None
|
||||
|
||||
return CondaVersion(Flavor(flavor), Suffix(suffix), VersionStr(ver), py_ver)
|
||||
|
||||
def to_filename(self):
|
||||
if self.py_version:
|
||||
return f"{self.flavor}{self.suffix}-{self.py_version.version()}-{self.version_str}"
|
||||
else:
|
||||
return f"{self.flavor}{self.suffix}-{self.version_str}"
|
||||
|
||||
def default_py_version(self):
|
||||
"""
|
||||
:class:`PyVersion` of Python used with this Miniconda version
|
||||
"""
|
||||
if self.py_version:
|
||||
return self.py_version
|
||||
elif self.suffix == Suffix.TWO:
|
||||
return PyVersion.PY27
|
||||
|
||||
v = self.version_str.info()
|
||||
if self.flavor == "miniconda":
|
||||
# https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-python.html
|
||||
if v < (4, 7):
|
||||
return PyVersion.PY36
|
||||
if v < (4, 8):
|
||||
return PyVersion.PY37
|
||||
else:
|
||||
# since 4.8, Miniconda specifies versions explicitly in the file name
|
||||
raise ValueError("Miniconda 4.8+ is supposed to specify a Python version explicitly")
|
||||
if self.flavor == "anaconda":
|
||||
# Info about the bundled Python version
|
||||
# https://www.anaconda.com/docs/getting-started/anaconda/release-notes
|
||||
if v >= (2026,7):
|
||||
return PyVersion.PY314
|
||||
if v >= (2025,6):
|
||||
return PyVersion.PY313
|
||||
if v >= (2024,6):
|
||||
return PyVersion.PY312
|
||||
if v >= (2023,7):
|
||||
return PyVersion.PY311
|
||||
if v >= (2023,3):
|
||||
return PyVersion.PY310
|
||||
if v >= (2021,11):
|
||||
return PyVersion.PY39
|
||||
if v >= (2020,7):
|
||||
return PyVersion.PY38
|
||||
if v >= (2020,2):
|
||||
return PyVersion.PY37
|
||||
if v >= (5,3,0):
|
||||
return PyVersion.PY37
|
||||
return PyVersion.PY36
|
||||
|
||||
raise ValueError(self.flavor)
|
||||
|
||||
def requires_tos_accept(self):
|
||||
"""
|
||||
requires to accept TOS for installation
|
||||
"""
|
||||
return self.flavor == Flavor.MINICONDA \
|
||||
and self.version_str.info() >= (25,)
|
||||
|
||||
|
||||
class CondaSpec(NamedTuple):
|
||||
tflavor: TFlavor
|
||||
version: CondaVersion
|
||||
os: SupportedOS
|
||||
arch: SupportedArch
|
||||
md5: str
|
||||
repo: str
|
||||
py_version: Optional[PyVersion] = None
|
||||
|
||||
@classmethod
|
||||
def from_filestem(cls, stem, md5, repo, py_version=None):
|
||||
# The `*vers` captures the new trailing `-1` in some file names (a build number?)
|
||||
# so they can be processed properly.
|
||||
miniconda_n, *vers, os, arch = stem.split("-")
|
||||
ver = "-".join(vers)
|
||||
suffix = miniconda_n[-1]
|
||||
if suffix in string.digits:
|
||||
tflavor = miniconda_n[:-1]
|
||||
else:
|
||||
tflavor = miniconda_n
|
||||
suffix = ""
|
||||
flavor = tflavor.lower()
|
||||
|
||||
if ver.startswith("py"):
|
||||
py_ver, ver = ver.split("_", maxsplit=1)
|
||||
py_ver = PyVersion(py_ver)
|
||||
else:
|
||||
py_ver = None
|
||||
spec = CondaSpec(
|
||||
TFlavor(tflavor),
|
||||
CondaVersion(Flavor(flavor), Suffix(suffix), VersionStr(ver), py_ver),
|
||||
SupportedOS(os),
|
||||
SupportedArch(arch),
|
||||
md5,
|
||||
repo,
|
||||
py_ver
|
||||
)
|
||||
if py_version is None and py_ver is None and ver != "latest":
|
||||
spec = spec.with_py_version(spec.version.default_py_version())
|
||||
return spec
|
||||
|
||||
def to_install_lines(self):
|
||||
"""
|
||||
Installation command for this version of Miniconda for use in a Pyenv installation script
|
||||
"""
|
||||
return install_line_fmt.format(
|
||||
tflavor=self.tflavor,
|
||||
flavor=self.version.flavor,
|
||||
repo=self.repo,
|
||||
suffix=self.version.suffix,
|
||||
version_str=self.version.version_str,
|
||||
version_py_version=f"{self.version.py_version}_" if self.version.py_version else "",
|
||||
os=self.os,
|
||||
arch=self.arch,
|
||||
md5=self.md5,
|
||||
py_version=self.py_version,
|
||||
)
|
||||
|
||||
def with_py_version(self, py_version: PyVersion):
|
||||
return CondaSpec(*self[:-1], py_version=py_version)
|
||||
|
||||
|
||||
def make_script(specs: List[CondaSpec]):
|
||||
install_lines = [s.to_install_lines() for s in specs]
|
||||
return install_script_fmt.format(
|
||||
install_lines="\n".join(install_lines),
|
||||
tflavor=specs[0].tflavor,
|
||||
auto_accept_tos = auto_accept_tos_fmt if specs[0].version.requires_tos_accept() else ""
|
||||
).lstrip()
|
||||
|
||||
|
||||
def get_existing_condas(name):
|
||||
"""
|
||||
Enumerate existing Miniconda installation scripts in share/python-build/ except rolling releases.
|
||||
|
||||
:returns: A generator of :class:`CondaVersion` objects.
|
||||
"""
|
||||
logger.info("Getting known %(name)s versions",locals())
|
||||
for p in out_dir.iterdir():
|
||||
entry_name = p.name
|
||||
if not p.is_file() or not entry_name.startswith(name):
|
||||
continue
|
||||
try:
|
||||
v = CondaVersion.from_str(entry_name)
|
||||
if v.version_str != "latest":
|
||||
logger.debug("Found existing %(name)s version %(v)s", locals())
|
||||
yield v
|
||||
except ValueError as e:
|
||||
logger.error("Unable to parse existing version %s: %s", entry_name, e)
|
||||
|
||||
|
||||
def get_available_condas(name, repo):
|
||||
"""
|
||||
Fetch remote miniconda versions.
|
||||
|
||||
:returns: A generator of :class:`CondaSpec` objects for each release available for download
|
||||
except rolling releases.
|
||||
"""
|
||||
logger.info("Fetching remote %(name)s versions",locals())
|
||||
session = requests_html.HTMLSession()
|
||||
response = session.get(repo)
|
||||
page: requests_html.HTML = response.html
|
||||
table = page.find("table", first=True)
|
||||
rows = table.find("tr")[1:]
|
||||
for row in rows:
|
||||
f, size, date, md5 = row.find("td")
|
||||
fname = f.text
|
||||
md5 = md5.text
|
||||
|
||||
if not fname.endswith(".sh"):
|
||||
continue
|
||||
stem = fname[:-3]
|
||||
|
||||
try:
|
||||
s = CondaSpec.from_filestem(stem, md5, repo)
|
||||
if s.version.version_str != "latest":
|
||||
logger.debug("Found remote %(name)s version %(s)s", locals())
|
||||
yield s
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def key_fn(spec: CondaSpec):
|
||||
return (
|
||||
spec.tflavor,
|
||||
spec.version.version_str.info(),
|
||||
spec.version.suffix.value,
|
||||
spec.os.value,
|
||||
spec.arch.value,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"-d", "--dry-run", action="store_true",
|
||||
help="Do not write scripts, just report them to stdout",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="store_true", default=0,
|
||||
help="Increase verbosity of logging",
|
||||
)
|
||||
parsed = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG if parsed.verbose else logging.INFO)
|
||||
|
||||
existing_versions = set()
|
||||
available_specs = set()
|
||||
for name,repo in ("miniconda",MINICONDA_REPO),("anaconda",ANACONDA_REPO):
|
||||
existing_versions |= set(get_existing_condas(name))
|
||||
available_specs |= set(get_available_condas(name, repo))
|
||||
|
||||
# version triple to triple-ified spec to raw spec
|
||||
to_add: DefaultDict[
|
||||
CondaVersion, Dict[CondaSpec, CondaSpec]
|
||||
] = defaultdict(dict)
|
||||
|
||||
logger.info("Checking for new versions")
|
||||
for s in sorted(available_specs, key=key_fn):
|
||||
key = s.version
|
||||
vv = key.version_str.info()
|
||||
|
||||
reason = None
|
||||
if key in existing_versions:
|
||||
reason = "already exists"
|
||||
elif key.version_str.info() <= (4, 3, 30):
|
||||
reason = "too old"
|
||||
elif len(key.version_str.info()) >= 4 and "-" not in key.version_str:
|
||||
reason = "ignoring hotfix releases"
|
||||
|
||||
if reason:
|
||||
logger.debug("Ignoring version %(s)s (%(reason)s)", locals())
|
||||
continue
|
||||
|
||||
to_add[key][s] = s
|
||||
|
||||
logger.info("Writing %s scripts", len(to_add))
|
||||
for ver, d in to_add.items():
|
||||
specs = list(d.values())
|
||||
fpath = out_dir / ver.to_filename()
|
||||
script_str = make_script(specs)
|
||||
logger.info("Writing script for %s", ver)
|
||||
if parsed.dry_run:
|
||||
print(f"Would write spec to {fpath}:\n" + textwrap.indent(script_str, " "))
|
||||
else:
|
||||
with open(fpath, "w") as f:
|
||||
f.write(script_str)
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
'Adds the latest miniforge and mambaforge releases.'
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import string
|
||||
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
MINIFORGE_REPO = 'conda-forge/miniforge'
|
||||
DISTRIBUTIONS = ['miniforge']
|
||||
DISTRIBUTIONS_PRE25 = ['miniforge', 'mambaforge']
|
||||
|
||||
SKIPPED_RELEASES = [
|
||||
'4.13.0-0', #has no Mambaforge. We already generated scripts for Miniforge
|
||||
'22.11.1-0', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403)
|
||||
'22.11.1-1', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403)
|
||||
'22.11.1-2', #MacOS packages are broken (have broken dep tarballs, downloading them fails with 403)
|
||||
'25.3.0-0', #marked as prerelease, no Linux version
|
||||
'25.11.0-0', #regression reported in constructor, re-released as 25.11.0-1 with hotfix bumping to constructor>=3.14 (was >=3.12, 3.13 implicit)
|
||||
'26.1.1-0', #prerelease, no binary assets
|
||||
'26.1.1-1', #prerelease, no binary assets
|
||||
]
|
||||
|
||||
install_script_fmt = """
|
||||
case "$(anaconda_architecture 2>/dev/null || true)" in
|
||||
{install_lines}
|
||||
* )
|
||||
{{ echo
|
||||
colorize 1 "ERROR"
|
||||
echo ": The binary distribution of {flavor} is not available for $(anaconda_architecture 2>/dev/null || true)."
|
||||
echo
|
||||
}} >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
""".lstrip()
|
||||
|
||||
install_line_fmt = """
|
||||
"{os}-{arch}" )
|
||||
install_script "{filename}" "{url}#{sha}" "miniconda" verify_py{py_version}
|
||||
;;
|
||||
""".strip()
|
||||
|
||||
here = Path(__file__).resolve()
|
||||
out_dir: Path = here.parent.parent / "share" / "python-build"
|
||||
|
||||
def download_sha(url):
|
||||
logger.info(f'Downloading SHA file {url}')
|
||||
tup = tuple(reversed(requests.get(url).text.replace('./', '').rstrip().split()))
|
||||
logger.debug(f'Got {tup}')
|
||||
return tup
|
||||
|
||||
def create_spec(filename, sha, url):
|
||||
flavor_with_suffix, version, subversion, os, arch = filename.replace('.sh', '').split('-')
|
||||
suffix = flavor_with_suffix[-1]
|
||||
|
||||
if suffix in string.digits:
|
||||
flavor = flavor_with_suffix[:-1]
|
||||
else:
|
||||
flavor = flavor_with_suffix
|
||||
|
||||
spec = {
|
||||
'filename': filename,
|
||||
'sha': sha,
|
||||
'url': url,
|
||||
'py_version': py_version(version),
|
||||
'flavor': flavor,
|
||||
'os': os,
|
||||
'arch': arch,
|
||||
'installer_filename': f'{flavor_with_suffix.lower()}-{version}-{subversion}',
|
||||
}
|
||||
|
||||
logger.debug(f'Created spec {spec}')
|
||||
|
||||
return spec
|
||||
|
||||
def version_tuple(version):
|
||||
return tuple(int(part) for part in version.split('-')[0].split("."))
|
||||
|
||||
def py_version(version):
|
||||
"""Suffix for `verify_pyXXX` to call in the generated build script"""
|
||||
version_tuple_ = version_tuple(version)
|
||||
# current version: mentioned under https://github.com/conda-forge/miniforge?tab=readme-ov-file#requirements-and-installers
|
||||
# transition points:
|
||||
# https://github.com/conda-forge/miniforge/blame/main/Miniforge3/construct.yaml
|
||||
# look for "- python <version>" in non-pypy branch and which tag the commit is first in
|
||||
if version_tuple_ >= (26,1):
|
||||
# https://github.com/conda-forge/miniforge/commit/0016367731e52c67234d6d0e7e6a24c6bf7673e4
|
||||
return "313"
|
||||
if version_tuple_ >= (24,5):
|
||||
# yes, they jumped from 3.10 directly to 3.12
|
||||
# https://github.com/conda-forge/miniforge/commit/bddad0baf22b37cfe079e47fd1680fdfb2183590
|
||||
return "312"
|
||||
if version_tuple_ >= (4,14):
|
||||
return "310"
|
||||
raise ValueError("Bundled Python version unknown for release `%s'"%version)
|
||||
|
||||
def supported(filename):
|
||||
return ('pypy' not in filename) and ('Windows' not in filename) and (not filename.endswith('.pkg'))
|
||||
|
||||
def add_version(release, distributions):
|
||||
tag_name = release['tag_name']
|
||||
download_urls = { f['name']: f['browser_download_url'] for f in release['assets'] }
|
||||
# can assume that sha files are named similar to release files so can also check supported(on their names)
|
||||
shas = dict([download_sha(url) for (name, url) in download_urls.items()
|
||||
if name.endswith('.sha256') and supported(os.path.splitext(name)[0]) and tag_name in name])
|
||||
specs = [create_spec(filename, sha, download_urls[filename]) for (filename, sha) in shas.items() if supported(filename)]
|
||||
|
||||
|
||||
for distribution in distributions:
|
||||
distribution_specs = [spec for spec in specs if distribution in spec['flavor'].lower()]
|
||||
count = len(distribution_specs)
|
||||
|
||||
if count > 0:
|
||||
output_file = out_dir / distribution_specs[0]['installer_filename']
|
||||
|
||||
logger.info(f'Writing {count} specs for {distribution} to {output_file}')
|
||||
|
||||
script_str = install_script_fmt.format(
|
||||
install_lines="\n".join([install_line_fmt.format_map(s) for s in distribution_specs]),
|
||||
flavor=distribution_specs[0]['flavor'],
|
||||
)
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(script_str)
|
||||
else:
|
||||
logger.info(f'Did not find specs for {distribution}')
|
||||
|
||||
|
||||
def main():
|
||||
for release in requests.get(f'https://api.github.com/repos/{MINIFORGE_REPO}/releases').json():
|
||||
version = release['tag_name']
|
||||
|
||||
if version in SKIPPED_RELEASES:
|
||||
continue
|
||||
|
||||
logger.debug(f'Looking for {version} in {out_dir}')
|
||||
|
||||
# mambaforge is retired https://github.com/conda-forge/miniforge/releases/tag/24.11.2-0
|
||||
if version_tuple(version) >= (24, 11, 2):
|
||||
distributions = DISTRIBUTIONS
|
||||
else:
|
||||
distributions = DISTRIBUTIONS_PRE25
|
||||
|
||||
if any(not list(out_dir.glob(f'{distribution}*-{version}')) for distribution in distributions):
|
||||
logger.info(f'Downloading {version}')
|
||||
add_version(release, distributions)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"-d", "--dry-run", action="store_true",
|
||||
help="Do not write scripts, just report them to stdout",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="store_true", default=0,
|
||||
help="Increase verbosity of logging",
|
||||
)
|
||||
parsed = parser.parse_args()
|
||||
|
||||
if parsed.verbose: logging.getLogger.setLevel(logging.DEBUG)
|
||||
|
||||
main()
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
more_itertools
|
||||
requests-html
|
||||
fake_useragent<2; python_version < "3.9"
|
||||
lxml[html_clean]
|
||||
packaging
|
||||
requests
|
||||
sortedcontainers
|
||||
tqdm
|
||||
jc
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-6.2" "https://ftpmirror.gnu.org/readline/readline-6.2.tar.gz#79a696070a058c233c72dd6ac697021cc64abd5ed51e59db867d66d196a89381" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.1.3" "https://www.python.org/ftp/python/2.1.3/Python-2.1.3.tgz#1bcb5bb587948bc38f36db60e15c376009c56c66570e563a08a82bf7f227afb9" standard verify_py21
|
||||
install_package "readline-6.2" "https://ftpmirror.gnu.org/readline/readline-6.2.tar.gz#79a696070a058c233c72dd6ac697021cc64abd5ed51e59db867d66d196a89381" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.1.3" "https://www.python.org/ftp/python/2.1.3/Python-2.1.3.tgz#1bcb5bb587948bc38f36db60e15c376009c56c66570e563a08a82bf7f227afb9" ldflags_dirs standard verify_py21
|
||||
#install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
#install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-6.2" "https://ftpmirror.gnu.org/readline/readline-6.2.tar.gz#79a696070a058c233c72dd6ac697021cc64abd5ed51e59db867d66d196a89381" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.2.3" "https://www.python.org/ftp/python/2.2.3/Python-2.2.3.tgz#a8f92e6b89d47359fff0d1fbfe47f104afc77fd1cd5143e7332758b7bc100188" standard verify_py22
|
||||
install_package "readline-6.2" "https://ftpmirror.gnu.org/readline/readline-6.2.tar.gz#79a696070a058c233c72dd6ac697021cc64abd5ed51e59db867d66d196a89381" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.2.3" "https://www.python.org/ftp/python/2.2.3/Python-2.2.3.tgz#a8f92e6b89d47359fff0d1fbfe47f104afc77fd1cd5143e7332758b7bc100188" ldflags_dirs standard verify_py22
|
||||
#install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
#install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-6.2" "https://ftpmirror.gnu.org/readline/readline-6.2.tar.gz#79a696070a058c233c72dd6ac697021cc64abd5ed51e59db867d66d196a89381" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.3.7" "https://www.python.org/ftp/python/2.3.7/Python-2.3.7.tgz#969a9891dce9f50b13e54f9890acaf2be66715a5895bf9b11111f320c205b90e" standard verify_py23
|
||||
install_package "readline-6.2" "https://ftpmirror.gnu.org/readline/readline-6.2.tar.gz#79a696070a058c233c72dd6ac697021cc64abd5ed51e59db867d66d196a89381" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.3.7" "https://www.python.org/ftp/python/2.3.7/Python-2.3.7.tgz#969a9891dce9f50b13e54f9890acaf2be66715a5895bf9b11111f320c205b90e" ldflags_dirs standard verify_py23
|
||||
#install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
#install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.4" "https://www.python.org/ftp/python/2.4/Python-2.4.tgz#ff746de0fae8691c082414b42a2bb172da8797e6e8ff66c9a39d2e452f7034e9" standard verify_py24
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.4" "https://www.python.org/ftp/python/2.4/Python-2.4.tgz#ff746de0fae8691c082414b42a2bb172da8797e6e8ff66c9a39d2e452f7034e9" ldflags_dirs standard verify_py24
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.4.1" "https://www.python.org/ftp/python/2.4.1/Python-2.4.1.tgz#f449c3b167389324c525ad99d02376c518ac11e163dbbbc13bc88a5c7101fd00" standard verify_py24
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.4.1" "https://www.python.org/ftp/python/2.4.1/Python-2.4.1.tgz#f449c3b167389324c525ad99d02376c518ac11e163dbbbc13bc88a5c7101fd00" ldflags_dirs standard verify_py24
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.4.2" "https://www.python.org/ftp/python/2.4.2/Python-2.4.2.tgz#2653e1846e87fd9b3ee287fefc965c80c54646548b4913a22265b0dd54493adf" standard verify_py24
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.4.2" "https://www.python.org/ftp/python/2.4.2/Python-2.4.2.tgz#2653e1846e87fd9b3ee287fefc965c80c54646548b4913a22265b0dd54493adf" ldflags_dirs standard verify_py24
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.4.3" "https://www.python.org/ftp/python/2.4.3/Python-2.4.3.tgz#985a413932f5e31e6280b37da6b285a3a0b2748c6786643989ed9b23de97e2d5" standard verify_py24
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.4.3" "https://www.python.org/ftp/python/2.4.3/Python-2.4.3.tgz#985a413932f5e31e6280b37da6b285a3a0b2748c6786643989ed9b23de97e2d5" ldflags_dirs standard verify_py24
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.4.4" "https://www.python.org/ftp/python/2.4.4/Python-2.4.4.tgz#92be6e20cbc3111d9dd0c016d72ef7914c23b879dc52df7ba28df97afbf12e2e" standard verify_py24
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.4.4" "https://www.python.org/ftp/python/2.4.4/Python-2.4.4.tgz#92be6e20cbc3111d9dd0c016d72ef7914c23b879dc52df7ba28df97afbf12e2e" ldflags_dirs standard verify_py24
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.4.5" "https://www.python.org/ftp/python/2.4.5/Python-2.4.5.tgz#6ae6f67a388a7f70ed3a20eebab5aae995ee433089d1f1724095c62f4b7389a1" standard verify_py24
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.4.5" "https://www.python.org/ftp/python/2.4.5/Python-2.4.5.tgz#6ae6f67a388a7f70ed3a20eebab5aae995ee433089d1f1724095c62f4b7389a1" ldflags_dirs standard verify_py24
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require_gcc
|
||||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.4.6" "https://www.python.org/ftp/python/2.4.6/Python-2.4.6.tgz#b03f269e826927f05c966cf4f4414f3c93ee2314960859e7f8375e24e82f8b02" standard verify_py24
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.4.6" "https://www.python.org/ftp/python/2.4.6/Python-2.4.6.tgz#b03f269e826927f05c966cf4f4414f3c93ee2314960859e7f8375e24e82f8b02" ldflags_dirs standard verify_py24
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.1" "https://pypi.python.org/packages/source/p/pip/pip-1.1.tar.gz#993804bb947d18508acee02141281c77d27677f8c14eaa64d6287a1c53ef01c8" python
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.5" "https://www.python.org/ftp/python/2.5/Python-2.5.tgz#d7bbf42e36003c6065cd19f3e67d283521858515ee923220f654131cebe1d8f2" standard verify_py25
|
||||
#require_gcc
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.5" "https://www.python.org/ftp/python/2.5/Python-2.5.tgz#d7bbf42e36003c6065cd19f3e67d283521858515ee923220f654131cebe1d8f2" ldflags_dirs standard verify_py25
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.3.1" "https://pypi.python.org/packages/source/p/pip/pip-1.3.1.tar.gz#145eaa5d1ea1b062663da1f3a97780d7edea4c63c68a37c463b1deedf7bb4957" python
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.5.1" "https://www.python.org/ftp/python/2.5.1/Python-2.5.1.tgz#1f5caee846049ca30d996f9403eefdb996295c4af664867e35dcc5eb36e4e7e8" standard verify_py25
|
||||
#require_gcc
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.5.1" "https://www.python.org/ftp/python/2.5.1/Python-2.5.1.tgz#1f5caee846049ca30d996f9403eefdb996295c4af664867e35dcc5eb36e4e7e8" ldflags_dirs standard verify_py25
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.3.1" "https://pypi.python.org/packages/source/p/pip/pip-1.3.1.tar.gz#145eaa5d1ea1b062663da1f3a97780d7edea4c63c68a37c463b1deedf7bb4957" python
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.5.2" "https://www.python.org/ftp/python/2.5.2/Python-2.5.2.tgz#834afe8a88adaf623b05ac5dd6700dd5bb5d0d5553fc74ad529359a3496e4ae3" standard verify_py25
|
||||
#require_gcc
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.5.2" "https://www.python.org/ftp/python/2.5.2/Python-2.5.2.tgz#834afe8a88adaf623b05ac5dd6700dd5bb5d0d5553fc74ad529359a3496e4ae3" ldflags_dirs standard verify_py25
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.3.1" "https://pypi.python.org/packages/source/p/pip/pip-1.3.1.tar.gz#145eaa5d1ea1b062663da1f3a97780d7edea4c63c68a37c463b1deedf7bb4957" python
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.5.3" "https://www.python.org/ftp/python/2.5.3/Python-2.5.3.tgz#c3fee607d20a77dfb72ea2e627eb4d95d25c735603435abde62c57015a0445bd" standard verify_py25
|
||||
#require_gcc
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.5.3" "https://www.python.org/ftp/python/2.5.3/Python-2.5.3.tgz#c3fee607d20a77dfb72ea2e627eb4d95d25c735603435abde62c57015a0445bd" ldflags_dirs standard verify_py25
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.3.1" "https://pypi.python.org/packages/source/p/pip/pip-1.3.1.tar.gz#145eaa5d1ea1b062663da1f3a97780d7edea4c63c68a37c463b1deedf7bb4957" python
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.5.4" "https://www.python.org/ftp/python/2.5.4/Python-2.5.4.tgz#3d3b205611ee503a38a9433d5645a571668420bb219242c7f51af85f05664da6" standard verify_py25
|
||||
#require_gcc
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.5.4" "https://www.python.org/ftp/python/2.5.4/Python-2.5.4.tgz#3d3b205611ee503a38a9433d5645a571668420bb219242c7f51af85f05664da6" ldflags_dirs standard verify_py25
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.3.1" "https://pypi.python.org/packages/source/p/pip/pip-1.3.1.tar.gz#145eaa5d1ea1b062663da1f3a97780d7edea4c63c68a37c463b1deedf7bb4957" python
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.5.5" "https://www.python.org/ftp/python/2.5.5/Python-2.5.5.tgz#03be1019c4fe93daeb53ba9e4294bf22a8ed4cb854cbd57e24e16f6bf63e2392" standard verify_py25
|
||||
#require_gcc
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.5.5" "https://www.python.org/ftp/python/2.5.5/Python-2.5.5.tgz#03be1019c4fe93daeb53ba9e4294bf22a8ed4cb854cbd57e24e16f6bf63e2392" ldflags_dirs standard verify_py25
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.3.1" "https://pypi.python.org/packages/source/p/pip/pip-1.3.1.tar.gz#145eaa5d1ea1b062663da1f3a97780d7edea4c63c68a37c463b1deedf7bb4957" python
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline
|
||||
install_package "Python-2.5.6" "https://www.python.org/ftp/python/2.5.6/Python-2.5.6.tgz#c2e4377597241b1065677d23327c04d0f41945d370c61a491cc88be367234c5d" standard verify_py25
|
||||
#require_gcc
|
||||
install_package "readline-6.3" "https://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
|
||||
install_package "Python-2.5.6" "https://www.python.org/ftp/python/2.5.6/Python-2.5.6.tgz#c2e4377597241b1065677d23327c04d0f41945d370c61a491cc88be367234c5d" ldflags_dirs standard verify_py25
|
||||
install_package "setuptools-1.4.2" "https://pypi.python.org/packages/source/s/setuptools/setuptools-1.4.2.tar.gz#263986a60a83aba790a5bffc7d009ac88114ba4e908e5c90e453b3bf2155dbbd" python
|
||||
install_package "pip-1.3.1" "https://pypi.python.org/packages/source/p/pip/pip-1.3.1.tar.gz#145eaa5d1ea1b062663da1f3a97780d7edea4c63c68a37c463b1deedf7bb4957" python
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue