Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: install dependencies in .tool-versions order #1723

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
233 changes: 218 additions & 15 deletions lib/functions/installs.bash
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ install_local_tool_versions() {
local search_path
search_path=$PWD

local some_tools_installed
local some_plugin_not_installed

local tool_versions_path
Expand All @@ -102,6 +101,7 @@ install_local_tool_versions() {
fi

# Locate all the plugins defined in the versions file.
# This just checks the current directory
local tools_file
if [ -f "$tool_versions_path" ]; then
tools_file=$(strip_tool_version_comments "$tool_versions_path" | cut -d ' ' -f 1)
Expand All @@ -117,30 +117,189 @@ install_local_tool_versions() {
exit 1
fi

local tools_installed
if [ -n "$plugins_installed" ]; then
for plugin_name in $plugins_installed; do
local plugin_version_and_path
plugin_version_and_path="$(find_versions "$plugin_name" "$search_path")"

if [ -n "$plugin_version_and_path" ]; then
local plugin_version
some_tools_installed='yes'
plugin_versions=$(cut -d '|' -f 1 <<<"$plugin_version_and_path")
for plugin_version in $plugin_versions; do
install_tool_version "$plugin_name" "$plugin_version"
done
fi
done
plugins_installed=$(printf "%s" "$plugins_installed" | tr "\n" " " | awk '{$1=$1};1')
display_debug "install_local_tool_versions: plugins_installed='$plugins_installed'"
tools_installed=$(install_directory_tools_recursive "$search_path" "$plugins_installed")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the $() on lines 122 and 124 should be quoted.

fi

if [ -z "$some_tools_installed" ]; then
if [ -z "$tools_installed" ]; then
printf "Either specify a tool & version in the command\n"
printf "OR add .tool-versions file in this directory\n"
printf "or in a parent directory\n"
exit 1
fi
}

install_directory_tools_recursive() {
local search_path=$1
local plugins_installed=$2
local tools_installed=""

display_debug "install_directory_tools_recursive '$search_path': entered with plugins_installed='$plugins_installed'"

while [ "$search_path" != "/" ]; do
# install tools from files in current directory
display_debug_hr
tools_installed=$(install_directory_tools "$search_path" "$plugins_installed" "$tools_installed")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quote this one too I think.

display_debug "install_directory_tools_recursive '$search_path': install_directory_tools returned tools_installed='$tools_installed'"

# terminate if all tools are installed
if [[ -n $(stringlist_a_subset_of_b "$plugins_installed" "$tools_installed") ]]; then
display_debug "install_directory_tools_recursive '$search_path': exiting because all known tools ($plugins_installed) are installed ($tools_installed)"
printf "%s\n" "$tools_installed"
return 0
fi

# go up a directory
search_path=$(dirname "$search_path")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one as well. paths can contain spaces but they should be treated like a single string.

done

printf "%s\n" "$tools_installed"
}

install_directory_tools() {
local search_path=$1
local plugins_installed=$2
local tools_installed=$3
display_debug "install_directory_tools '$search_path': starting install. tools_installed='$tools_installed'"

# install tools from .tool-versions
# install order is the order listed in .tool-versions
file_name=$(asdf_tool_versions_filename)
tools_installed=$(_install_directory_tools "$search_path" "$file_name" "$plugins_installed" "$tools_installed")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm used to wrapping everything in quotes. Is there a reason the $() on these two lines aren't quoted? Like this:

file_name="$(asdf_tool_versions_filename)"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary. Word splitting is not done on values in assignments (see Bash Shell Parameters), i.e., quotes are not needed when assigning command substitutions ($(...)) or variables ($var). E.g.:

foo='a space-filled value'
bar=$foo
echo $bar

a space-filled value

foo=$(echo this is a command substitution with spaces)
echo $foo

this is a command substitution with spaces


# install tools from legacy version files
# install order is plugin order which is alphabetical
local legacy_config
legacy_config=$(get_asdf_config_value "legacy_version_file")
if [ "$legacy_config" = "yes" ]; then
tools_installed=$(_install_directory_tools_legacy "$search_path" "$plugins_installed" "$tools_installed")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote $() here too I think.

fi

printf "%s\n" "$tools_installed"
}

_install_directory_tools() {
local search_path=$1
local file_name=$2
local plugins_installed=$3
local tools_installed=$4

local tool_versions
if ! [[ -f "$search_path/$file_name" ]]; then
display_debug "_install_directory_tools '$search_path': exiting early... $file_name file not found"
printf "%s\n" "$tools_installed"
return 0
fi

tool_versions=$(strip_tool_version_comments "$search_path/$file_name" | awk '{$1=$1};1')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want this $() to be quoted right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure to be honest. I noticed that most of the assignments in the installs.bash file do not put quotes around the $(). When I ran the linter, the linter pulled up times where I had passed a $() to a bash function if I didn't wrap it in quotes. It would give the following warning: SC2046 (warning): Quote this to prevent word splitting.. This makes me think that variable assignment is okay but potentially passing as arguments without wrapping in quotes is the one that isn't right. Not sure though, no expert on bash myself.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically if the value produced by the expression contains spaces and you want it to stay as a string you should quote - string_with_spaces="$(echo "string with spaces")". I think that is the case here, but I may be wrong. Generally, quoting is safer in Bash (unless you are dealing with an array, in which case it won't work).

if [[ -z $tool_versions ]]; then
display_debug "_install_directory_tools '$search_path': exiting early... no tools found in directory"
printf "%s\n" "$tools_installed"
return 0
fi
while IFS=' ' read -r tool_version; do
display_debug "_install_directory_tools '$search_path': found '$tool_version'"

# read one version from the file
IFS=' ' read -ra parts <<<"$tool_version"
local plugin_name
plugin_name=${parts[0]}

# skip if plugin is installed already
if [[ -n $(stringlist_contains "$tools_installed" "$plugin_name") ]]; then
display_debug "_install_directory_tools '$search_path': '$plugin_name' is already installed... skipping"
continue
fi

# install the versions
local plugin_version
for plugin_version in "${parts[@]:1}"; do
# install the version
display_none "$(install_tool_version "$plugin_name" "$plugin_version")"
tools_installed=$(printf "%s %s" "$tools_installed" "$plugin_name" | awk '{$1=$1};1')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote $() here too I think.

done

# check if there is an environment override for it
# if so also install the environment version
local env_version
env_version=$(get_version_from_env "$plugin_name")
if [ -n "$env_version" ]; then
display_debug "_install_directory_tools '$search_path': $plugin_name: using environment override $env_version"
plugin_version=$env_version

# install the version
display_none "$(install_tool_version "$plugin_name" "$plugin_version")"
tools_installed=$(printf "%s %s" "$tools_installed" "$plugin_name" | awk '{$1=$1};1')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote here.

fi

display_debug "_install_directory_tools '$search_path': installed '$plugin_name':'$plugin_version' new state of tools_installed='$tools_installed'"
done <<<"$tool_versions"

printf "%s\n" "$tools_installed"
}

_install_directory_tools_legacy() {
local search_path=$1
local plugins_installed=$2
local tools_installed=$3

display_debug "_install_directory_tools_legacy '$search_path': resolving legacy files"
local plugin_name
for plugin_name in $plugins_installed; do
# skip if plugin is installed already
if [[ -n $(stringlist_contains "$tools_installed" "$plugin_name") ]]; then
display_debug "_install_directory_tools_legacy '$search_path': legacy_install $plugin_name: skipping as tool was already installed"
continue
fi

# extract plugin legacy information
local plugin_path
plugin_path=$(get_plugin_path "$plugin_name")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote here. Path may contain whitespace.

local legacy_list_filenames_script
legacy_list_filenames_script="${plugin_path}/bin/list-legacy-filenames"

# skip if no legacy_list_filenames_script available
if ! [[ -f "$legacy_list_filenames_script" ]]; then
display_debug "_install_directory_tools_legacy '$search_path': legacy_install $plugin_name: skipping as legacy files are not supported"
continue
fi

# extract plugin legacy filenames
local legacy_filenames=""
legacy_filenames=$("$legacy_list_filenames_script")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For good measure quote here. I can't imagine any legacy file names would contain whitespace since these are typically filenames chosen for software developers, but it won't hurt.


# lookup plugin version in current dir
local plugin_version
plugin_version=$(get_legacy_version_in_dir "$plugin_name" "$search_path" "$legacy_filenames")

# check if there is an environment override for it
# if so take the environment version
local env_version
env_version=$(get_version_from_env "$plugin_name")
if [ -n "$env_version" ]; then
display_debug "_install_directory_tools_legacy '$search_path': legacy_install $plugin_name: using environment override $env_version"
plugin_version=$env_version
fi

# skip if version cannot be found
if [ -z "$plugin_version" ]; then
display_debug "_install_directory_tools_legacy '$search_path': legacy_install $plugin_name: skipping as version cannot be found"
continue
fi

display_debug "_install_directory_tools_legacy '$search_path': legacy_install $plugin_name: plugin_version='$plugin_version'"
display_none "$(install_tool_version "$plugin_name" "$plugin_version")"

tools_installed=$(printf "%s %s" "$tools_installed" "$plugin_name" | awk '{$1=$1};1')
display_debug "_install_directory_tools_legacy '$search_path': legacy_install $plugin_name: installed '$plugin_version' new state of tools_installed='$tools_installed'"
done

printf "%s\n" "$tools_installed"
}

install_tool_version() {
local plugin_name=$1
local full_version=$2
Expand Down Expand Up @@ -253,3 +412,47 @@ install_tool_version() {
fi
fi
}

get_legacy_version_in_dir() {
local plugin_name=$1
local search_path=$2
local legacy_filenames=$3

local asdf_version
for filename in $legacy_filenames; do
local legacy_version
legacy_version=$(parse_legacy_version_file "$search_path/$filename" "$plugin_name")

if [ -n "$legacy_version" ]; then
printf "%s\n" "$legacy_version"
return 0
fi
done
}

stringlist_a_subset_of_b() {
local list_a=$1
local list_b=$2
local array_a
IFS=' ' read -r -a array_a <<<"$list_a"
for item_a in "${array_a[@]}"; do
if [[ -z $(stringlist_contains "$list_b" "$item_a") ]]; then
return 0
fi
done
printf "true\n"
return 0
}

stringlist_contains() {
local list=$1
local search=$2
local array
IFS=' ' read -r -a array <<<"$list"
for item in "${array[@]}"; do
if [ "$item" = "$search" ]; then
printf "true\n"
return 0
fi
done
}
14 changes: 14 additions & 0 deletions lib/utils.bash
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,20 @@ display_error() {
printf "%s\n" "$1" >&2
}

display_debug_hr() {
display_debug "--------------------------------------------------------------------------------------------------------------"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we only use this function once. Are we certain we need it?


display_debug() {
if [[ $DEBUG = "true" ]]; then
printf "debug: %s\n" "$1" >&2
fi
}

display_none() {
printf ""
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is display_none for?


get_version_in_dir() {
local plugin_name=$1
local search_path=$2
Expand Down
2 changes: 2 additions & 0 deletions scripts/lint.bash
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ run_shfmt_stylecheck() {
test/test_helpers.bash \
test/fixtures/dummy_broken_plugin/bin/* \
test/fixtures/dummy_legacy_plugin/bin/* \
test/fixtures/dummy_dependent_plugin/bin/* \
test/fixtures/dummy_plugin/bin/*

print.info "Checking .bats with shfmt"
Expand All @@ -65,6 +66,7 @@ run_shellcheck_linter() {
test/test_helpers.bash \
test/fixtures/dummy_broken_plugin/bin/* \
test/fixtures/dummy_legacy_plugin/bin/* \
test/fixtures/dummy_dependent_plugin/bin/* \
test/fixtures/dummy_plugin/bin/*

print.info "Checking .bats files with Shellcheck"
Expand Down
20 changes: 20 additions & 0 deletions test/fixtures/dummy_dependent_plugin/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
The MIT License (MIT)

Copyright (c) 2014 Akash Manohar J

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.
17 changes: 17 additions & 0 deletions test/fixtures/dummy_dependent_plugin/bin/install
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

# recurse 2 directories up to get to the
# installs directory
_ASDF_PATH=$(dirname "$ASDF_INSTALL_PATH")
_ASDF_PATH=$(dirname "$_ASDF_PATH")
DUMMY_EXPECTED_INSTALL="$_ASDF_PATH/dummy/1.2.0/version"

mkdir -p "$ASDF_INSTALL_PATH"
if ! [[ -f "$DUMMY_EXPECTED_INSTALL" ]]; then
echo "Could not detect dependent plugin dummy. at path $DUMMY_EXPECTED_INSTALL"
exit 1
fi
echo "Successfully detected depdendent plugin dummy."

env >"$ASDF_INSTALL_PATH/env"
echo "$ASDF_INSTALL_VERSION" >"$ASDF_INSTALL_PATH/version"
10 changes: 10 additions & 0 deletions test/install_command.bats
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ setup() {
install_dummy_legacy_plugin
install_dummy_plugin
install_dummy_broken_plugin
install_dummy_dependent_plugin

PROJECT_DIR="$HOME/project"
mkdir -p "$PROJECT_DIR"
Expand Down Expand Up @@ -36,6 +37,15 @@ teardown() {
[ "$(cat "$ASDF_DIR/installs/dummy/1.2.0/version")" = "1.2.0" ]
}

@test "install_command installs plugins in order" {
cd "$PROJECT_DIR"
printf 'dummy 1.2.0\ndummy-dependent 1.3.0' >".tool-versions"
run asdf install
[ "$status" -eq 0 ]
[ "$(cat "$ASDF_DIR/installs/dummy/1.2.0/version")" = "1.2.0" ]
[ "$(cat "$ASDF_DIR/installs/dummy-dependent/1.3.0/version")" = "1.3.0" ]
}

@test "install_command with only name installs the version in .tool-versions" {
cd "$PROJECT_DIR"
echo -n 'dummy 1.2.0' >".tool-versions"
Expand Down
10 changes: 10 additions & 0 deletions test/test_helpers.bash
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ install_mock_broken_plugin() {
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_broken_plugin" "$location/plugins/$plugin_name"
}

install_mock_dependent_plugin() {
local plugin_name=$1
local location="${2:-$ASDF_DIR}"
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_dependent_plugin" "$location/plugins/$plugin_name"
}

install_mock_plugin_repo() {
local plugin_name=$1
local location="${BASE_DIR}/repo-${plugin_name}"
Expand Down Expand Up @@ -82,6 +88,10 @@ install_dummy_broken_plugin() {
install_mock_broken_plugin "dummy-broken"
}

install_dummy_dependent_plugin() {
install_mock_dependent_plugin "dummy-dependent"
}

install_dummy_version() {
install_mock_plugin_version "dummy" "$1"
}
Expand Down