#!/bin/sh # shellcheck shell=dash # shellcheck disable=SC2039 # This is just a little script that can be downloaded from the internet to install Starknet Foundry. # It just does platform detection, downloads the release archive, extracts it and tries to make # the `snforge` and `sncast` binaries available in $PATH in least invasive way possible. # # It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local` extension. # Note: Most shells limit `local` to 1 var per line, contra bash. # # Most of this code is based on/copy-pasted from rustup and protostar installers. set -u REPO="https://github.com/foundry-rs/starknet-foundry" XDG_DATA_HOME="${XDG_DATA_HOME:-"${HOME}/.local/share"}" INSTALL_ROOT="${XDG_DATA_HOME}/starknet-foundry-install" LOCAL_BIN="${HOME}/.local/bin" LOCAL_BIN_ESCAPED="\$HOME/.local/bin" BINARIES="snforge sncast" # Array syntax for shells. List can be expanded for new binaries usage() { cat <&2 exit 1 } need_cmd() { if ! check_cmd "$1"; then err "need '$1' (command not found)" fi } check_cmd() { command -v "$1" >/dev/null 2>&1 } assert_nz() { if [ -z "$1" ]; then err "assert_nz $2"; fi } # Run a command that should never fail. # If the command fails execution will immediately terminate with an error showing the failing command. ensure() { if ! "$@"; then err "command failed: $*"; fi } # This is just for indicating that commands' results are being intentionally ignored. # Usually, because it's being executed as part of error handling. ignore() { "$@" } download_universal_sierra_compiler() { curl -L https://raw.githubusercontent.com/software-mansion/universal-sierra-compiler/master/scripts/install.sh | sh } # This function has been copied verbatim from rustup install script. get_architecture() { local _ostype _cputype _bitness _arch _clibtype _ostype="$(uname -s)" _cputype="$(uname -m)" _clibtype="gnu" if [ "$_ostype" = Linux ]; then if [ "$(uname -o)" = Android ]; then _ostype=Android fi if ldd --_requested_version 2>&1 | grep -q 'musl'; then _clibtype="musl" fi fi if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then # Darwin `uname -m` lies if sysctl hw.optional.x86_64 | grep -q ': 1'; then _cputype=x86_64 fi fi if [ "$_ostype" = SunOS ]; then # Both Solaris and illumos presently announce as "SunOS" in "uname -s" # so use "uname -o" to disambiguate. We use the full path to the # system uname in case the user has coreutils uname first in PATH, # which has historically sometimes printed the wrong value here. if [ "$(/usr/bin/uname -o)" = illumos ]; then _ostype=illumos fi # illumos systems have multi-arch userlands, and "uname -m" reports the # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86 # systems. Check for the native (widest) instruction set on the # running kernel: if [ "$_cputype" = i86pc ]; then _cputype="$(isainfo -n)" fi fi case "$_ostype" in Android) _ostype=linux-android ;; Linux) check_proc _ostype=unknown-linux-$_clibtype _bitness=$(get_bitness) ;; FreeBSD) _ostype=unknown-freebsd ;; NetBSD) _ostype=unknown-netbsd ;; DragonFly) _ostype=unknown-dragonfly ;; Darwin) _ostype=apple-darwin ;; illumos) _ostype=unknown-illumos ;; MINGW* | MSYS* | CYGWIN* | Windows_NT) _ostype=pc-windows-gnu ;; *) err "unrecognized OS type: $_ostype" ;; esac case "$_cputype" in i386 | i486 | i686 | i786 | x86) _cputype=i686 ;; xscale | arm) _cputype=arm if [ "$_ostype" = "linux-android" ]; then _ostype=linux-androideabi fi ;; armv6l) _cputype=arm if [ "$_ostype" = "linux-android" ]; then _ostype=linux-androideabi else _ostype="${_ostype}eabihf" fi ;; armv7l | armv8l) _cputype=armv7 if [ "$_ostype" = "linux-android" ]; then _ostype=linux-androideabi else _ostype="${_ostype}eabihf" fi ;; aarch64 | arm64) _cputype=aarch64 ;; x86_64 | x86-64 | x64 | amd64) _cputype=x86_64 ;; mips) _cputype=$(get_endianness mips '' el) ;; mips64) if [ "$_bitness" -eq 64 ]; then # only n64 ABI is supported for now _ostype="${_ostype}abi64" _cputype=$(get_endianness mips64 '' el) fi ;; ppc) _cputype=powerpc ;; ppc64) _cputype=powerpc64 ;; ppc64le) _cputype=powerpc64le ;; s390x) _cputype=s390x ;; riscv64) _cputype=riscv64gc ;; loongarch64) _cputype=loongarch64 ;; *) err "unknown CPU type: $_cputype" ;; esac # Detect 64-bit linux with 32-bit userland if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then case $_cputype in x86_64) if [ -n "${RUSTUP_CPUTYPE:-}" ]; then _cputype="$RUSTUP_CPUTYPE" else { # 32-bit executable for amd64 = x32 if is_host_amd64_elf; then err "x86_64 linux with x86 userland unsupported" else _cputype=i686 fi }; fi ;; mips64) _cputype=$(get_endianness mips '' el) ;; powerpc64) _cputype=powerpc ;; aarch64) _cputype=armv7 if [ "$_ostype" = "linux-android" ]; then _ostype=linux-androideabi else _ostype="${_ostype}eabihf" fi ;; riscv64gc) err "riscv64 with 32-bit userland unsupported" ;; esac fi # Detect armv7 but without the CPU features Rust needs in that build, # and fall back to arm. # See https://github.com/rust-lang/rustup.rs/issues/587. if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then # At least one processor does not have NEON. _cputype=arm fi fi _arch="${_cputype}-${_ostype}" RETVAL="$_arch" } resolve_version() { local _requested_version=$1 local _requested_ref=$2 local _response say "retrieving $_requested_version version from ${REPO}..." _response=$(ensure curl -# -Ls -H 'Accept: application/json' "${REPO}/releases/${_requested_ref}") if [ "{\"error\":\"Not Found\"}" = "$_response" ]; then err "version $_requested_version not found" fi RETVAL=$(echo "$_response" | sed -e 's/.*"tag_name":"\([^"]*\)".*/\1/') } create_install_dir() { local _requested_version=$1 local _installdir="${INSTALL_ROOT}/${_requested_version}" if [ -d "$_installdir" ]; then ensure rm -rf "$_installdir" say "removed existing snforge and sncast installation at ${_installdir}" fi ensure mkdir -p "$_installdir" RETVAL=$_installdir } download() { local _resolved_version=$1 local _arch=$2 local _installdir=$3 local _tempdir=$4 local _tarball="starknet-foundry-${_resolved_version}-${_arch}.tar.gz" local _url="${REPO}/releases/download/${_resolved_version}/${_tarball}" local _dl="$_tempdir/starknet-foundry.tar.gz" say "downloading ${_tarball}..." ensure curl -# -fLS -o "$_dl" "$_url" ensure tar -xz -C "$_installdir" --strip-components=1 -f "$_dl" } create_symlinks() { local _installdir=$1 for binary in $BINARIES; do local _binary="${_installdir}/bin/${binary}" local _symlink="${LOCAL_BIN}/${binary}" ensure mkdir -p "$LOCAL_BIN" ensure ln -fs "$_binary" "$_symlink" ensure chmod u+x "$_symlink" say "created symlink ${_symlink} -> ${_binary}" done } add_local_bin_to_path() { local _profile local _pref_shell case ${SHELL:-""} in */zsh) _profile=$HOME/.zshrc _pref_shell=zsh ;; */ash) _profile=$HOME/.profile _pref_shell=ash ;; */bash) _profile=$HOME/.bashrc _pref_shell=bash ;; */fish) _profile=$HOME/.config/fish/config.fish _pref_shell=fish ;; *) err "could not detect shell, manually add '${LOCAL_BIN_ESCAPED}' to your PATH." ;; esac echo >>"$_profile" && echo "export PATH=\"\$PATH:${LOCAL_BIN_ESCAPED}\"" >>"$_profile" echo \ "Detected your preferred shell is ${_pref_shell} and added '${LOCAL_BIN_ESCAPED}' to PATH." \ "Run 'source ${_profile}' or start a new terminal session to use Starknet Foundry." } clone_and_build() { local _commit_hash=$1 local _tempdir if ! _tempdir="$(ensure mktemp -d)"; then exit 1 fi say "Cloning repository at commit $_commit_hash..." ensure git clone "$REPO" "$_tempdir" ensure cd "$_tempdir" ensure git fetch --all ensure git checkout "$_commit_hash" say "Building binaries with cargo..." ensure cargo build --release local _build_output_dir="$PWD/target/release" create_install_dir "${_commit_hash}/bin" local _installdir=${RETVAL%????} # create_symlinks expects a PATH with /bin stripped, rather than modifying the function, we just strip it here. assert_nz "$_installdir" "installdir" ensure cp "$_build_output_dir/snforge" "$_installdir/bin/snforge" ensure cp "$_build_output_dir/sncast" "$_installdir/bin/sncast" create_symlinks "$_installdir" ensure cd - > /dev/null # Go back to the previous directory and suppress output } main "$@" || exit 1