uv/scripts/bootstrap/install.sh

138 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Download required Python versions and install to `bin`
# Uses prebuilt Python distributions from indygreg/python-build-standalone
#
# Requirements
#
# macOS
#
# brew install zstd jq coreutils
#
# Ubuntu
#
# apt install zstd jq
#
# Arch Linux
#
# pacman -S zstd jq libxcrypt-compat
#
# Windows
#
# winget install jqlang.jq
#
# Usage
#
# ./scripts/bootstrap/install.sh
#
# The Python versions are installed from `.python_versions`.
# Python versions are linked in-order such that the _last_ defined version will be the default.
#
# Version metadata can be updated with `fetch-version-metadata.py` which requires Python 3.12
set -euo pipefail
# Convenience function for displaying URLs
function urldecode() { : "${*//+/ }"; echo -e "${_//%/\\x}"; }
# Convenience function for checking that a command exists.
requires() {
cmd="$1"
if ! command -v "$cmd" > /dev/null 2>&1; then
echo "DEPENDENCY MISSING: $(basename $0) requires $cmd to be installed" >&2
exit 1
fi
}
requires jq
requires zstd
# Setup some file paths
this_dir=$(realpath "$(dirname "$0")")
root_dir=$(dirname "$(dirname "$this_dir")")
bin_dir="$root_dir/bin"
install_dir="$bin_dir/versions"
versions_file="$root_dir/.python-versions"
versions_metadata="$this_dir/versions.json"
# Determine system metadata
os=$(uname -s | tr '[:upper:]' '[:lower:]')
arch=$(uname -m)
interpreter='cpython'
# On macOS, we need a newer version of `realpath` for `--relative-to` support
realpath="$(which grealpath || which realpath)"
# Utility for linking executables for the version being installed
# We need to update executables even if the version is already downloaded and extracted
# to ensure that changes to the precedence of versions are respected
link_executables() {
# Use relative paths for links so if the bin is moved they don't break
local link=$($realpath --relative-to="$bin_dir" "$install_key/install/bin/python3")
local minor=$(jq --arg key "$key" '.[$key] | .minor' -r < "$versions_metadata")
# Link as all version tuples, later versions in the file will take precedence
ln -sf "./$link" "$bin_dir/python$version"
ln -sf "./$link" "$bin_dir/python3.$minor"
ln -sf "./$link" "$bin_dir/python3"
ln -sf "./$link" "$bin_dir/python"
}
# Read requested versions into an array
versions=()
while IFS= read -r version; do
versions+=("$version")
done < "$versions_file"
# Install each version
for version in "${versions[@]}"; do
key="$interpreter-$version-$os-$arch"
install_key="$install_dir/$interpreter@$version"
echo "Installing $key"
if [ -d "$install_key" ]; then
echo "Already available, skipping download"
link_executables
echo "Updated executables for python$version"
continue
fi
url=$(jq --arg key "$key" '.[$key] | .url' -r < "$versions_metadata")
if [ "$url" == 'null' ]; then
echo "No matching download for $key"
exit 1
fi
filename=$(basename "$url")
echo "Downloading $(urldecode "$filename")"
curl -L --progress-bar -o "$filename" "$url" --output-dir "$this_dir"
expected_sha=$(jq --arg key "$key" '.[$key] | .sha256' -r < "$versions_metadata")
if [ "$expected_sha" == 'null' ]; then
echo "WARNING: no checksum for $key"
else
echo -n "Verifying checksum..."
echo "$expected_sha $this_dir/$filename" | sha256sum -c --quiet
echo " OK"
fi
rm -rf "$install_key"
echo "Extracting to $($realpath --relative-to="$root_dir" "$install_key")"
mkdir -p "$install_key"
zstd -d "$this_dir/$filename" --stdout | tar -x -C "$install_key"
# Setup the installation
mv "$install_key/python/"* "$install_key"
link_executables
echo "Installed executables for python$version"
# Cleanup
rmdir "$install_key/python/"
rm "$this_dir/$filename"
done
echo "Done!"