Skip to content

Commit 8b175d3

Browse files
committed
feat: Enable use of latest: in .tool-versions files
This change enables `asdf`'s existing latest-version-resolution functionality within the `.tool-versions` file itself. Rather than having to have a `.tool-versions` file that contains a full version number: ``` java corretto-21.0.5.11.1 ``` ...you can now use the same `latest:` syntax that is already available in the `local` & `global` commands, ie: ``` java latest:corretto-21 ``` ### Use case For many tool/runtime ecosystems (eg Java), if a program runs correctly under a specific version of that runtime, it can generally be relied on to run correctly under any _later_ version of that runtime with the same major version number (eg if a project runs under Corretto Java 21.0.5.11.1, it will run on any _later_ version of Corretto Java 21). This means that for projects in those ecosystems, there is little incentive to pin to fully-specified versions like `21.0.5.11.1`, and in fact there are downsides - over time, developers will default to using older, unpatched versions of Java, unless they are assiduous in continually updating the contents of the `.tool-versions` file, or have tooling devoted to doing so. At the Guardian we have several hundred projects that run on the Java platform, and due to our security obligations we generally want to be running under the _latest_ security-patched version of the Java runtime that matches our major-version requirement. We love `asdf` as a tool, and like that the `.tool-versions` file can become a source-of-truth documenting which version of Java a project uses, but we don't want to have to commit fully-specified version numbers like `21.0.5.11.1` to source control, or set up tooling to increment those version numbers across those hundreds of repositories. Allowing the use of `latest:` in the `.tool-versions` file means that we don't need to continually update those `.tool-versions` files. It also partially addresses some of the needs raised by #1736, though this solution uses the existing `asdf` version-resolution functionality, rather than adopting the version requirements system used in nodejs. ### Implementation A new `resolve_version_spec()` function has been extracted from the existing `version_command()` function. This takes a version-spec string, like `latest:corretto-11` or `corretto-21.0.5.11.1`, and resolves it to a definite installed version number (if the resolved version is not installed, the appropriate error message is shown). This new `resolve_version_spec()` function is now also called in `select_version()`, used by `with_shim_executable()`, meaning that any execution of the `asdf` shim (eg, executing `java`) will now resolve any version specifications found in the `.tool-versions` file - if `.tool-versions` contains `java latest:corretto-21`, this will be resolved and the latest version of Java 21 used. ## Other Information Previous `asdf` PRs relating to `latest`: * #575 in November 2019: added the `latest` command, eg `asdf latest python 3.6` reports the latest version of Python 3.6. * #633 in July 2021: made it possible to specify `latest` when using the `local` & `global` commands, eg: `asdf local python latest:3.7` - this would save a precise version number to `.tools-versions`, which is undesired behaviour for us at the Guardian. A couple of Guardian systems attempting to standardise on using `.tool-versions` as a source of truth: * guardian/gha-scala-library-release-workflow#36 * https://github.com/guardian/setup-scala
1 parent c5116dc commit 8b175d3

File tree

9 files changed

+49
-11
lines changed

9 files changed

+49
-11
lines changed

lib/commands/command-current.bash

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# -*- sh -*-
22
# shellcheck source=lib/functions/plugins.bash
33
. "$(dirname "$(dirname "$0")")/lib/functions/plugins.bash"
4+
# shellcheck source=lib/functions/versions.bash
5+
. "$(dirname "$(dirname "$0")")/lib/functions/versions.bash"
46

57
# shellcheck disable=SC2059
68
plugin_current_command() {
@@ -21,7 +23,8 @@ plugin_current_command() {
2123
local description=""
2224

2325
IFS=' ' read -r -a versions <<<"$full_version"
24-
for version in "${versions[@]}"; do
26+
for version_spec in "${versions[@]}"; do
27+
version="$(resolve_version_spec "$version_spec")"
2528
if ! (check_if_version_exists "$plugin_name" "$version"); then
2629
version_not_installed="$version"
2730
fi

lib/commands/command-env.bash

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- sh -*-
2+
# shellcheck source=lib/functions/versions.bash
3+
. "$(dirname "$(dirname "$0")")/lib/functions/versions.bash"
24

35
shim_env_command() {
46
local shim_name="$1"

lib/commands/command-exec.bash

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- sh -*-
2+
# shellcheck source=lib/functions/versions.bash
3+
. "$(dirname "$(dirname "$0")")/lib/functions/versions.bash"
24

35
shim_exec_command() {
46
local shim_name

lib/commands/command-which.bash

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- sh -*-
2+
# shellcheck source=lib/functions/versions.bash
3+
. "$(dirname "$(dirname "$0")")/lib/functions/versions.bash"
24

35
which_command() {
46
local shim_name

lib/functions/versions.bash

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,7 @@ version_command() {
3737
declare -a resolved_versions
3838
local item
3939
for item in "${!versions[@]}"; do
40-
IFS=':' read -r -a version_info <<<"${versions[$item]}"
41-
if [ "${version_info[0]}" = "latest" ] && [ -n "${version_info[1]}" ]; then
42-
version=$(latest_command "$plugin_name" "${version_info[1]}")
43-
elif [ "${version_info[0]}" = "latest" ] && [ -z "${version_info[1]}" ]; then
44-
version=$(latest_command "$plugin_name")
45-
else
46-
# if branch handles ref: || path: || normal versions
47-
version="${versions[$item]}"
48-
fi
40+
version="$(resolve_version_spec "${versions[$item]}")"
4941

5042
# check_if_version_exists should probably handle if either param is empty string
5143
if [ -z "$version" ]; then
@@ -79,6 +71,22 @@ version_command() {
7971
fi
8072
}
8173

74+
resolve_version_spec() {
75+
local version_spec=$1
76+
77+
IFS=':' read -r -a version_info <<<"$version_spec"
78+
if [ "${version_info[0]}" = "latest" ] && [ -n "${version_info[1]}" ]; then
79+
version=$(latest_command "$plugin_name" "${version_info[1]}")
80+
elif [ "${version_info[0]}" = "latest" ] && [ -z "${version_info[1]}" ]; then
81+
version=$(latest_command "$plugin_name")
82+
else
83+
# if branch handles ref: || path: || normal versions
84+
version="$version_spec"
85+
fi
86+
87+
printf "%s\n" "$version"
88+
}
89+
8290
list_all_command() {
8391
local plugin_name=$1
8492
local query=$2

lib/utils.bash

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,8 @@ select_version() {
736736
version_and_path=$(find_versions "$plugin_name" "$search_path")
737737
IFS='|' read -r version_string _path <<<"$version_and_path"
738738
IFS=' ' read -r -a usable_plugin_versions <<<"$version_string"
739-
for plugin_version in "${usable_plugin_versions[@]}"; do
739+
for version_spec in "${usable_plugin_versions[@]}"; do
740+
plugin_version="$(resolve_version_spec "$version_spec")"
740741
for plugin_and_version in "${shim_versions[@]}"; do
741742
local plugin_shim_name
742743
local plugin_shim_version

test/current_command.bats

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ teardown() {
4747
[ "$output" = "$expected" ]
4848
}
4949

50+
@test "current should not error on version specifications like 'latest:'" {
51+
cd "$PROJECT_DIR"
52+
echo "dummy 1.2.0 latest:1.1" >>"$PROJECT_DIR/.tool-versions"
53+
expected="dummy 1.2.0 latest:1.1 $PROJECT_DIR/.tool-versions"
54+
55+
run asdf current "dummy"
56+
[ "$status" -eq 0 ]
57+
[ "$output" = "$expected" ]
58+
}
59+
5060
@test "current should derive from the legacy file if enabled" {
5161
cd "$PROJECT_DIR"
5262
echo 'legacy_version_file = yes' >"$HOME/.asdfrc"

test/install_command.bats

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ teardown() {
4545
[ "$(cat "$ASDF_DIR/installs/dummy/1.2.0/version")" = "1.2.0" ]
4646
}
4747

48+
@test "install_command installs the 'latest:' version in .tool-versions" {
49+
cd "$PROJECT_DIR"
50+
echo -n 'dummy latest:1.1' >".tool-versions"
51+
run asdf install dummy
52+
[ "$status" -eq 0 ]
53+
[ "$(cat "$ASDF_DIR/installs/dummy/1.1.0/version")" = "1.1.0" ]
54+
}
55+
4856
@test "install_command set ASDF_CONCURRENCY" {
4957
run asdf install dummy 1.0.0
5058
[ "$status" -eq 0 ]

test/test_helpers.bash

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ bats_require_minimum_version 1.7.0
44

55
# shellcheck source=lib/utils.bash
66
. "$(dirname "$BATS_TEST_DIRNAME")"/lib/utils.bash
7+
# shellcheck source=lib/functions/versions.bash
8+
. "$(dirname "$BATS_TEST_DIRNAME")"/lib/functions/versions.bash
79

810
setup_asdf_dir() {
911
if [ "$BATS_TEST_NAME" = 'test_shim_exec_should_use_path_executable_when_specified_version_path-3a-3cpath-3e' ]; then

0 commit comments

Comments
 (0)