From 9d6c88c905271564ef014edf27c3566caa21f09b Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Tue, 22 Aug 2023 16:22:08 -0700 Subject: [PATCH] Add a new `asdf direnv install` command Before this diff, it was difficult to install tools that depend on other tools. For example, here's what happens if I try to install a version of poetry on my machine: $ asdf install poetry 1.5.1 No version is set for command python3 Consider adding one of the following versions in your config file at python 3.10.2 python 3.8.10 curl: (23) Failure writing output to destination Cleanup: Something went wrong! 48 /home/jeremy/.asdf/plugins/poetry/bin/install: POETRY_HOME=$install_path python3 - --version "$version" $flags This is because my system doesn't actually have a global `python` or `python3`. While I *could* install python with my system's package manager, I'd rather re-use my asdf-managed python if at all possible. Plus, poetry's installer doesn't even work with the 2 most popular ways of installing python systemwide on macOS (see [this poetry issue](https://github.com/python-poetry/install.python-poetry.org/issues/24#issuecomment-1314368112) and [this homebrew issue](https://github.com/Homebrew/homebrew-core/issues/138159)). I know this doesn't solve the general build-dependencies issue: asdf is not a full package manager and I doubt it ever will become one. I do think this fairly minor change is worth implementing though, as it will solve a pain point my team has started running into ever since [homebrew changed they way they build binaries, which broke poetry install](https://github.com/Homebrew/homebrew-core/issues/138159). As a happy side effect, we can now run `direnv reload` for the user, which saves them a step whenever they need to install missing dependencies. --- CHANGELOG.md | 5 ++ lib/commands/command-install.bash | 8 +++ lib/install-lib.bash | 97 +++++++++++++++++++++++++++++++ lib/tools-environment-lib.bash | 35 ++++++++--- 4 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 lib/commands/command-install.bash create mode 100644 lib/install-lib.bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f4c9d8..b79f6e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,16 @@ github compare-link with the previous one. ## [Unreleased](https://github.com/asdf-community/asdf-direnv/compare/v0.3.0..master) +- Add new `asdf direnv install` command to help when installing tools that depend on each other. #180 + - Fix `find` warning. #178 + - Add '--no-touch-rc-file' option to prevent rc file modification during updates. #176 + - Alternatively, `export ASDF_DIRENV_NO_TOUCH_RC_FILE=1` to prevent rc file modification. #176 - Add support for resolving 'latest:X' in .tool-versions #136 + - Add support for resolving 'latest' in .tool-versions #168 ## [0.3.0](https://github.com/asdf-community/asdf-direnv/compare/v0.3.0) (2022-04-03) diff --git a/lib/commands/command-install.bash b/lib/commands/command-install.bash new file mode 100644 index 0000000..4c2a07c --- /dev/null +++ b/lib/commands/command-install.bash @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Exit on error, since this is an executable and not a sourced file. +set -Eeuo pipefail + +# shellcheck source=lib/install-lib.bash +source "$(dirname "${BASH_SOURCE[0]}")/../install-lib.bash" +install_command "$@" diff --git a/lib/install-lib.bash b/lib/install-lib.bash new file mode 100644 index 0000000..c8a3756 --- /dev/null +++ b/lib/install-lib.bash @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +# shellcheck source=lib/tools-environment-lib.bash +source "$(dirname "${BASH_SOURCE[0]}")/tools-environment-lib.bash" + +function print_usage() { + echo "Usage: asdf direnv install" + echo "" + echo "Installs tools needed for the current directory. This is very similar" + echo "to 'asdf install' except it installs the tools in the exact order specified" + echo "in the '.tool-versions' file, and loads the environment of each tool in order." + echo "" + echo "This is useful when installing tools that depend on other tools, for" + echo "example: the poetry plugin expects python to be available." + echo "" + echo "Note: this problem isn't entirely unique to asdf-direnv. asdf itself" + echo "isn't a full-fledged package manager. It simply doesn't understand" + echo "dependencies between plugins and therefore cannot do a dependency sort of" + echo "tools. You'll need to manually sort the lines of your '.tool-versions' file" + echo "for this to work as intended. See these discussions in core asdf for" + echo "more information:" + echo "" + echo " - https://github.com/asdf-vm/asdf/issues/929" + echo " - https://github.com/asdf-vm/asdf/issues/1127" + echo " - https://github.com/asdf-vm/asdf/issues/196" +} + +function install_command() { + while [[ $# -gt 0 ]]; do + arg=$1 + shift + case $arg in + -h | --help) + print_usage + exit 1 + ;; + *) + echo "Unknown option: $arg" + exit 1 + ;; + esac + done + + install_tools +} + +_load_asdf_functions_installs() { + # `install_tool_version` depends on `reshim_command` from reshim.bash. + # See https://github.com/asdf-vm/asdf/blob/v0.12.0/lib/functions/installs.bash#L243 + _load_asdf_lib reshim_command commands/reshim.bash + + _load_asdf_lib install_tool_version functions/installs.bash +} + +function maybe_install_tool_version() { + _load_asdf_functions_installs + + local install_path + install_path=$(get_install_path "$plugin_name" "version" "$version") + + if [ -d "$install_path" ]; then + printf "%s %s is already installed\n" "$plugin_name" "$version" + else + + # NOTE: we temporarily loosen the rules while invoking + # install_tool_version because it's from core asdf and it doesn't run + # well under "strict mode" (asdf_run_hook invokes get_asdf_config_value + # in a way such that if the config piece is missing, the program exits + # immediately if `set -e` is enabled. + ( + set +ue + install_tool_version "$plugin_name" "$version" + set -ue + ) + fi +} + +function install_tools { + local tools_file + tools_file="$(_local_versions_file)" + + while IFS=$'\n' read -r plugin_name; do + while IFS=$'\n' read -r version_and_path; do + local version _path + IFS='|' read -r version _path <<<"$version_and_path" + + # Install this tool version if not already installed. + maybe_install_tool_version "$plugin_name" "$version" + + # Load the tools environment so subsequent installs can use this tool. + direnv_code=$(_plugin_env_bash "$plugin_name" "$version" ">>> UH OH <<<") + eval "$direnv_code" + done <<<"$(_plugin_versions_and_path "$plugin_name")" + done <<<"$(_plugins_in_file "$tools_file")" + + direnv reload +} diff --git a/lib/tools-environment-lib.bash b/lib/tools-environment-lib.bash index 78f76c3..a8de1d9 100644 --- a/lib/tools-environment-lib.bash +++ b/lib/tools-environment-lib.bash @@ -213,22 +213,23 @@ _load_local_plugins_env() { fi } -# from asdf plugin_current_command -_load_plugin_version_and_file() { +function _plugin_versions_and_path() { local plugin_name=$1 local versions_and_path - # NOTE: temporary disable nounset since find_versions expects ASDF_DEFAULT_TOOL_VERSIONS_FILENAME to be set. + # NOTE: temporarily disable nounset since find_versions expects + # ASDF_DEFAULT_TOOL_VERSIONS_FILENAME to be set. versions_and_path="$( set +u find_versions "$plugin_name" "$(pwd)" set -u )" - if test -z "$versions_and_path"; then + if [ -z "$versions_and_path" ]; then return 0 fi local path path=$(cut -d '|' -f 2 <<<"$versions_and_path") + local versions=() while IFS=$' \t' read -r -a inline_versions; do for ((idx = ${#inline_versions[@]} - 1; idx >= 0; idx--)); do @@ -237,12 +238,30 @@ _load_plugin_version_and_file() { done <<<"$(cut -d '|' -f 1 <<<"$versions_and_path" | uniq | _tail_r)" for version in "${versions[@]}"; do - echo log_status "using asdf ${plugin_name} ${version}" - _plugin_env_bash "$plugin_name" "$version" "$plugin_name $version not installed. Run 'asdf install' and then 'direnv reload'." + printf '%q|%q\n' "$version" "$path" done - if [ -f "$path" ]; then - printf 'watch_file %q\n' "$path" +} + +# from asdf plugin_current_command +_load_plugin_version_and_file() { + local plugin_name=$1 + + local plugin_versions_and_path + plugin_versions_and_path="$(_plugin_versions_and_path "$plugin_name")" + if [ -z "$plugin_versions_and_path" ]; then + return 0 fi + + while IFS=$'\n' read -r version_and_path; do + local version path + IFS='|' read -r version path <<<"$version_and_path" + + echo log_status "using asdf ${plugin_name} ${version}" + _plugin_env_bash "$plugin_name" "$version" "$plugin_name $version not installed. Run 'asdf direnv install' to install." + if [ -f "$path" ]; then + printf 'watch_file %q\n' "$path" + fi + done <<<"$plugin_versions_and_path" } _new_items() {