Skip to content

Instantly share code, notes, and snippets.

@passcod
Last active June 9, 2019 06:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save passcod/e438c1ee035abe7b3413e26880ea47d1 to your computer and use it in GitHub Desktop.
Save passcod/e438c1ee035abe7b3413e26880ea47d1 to your computer and use it in GitHub Desktop.
A tool to find out what the earliest version that your project supports is!
#!/usr/bin/env bash
# With revisions from @parasyte for macOS compatibility
# Requires bash >= 5 (mapfile)
if [[ "$1" == "--keep-versions" ]]; then
keep_versions="1"
fi
purple="\e[0;35m"
reset="\e[0m"
releases_url="https://raw.githubusercontent.com/rust-lang/rust/master/RELEASES.md"
mapfile -t versions < <(curl -s "$releases_url" \
| grep -v alpha \
| egrep -o '^Version [1-9][0-9.]+' \
| cut -d\ -f2)
# Before that, we have to use `cargo build`
checkindex=$(echo ${versions[@]/1.15.0//} | cut -d/ -f1 | wc -w | tr -d ' ')
n_versions=${#versions[@]}
echo -e "${purple}We know of ${reset}${n_versions}${purple} stable Rust versions."
mapfile -t priors < <(rustup toolchain list \
| egrep -o '^[0-9.]+')
echo -e "There are ${reset}${#priors[@]}${purple} past versions explicitly installed here."
current=$(rustc --version | cut -d\ -f2)
curindex=$(echo ${versions[@]/$current//} | cut -d/ -f1 | wc -w | tr -d ' ')
if [[ $curindex == 0 ]]; then
echo -e "You are on the latest stable: ${reset}${current}${purple}."
elif [[ $curindex == $n_versions ]]; then
echo -e "You are not currently on a stable: ${reset}${current}${purple}" \
"(latest is ${reset}${versions[0]}${purple})."
else
echo -e "You are not on the latest stable: ${reset}${current}${purple}" \
"(latest is ${reset}${versions[0]}${purple})."
fi
# Exaggerated (+2) from theoretical given observations
est_steps=$(echo "l($n_versions - 1)/l(2) + 3" | bc -l | cut -d. -f1)
echo
echo "Now going to binary search over Rust versions to find the earliest one" \
"which compiles your project. We do this by repeatedly installing and checking" \
$(if [[ -z "$keep_versions" ]]; then
echo -e "then uninstalling Rust versions through rustup. Versions that are" \
"already installed will not be removed (at the end, your system should" \
"look the same). If you want to keep the versions this script downloads" \
"instead, quit and run again with the ${reset}--keep-versions${purple} flag."
else
echo -e "Rust versions through rustup. As you've passed the" \
"${reset}--keep-versions${purple} flag, versions this script downloads will" \
"be kept instead of being cleaned up immediately."
fi) \
| fold -s
echo
echo "You'll want a fast, unmetered internet connection. If you're running Windows," \
"it will take forever due to https://github.com/rust-lang/rustup.rs/issues/1540." \
| fold -s
if [[ -z "$keep_versions" ]]; then
echo
echo "To pause on Linux/macOS, prefer Ctrl-Z / fg." \
| fold -s
fi
echo
echo -ne "If you still want to proceed (estimated ${reset}${est_steps}${purple} iterations)," \
"press enter now. Otherwise, abort with Ctrl-C.\n" \
"\n${reset}=> " \
| fold -s
read
echo -e "${purple}"
# absolute smallest
absmin="$((n_versions - 1))"
# smallest unsupported
trymin="$absmin"
# smallest supported
trymax="0"
# what we're checking now
target="$trymin"
# default variable values, needed for cleanup
a_priori=
target_v="${versions[$target]}"
function cleanup {
if [[ -z "$a_priori" ]] && [[ -z "$keep_versions" ]]; then
echo -e "${reset}"
rustup uninstall "$target_v"
echo -e "${purple}"
fi
}
# remove the last installed version when the script is interrupted
trap 'cleanup ; echo -e "${reset}" ; exit 1' SIGINT
while true; do
#echo "target: $target trymin: $trymin trymax: $trymax"
# the above are indexes, this is the version string
target_v="${versions[$target]}"
echo -ne "Going to check against version ${reset}${target_v}${purple}"
a_priori=
if [[ " ${priors[@]} " =~ " ${target_v} " ]]; then
a_priori=1
echo -e ", which already exists locally.${reset}"
else
echo -e ".${reset}"
rustup install "$target_v"
fi
echo
if if [[ $target > $checkindex ]]; then
rustup run "$target_v" cargo build
else
rustup run "$target_v" cargo check
fi; then
echo -e "\n${purple}Version ${reset}${target_v}${purple} is supported!"
trymax="$target"
target=$(((trymin - trymax) / 2 + trymax))
if [[ $trymax == $target ]] || [[ $target_v == "1.0.0" ]]; then
echo "That's the earliest! We're done!"
cleanup
break
fi
else
echo -e "\n${purple}Version ${reset}${target_v}${purple} is not supported! See above for why."
trymin="$target"
target=$(((trymin - trymax) / 2 + trymax))
fi
cleanup
done
echo -e "${reset}"
@passcod
Copy link
Author

passcod commented Jan 25, 2019

@parasyte
Copy link

parasyte commented Feb 12, 2019

Here's a patch to get this script working on macOS. (NOTE: I updated bash to version 5 to get mapfile, but it could also be replaced with a read loop.)

--- rustup-bisect.txt	2019-02-12 14:27:47.000000000 -0800
+++ rustup-bisect	2019-02-12 14:31:22.000000000 -0800
@@ -11,7 +11,7 @@ releases_url="https://raw.githubusercont

 mapfile -t versions < <(curl -s "$releases_url" \
     | grep -v alpha \
-    | grep -oP '^Version [1-9][\d.]+' \
+    | egrep -o '^Version [1-9][0-9.]+' \
     | cut -d\  -f2)

 # Before that, we have to use `cargo build`
@@ -21,7 +21,7 @@ n_versions=${#versions[@]}
 echo -e "${purple}We know of ${reset}${n_versions}${purple} stable Rust versions."

 mapfile -t priors < <(rustup toolchain list \
-    | grep -oP '^[\d.]+')
+    | egrep -o '^[\d.]+')

 echo -e "There are ${reset}${#priors[@]}${purple} past versions explicitly installed here."

@@ -129,3 +129,5 @@ while true; do
         echo -e "${purple}"
     fi
 done
+
+echo -e "${reset}"

@passcod
Copy link
Author

passcod commented Jun 4, 2019

@parasyte I merged those in, thank you!

@parasyte
Copy link

parasyte commented Jun 9, 2019

@passcod The latest change missed one of the required fixes to the regular expression for matching versions. egrep doesn't support \d inside character ranges. But you can still use 0-9.

I also decided to fix the toolchain uninstall when the script is interrupted. These changes are in my fork of the gist, making it a bit easier to contribute. :)

@passcod
Copy link
Author

passcod commented Jun 9, 2019

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment