diff --git a/lib/compare_semver b/lib/compare_semver new file mode 100755 index 0000000..d4cbf13 --- /dev/null +++ b/lib/compare_semver @@ -0,0 +1,142 @@ +#!/bin/bash + +set -euo pipefail + +# Validate a semver string, exit if invalid. +# Arguments: +# $1: semver string to validate +validate_semver() { + + # Regex for a semver digit + D='0|[1-9][0-9]*' + # Regex for a semver pre-release word + PW='[0-9]*[a-zA-Z-][0-9a-zA-Z-]*' + # Regex for a semver build-metadata word + MW='[0-9a-zA-Z-]+' + + # Regex pattern to match a semver string with numeric version identifiers, pre-release identifiers, and build metadata + semver_pattern="^($D)\.($D)\.($D)(-(($D|$PW)(\.($D|$PW))*))?(\+($MW(\.$MW)*))?$" + + if ! [[ $1 =~ $semver_pattern ]]; then + echo "$0: Invalid semver string: $1" >&2 + echo "$0: Expected format 'x.y.z' or 'x.y.z-pre-release'." + exit 1 + fi +} + +# Function to compare two component strings. +# Arguments: +# $1: First component string +# $2: Second component string +# Output: +# -1 if the first component has lower precedence +# 1 if the first component has higher precedence +compare_components() { + if (($1 > $2)); then + echo 1 && exit 0 + elif (($1 < $2)); then + echo -1 && exit 0 + fi +} + +# Function to compare pre-release identifiers. +# Arguments: +# $1: First pre-release identifier +# $2: Second pre-release identifier +# Output: +# -1 if the first pre-release identifier has lower precedence +# 0 if both identifiers are equal +# 1 if the first pre-release identifier has higher precedence +compare_prerelease() { + # If both pre-release identifiers are empty, they are considered equal + if [ -z "$1" ] && [ -z "$2" ]; then + echo 0 && exit 0 + fi + + # If one of the pre-release identifiers is empty, the one with the non-empty identifier has lower precedence + if [ -z "$1" ]; then + echo 1 && exit 0 + elif [ -z "$2" ]; then + echo -1 && exit 0 + fi + + # Split pre-release identifiers into individual parts + IFS='.' read -r -a parts1 <<<"$1" + IFS='.' read -r -a parts2 <<<"$2" + + # Compare each part of the pre-release identifiers + for ((i = 0; i < ${#parts1[@]} || i < ${#parts2[@]}; i++)); do + part1="${parts1[i]:=""}" + part2="${parts2[i]:=""}" + + # If one part is missing, it has lower precedence + if [ -z "$part1" ]; then + echo -1 && exit 0 + elif [ -z "$part2" ]; then + echo 1 && exit 0 + fi + + # If parts are numeric, compare numerically + if [[ "$part1" =~ ^[0-9]+$ ]] && [[ "$part2" =~ ^[0-9]+$ ]]; then + compare_components "$part1" "$part2" + fi + + # Lexicographically compare non-numeric parts + [[ "$part1" < "$part2" ]] && echo -1 && exit 0 + [[ "$part1" > "$part2" ]] && echo 1 && exit 0 + done + + # Versions are equal + echo 0 +} + +# Function to compare two semver strings. +# Arguments: +# $1: First semver string +# $2: Second semver string +# Output: +# -1 if the first version is less than the second version +# 0 if the versions are equal +# 1 if the first version is greater than the second version +compare_semver() { + # Validate the semver strings + validate_semver "$1" + validate_semver "$2" + + # Remove build metadata if present + semver1=$(cut -d+ -f1 <<<"$1") + semver2=$(cut -d+ -f1 <<<"$2") + + # Extract major, minor, patch, and pre-release versions from the input strings + major1=$(cut -d. -f1 <<<"$semver1") + minor1=$(cut -d. -f2 <<<"$semver1") + patch1=$(cut -d. -f3 -s <<<"$semver1" | cut -d- -f1) + prerelease1=$(cut -d- -f2- -s <<<"$semver1") + + major2=$(cut -d. -f1 <<<"$semver2") + minor2=$(cut -d. -f2 <<<"$semver2") + patch2=$(cut -d. -f3 -s <<<"$semver2" | cut -d- -f1) + prerelease2=$(cut -d- -f2- -s <<<"$semver2") + + # Compare each component of the versions + compare_components "$major1" "$major2" + compare_components "$minor1" "$minor2" + compare_components "$patch1" "$patch2" + + # Check if one version has a pre-release identifier while the other does not + if [ -n "$prerelease1" ] && [ -z "$prerelease2" ]; then + echo -1 && exit 0 + elif [ -z "$prerelease1" ] && [ -n "$prerelease2" ]; then + echo 1 && exit 0 + fi + + # Compare pre-release identifiers if they exist + if [ -n "$prerelease1" ] && [ -n "$prerelease2" ]; then + compare_prerelease "$prerelease1" "$prerelease2" && exit 0 + fi + + # Versions are equal + echo 0 +} + +compare_semver "$1" "$2" diff --git a/lib/test_compare_semver b/lib/test_compare_semver new file mode 100755 index 0000000..837c0fb --- /dev/null +++ b/lib/test_compare_semver @@ -0,0 +1,67 @@ +#!/bin/bash + +test_cases=( + # Versions are equal + "1.0.0 1.0.0 0" + "1.1.0 1.1.0 0" + "1.0.1 1.0.1 0" + "1.0.0-alpha 1.0.0-alpha 0" + "1.0.0-alpha.beta 1.0.0-alpha.beta 0" + "1.0.0-abc123.1.1.1 1.0.0-abc123.1.1.1 0" + + # First version is less than second version + "1.0.0 2.0.0 -1" + "1.0.0 1.1.0 -1" + "1.0.0 1.0.1 -1" + "1.0.0-alpha 1.0.0 -1" + + # First version is greater than second version + "2.0.0 1.0.0 1" + "1.1.0 1.0.0 1" + "1.0.1 1.0.0 1" + "1.0.0 1.0.0-alpha 1" + "1.0.0-alpha.1 1.0.0-alpha 1" + + # Pre-release versions comparison + "1.0.0-alpha 1.0.0-alpha.beta -1" + "1.0.0-alpha.beta 1.0.0-beta -1" + "1.0.0-alpha.beta 1.0.0-beta.2 -1" + "1.0.0-alpha.beta 1.0.0-beta.11 -1" + "1.0.0-alpha.beta 1.0.0-rc.1 -1" + "1.0.0-alpha.beta 1.0.0 -1" + "1.0.0-beta 1.0.0-beta.2 -1" + "1.0.0-beta 1.0.0-beta.11 -1" + "1.0.0-beta 1.0.0-rc.1 -1" + "1.0.0-beta 1.0.0 -1" + "1.0.0-beta.2 1.0.0-beta.11 -1" + "1.0.0-beta.2 1.0.0-rc.1 -1" + "1.0.0-beta.2 1.0.0 -1" + "1.0.0-beta.11 1.0.0-rc.1 -1" + "1.0.0-rc.1 1.0.0-rc.2 -1" + "1.0.0-rc.1 1.0.0 -1" + + # Versions with build metadata + "1.0.0 1.0.0+build.1 0" + "1.0.0 1.0.0+build.2 0" + "1.0.0-alpha 1.0.0-alpha+build.1 0" + "1.0.0-alpha+build.1 1.0.0-alpha+build.2 0" + "1.0.0-alpha+build.1 1.0.0+build.1 -1" + "1.0.0+early 1.0.1+late -1" + "1.0.0+early 1.1.0+late -1" + "1.0.0+early 2.0.0+late -1" +) + +LIB="$(dirname "$(readlink -f "$0")")" + +# Run test cases +for test_case in "${test_cases[@]}"; do + versions=() + read -ra versions <<<"$test_case" + result=$("$LIB/compare_semver" "${versions[0]}" "${versions[1]}") + expected="${versions[2]}" + if [ "$result" -eq "$expected" ]; then + echo "OK: ${versions[0]} compared to ${versions[1]} :: Result - $result" + else + echo "FAIL: ${versions[0]} compared to ${versions[1]} :: Expected - $expected, Actual - $result" + fi +done diff --git a/lib/utils.bash b/lib/utils.bash index 24a1202..35eb1c0 100644 --- a/lib/utils.bash +++ b/lib/utils.bash @@ -6,9 +6,10 @@ REPO="smithy-lang/smithy" GH_REPO="https://github.com/$REPO" GH_REPO_API="https://api.github.com/repos/$REPO" TOOL_NAME="smithy" +LIB="$(dirname "$(dirname "$(readlink -f "$0")")")/lib" fail() { - echo -e "asdf-$TOOL_NAME: $*" + printf '%s\n' "asdf-$TOOL_NAME: $*" >&2 exit 1 } @@ -44,6 +45,11 @@ get_platform() { # get architecture, output it, quit if unsupported get_arch() { arch="$(uname -m)" + if [[ "$arch" =~ "arm64" ]]; then + # uname may return arm64 for arm-based machines, they're synonymous + arch="aarch64" + fi + if [[ ! $(check_arch "$arch") ]]; then fail "unsupported architecture ($arch)" fi @@ -59,7 +65,7 @@ check_arch() { } # get the url for the artifact to download, based on -# the system (1st arg) and the version (2nd arg) +# the system (1st arg), the version (2nd arg), and the file extensions (3rd arg) get_artifact_url() { check jq if [ "$#" -gt 1 ]; then @@ -70,7 +76,7 @@ get_artifact_url() { else tag=$2 fi - echo "$GH_REPO/releases/download/$tag/smithy-cli-$1.tar.gz" + echo "$GH_REPO/releases/download/$tag/smithy-cli-$1.$3" else fail "platform or version were not specified." fi @@ -86,12 +92,37 @@ download_release() { if [ "$type" = "version" ]; then echo "* Downloading $TOOL_NAME release ($version)..." - url=$(get_artifact_url "$(get_platform)-$(get_arch)" "$version") - if [ "$url" ]; then - curl "${curl_opts[@]}" "$url" | tar xzf - -C "$path" || - fail "Request to '$url' returned bad response ($?)." + platform=$(get_platform) + arch=$(get_arch) + # Based on the version, install from tar or zip + # versions earlier than 1.47.0 must install from tarball + vercmp="$(. "$LIB"/compare_semver "$version" "1.47.0")" + if [ "$vercmp" = "-1" ]; then + # version is less than 1.47.0, use tar + url=$(get_artifact_url "$(get_platform)-$(get_arch)" "$version" "tar.gz") + if [ "$url" ]; then + echo "Retrieving release at $url..." + curl "${curl_opts[@]}" "$url" | tar xzf - -C "$path" || + fail "Request to '$url' returned bad response ($?)." + else + fail "Could not form url." + fi else - fail "Could not form url." + # version is greater than or equal to 1.47.0, use zip + url=$(get_artifact_url "$platform-$arch" "$version" "zip") + if [ "$url" ]; then + mkdir -p "$path" + echo "Retrieving release at $url..." + tmp_download_dir=$(mktemp -d -t asdf_extract_XXXXXXX) + cd "$tmp_download_dir" + curl "${curl_opts[@]}" -o smithy.zip "$url" || fail "Request to '$url' returned bad response ($?)." + unzip -oq smithy.zip + mv smithy-cli-"$platform"-"$arch"/* "$path" + rm -rf smithy-cli-"$platform"-"$arch" smithy.zip + cd - + else + fail "Could not form url." + fi fi else fail "Download by '$type' is not supported." @@ -117,6 +148,8 @@ install_version() { ( mkdir -p "$path" + # zips don't maintain permissions, so we need to add x perms to the right files + chmod +x "$download_path"/bin/* "$download_path"/lib/jspawnhelper cp -r "$download_path"/* "$path" # assert smithy exists and runs