Skip to content

Instantly share code, notes, and snippets.

@tgran2028
Last active December 29, 2023 01:17
Show Gist options
  • Save tgran2028/7f00f2f5b96ace099916d547040b22e1 to your computer and use it in GitHub Desktop.
Save tgran2028/7f00f2f5b96ace099916d547040b22e1 to your computer and use it in GitHub Desktop.
Update all installed crates at $CARGO_HOME to latest version
#!/usr/bin/env bash
# ---
# @name update-cargo-binaries.sh
# ---
# @description This script updates installed Cargo crates to their latest versions.
# It checks for the presence of required commands, verifies the CARGO_HOME environment variable,
# and updates each crate in parallel up to a specified limit.
#
# +----------------+----------------+----------------+----------------+----------------+----------------+
set -euo pipefail
# Check for required commands: curl, jq, and cargo
for cmd in curl jq cargo; do
if ! command -v "$cmd" &>/dev/null; then
echo "Error: $cmd is not installed." >&2
exit 1
fi
done
# Check if CARGO_HOME environment variable is set
if [ -z "$CARGO_HOME" ]; then
echo "Error: CARGO_HOME is not set."
exit 1
fi
# Function to check if a process with a given PID is running
# Args:
# pid: The process ID to check
check_pid() {
local pid="$1"
if ps -p "$pid" > /dev/null 2>&1; then
return 0
else
return 1
fi
}
# Function to get the latest version of a crate from crates.io
# Args:
# crate_name: Name of the crate
latest_crate_version() {
local crate_name="$1"
curl --silent "https://crates.io/api/v1/crates/$crate_name" | jq -e -rM '.versions[0].num' || {
echo "$(date): Failed to get the latest version of the crate $crate_name"
exit 1
}
}
# Function to get the installed version of a crate
# Args:
# crate_name: Name of the crate
installed_crate_version() {
local crate_name="$1"
cargo install --list \
| grep "^$crate_name\s" \
| awk '{print $2}' \
| sed -e 's/^ *//' -e 's/ *$//' \
| sed 's/:$//' \
| sed 's/^v//'
}
# Function to create a JSON object with crate version information
# Args:
# crate_name: Name of the crate
crate_versions_json() {
local crate_name="$1"
jq --null-input \
--arg name "$crate_name" \
--arg latest "$(latest_crate_version "$crate_name")" \
--arg installed "$(installed_crate_version "$crate_name")" \
--argjson requires_update "$([[ "$latest_version" != "$installed_version" ]] && echo true || echo false)" \
'{name: $name, latest: $latest, installed: $installed, requires_update: $requires_update}'
}
# Function to check if the latest version of a crate is installed
# Args:
# crate_name: Name of the crate
is_latest_version_installed() {
local crate_name="$1"
local json_data
if [ -z "$json_data" ]; then
requires_update=$(crate_versions_json "$crate_name" | jq -rM '.requires_update')
else
requires_update=$(echo "$json_data" | jq -rM '.requires_update')
fi
echo "$requires_update"
}
# Function to get all installed crates
get_all_installed_crates() {
cargo install --list \
| grep -v "^\s" \
| awk '{print $1}' \
| sed -e 's/^ *//' -e 's/ *$//' \
| sed 's/:$//'
}
# Main function to update cargo binaries
update_cargo_binaries() {
local log_dir=~/.local/var/log/cargo/install
local pid versions_json installed_version latest_version crate
# Maximum number of concurrent updates
declare -i max_concurrent_updates=5
declare -i job_count=0
mkdir -p "$log_dir"
# Loop through all installed crates
for crate in $(get_all_installed_crates); do
# Check if the maximum number of concurrent updates has been reached
while [ "$job_count" -ge "$max_concurrent_updates" ]; do
sleep 5
job_count=$(jobs -p | wc -l) || {
echo "$(date): Failed to get the number of background jobs"
exit 1
}
done
versions_json="$(crate_versions_json "$crate")"
latest_version=$(echo "$versions_json" | jq -rM '.latest')
installed_version=$(echo "$versions_json" | jq -rM '.installed')
# Check if an update is required
if [ "$latest_version" != "$installed_version" ]; then
printf "%s: %s -> %s\n" "$crate" "$installed_version" "$latest_version"
# Update the crate in the background
cargo install \
--force \
--locked \
--all-features \
--jobs 2 \
--message-format json-diagnostic-short \
"$crate" \
>"$log_dir/$crate.json" 2>&1 &
((job_count++))
else
printf "%s: %s is the latest version\n" "$crate" "$installed_version"
fi
done
# Wait for all background jobs to finish
for pid in $(jobs -pr); do
wait "$pid"
done
}
# Run the update function if the script is not sourced
if [ "$0" = "${BASH_SOURCE[0]}" ]; then
update_cargo_binaries
else
# Export functions if the script is sourced
export -f check_pid
export -f latest_crate_version
export -f installed_crate_version
export -f is_latest_version_installed
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment