├── .gitignore ├── .dockerignore ├── .envrc ├── .tool-versions ├── lib ├── helpers │ └── link_apps │ │ ├── requirements.txt │ │ └── link_apps.py └── utils.bash ├── .editorconfig ├── bin ├── post-plugin-add ├── list-all ├── download └── install ├── test ├── README.md ├── docker │ └── bionic │ │ └── Dockerfile └── integration-tests.bats ├── contributing.md ├── .gitmodules ├── .github ├── workflows │ ├── lint.yml │ └── build.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST │ └── pull_request_template.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | include_deps 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # intentionally left blank 2 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | # empty envrc for asdf-direnv users 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | shellcheck 0.7.2 2 | shfmt 3.2.4 3 | -------------------------------------------------------------------------------- /lib/helpers/link_apps/requirements.txt: -------------------------------------------------------------------------------- 1 | pipx==0.16.5 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /bin/post-plugin-add: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | if [[ "${ASDF_PYAPP_INCLUDE_DEPS-}" = "1" ]]; then 6 | touch "$ASDF_PLUGIN_PATH/include_deps" 7 | fi 8 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # asdf-pyapp Tests 2 | 3 | ## Integration Tests 4 | 5 | Requirements: 6 | 7 | - docker 8 | 9 | Run: 10 | 11 | ```shell 12 | ./bats/bin/bats integration-tests.bats 13 | ``` 14 | -------------------------------------------------------------------------------- /bin/list-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | current_script_path=${BASH_SOURCE[0]} 6 | plugin_dir=$(dirname "$(dirname "$current_script_path")") 7 | toolname=$(basename "$plugin_dir") 8 | 9 | # shellcheck source=../lib/utils.bash 10 | source "${plugin_dir}/lib/utils.bash" 11 | 12 | get_package_versions "${toolname}" | sort_versions | xargs echo 13 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Testing Locally: 4 | 5 | ```shell 6 | asdf plugin test [--asdf-tool-version ] [--asdf-plugin-gitref ] [test-command*] 7 | 8 | # 9 | asdf plugin test cowsay https://github.com/amrox/asdf-pyapp.git "cowsay Hi" 10 | ``` 11 | 12 | Tests are automatically run in GitHub Actions on push and PR. 13 | -------------------------------------------------------------------------------- /bin/download: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # shellcheck source=../lib/utils.bash 6 | source "$(dirname "$0")/../lib/utils.bash" 7 | 8 | # According to https://asdf-vm.com/#/plugins-create?id=required-scripts 9 | # a `download` script is required. However, it doesn't really make sense 10 | # for this plugin... Maybe "install" pipx itself to `downloads`? 11 | 12 | exit 0 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/bats"] 2 | path = test/bats 3 | url = https://github.com/bats-core/bats-core.git 4 | [submodule "test/test_helper/bats-support"] 5 | path = test/test_helper/bats-support 6 | url = https://github.com/bats-core/bats-support.git 7 | [submodule "test/test_helper/bats-assert"] 8 | path = test/test_helper/bats-assert 9 | url = https://github.com/bats-core/bats-assert.git 10 | -------------------------------------------------------------------------------- /bin/install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | current_script_path=${BASH_SOURCE[0]} 6 | plugin_dir=$(dirname "$(dirname "$current_script_path")") 7 | toolname=$(basename "$plugin_dir") 8 | 9 | # shellcheck source=../lib/utils.bash 10 | source "${plugin_dir}/lib/utils.bash" 11 | 12 | install_version "${toolname}" "${ASDF_INSTALL_TYPE}" "${ASDF_INSTALL_VERSION}" "${ASDF_INSTALL_PATH}" 13 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | pull_request: 4 | paths-ignore: 5 | - "**.md" 6 | push: 7 | paths-ignore: 8 | - "**.md" 9 | 10 | jobs: 11 | lint: 12 | name: Shellcheck and Shell Format 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | - name: asdf_install 18 | uses: asdf-vm/actions/install@v1 19 | - name: Shellcheck 20 | run: shellcheck -x bin/* -P lib/ 21 | - name: Shell Format - Validate 22 | run: shfmt -d -i 2 -ci bin/* lib/* 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | pull_request: 4 | paths-ignore: 5 | - "**.md" 6 | push: 7 | paths-ignore: 8 | - "**.md" 9 | workflow_dispatch: 10 | paths-ignore: 11 | - "**.md" 12 | 13 | jobs: 14 | plugin_test: 15 | name: asdf plugin test 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, macos-latest] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - name: asdf_plugin_test 22 | uses: asdf-vm/actions/plugin-test@v1 23 | with: 24 | plugin: cowsay 25 | command: cowsay Hi! 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | 11 | 12 | 13 | **Steps to reproduce** 14 | 15 | 16 | 17 | **Expected behavior** 18 | 19 | 20 | 21 | **Screenshots** 22 | 23 | 24 | 25 | **Additional context** 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: enhancement 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | 17 | **Describe alternatives you've considered** 18 | 19 | 20 | 21 | **Additional context** 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2021 Andy Mroczkowski 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Motivation and Context 8 | 9 | 10 | 11 | 12 | ## Types of changes 13 | 14 | 15 | 16 | - [ ] Bug fix (non-breaking change which fixes an issue) 17 | - [ ] New feature (non-breaking change which adds functionality) 18 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 19 | 20 | ## Usage examples 21 | 22 | 23 | 24 | ## How Has This Been Tested? 25 | 26 | 27 | 28 | ## Checklist: 29 | 30 | 31 | 32 | 33 | - [ ] I have updated the documentation accordingly. 34 | - [ ] I have added tests to cover my changes. 35 | -------------------------------------------------------------------------------- /lib/helpers/link_apps/link_apps.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | from pathlib import Path 5 | 6 | from pipx.venv import Venv 7 | 8 | 9 | def link_apps( 10 | venv_path: Path, package_name: str, dest_dir: Path, force: bool = False 11 | ) -> None: 12 | venv = Venv(venv_path) 13 | venv_metadata = venv.get_venv_metadata_for_package(package_name, set()) 14 | apps = venv_metadata.apps 15 | 16 | if (Path(__file__).parents[3] / "include_deps").exists(): 17 | apps = venv_metadata.apps_of_dependencies + apps 18 | 19 | os.makedirs(dest_dir, exist_ok=True) 20 | 21 | for app in apps: 22 | app_path = venv.bin_path / app 23 | dest_path = dest_dir / app 24 | if force and os.path.exists(dest_path): 25 | os.remove(dest_path) 26 | os.symlink(app_path, dest_path) 27 | 28 | 29 | def main(): 30 | parser = argparse.ArgumentParser() 31 | parser.add_argument("venv_path", type=str, help="path to virtual environment") 32 | parser.add_argument("package_name", type=str, help="name of package") 33 | parser.add_argument("dest_dir", type=str, help="destintation dir for symlinks") 34 | parser.add_argument( 35 | "--force", 36 | action="store_true", 37 | default=False, 38 | help="removes dest symlink paths if they exist", 39 | ) 40 | 41 | args = parser.parse_args() 42 | 43 | link_apps( 44 | Path(args.venv_path), args.package_name, Path(args.dest_dir), force=args.force 45 | ) 46 | 47 | 48 | if __name__ == "__main__": 49 | main() 50 | -------------------------------------------------------------------------------- /test/docker/bionic/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | ARG DEBIAN_FRONTEND noninteractive 5 | 6 | # Deps required for asdf 7 | RUN apt-get update \ 8 | && apt-get install -y --no-install-recommends \ 9 | ca-certificates \ 10 | curl \ 11 | git \ 12 | && rm -rf /var/lib/apt 13 | 14 | # Install system python3 15 | RUN apt-get update \ 16 | && apt-get install -y --no-install-recommends \ 17 | python3 \ 18 | python3-dev \ 19 | python3-pip \ 20 | python3-venv \ 21 | python3-wheel \ 22 | && rm -rf /var/lib/apt 23 | 24 | # Deps required to build python and common deps via asdf 25 | RUN apt-get update \ 26 | && apt-get install -y --no-install-recommends \ 27 | make \ 28 | build-essential \ 29 | libbz2-dev \ 30 | libffi-dev \ 31 | libreadline-dev \ 32 | libssl-dev \ 33 | libsqlite3-dev \ 34 | xz-utils \ 35 | zlib1g-dev \ 36 | && rm -rf /var/lib/apt 37 | 38 | ARG ASDF_BRANCH=v0.8.1 39 | RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch ${ASDF_BRANCH} 40 | 41 | RUN echo ". $HOME/.asdf/asdf.sh" >> ~/.profile 42 | 43 | RUN source /root/.asdf/asdf.sh && asdf plugin add python 44 | RUN source /root/.asdf/asdf.sh && asdf install python 3.5.10 45 | RUN source /root/.asdf/asdf.sh && asdf install python 3.8.10 46 | 47 | RUN source /root/.asdf/asdf.sh && asdf plugin add direnv 48 | RUN source /root/.asdf/asdf.sh && asdf install direnv 2.28.0 49 | 50 | # NOTE: direnv hook only works for interactive shells 51 | RUN echo -e '#eval "$(asdf exec direnv hook bash)"\n\ 52 | direnv() { asdf exec direnv "$@"; }\n'\ 53 | >> ~/setup-direnv.bash 54 | 55 | #RUN echo -e 'eval "$(asdf exec direnv export bash)"\n\ 56 | # direnv() { asdf exec direnv "$@"; }\n'\ 57 | # >> ~/setup-direnv.bash 58 | 59 | ## Hook direnv into your shell. 60 | #RUN echo "eval \"\$(asdf exec direnv hook bash)\"" >> ~/.profile 61 | ## A shortcut for asdf managed direnv. 62 | #RUN echo "eval direnv() { asdf exec direnv \"\$@\"; }" >> ~/.profile 63 | ## Make the 'use asdf' feature available: 64 | RUN mkdir -p ~/.config/direnv && echo "source \"\$(asdf direnv hook asdf)\"" >> ~/.config/direnv/direnvrc 65 | 66 | # Extras: 67 | # - groff is required for awscli 68 | RUN apt-get update \ 69 | && apt-get install -y --no-install-recommends \ 70 | groff \ 71 | && rm -rf /var/lib/apt 72 | 73 | WORKDIR /root 74 | COPY . asdf-pyapp 75 | 76 | # app pipenv needs this, and probably generally a good thing 77 | ENV LC_ALL=C.UTF-8 78 | ENV LANG=C.UTF-8 79 | 80 | ENTRYPOINT ["/bin/bash", "-l", "-c"] 81 | CMD ["tail", "-f", "/dev/null"] 82 | 83 | -------------------------------------------------------------------------------- /lib/utils.bash: -------------------------------------------------------------------------------- 1 | ASDF_PYAPP_MY_NAME=asdf-pyapp 2 | 3 | # 0: (default) Copy venvs with explicit python version, symlink otherwise. 4 | # 1: Prefer copies. 5 | ASDF_PYAPP_VENV_COPY_MODE=${ASDF_PYAPP_VENV_COPY_MODE:-0} 6 | 7 | ASDF_PYAPP_RESOLVED_PYTHON_PATH= 8 | 9 | if [[ ${ASDF_PYAPP_DEBUG:-} -eq 1 ]]; then 10 | # In debug mode, dunp everything to a log file 11 | # got a little help from https://askubuntu.com/a/1345538/985855 12 | 13 | ASDF_PYAPP_DEBUG_LOG_PATH="/tmp/${ASDF_PYAPP_MY_NAME}-debug.log" 14 | mkdir -p "$(dirname "$ASDF_PYAPP_DEBUG_LOG_PATH")" 15 | 16 | printf "\n\n-------- %s ----------\n\n" "$(date)" >>"$ASDF_PYAPP_DEBUG_LOG_PATH" 17 | 18 | exec > >(tee -ia "$ASDF_PYAPP_DEBUG_LOG_PATH") 19 | exec 2> >(tee -ia "$ASDF_PYAPP_DEBUG_LOG_PATH" >&2) 20 | 21 | exec 19>>"$ASDF_PYAPP_DEBUG_LOG_PATH" 22 | export BASH_XTRACEFD=19 23 | set -x 24 | fi 25 | 26 | fail() { 27 | echo >&2 -e "${ASDF_PYAPP_MY_NAME}: [ERROR] $*" 28 | exit 1 29 | } 30 | 31 | log() { 32 | if [[ ${ASDF_PYAPP_DEBUG:-} -eq 1 ]]; then 33 | echo >&2 -e "${ASDF_PYAPP_MY_NAME}: $*" 34 | fi 35 | } 36 | 37 | get_python_version() { 38 | local python_path="$1" 39 | local regex='Python (.+)' 40 | 41 | python_version_raw=$("$python_path" --version) 42 | 43 | if [[ $python_version_raw =~ $regex ]]; then 44 | echo -n "${BASH_REMATCH[1]}" 45 | else 46 | fail "Unable to determine python version" 47 | fi 48 | } 49 | 50 | get_python_pip_versions() { 51 | local python_path="$1" 52 | 53 | local pip_version_raw 54 | pip_version_raw=$("${python_path}" -m pip --version) 55 | local regex='pip (.+) from.*\(python (.+)\)' 56 | 57 | if [[ $pip_version_raw =~ $regex ]]; then 58 | echo -n "${BASH_REMATCH[1]}" 59 | #ASDF_PYAPP_PYTHON_VERSION="${BASH_REMATCH[2]}" # probably not longer needed 60 | else 61 | fail "Unable to determine pip version" 62 | fi 63 | } 64 | 65 | resolve_python_path() { 66 | # if ASDF_PYAPP_DEFAULT_PYTHON_PATH is set, use it, else: 67 | # 1. try $(asdf which python) 68 | # 2. try $(which python3) 69 | 70 | if [ -n "${ASDF_PYAPP_DEFAULT_PYTHON_PATH+x}" ]; then 71 | ASDF_PYAPP_RESOLVED_PYTHON_PATH="$ASDF_PYAPP_DEFAULT_PYTHON_PATH" 72 | return 73 | fi 74 | 75 | # cd to $HOME to avoid picking up a local python from .tool-versions 76 | # pipx is best when install with a global python 77 | pushd "$HOME" >/dev/null || fail "Failed to pushd \$HOME" 78 | 79 | # run direnv in $HOME to escape any direnv we might already be in 80 | if type -P direnv &>/dev/null; then 81 | eval "$(DIRENV_LOG_FORMAT= direnv export bash)" 82 | fi 83 | 84 | local pythons=() 85 | 86 | local asdf_python 87 | if asdf_python=$(asdf which python3 2>/dev/null); then 88 | pythons+=("$asdf_python") 89 | else 90 | local global_python 91 | global_python=$(which python3) 92 | pythons+=("$global_python") 93 | fi 94 | 95 | for p in "${pythons[@]}"; do 96 | local python_version 97 | log "Testing '$p' ..." 98 | python_version=$(get_python_version "$p") 99 | if [[ $python_version =~ ^([0-9]+)\.([0-9]+)\. ]]; then 100 | local python_version_major=${BASH_REMATCH[1]} 101 | local python_version_minor=${BASH_REMATCH[2]} 102 | if [ "$python_version_major" -ge 3 ] && [ "$python_version_minor" -ge 6 ]; then 103 | ASDF_PYAPP_RESOLVED_PYTHON_PATH="$p" 104 | break 105 | fi 106 | else 107 | continue 108 | fi 109 | done 110 | 111 | popd >/dev/null || fail "Failed to popd" 112 | 113 | if [ -z "$ASDF_PYAPP_RESOLVED_PYTHON_PATH" ]; then 114 | fail "Failed to find python3 >= 3.6" 115 | else 116 | log "Using python3 at '$ASDF_PYAPP_RESOLVED_PYTHON_PATH'" 117 | fi 118 | } 119 | 120 | get_package_versions() { 121 | # Returns a newline-separted list of versions. `list-all` must output 122 | # versions on one line, so this expects it's output to be further processed. 123 | # 124 | # TODO: this uses ASDF_PYAPP_RESOLVED_PYTHON_PATH, but technically python 3.6 125 | # isn't required to list versions... 126 | 127 | local package=$1 128 | 129 | local pip_version 130 | pip_version=$(get_python_pip_versions "$ASDF_PYAPP_RESOLVED_PYTHON_PATH") 131 | if [[ $pip_version =~ ^([0-9]+)\.([0-9]+)\.? ]]; then 132 | local pip_version_major=${BASH_REMATCH[1]} 133 | local pip_version_minor=${BASH_REMATCH[2]} 134 | else 135 | fail "Unable to parse pip version" 136 | fi 137 | 138 | local pip_install_args=() 139 | local version_output_raw 140 | 141 | # we rely on the "legacy resolver" to get versions, which was introduced in 20.3 142 | if [ "${pip_version_major}" -ge 21 ] || 143 | { [ "${pip_version_major}" -eq 20 ] && [ "${pip_version_minor}" -ge 3 ]; }; then 144 | pip_install_args+=("--use-deprecated=legacy-resolver") 145 | fi 146 | version_output_raw=$("${ASDF_PYAPP_RESOLVED_PYTHON_PATH}" -m pip install ${pip_install_args[@]+"${pip_install_args[@]}"} "${package}==" 2>&1) || true 147 | 148 | local regex='.*from versions:(.*)\)' 149 | if [[ $version_output_raw =~ $regex ]]; then 150 | local version_substring="${BASH_REMATCH[1]//','/}" 151 | # trim whitespace with 'xargs echo' and convert spaces to newlines with 'tr' 152 | local version_list 153 | version_list=$(echo "$version_substring" | xargs echo | tr " " "\n") 154 | echo "$version_list" 155 | else 156 | fail "Unable to parse versions for '${package}'" 157 | fi 158 | } 159 | 160 | sort_versions() { 161 | sed 'h; s/[+-]/./g; s/.p\([[:digit:]]\)/.z\1/; s/$/.z/; G; s/\n/ /' | 162 | LC_ALL=C sort -t. -k 1,1 -k 2,2n -k 3,3n -k 4,4n -k 5,5n | awk '{print $2}' 163 | } 164 | 165 | install_version() { 166 | local package="$1" 167 | local install_type="$2" 168 | local full_version="$3" 169 | local install_path="$4" 170 | 171 | local venv_args=() 172 | local pip_args=("--disable-pip-version-check") 173 | 174 | local versions=(${full_version//\@/ }) 175 | local app_version=${versions[0]} 176 | if [ "${#versions[@]}" -gt 1 ]; then 177 | 178 | if ! asdf plugin list | grep python; then 179 | fail "Cannot install $1 $3 - asdf python plugin is not installed!" 180 | fi 181 | 182 | python_version=${versions[1]} 183 | asdf install python "$python_version" 184 | ASDF_PYAPP_RESOLVED_PYTHON_PATH=$(ASDF_PYTHON_VERSION="$python_version" asdf which python3) 185 | fi 186 | 187 | # check for venv copies 188 | if [ "${#versions[@]}" -gt 1 ] || [ "$ASDF_PYAPP_VENV_COPY_MODE" == "1" ]; then 189 | # special check for macOS 190 | # TODO: write a test for this somehow 191 | if [ "$ASDF_PYAPP_RESOLVED_PYTHON_PATH" == "/usr/bin/python3" ] && [[ "$OSTYPE" == "darwin"* ]]; then 192 | log "Copying /usr/bin/python3 on macOS does not work, symlinking" 193 | else 194 | venv_args+=("--copies") 195 | fi 196 | fi 197 | 198 | if [ "${install_type}" != "version" ]; then 199 | fail "supports release installs only" 200 | fi 201 | 202 | mkdir -p "${install_path}" 203 | 204 | # Make a venv for the app 205 | local venv_path="$install_path"/venv 206 | "$ASDF_PYAPP_RESOLVED_PYTHON_PATH" -m venv ${venv_args[@]+"${venv_args[@]}"} "$venv_path" 207 | # setuptools might be upgraded by itself https://stackoverflow.com/a/71239956/4468 208 | "$venv_path"/bin/python3 -m pip install ${pip_args[@]+"${pip_args[@]}"} --upgrade setuptools 209 | "$venv_path"/bin/python3 -m pip install ${pip_args[@]+"${pip_args[@]}"} --upgrade pip wheel 210 | 211 | # Install the App 212 | "$venv_path"/bin/python3 -m pip install "$package"=="$app_version" 213 | 214 | # Set up a venv for the linker helper 215 | local link_apps_venv="$install_path"/tmp/link_apps 216 | mkdir -p "$(dirname "$link_apps_venv")" 217 | "$ASDF_PYAPP_RESOLVED_PYTHON_PATH" -m venv "$link_apps_venv" 218 | "$link_apps_venv"/bin/python3 -m pip install "${pip_args[@]}" -r "$plugin_dir"/lib/helpers/link_apps/requirements.txt 219 | 220 | # Link Apps 221 | "$link_apps_venv"/bin/python3 "$plugin_dir"/lib/helpers/link_apps/link_apps.py "$venv_path" "$package" "$install_path"/bin 222 | } 223 | 224 | resolve_python_path 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # asdf-pyapp ![Build](https://github.com/amrox/asdf-pyapp/workflows/Build/badge.svg) ![Lint](https://github.com/amrox/asdf-pyapp/workflows/Lint/badge.svg) 4 | 5 | A generic Python Application plugin the [asdf version manager](https://asdf-vm.com). 6 | 7 |
8 | 9 | **What is a "Python Application"?** 10 | 11 | For purposes of this plugin, a Python Application is program that _happens_ to be written in Python, but otherwise behaves like a regular command-line tool. The term "Python Application" comes from [pipx](https://pypa.github.io/pipx/). 12 | 13 | Examples of Python Applications are [awscli](https://pypi.org/project/awscli/) and [conan](https://pypi.org/project/conan/). See below for more compatible applications. 14 | 15 | # Dependencies 16 | 17 | - `python`/`python3` >= 3.6 with pip and venv 18 | - OR [asdf-python](https://github.com/danhper/asdf-python) installed 19 | 20 | # Install 21 | 22 | Plugin: 23 | 24 | ```shell 25 | asdf plugin add https://github.com/amrox/asdf-pyapp.git 26 | # for example 27 | asdf plugin add cowsay https://github.com/amrox/asdf-pyapp.git 28 | ``` 29 | 30 | Example using `cowsay`: 31 | 32 | ```shell 33 | # Show all installable versions 34 | asdf list-all cowsay 35 | 36 | # Install specific version 37 | asdf install cowsay latest 38 | 39 | # Set a version globally (on your ~/.tool-versions file) 40 | asdf global cowsay latest 41 | 42 | # Now cowsay commands are available 43 | cowsay "Hi!" 44 | ``` 45 | 46 | ## Compatible Python Applications 47 | 48 | This is a non-exhaustive list of Python Applications that work with this plugin. 49 | 50 | | App | Command to add Plugin | Notes | 51 | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | 52 | | [ansible](https://pypi.org/project/ansible/) | `ASDF_PYAPP_INCLUDE_DEPS=1 asdf plugin add ansible https://github.com/amrox/asdf-pyapp.git` | [(info)](#environment-variables) | 53 | | [awscli](https://pypi.org/project/awscli/) | `asdf plugin add awscli https://github.com/amrox/asdf-pyapp.git` | | 54 | | [awsebcli](https://pypi.org/project/awsebcli/) | `asdf plugin add awsebcli https://github.com/amrox/asdf-pyapp.git` | | 55 | | [aws-sam-cli](https://pypi.org/project/aws-sam-cli/) | `asdf plugin add aws-sam-cli https://github.com/amrox/asdf-pyapp.git` | | 56 | | [aws-ssm-tools](https://pypi.org/project/aws-ssm-tools/) | `asdf plugin add aws-ssm-tools https://github.com/amrox/asdf-pyapp.git` | | 57 | | [black](https://pypi.org/project/black/) | `asdf plugin add black https://github.com/amrox/asdf-pyapp.git` | | 58 | | [bpython](https://pypi.org/project/bpython/) | `asdf plugin add bpython https://github.com/amrox/asdf-pyapp.git` | | 59 | | [conan](https://pypi.org/project/conan/) | `asdf plugin add conan https://github.com/amrox/asdf-pyapp.git` | | 60 | | [cowsay](https://pypi.org/project/cowsay/) | `asdf plugin add cowsay https://github.com/amrox/asdf-pyapp.git` | | 61 | | [dbt](https://pypi.org/project/dbt/) | `ASDF_PYAPP_INCLUDE_DEPS=1 asdf plugin add dbt https://github.com/amrox/asdf-pyapp.git` | [(info)](#environment-variables) | 62 | | [doit](https://pypi.org/project/doit/) | `asdf plugin add doit https://github.com/amrox/asdf-pyapp.git` | | 63 | | [flake8](https://pypi.org/project/flake8/) | `asdf plugin add flake8 https://github.com/amrox/asdf-pyapp.git` | | 64 | | [hy](https://pypi.org/project/hy/) | `asdf plugin add hy https://github.com/amrox/asdf-pyapp.git` | The latest stable version of hy (0.20.0 atm) requires python > 3.8 | 65 | | [meson](https://pypi.org/project/meson/) | `asdf plugin add meson https://github.com/amrox/asdf-pyapp.git` | | 66 | | [mypy](https://pypi.org/project/mypy/) | `asdf plugin add mypy https://github.com/amrox/asdf-pyapp.git` | | 67 | | [pipenv](https://pypi.org/project/pipenv/) | `asdf plugin add pipenv https://github.com/amrox/asdf-pyapp.git` | | 68 | | [pre-commit](https://pypi.org/project/pre-commit/) | `asdf plugin add pre-commit https://github.com/amrox/asdf-pyapp.git` | | 69 | | [salt](https://pypi.org/project/salt/) | `asdf plugin add salt https://github.com/amrox/asdf-pyapp.git` | | 70 | | [sphinx](https://pypi.org/project/Sphinx/) | `asdf plugin add sphinx https://github.com/amrox/asdf-pyapp.git` | | 71 | | [yawsso](https://pypi.org/project/yawsso/) | `asdf plugin add sphinx https://github.com/amrox/asdf-pyapp.git` | | 72 | | [youtube-dl](https://pypi.org/project/youtube-dl/) | `asdf plugin add sphinx https://github.com/amrox/asdf-pyapp.git` | | 73 | 74 | Check [asdf](https://github.com/asdf-vm/asdf) readme for more instructions on how to install & manage versions. 75 | 76 | # How it Works 77 | 78 | asdf-pyapp is a lot more complex than most asdf plugins since it's designed to work with generic Python Applications, and challenges that come with Python itself. 79 | 80 | asdf-pyapp uses the same technique as [asdf-hashicorp](https://github.com/asdf-community/asdf-hashicorp) to use a single plugin for multiple tools. 81 | 82 | When installing a tool, asdf-pyapp creates a fresh [virtual environment](https://docs.python.org/3/tutorial/venv.html) and pip-installs the package matching the plugin name. Then it uses pipx under the hood to extract the entrypoints for the package exposes them to asdf. 83 | 84 | ## Python Resolution 85 | 86 | To run Python Applications, you need Python: 87 | 88 | 1. If `ASDF_PYAPP_DEFAULT_PYTHON_PATH` is set - use it 89 | 1. Else if the `asdf-python` plugin is installed - use the **global** `python3`\*\*. 90 | 1. Finally, just use `python3` in our path. 91 | 92 | \*\* _We use the global `python3` to avoid picking up local python versions inside projects, which would result in inconsistent tool installations. If you want to install a tool with a specific version of Python see the following section on asdf-python Integration._ 93 | 94 | ## asdf-python Integration (Experimental) 95 | 96 | Here we color outside the lines a bit :) 97 | 98 | asdf-python supports installing a Python App with a _specific_ Python version using a special syntax. This feature requires the [asdf-python](https://github.com/danhper/asdf-python) plugin to be installed. 99 | 100 | The general form is: 101 | 102 | ```shell 103 | asdf install @ 104 | ``` 105 | 106 | For example, to install `cowsay` 3.0 with Python 3.9.1: 107 | 108 | ```shell 109 | asdf cowsay install 3.0@3.9.1 110 | ``` 111 | 112 | Python Apps with different python versions and python itself can all happily co-exist in the same project. For example, take this `.tool-versions`: 113 | 114 | ```shell 115 | python 3.8.5 116 | awscli 1.19.93 117 | cowsay 3.0@3.9.1 118 | conan 1.36.0@3.8.5 119 | ``` 120 | 121 | - `awscli` will be installed with the global Python (see Python Resolution), in an isolated virtual environment 122 | - Python 3.9.1 will be installed, and then `cowsay` will be installed using that Python (in a venv) 123 | - `conan` will be installed with Python 3.8.5, but isolated from the project's Python, which is also 3.8.5. 124 | 125 | # Configuration 126 | 127 | ## Environment Variables 128 | 129 | - `ASDF_PYAPP_INCLUDE_DEPS` - when set to `1`, this plugin will consider the executables of the dependencies of the installed package as well. For example, when installing `ansible`, the `ansible` command actually comes from its depency, `ansible-core`. This is the same as Pipx's `--include-deps` flag. 130 | - `ASDF_PYAPP_DEFAULT_PYTHON_PATH` - Path to a `python`/`python3` binary this plugin should use. Default is unset. See Python Resolution section for more details. 131 | - `ASDF_PYAPP_VENV_COPY_MODE`: 132 | - `0`: (default) Add `--copies` flag to venvs created with a specific Python version. Symlinks otherwise. 133 | - `1`: Prefer `--copies` whenever possible (`--copies` does not work with `/usr/bin/python3` on macOS). 134 | - `ASDF_PYAPP_DEBUG` - Set to `1` for additional logging 135 | 136 | # Background and Inspiration 137 | 138 | asdf-pyapp was inspired by [asdf-hashicorp](https://github.com/asdf-community/asdf-hashicorp) and [pipx](https://pypa.github.io/pipx/) - which is also used under the hood. Big thanks to the creators, contributors, and maintainers of both these projects. 139 | 140 | # Contributing 141 | 142 | Contributions of any kind welcome! See the [contributing guide](contributing.md). 143 | 144 | [Thanks goes to these contributors](https://github.com/amrox/asdf-pyapp/graphs/contributors)! 145 | 146 | # License 147 | 148 | See [LICENSE](LICENSE) © [Andy Mroczkowski](https://github.com/amrox/) 149 | -------------------------------------------------------------------------------- /test/integration-tests.bats: -------------------------------------------------------------------------------- 1 | in_container() { 2 | args=($*) 3 | docker exec -it "$CONTAINER" bash -l -c "${args[*]@Q}" 4 | } 5 | 6 | setup() { 7 | load 'test_helper/bats-support/load' 8 | load 'test_helper/bats-assert/load' 9 | 10 | MY_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" >/dev/null 2>&1 && pwd)" 11 | SRCROOT=$(dirname "$MY_DIR") 12 | 13 | TAG="asdf-pyapp-integration-bionic" 14 | CONTAINER="${TAG}-container" 15 | 16 | DOCKER_BUILDKIT=0 docker build \ 17 | -f docker/bionic/Dockerfile \ 18 | -t "$TAG" \ 19 | "$SRCROOT" || return 1 20 | docker run --rm -d -it --init --name "$CONTAINER" "$TAG" 21 | in_container mkdir /root/.asdf/plugins || true 22 | } 23 | 24 | teardown() { 25 | docker stop "$CONTAINER" 26 | 27 | # wait for container to be _removed_ 28 | # https://stackoverflow.com/a/57631771/4468 29 | while docker container inspect "$CONTAINER" >/dev/null 2>&1; do sleep 1; done 30 | } 31 | 32 | @test "install with system python no asdf" { 33 | 34 | # asdf python is baked into the container, remove it first 35 | in_container asdf plugin remove python 36 | 37 | run in_container which python3 38 | assert_output --partial /usr/bin/python3 #TODO: why is --partial required? newline? 39 | 40 | in_container asdf plugin add cowsay /root/asdf-pyapp 41 | in_container asdf install cowsay 4.0 42 | in_container asdf global cowsay 4.0 43 | in_container cowsay "woo woo" 44 | 45 | run in_container readlink /root/.asdf/installs/cowsay/4.0/venv/bin/python3 46 | assert_output --partial /usr/bin/python3 47 | } 48 | 49 | @test "install with system python via asdf" { 50 | 51 | in_container asdf global python system 52 | 53 | run in_container which python3 54 | assert_output --partial /root/.asdf/shims/python3 55 | 56 | in_container asdf plugin add cowsay /root/asdf-pyapp 57 | in_container asdf install cowsay 4.0 58 | in_container asdf global cowsay 4.0 59 | in_container cowsay "woo woo" 60 | 61 | run in_container readlink /root/.asdf/installs/cowsay/4.0/venv/bin/python3 62 | assert_output --partial /usr/bin/python3 63 | } 64 | 65 | @test "install with asdf python 3.8.10" { 66 | 67 | in_container asdf global python 3.8.10 68 | 69 | run in_container which python3 70 | assert_output --partial /root/.asdf/shims/python3 71 | 72 | in_container asdf plugin add cowsay /root/asdf-pyapp 73 | in_container asdf install cowsay 4.0 74 | in_container asdf global cowsay 4.0 75 | in_container cowsay "woo woo" 76 | 77 | run in_container readlink /root/.asdf/installs/cowsay/4.0/venv/bin/python3 78 | assert_output --partial /root/.asdf/installs/python/3.8.10/bin/python3 79 | } 80 | 81 | @test "install with asdf python 3.5.10 system python 3.6" { 82 | # we require python >= 3.6. asdf-pyapp should detect that 83 | # asdf python3 should override the system python3 84 | 85 | in_container asdf global python 3.5.10 86 | 87 | run in_container which python3 88 | assert_output --partial /root/.asdf/shims/python3 89 | 90 | in_container asdf plugin add cowsay /root/asdf-pyapp 91 | 92 | run in_container asdf install cowsay 4.0 93 | assert_output --partial "Failed to find python3 >= 3.6" 94 | } 95 | 96 | @test "install with asdf python 3.5.10 no system python3" { 97 | # we only have python 3.5, asdf-pyapp should fail 98 | 99 | in_container apt remove -y -f python3 python3-minimal 100 | 101 | in_container asdf global python 3.5.10 102 | 103 | run in_container which python3 104 | assert_output --partial /root/.asdf/shims/python3 105 | 106 | in_container asdf plugin add cowsay /root/asdf-pyapp 107 | 108 | run in_container asdf install cowsay 4.0 109 | assert_output --partial "Failed to find python3 >= 3.6" 110 | } 111 | 112 | @test "check latest with pip version 19.3" { 113 | 114 | local pip_version=19.3.1 115 | 116 | in_container asdf global python 3.8.10 117 | 118 | in_container python3 -m pip install --upgrade pip=="$pip_version" 119 | 120 | run in_container python3 -m pip --version 121 | assert_output --partial "$pip_version" 122 | 123 | in_container asdf plugin add cowsay /root/asdf-pyapp 124 | run in_container asdf list all cowsay 125 | # TODO: "asdf list all" seems to mask return codes. It exits 0 even if we 126 | # exit nonzero in our function. So just check output for errors for now... 127 | refute_output --partial "asdf-pyapp: [ERROR]" 128 | } 129 | 130 | @test "check latest with pip version 20.0" { 131 | 132 | local pip_version=20.0.1 133 | 134 | in_container asdf global python 3.8.10 135 | 136 | in_container python3 -m pip install --upgrade pip=="$pip_version" 137 | 138 | run in_container python3 -m pip --version 139 | assert_output --partial "$pip_version" 140 | 141 | in_container asdf plugin add cowsay /root/asdf-pyapp 142 | run in_container asdf list all cowsay 143 | # TODO: "asdf list all" seems to mask return codes. It exits 0 even if we 144 | # exit nonzero in our function. So just check output for errors for now... 145 | refute_output --partial "asdf-pyapp: [ERROR]" 146 | } 147 | 148 | @test "check latest with pip version 20.1" { 149 | 150 | local pip_version=20.1.1 151 | 152 | in_container asdf global python 3.8.10 153 | 154 | in_container python3 -m pip install --upgrade pip=="$pip_version" 155 | 156 | run in_container python3 -m pip --version 157 | assert_output --partial "$pip_version" 158 | 159 | in_container asdf plugin add cowsay /root/asdf-pyapp 160 | run in_container asdf list all cowsay 161 | # TODO: "asdf list all" seems to mask return codes. It exits 0 even if we 162 | # exit nonzero in our function. So just check output for errors for now... 163 | refute_output --partial "asdf-pyapp: [ERROR]" 164 | } 165 | 166 | @test "check latest with pip version 20.2" { 167 | 168 | local pip_version=20.2.4 169 | 170 | in_container asdf global python 3.8.10 171 | 172 | in_container python3 -m pip install --upgrade pip=="$pip_version" 173 | 174 | run in_container python3 -m pip --version 175 | assert_output --partial "$pip_version" 176 | 177 | in_container asdf plugin add cowsay /root/asdf-pyapp 178 | run in_container asdf list all cowsay 179 | # TODO: "asdf list all" seems to mask return codes. It exits 0 even if we 180 | # exit nonzero in our function. So just check output for errors for now... 181 | refute_output --partial "asdf-pyapp: [ERROR]" 182 | } 183 | 184 | @test "check latest with pip version 20.3" { 185 | 186 | local pip_version=20.3.4 187 | 188 | in_container asdf global python 3.8.10 189 | 190 | in_container python3 -m pip install --upgrade pip=="$pip_version" 191 | 192 | run in_container python3 -m pip --version 193 | assert_output --partial "$pip_version" 194 | 195 | in_container asdf plugin add cowsay /root/asdf-pyapp 196 | run in_container asdf list all cowsay 197 | # TODO: "asdf list all" seems to mask return codes. It exits 0 even if we 198 | # exit nonzero in our function. So just check output for errors for now... 199 | refute_output --partial "asdf-pyapp: [ERROR]" 200 | } 201 | 202 | @test "check latest with pip version 21.3" { 203 | 204 | local pip_version=21.3.1 205 | 206 | in_container asdf global python 3.8.10 207 | 208 | in_container python3 -m pip install --upgrade pip=="$pip_version" 209 | 210 | run in_container python3 -m pip --version 211 | assert_output --partial "$pip_version" 212 | 213 | in_container asdf plugin add cowsay /root/asdf-pyapp 214 | run in_container asdf list all cowsay 215 | # TODO: "asdf list all" seems to mask return codes. It exits 0 even if we 216 | # exit nonzero in our function. So just check output for errors for now... 217 | refute_output --partial "asdf-pyapp: [ERROR]" 218 | } 219 | 220 | @test "check \$ASDF_PYAPP_DEFAULT_PYTHON_PATH works" { 221 | # When an app is installed without a python version specified, 222 | # the asdf-pyapp defaults to python3 in our $PATH, which is the 223 | # asdf shim. We override it to the system python3. 224 | 225 | in_container asdf global python 3.8.10 226 | 227 | in_container eval "echo \"export ASDF_PYAPP_DEFAULT_PYTHON_PATH=/usr/bin/python3\" >> /root/.profile" 228 | 229 | in_container asdf plugin add cowsay /root/asdf-pyapp 230 | in_container asdf install cowsay 4.0 231 | 232 | run in_container readlink /root/.asdf/installs/cowsay/4.0/venv/bin/python3 233 | assert_output --partial /usr/bin/python3 234 | } 235 | 236 | setup_direnv() { 237 | 238 | in_container asdf global direnv 2.28.0 239 | in_container eval 'echo "source setup-direnv.bash" >> ~/.profile' 240 | 241 | DIRENV="direnv allow . && eval \"\$(direnv export bash)\"" 242 | } 243 | 244 | @test "install with local python in direnv" { 245 | 246 | setup_direnv 247 | 248 | in_container asdf global python system 249 | in_container python3 --version 250 | in_container asdf plugin add cowsay /root/asdf-pyapp 251 | 252 | in_container mkdir project 253 | in_container eval "echo \"python 3.8.10\" >> project/.tool-versions" 254 | in_container eval "echo \"cowsay 4.0\" >> project/.tool-versions" 255 | in_container eval "echo \"use asdf\" >> project/.envrc" 256 | 257 | in_container eval "cd project && $DIRENV && asdf install" 258 | 259 | run in_container readlink /root/.asdf/installs/cowsay/4.0/venv/bin/python3 260 | assert_output --partial /usr/bin/python3 261 | } 262 | 263 | @test "install with python plugin integration" { 264 | 265 | local cowsay_ver="4.0" 266 | local python_ver="3.8.2" 267 | local combined_ver="${cowsay_ver}@${python_ver}" 268 | 269 | in_container asdf global python system 270 | 271 | in_container asdf plugin add cowsay /root/asdf-pyapp 272 | in_container asdf install cowsay "$combined_ver" 273 | in_container asdf local cowsay "$combined_ver" 274 | in_container cowsay "woo woo" 275 | 276 | # by default, the venv is created with "--copies", so python should not be a symlink 277 | refute in_container readlink /root/.asdf/installs/cowsay/4.0/venv/bin/python3 278 | 279 | # python3 should be the right version 280 | run in_container /root/.asdf/installs/cowsay/"$combined_ver"/venv/bin/python3 --version 281 | assert_output --partial "$python_ver" 282 | } 283 | 284 | @test "install with python plugin integration without python plugin installed" { 285 | 286 | local cowsay_ver="4.0" 287 | local python_ver="3.8.2" 288 | local combined_ver="${cowsay_ver}@${python_ver}" 289 | 290 | in_container asdf plugin remove python 291 | 292 | in_container asdf plugin add cowsay /root/asdf-pyapp 293 | run in_container asdf install cowsay "$combined_ver" 294 | # TODO: not sure I like matching on error message text... 295 | assert_output --partial "asdf python plugin is not installed!" 296 | } 297 | 298 | @test "install with asdf direnv no shims no global python" { 299 | 300 | # remove regular asdf setup 301 | # TODO: this seems fragile, consider refactoring 302 | in_container sed -i '/asdf.sh/d' /root/.profile 303 | 304 | # add asdf without shims in path 305 | # https://github.com/asdf-community/asdf-direnv#pro-tips 306 | # TODO: this seems fragile, consider refactoring 307 | in_container eval "echo \"PATH=\$PATH:\$HOME/.asdf/bin\" >> /root/.profile" 308 | in_container eval "echo source \$HOME/.asdf/lib/asdf.sh >> /root/.profile" 309 | 310 | setup_direnv 311 | 312 | local cowsay_ver="4.0" 313 | 314 | in_container asdf plugin add cowsay /root/asdf-pyapp 315 | 316 | in_container mkdir project 317 | in_container eval "echo \"cowsay 4.0\" >> project/.tool-versions" 318 | in_container eval "echo \"use asdf\" >> project/.envrc" 319 | 320 | in_container eval "cd project && $DIRENV && asdf install" 321 | 322 | run in_container readlink /root/.asdf/installs/cowsay/4.0/venv/bin/python3 323 | assert_output --partial /usr/bin/python3 324 | } 325 | 326 | @test "install with asdf direnv no shims with global python" { 327 | 328 | # remove regular asdf setup 329 | # TODO: this seems fragile, consider refactoring 330 | in_container sed -i '/asdf.sh/d' /root/.profile 331 | 332 | # add asdf without shims in path 333 | # https://github.com/asdf-community/asdf-direnv#pro-tips 334 | # TODO: this seems fragile, consider refactoring 335 | in_container eval "echo \"PATH=\$PATH:\$HOME/.asdf/bin\" >> /root/.profile" 336 | in_container eval "echo source \$HOME/.asdf/lib/asdf.sh >> /root/.profile" 337 | 338 | setup_direnv 339 | 340 | local cowsay_ver="4.0" 341 | local python_ver="3.8.10" 342 | 343 | in_container asdf global python $python_ver 344 | in_container asdf install 345 | 346 | in_container asdf plugin add cowsay /root/asdf-pyapp 347 | 348 | in_container mkdir project 349 | in_container eval "echo \"cowsay 4.0\" >> project/.tool-versions" 350 | in_container eval "echo \"use asdf\" >> project/.envrc" 351 | 352 | in_container eval "cd project && $DIRENV && asdf install" 353 | 354 | run in_container readlink /root/.asdf/installs/cowsay/4.0/venv/bin/python3 355 | #assert_output --partial /usr/bin/python3 356 | assert_output --partial /root/.asdf/installs/python/3.8.10/bin/python3 357 | } 358 | 359 | @test "check ASDF_PYAPP_VENV_COPY_MODE=1" { 360 | 361 | in_container asdf global python system 362 | 363 | in_container asdf plugin add cowsay /root/asdf-pyapp 364 | in_container eval "ASDF_PYAPP_VENV_COPY_MODE=1 asdf install cowsay 4.0" 365 | in_container asdf global cowsay 4.0 366 | in_container cowsay "woo woo" 367 | 368 | refute in_container readlink /root/.asdf/installs/cowsay/4.0/venv/bin/python3 369 | } 370 | 371 | @test "check ASDF_PYAPP_INCLUDE_DEPS=0 doesn't install executables of dependencies" { 372 | 373 | in_container asdf global python 3.8.10 374 | 375 | in_container eval "ASDF_PYAPP_INCLUDE_DEPS=0 asdf plugin add ansible /root/asdf-pyapp" 376 | in_container asdf install ansible latest 377 | in_container asdf global ansible latest 378 | 379 | ### Ansiblee should *not* be available 380 | refute in_container ansible --version 381 | } 382 | 383 | @test "check ASDF_PYAPP_INCLUDE_DEPS=1 installs executables of dependencies" { 384 | 385 | in_container asdf global python 3.8.10 386 | 387 | in_container eval "ASDF_PYAPP_INCLUDE_DEPS=1 asdf plugin add ansible /root/asdf-pyapp" 388 | in_container asdf install ansible latest 389 | in_container asdf global ansible latest 390 | 391 | assert in_container ansible --version 392 | } 393 | 394 | ################################################## 395 | # Individual App Checks 396 | 397 | check_app() { 398 | local app="$1" 399 | local version="$2" 400 | shift 401 | shift 402 | 403 | in_container asdf global python system 404 | in_container asdf plugin add "$app" /root/asdf-pyapp 405 | in_container asdf install "$app" "$version" 406 | in_container asdf global "$app" "$version" 407 | 408 | in_container $* 409 | } 410 | 411 | @test "check app awscli latest" { 412 | check_app awscli latest aws --version 413 | } 414 | 415 | @test "check app awsebcli latest" { 416 | check_app awsebcli latest eb --version 417 | } 418 | 419 | @test "check app aws-sam-cli latest" { 420 | check_app aws-sam-cli latest sam --version 421 | } 422 | 423 | @test "check app black latest" { 424 | skip 425 | # TODO: black latest doesn't work for some reason 426 | check_app black latest black --version 427 | } 428 | 429 | @test "check app black 21.5b2" { 430 | check_app black 21.5b2 black --version 431 | } 432 | 433 | @test "check app bpython latest" { 434 | check_app bpython latest bpython --version 435 | } 436 | 437 | @test "check app conan latest" { 438 | check_app conan latest conan --version 439 | } 440 | 441 | @test "check app doit latest" { 442 | check_app doit latest doit --version 443 | } 444 | 445 | @test "check app flake8 latest" { 446 | check_app flake8 latest flake8 --version 447 | } 448 | 449 | @test "check app hy latest" { 450 | skip 451 | # hy latest (0.20.0 atm) does not install with python 3.6 452 | check_app hy latest --version 453 | } 454 | 455 | @test "check app hy 0.20.0@3.8.2" { 456 | check_app hy 0.20.0@3.8.2 hy --version 457 | } 458 | 459 | @test "check app meson latest" { 460 | check_app meson latest meson --version 461 | } 462 | 463 | @test "check app mypy latest" { 464 | check_app mypy latest mypy --version 465 | } 466 | 467 | @test "check app pipenv latest" { 468 | check_app pipenv latest pipenv --version 469 | } 470 | 471 | @test "check app salt latest" { 472 | check_app salt latest salt --version 473 | } 474 | 475 | @test "check app sphinx latest" { 476 | check_app sphinx latest sphinx-build --version 477 | } 478 | 479 | @test "check app aws-ssm-tools latest" { 480 | check_app aws-ssm-tools latest ssm-session --version 481 | } 482 | 483 | @test "check app yawsso latest" { 484 | check_app yawsso latest yawsso --version 485 | } 486 | 487 | @test "check app pre-commit latest" { 488 | check_app pre-commit latest pre-commit --version 489 | } 490 | --------------------------------------------------------------------------------