├── .github └── CODEOWNERS ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── help.config ├── help.links ├── help.overview ├── install ├── list-all ├── list-legacy-filenames └── utils.sh ├── renovate.json └── shims └── pip /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @asdf-community/asdf-python 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /pyenv 2 | /pyenv_last_update 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | script: asdf plugin-test python https://github.com/danhper/asdf-python.git 3 | before_script: 4 | - git clone https://github.com/asdf-vm/asdf.git asdf 5 | - . asdf/asdf.sh 6 | os: 7 | - linux 8 | - osx 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Daniel Perez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asdf-python 2 | 3 | [![Build Status](https://travis-ci.org/danhper/asdf-python.svg?branch=master)](https://travis-ci.org/danhper/asdf-python) 4 | 5 | Python plugin for [asdf](https://github.com/asdf-vm/asdf) version manager 6 | 7 | ## Install 8 | 9 | ``` 10 | asdf plugin-add python 11 | ``` 12 | 13 | ### Install with `--patch` 14 | 15 | > Enable to fix macOS 11 issues 16 | 17 | You can use environment variable `ASDF_PYTHON_PATCH_URL` to install with `--patch` like that: 18 | 19 | ``` 20 | export ASDF_PYTHON_PATCH_URL="https://github.com/python/cpython/commit/8ea6353.patch?full_index=1" 21 | asdf install python 3.6.12 22 | ``` 23 | 24 | or use environment variable `ASDF_PYTHON_PATCHES_DIRECTORY`. 25 | 26 | ## Use 27 | 28 | Check [asdf](https://github.com/asdf-vm/asdf) readme for instructions on how to install & manage versions of Python. 29 | Please make sure you have the required [system dependencies](https://github.com/pyenv/pyenv/wiki#suggested-build-environment) installed before trying to install Python. 30 | 31 | Under the hood, asdf-python uses [pyenv python-build](https://github.com/yyuu/pyenv/tree/master/plugins/python-build) 32 | to build and install Python, check its [README](https://github.com/yyuu/pyenv/tree/master/plugins/python-build) 33 | for more information about build options and the [common build problems](https://github.com/pyenv/pyenv/wiki/Common-build-problems) wiki page for any issues encountered 34 | during installation of python versions. You may also want to check [Python's official setup building section](https://devguide.python.org/getting-started/setup-building/#install-dependencies) for latest version dependencies. 35 | 36 | ## Using multiple versions of Python 37 | 38 | A common request for Python is being able to use the `python2` and `python3` commands without needing to switch version. 39 | This can be achieved by setting multiple versions of Python, for example with 40 | 41 | ``` 42 | asdf global python 3.6.2 2.7.13 43 | ``` 44 | 45 | Executables in the first version will take priority over the executables in the next one. Note that you can use an arbitrary number over versions, if needed. 46 | With the above example, `python` will therefore use the `python` executable found in version 3.6.2. 47 | However, as the `python2` does not exist in Python 3.6.2, `python2` will use the `python2` executable found in version 2.7.13. 48 | 49 | ``` 50 | python -V 51 | Python 3.6.2 52 | 53 | python3 -V 54 | Python 3.6.2 55 | 56 | python2 -V 57 | Python 2.7.13 58 | ``` 59 | 60 | ## Pip installed modules and binaries 61 | 62 | If you use pip to install a module like ipython that has binaries. You will need to run `asdf reshim python` for the binary to be in your path. 63 | 64 | ## Default Python packages 65 | 66 | asdf-python can automatically install a default set of Python packages with pip right after installing a Python version. To enable this feature, provide a `$HOME/.default-python-packages` file that lists one package per line, for example: 67 | 68 | ``` 69 | ansible 70 | pipenv 71 | ``` 72 | 73 | You can specify a non-default location of this file by setting a `ASDF_PYTHON_DEFAULT_PACKAGES_FILE` variable. 74 | -------------------------------------------------------------------------------- /bin/help.config: -------------------------------------------------------------------------------- 1 | # Output should be freeform text. asdf decides how to reformat it. 2 | 3 | echo 'asdf-python can automatically install a default set of Python packages with pip right after installing a Python version. To enable this feature, provide a $HOME/.default-python-packages file that lists one package per line, for example: 4 | 5 | ansible 6 | pipenv 7 | 8 | You can specify a non-default location of this file by setting a ASDF_PYTHON_DEFAULT_PACKAGES_FILE variable. 9 | ' 10 | -------------------------------------------------------------------------------- /bin/help.links: -------------------------------------------------------------------------------- 1 | # Output should be : <link> 2 | 3 | echo 'home-page: https://github.com/danhper/asdf-python 4 | dependencies: https://github.com/pyenv/pyenv/wiki#suggested-build-environment 5 | troubleshooting: https://github.com/pyenv/pyenv/wiki/Common-build-problems 6 | ' 7 | -------------------------------------------------------------------------------- /bin/help.overview: -------------------------------------------------------------------------------- 1 | # Output should be freeform text. asdf decides how to reformat it. 2 | 3 | echo 'Basic usage: 4 | 5 | asdf install python 3.6.12 6 | asdf global python 3.6.12 7 | python -V 8 | Python 3.6.12 9 | 10 | Check the asdf documentation for more instructions on how to install & manage versions of Python. Please make sure you have the required system dependencies installed before trying to install Python. 11 | 12 | If you use pip to install a module like `ipython` that has binaries, you will need to run `asdf reshim python` for the binary to be in your path. 13 | 14 | Under the hood, asdf-python uses python-build (the same backend of pyenv) to build and install Python. Check its readme for more information about build options and the common build problems wiki page for any issues encountered during installation of python versions. 15 | ' 16 | -------------------------------------------------------------------------------- /bin/install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | source "$(dirname "$0")/utils.sh" 6 | 7 | install_python() { 8 | local install_type=$1 9 | local version=$2 10 | local install_path=$3 11 | 12 | if [ "$install_type" != "version" ]; then 13 | echoerr "Cannot install specific ref from source, sorry." 14 | echoerr "For a list of available versions, see \`asdf list-all python\`." 15 | exit 1 16 | fi 17 | install_or_update_python_build 18 | 19 | if [[ -n "${ASDF_PYTHON_PATCH_URL:-}" ]]; then 20 | echo "python-build --patch $version $install_path" 21 | echo "with patch file from: $ASDF_PYTHON_PATCH_URL" 22 | $(python_build_path) --patch "$version" "$install_path" < <(curl -sSL "$ASDF_PYTHON_PATCH_URL") 23 | elif [[ -n "${ASDF_PYTHON_PATCHES_DIRECTORY:-}" ]] && [[ -f ${ASDF_PYTHON_PATCHES_DIRECTORY}/${version}.patch ]]; then 24 | local patch_file=${ASDF_PYTHON_PATCHES_DIRECTORY}/${version}.patch 25 | echo "python-build $version $install_path -p < $patch_file" 26 | $(python_build_path) "$version" "$install_path" -p < $patch_file 27 | else 28 | echo "python-build $version $install_path" 29 | $(python_build_path) "$version" "$install_path" 30 | fi 31 | } 32 | 33 | install_default_python_packages() { 34 | local packages_file="${ASDF_PYTHON_DEFAULT_PACKAGES_FILE:-$HOME/.default-python-packages}" 35 | 36 | if [ -f "$packages_file" ]; then 37 | echo -ne "\nInstalling default python packages..." 38 | PATH="$ASDF_INSTALL_PATH/bin:$PATH" pip install -U -r "$packages_file" 39 | fi 40 | } 41 | 42 | ensure_python_build_installed 43 | install_python "$ASDF_INSTALL_TYPE" "$ASDF_INSTALL_VERSION" "$ASDF_INSTALL_PATH" 44 | install_default_python_packages 45 | -------------------------------------------------------------------------------- /bin/list-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(dirname "$0")/utils.sh" 4 | 5 | list_all() { 6 | install_or_update_python_build 7 | $(python_build_path) --definitions | tr '\n' ' ' 8 | } 9 | 10 | list_all 11 | -------------------------------------------------------------------------------- /bin/list-legacy-filenames: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo ".python-version" 4 | -------------------------------------------------------------------------------- /bin/utils.sh: -------------------------------------------------------------------------------- 1 | echoerr() { 2 | printf "\033[0;31m%s\033[0m" "$1" >&2 3 | } 4 | 5 | ensure_python_build_installed() { 6 | if [ ! -f "$(python_build_path)" ]; then 7 | download_python_build 8 | fi 9 | } 10 | 11 | download_python_build() { 12 | echo "Downloading python-build..." >&2 13 | local pyenv_url="https://github.com/pyenv/pyenv.git" 14 | git clone $pyenv_url "$(pyenv_path)" 15 | } 16 | 17 | python_build_path() { 18 | echo "$(pyenv_path)/plugins/python-build/bin/python-build" 19 | } 20 | 21 | update_python_build() { 22 | cd "$(pyenv_path)" && git fetch && git reset --hard origin/master > /dev/null 2>&1 23 | } 24 | 25 | pyenv_path() { 26 | echo "$(dirname $(dirname $0))/pyenv" 27 | } 28 | 29 | pyenv_update_timestamp_path() { 30 | echo "$(dirname $(dirname "$0"))/pyenv_last_update" 31 | } 32 | 33 | pyenv_should_update() { 34 | update_timeout=3600 35 | update_timestamp_path=$(pyenv_update_timestamp_path) 36 | 37 | if [ ! -f "$update_timestamp_path" ]; then 38 | return 0 39 | fi 40 | 41 | last_update=$(cat "$update_timestamp_path") 42 | current_timestamp=$(date +%s) 43 | invalidated_at=$(($last_update + $update_timeout)) 44 | 45 | [ $invalidated_at -lt $current_timestamp ] 46 | } 47 | 48 | install_or_update_python_build() { 49 | if [ ! -f "$(python_build_path)" ]; then 50 | download_python_build 51 | elif pyenv_should_update; then 52 | update_python_build 53 | date +%s > "$(pyenv_update_timestamp_path)" 54 | fi 55 | } 56 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /shims/pip: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # This script wraps pip to run `asdf reshim` after installs and uninstalls. 4 | # Any other cases are passed-through to pip. 5 | # 6 | # Inspired by the npm shim: https://github.com/asdf-vm/asdf-nodejs/blob/b2d06a768d9a14186db72/shims/npm 7 | 8 | set -euo pipefail 9 | 10 | this_dir=$(dirname "${BASH_SOURCE[0]}") 11 | this_dir=$(cd "$this_dir" && pwd -P) # Normalizes the directory; see https://stackoverflow.com/a/7666/2308068 12 | plugin_name=$(basename "$(dirname "$this_dir")") 13 | 14 | should_reshim() { 15 | if [ "${ASDF_PYTHON_SKIP_RESHIM:-}" ]; then 16 | return 1 17 | fi 18 | 19 | for arg; do 20 | case "$arg" in 21 | install|uninstall) 22 | return 0 23 | ;; 24 | esac 25 | done 26 | 27 | return 1 28 | } 29 | 30 | resolve_pip() { 31 | local pip_location="${ASDF_PYTHON_CANON_PIP_PATH:-$(search_pip_bin)}" 32 | 33 | if ! [ "$pip_location" ]; then 34 | echo "asdf-python couldn't find a suitable pip executable" 35 | echo "This is probably a problem with the plugin, please report this issue" 36 | exit 1 37 | fi 38 | 39 | echo "$pip_location" 40 | } 41 | 42 | search_pip_bin() { 43 | local probably_pip="$(asdf where python)/bin/pip" 44 | 45 | if [ -x "$probably_pip" ]; then 46 | echo "$probably_pip" 47 | return 0 48 | fi 49 | 50 | return 1 51 | } 52 | 53 | wrap_pip() { 54 | local pip=$(resolve_pip) 55 | 56 | if should_reshim "$@"; then 57 | "$pip" "$@" 58 | echo "Reshimming asdf $plugin_name..." 59 | asdf reshim "$plugin_name" 60 | else 61 | exec "$pip" "$@" 62 | fi 63 | } 64 | 65 | wrap_pip "$@" 66 | 67 | --------------------------------------------------------------------------------