diff --git a/docs/core-configuration.md b/docs/core-configuration.md index 4eae45374..fe661a56f 100644 --- a/docs/core-configuration.md +++ b/docs/core-configuration.md @@ -42,6 +42,7 @@ legacy_version_file = yes - `legacy_version_file` - defaults to `no`. If set to yes it will cause plugins that support this feature to read the version files used by other version managers (e.g. `.ruby-version` in the case of Ruby's `rbenv`). - `use_release_candidates` - defaults to `no`. If set to yes it will cause the `asdf update` command to upgrade to the latest release candidate release instead of the latest semantic version. +- `always_keep_download` - defaults to `no`. If set to `yes` it will cause `asdf install` always keep the source code or binary it downloads. If set to `no` the source code or binary downloaded by `asdf install` will be deleted after successful installation. ## Environment Variables diff --git a/docs/plugins-create.md b/docs/plugins-create.md index 58bd89538..193e9b99a 100644 --- a/docs/plugins-create.md +++ b/docs/plugins-create.md @@ -5,17 +5,25 @@ A plugin is a git repo, with a couple executable scripts, to support versioning ## Required Scripts - `bin/list-all` - lists all installable versions +- `bin/download` - download source code or binary for the specified version - `bin/install` - installs the specified version +## Environment Variables + All scripts except `bin/list-all` will have access to the following env vars to act upon: - `ASDF_INSTALL_TYPE` - `version` or `ref` - `ASDF_INSTALL_VERSION` - if `ASDF_INSTALL_TYPE` is `version` then this will be the version number. Else it will be the git ref that is passed. Might point to a tag/commit/branch on the repo. - `ASDF_INSTALL_PATH` - the dir where the it _has been_ installed (or _should_ be installed in case of the `bin/install` script) -These additional environment variables the `bin/install` script will also have accesss to: +These additional environment variables will be available to the `bin/install` script: - `ASDF_CONCURRENCY` - the number of cores to use when compiling the source code. Useful for setting `make -j`. +- `ASDF_DOWNLOAD_PATH` - the path to where the source code or binary was downloaded by the `bin/download` script. + +These additional environment variables will be available to the `bin/download` script: + +- `ASDF_DOWNLOAD_PATH` - the path to where the source code or binary should be downloaded. #### bin/list-all @@ -29,6 +37,16 @@ Note that the newest version should be listed last so it appears closer to the u If versions are being pulled from releases page on a website it's recommended to not sort the versions if at all possible. Often the versions are already in the correct order or, in reverse order, in which case something like `tac` should suffice. If you must sort versions manually you cannot rely on `sort -V` since it is not supported on OSX. An alternate sort function [like this is a better choice](https://github.com/vic/asdf-idris/blob/master/bin/list-all#L6). +#### bin/download + +This script must download the source or binary, in the path contained in the `ASDF_DOWNLOAD_PATH` environment variable. If the downloaded source or binary is compressed, only the uncompressed source code or binary may be placed in the `ASDF_DOWNLOAD_PATH` directory. + +The script must exit with a status of `0` when the download is successful. If the download fails the script must exit with any non-zero exit status. + +If possible the script should only place files in the `ASDF_DOWNLOAD_PATH`. If the download fails no files should be placed in the directory. + +If this script is not present asdf will assume that the `bin/install` script is present and will download and install the version. asdf only works without this script to support legacy plugins. All plugins must include this script, and eventually support for legacy plugins will be removed. + #### bin/install This script should install the version, in the path mentioned in `ASDF_INSTALL_PATH`. diff --git a/lib/commands/command-install.bash b/lib/commands/command-install.bash index c362ed3d4..cd4cbb14b 100644 --- a/lib/commands/command-install.bash +++ b/lib/commands/command-install.bash @@ -15,14 +15,15 @@ handle_cancel() { install_command() { local plugin_name=$1 local full_version=$2 + local extra_args="${*:3}" if [ "$plugin_name" = "" ] && [ "$full_version" = "" ]; then - install_local_tool_versions + install_local_tool_versions "$extra_args" elif [[ $# -eq 1 ]]; then display_error "You must specify a name and a version to install" exit 1 else - install_tool_version "$plugin_name" "$full_version" + install_tool_version "$plugin_name" "$full_version" "$extra_args" fi } @@ -80,10 +81,25 @@ install_local_tool_versions() { install_tool_version() { local plugin_name=$1 local full_version=$2 + local flags=$3 + local keep_download local plugin_path + plugin_path=$(get_plugin_path "$plugin_name") check_if_plugin_exists "$plugin_name" + for flag in $flags; do + case "$flag" in + "--keep-download") + keep_download=true + shift + ;; + *) + shift + ;; + esac + done + if [ "$full_version" = "system" ]; then return fi @@ -106,6 +122,8 @@ install_tool_version() { local install_path install_path=$(get_install_path "$plugin_name" "$install_type" "$version") + local download_path + download_path=$(get_download_path "$plugin_name" "$install_type" "$version") local concurrency concurrency=$(get_concurrency) trap 'handle_cancel $install_path' INT @@ -113,10 +131,35 @@ install_tool_version() { if [ -d "$install_path" ]; then echo "$plugin_name $full_version is already installed" else + + if [ -f "${plugin_path}/bin/download" ]; then + # Not a legacy plugin + # Run the download script + ( + # shellcheck disable=SC2030 + export ASDF_INSTALL_TYPE=$install_type + # shellcheck disable=SC2030 + export ASDF_INSTALL_VERSION=$version + # shellcheck disable=SC2030 + export ASDF_INSTALL_PATH=$install_path + # shellcheck disable=SC2030 + export ASDF_DOWNLOAD_PATH=$download_path + mkdir "$download_path" + asdf_run_hook "pre_asdf_download_${plugin_name}" "$full_version" + bash "${plugin_path}"/bin/download + ) + fi + ( + # shellcheck disable=SC2031 export ASDF_INSTALL_TYPE=$install_type + # shellcheck disable=SC2031 export ASDF_INSTALL_VERSION=$version + # shellcheck disable=SC2031 export ASDF_INSTALL_PATH=$install_path + # shellcheck disable=SC2031 + export ASDF_DOWNLOAD_PATH=$download_path + # shellcheck disable=SC2031 export ASDF_CONCURRENCY=$concurrency mkdir "$install_path" asdf_run_hook "pre_asdf_install_${plugin_name}" "$full_version" @@ -125,7 +168,14 @@ install_tool_version() { local exit_code=$? if [ $exit_code -eq 0 ]; then + # Remove download directory if --keep-download flag or always_keep_download config setting are not set + always_keep_download=$(get_asdf_config_value "always_keep_download") + if [ ! "$keep_download" = "true" ] && [ ! "$always_keep_download" = "yes" ] && [ -d "$download_path" ]; then + rm -r "$download_path" + fi + asdf reshim "$plugin_name" "$full_version" + asdf_run_hook "post_asdf_install_${plugin_name}" "$full_version" else handle_failure "$install_path" diff --git a/lib/commands/command-plugin-remove.bash b/lib/commands/command-plugin-remove.bash index cd0c6a477..90df543fa 100644 --- a/lib/commands/command-plugin-remove.bash +++ b/lib/commands/command-plugin-remove.bash @@ -19,6 +19,7 @@ plugin_remove_command() { rm -rf "$plugin_path" rm -rf "$(asdf_data_dir)/installs/${plugin_name}" + rm -rf "$(asdf_data_dir)/downloads/${plugin_name}" grep -l "asdf-plugin: ${plugin_name}" "$(asdf_data_dir)"/shims/* 2>/dev/null | xargs rm -f diff --git a/lib/utils.bash b/lib/utils.bash index 2f8566bbc..6d663df51 100644 --- a/lib/utils.bash +++ b/lib/utils.bash @@ -67,6 +67,25 @@ get_install_path() { fi } +get_download_path() { + local plugin=$1 + local install_type=$2 + local version=$3 + + local download_dir + download_dir="$(asdf_data_dir)/downloads" + + mkdir -p "${download_dir}/${plugin}" + + if [ "$install_type" = "version" ]; then + echo "${download_dir}/${plugin}/${version}" + elif [ "$install_type" = "path" ]; then + return + else + echo "${download_dir}/${plugin}/${install_type}-${version}" + fi +} + list_installed_versions() { local plugin_name=$1 local plugin_path diff --git a/test/fixtures/dummy_legacy_plugin/bin/get-version-from-legacy-file b/test/fixtures/dummy_legacy_plugin/bin/get-version-from-legacy-file new file mode 100755 index 000000000..801739d37 --- /dev/null +++ b/test/fixtures/dummy_legacy_plugin/bin/get-version-from-legacy-file @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +get_legacy_version() { + current_directory=$1 + version_file="$current_directory/.dummy-version" + + if [ -f "$version_file" ]; then + cat "$version_file" + fi +} + +get_legacy_version "$1" diff --git a/test/fixtures/dummy_legacy_plugin/bin/install b/test/fixtures/dummy_legacy_plugin/bin/install new file mode 100644 index 000000000..d51701226 --- /dev/null +++ b/test/fixtures/dummy_legacy_plugin/bin/install @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +mkdir -p "$ASDF_INSTALL_PATH" +env >"$ASDF_INSTALL_PATH/env" +echo "$ASDF_INSTALL_VERSION" >"$ASDF_INSTALL_PATH/version" + +# create the dummy executable +mkdir -p "$ASDF_INSTALL_PATH/bin" +cat <"$ASDF_INSTALL_PATH/bin/dummy" +echo This is Dummy ${ASDF_INSTALL_VERSION}! \$2 \$1 +EOF +chmod +x "$ASDF_INSTALL_PATH/bin/dummy" +mkdir -p "$ASDF_INSTALL_PATH/bin/subdir" +cat <"$ASDF_INSTALL_PATH/bin/subdir/other_bin" +echo This is Other Bin ${ASDF_INSTALL_VERSION}! \$2 \$1 +EOF +chmod +x "$ASDF_INSTALL_PATH/bin/subdir/other_bin" diff --git a/test/fixtures/dummy_legacy_plugin/bin/list-all b/test/fixtures/dummy_legacy_plugin/bin/list-all new file mode 100644 index 000000000..a9f42af27 --- /dev/null +++ b/test/fixtures/dummy_legacy_plugin/bin/list-all @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +versions_list=(1.0 1.1 2.0) +echo "${versions_list[@]}" diff --git a/test/fixtures/dummy_legacy_plugin/bin/list-legacy-filenames b/test/fixtures/dummy_legacy_plugin/bin/list-legacy-filenames new file mode 100644 index 000000000..ba4feb91b --- /dev/null +++ b/test/fixtures/dummy_legacy_plugin/bin/list-legacy-filenames @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo ".dummy-version .dummyrc" diff --git a/test/fixtures/dummy_legacy_plugin/bin/parse-legacy-file b/test/fixtures/dummy_legacy_plugin/bin/parse-legacy-file new file mode 100644 index 000000000..8a8f13404 --- /dev/null +++ b/test/fixtures/dummy_legacy_plugin/bin/parse-legacy-file @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2020 +tr <"$1" -d "dummy-" diff --git a/test/fixtures/dummy_plugin/bin/download b/test/fixtures/dummy_plugin/bin/download new file mode 100755 index 000000000..0fdcf0910 --- /dev/null +++ b/test/fixtures/dummy_plugin/bin/download @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +exit 0 diff --git a/test/install_command.bats b/test/install_command.bats index e34992477..97b7c6378 100644 --- a/test/install_command.bats +++ b/test/install_command.bats @@ -4,6 +4,7 @@ load test_helpers setup() { setup_asdf_dir + install_dummy_legacy_plugin install_dummy_plugin PROJECT_DIR=$HOME/project @@ -20,6 +21,12 @@ teardown() { [ $(cat $ASDF_DIR/installs/dummy/1.1/version) = "1.1" ] } +@test "install_command installs the correct version for plugins without download script" { + run asdf install legacy-dummy 1.1 + [ "$status" -eq 0 ] + [ $(cat $ASDF_DIR/installs/legacy-dummy/1.1/version) = "1.1" ] +} + @test "install_command without arguments installs even if the user is terrible and does not use newlines" { cd $PROJECT_DIR echo -n 'dummy 1.2' > ".tool-versions" @@ -56,6 +63,14 @@ teardown() { [ "$status" -eq 0 ] } +@test "install_command should create a shim with asdf-plugin metadata for plugins without download script" { + run asdf install legacy-dummy 1.0 + [ "$status" -eq 0 ] + [ -f $ASDF_DIR/installs/legacy-dummy/1.0/env ] + run grep "asdf-plugin: legacy-dummy 1.0" $ASDF_DIR/shims/dummy + [ "$status" -eq 0 ] +} + @test "install_command on two versions should create a shim with asdf-plugin metadata" { run asdf install dummy 1.1 [ "$status" -eq 0 ] @@ -200,3 +215,26 @@ EOM [ "$status" -eq 0 ] [ $(cat $ASDF_DIR/installs/dummy/1.1/version) = "1.1" ] } + +@test "install_command deletes the download directory" { + run asdf install dummy 1.1 + [ "$status" -eq 0 ] + [ ! -d $ASDF_DIR/downloads/dummy/1.1 ] + [ $(cat $ASDF_DIR/installs/dummy/1.1/version) = "1.1" ] +} + +@test "install_command keeps the download directory when --keep-download flag is provided" { + run asdf install dummy 1.1 --keep-download + [ "$status" -eq 0 ] + [ -d $ASDF_DIR/downloads/dummy/1.1 ] + [ $(cat $ASDF_DIR/installs/dummy/1.1/version) = "1.1" ] +} + +@test "install_command keeps the download directory when always_keep_download setting is true" { + echo 'always_keep_download = yes' > $HOME/.asdfrc + run asdf install dummy 1.1 + echo $output + [ "$status" -eq 0 ] + [ -d $ASDF_DIR/downloads/dummy/1.1 ] + [ $(cat $ASDF_DIR/installs/dummy/1.1/version) = "1.1" ] +} diff --git a/test/plugin_remove_command.bats b/test/plugin_remove_command.bats new file mode 100644 index 000000000..f31837b85 --- /dev/null +++ b/test/plugin_remove_command.bats @@ -0,0 +1,29 @@ +#!/usr/bin/env bats + +load test_helpers + +setup() { + setup_asdf_dir + install_dummy_plugin +} + +teardown() { + clean_asdf_dir +} + +@test "plugin_remove command removes the plugin directory" { + run asdf install dummy 1.0 + [ "$status" -eq 0 ] + [ -d "$ASDF_DIR/downloads/dummy" ] + + + run asdf plugin-remove "dummy" + [ "$status" -eq 0 ] + [ ! -d "$ASDF_DIR/downloads/dummy" ] +} + +@test "plugin_remove command fails if the plugin doesn't exist" { + run asdf plugin-remove "does-not-exist" + [ "$status" -eq 1 ] + echo "$output" | grep "No such plugin: does-not-exist" +} diff --git a/test/test_helpers.bash b/test/test_helpers.bash index 2728104b0..de6d40a06 100644 --- a/test/test_helpers.bash +++ b/test/test_helpers.bash @@ -27,6 +27,12 @@ install_mock_plugin() { cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugin" "$location/plugins/$plugin_name" } +install_mock_legacy_plugin() { + local plugin_name=$1 + local location="${2:-$ASDF_DIR}" + cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_legacy_plugin" "$location/plugins/$plugin_name" +} + install_mock_plugin_repo() { local plugin_name=$1 local location="${BASE_DIR}/repo-${plugin_name}" @@ -49,6 +55,10 @@ install_dummy_plugin() { install_mock_plugin "dummy" } +install_dummy_legacy_plugin() { + install_mock_legacy_plugin "legacy-dummy" +} + install_dummy_version() { install_mock_plugin_version "dummy" "$1" } diff --git a/test/utils.bats b/test/utils.bats index 6bff52bd4..6bd99bbbb 100644 --- a/test/utils.bats +++ b/test/utils.bats @@ -18,6 +18,47 @@ teardown() { clean_asdf_dir } +@test "get_install_path should output version path when version is provided" { + run get_install_path foo version "1.0.0" + [ "$status" -eq 0 ] + install_path=${output#$HOME/} + [ "$install_path" = ".asdf/installs/foo/1.0.0" ] +} + +@test "get_install_path should output custom path when custom install type is provided" { + run get_install_path foo custom "1.0.0" + [ "$status" -eq 0 ] + install_path=${output#$HOME/} + [ "$install_path" = ".asdf/installs/foo/custom-1.0.0" ] +} + +@test "get_install_path should output path when path version is provided" { + run get_install_path foo path "/some/path" + [ "$status" -eq 0 ] + [ "$output" = "/some/path" ] +} + +@test "get_download_path should output version path when version is provided" { + run get_download_path foo version "1.0.0" + [ "$status" -eq 0 ] + download_path=${output#$HOME/} + echo $download_path + [ "$download_path" = ".asdf/downloads/foo/1.0.0" ] +} + +@test "get_download_path should output custom path when custom download type is provided" { + run get_download_path foo custom "1.0.0" + [ "$status" -eq 0 ] + download_path=${output#$HOME/} + [ "$download_path" = ".asdf/downloads/foo/custom-1.0.0" ] +} + +@test "get_download_path should output nothing when path version is provided" { + run get_download_path foo path "/some/path" + [ "$status" -eq 0 ] + [ "$output" = "" ] +} + @test "check_if_version_exists should exit with 1 if plugin does not exist" { run check_if_version_exists "inexistent" "1.0.0" [ "$status" -eq 1 ]