blob: b79cd43de05827109fc96084d849b046a3f88e9c [file] [log] [blame]
#!/usr/bin/env bash
# Copied from
# https://github.com/volta-cli/volta/blob/master/dev/unix/volta-install.sh
# LICENSE:
# BSD 2-CLAUSE LICENSE
#
# Copyright (c) 2017, The Wasmtime Contributors.
# All rights reserved.
#
# This product includes:
#
# Contributions from LinkedIn Corporation
# Copyright (c) 2017, LinkedIn Corporation.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of the FreeBSD Project.
get_latest_release() {
curl --silent "https://api.github.com/repos/bytecodealliance/wasmtime/releases/latest" | \
grep tag_name | \
cut -d '"' -f 4
}
release_url() {
echo "https://github.com/bytecodealliance/wasmtime/releases"
}
download_release_from_repo() {
local version="$1"
local arch="$2"
local os_info="$3"
local tmpdir="$4"
local filename="wasmtime-$version-$arch-$os_info.tar.xz"
local download_file="$tmpdir/$filename"
local archive_url="$(release_url)/download/$version/$filename"
info $archive_url
curl --progress-bar --show-error --location --fail "$archive_url" \
--output "$download_file" && echo "$download_file"
}
usage() {
cat >&2 <<END_USAGE
wasmtime-install: The installer for Wasmtime
USAGE:
wasmtime-install [FLAGS] [OPTIONS]
FLAGS:
-h, --help Prints help information
OPTIONS:
--dev Compile and install Wasmtime locally, using the dev target
--release Compile and install Wasmtime locally, using the release target
--version <version> Install a specific release version of Wasmtime
END_USAGE
}
info() {
local action="$1"
local details="$2"
command printf '\033[1;32m%12s\033[0m %s\n' "$action" "$details" 1>&2
}
error() {
command printf '\033[1;31mError\033[0m: %s\n\n' "$1" 1>&2
}
warning() {
command printf '\033[1;33mWarning\033[0m: %s\n\n' "$1" 1>&2
}
request() {
command printf '\033[1m%s\033[0m\n' "$1" 1>&2
}
eprintf() {
command printf '%s\n' "$1" 1>&2
}
bold() {
command printf '\033[1m%s\033[0m' "$1"
}
# If file exists, echo it
echo_fexists() {
[ -f "$1" ] && echo "$1"
}
detect_profile() {
local shellname="$1"
local uname="$2"
if [ -f "$PROFILE" ]; then
echo "$PROFILE"
return
fi
# try to detect the current shell
case "$shellname" in
bash)
# Shells on macOS default to opening with a login shell, while Linuxes
# default to a *non*-login shell, so if this is macOS we look for
# `.bash_profile` first; if it's Linux, we look for `.bashrc` first. The
# `*` fallthrough covers more than just Linux: it's everything that is not
# macOS (Darwin). It can be made narrower later if need be.
case $uname in
Darwin)
echo_fexists "$HOME/.bash_profile" || echo_fexists "$HOME/.bashrc"
;;
*)
echo_fexists "$HOME/.bashrc" || echo_fexists "$HOME/.bash_profile"
;;
esac
;;
zsh)
echo "$HOME/.zshrc"
;;
fish)
echo "$HOME/.config/fish/config.fish"
;;
*)
# Fall back to checking for profile file existence. Once again, the order
# differs between macOS and everything else.
local profiles
case $uname in
Darwin)
profiles=( .profile .bash_profile .bashrc .zshrc .config/fish/config.fish )
;;
*)
profiles=( .profile .bashrc .bash_profile .zshrc .config/fish/config.fish )
;;
esac
for profile in "${profiles[@]}"; do
echo_fexists "$HOME/$profile" && break
done
;;
esac
}
# generate shell code to source the loading script and modify the path for the input profile
build_path_str() {
local profile="$1"
local profile_install_dir="$2"
if [[ $profile =~ \.fish$ ]]; then
# fish uses a little different syntax to modify the PATH
cat <<END_FISH_SCRIPT
set -gx WASMTIME_HOME "$profile_install_dir"
string match -r ".wasmtime" "\$PATH" > /dev/null; or set -gx PATH "\$WASMTIME_HOME/bin" \$PATH
END_FISH_SCRIPT
else
# bash and zsh
cat <<END_BASH_SCRIPT
export WASMTIME_HOME="$profile_install_dir"
export PATH="\$WASMTIME_HOME/bin:\$PATH"
END_BASH_SCRIPT
fi
}
# check for issue with WASMTIME_HOME
# if it is set, and exists, but is not a directory, the install will fail
wasmtime_home_is_ok() {
if [ -n "${WASMTIME_HOME-}" ] && [ -e "$WASMTIME_HOME" ] && ! [ -d "$WASMTIME_HOME" ]; then
error "\$WASMTIME_HOME is set but is not a directory ($WASMTIME_HOME)."
eprintf "Please check your profile scripts and environment."
return 1
fi
return 0
}
update_profile() {
local install_dir="$1"
local profile_install_dir=$(echo "$install_dir" | sed "s:^$HOME:\$HOME:")
local detected_profile="$(detect_profile $(basename "/$SHELL") $(uname -s) )"
local path_str="$(build_path_str "$detected_profile" "$profile_install_dir")"
info 'Editing' "user profile ($detected_profile)"
if [ -z "${detected_profile-}" ] ; then
error "No user profile found."
eprintf "Tried \$PROFILE ($PROFILE), ~/.bashrc, ~/.bash_profile, ~/.zshrc, ~/.profile, and ~/.config/fish/config.fish."
eprintf ''
eprintf "You can either create one of these and try again or add this to the appropriate file:"
eprintf "$path_str"
return 1
else
if ! command grep -qc 'WASMTIME_HOME' "$detected_profile"; then
command printf "$path_str" >> "$detected_profile"
else
warning "Your profile ($detected_profile) already mentions Wasmtime and has not been changed."
fi
fi
}
# Check if it is OK to upgrade to the new version
upgrade_is_ok() {
local will_install_version="$1"
local install_dir="$2"
local is_dev_install="$3"
local wasmtime_bin="$install_dir/wasmtime"
if [[ -n "$install_dir" && -x "$wasmtime_bin" ]]; then
local prev_version="$( ($wasmtime_bin --version 2>/dev/null || echo 0.1) | sed -E 's/^.*([0-9]+\.[0-9]+\.[0-9]+).*$/\1/')"
# if this is a local dev install, skip the equality check
# if installing the same version, this is a no-op
if [ "$is_dev_install" != "true" ] && [ "$prev_version" == "$will_install_version" ]; then
eprintf "Version $will_install_version already installed"
return 1
fi
# in the future, check $prev_version for incompatible upgrades
fi
return 0
}
# returns the os name to be used in the packaged release,
# including the openssl info if necessary
parse_os_info() {
local uname_str="$1"
local openssl_version="$2"
case "$uname_str" in
Linux)
echo "linux"
;;
Darwin)
echo "macos"
;;
*)
return 1
esac
return 0
}
parse_os_pretty() {
local uname_str="$1"
case "$uname_str" in
Linux)
echo "Linux"
;;
Darwin)
echo "macOS"
;;
*)
echo "$uname_str"
esac
}
# return true(0) if the element is contained in the input arguments
# called like:
# if element_in "foo" "${array[@]}"; then ...
element_in() {
local match="$1";
shift
local element;
# loop over the input arguments and return when a match is found
for element in "$@"; do
[ "$element" == "$match" ] && return 0
done
return 1
}
create_tree() {
local install_dir="$1"
info 'Creating' "directory layout"
# .wasmtime/
# bin/
mkdir -p "$install_dir"
mkdir -p "$install_dir"/bin
}
install_version() {
local version_to_install="$1"
local install_dir="$2"
if ! wasmtime_home_is_ok; then
exit 1
fi
case "$version_to_install" in
latest)
local latest_version="$(get_latest_release)"
info 'Installing' "latest version of Wasmtime ($latest_version)"
install_release "$latest_version" "$install_dir"
;;
local-dev)
info 'Installing' "Wasmtime locally after compiling"
install_local "dev" "$install_dir"
;;
local-release)
info 'Installing' "Wasmtime locally after compiling with '--release'"
install_local "release" "$install_dir"
;;
*)
# assume anything else is a specific version
info 'Installing' "Wasmtime version $version_to_install"
install_release "$version_to_install" "$install_dir"
;;
esac
if [ "$?" == 0 ]
then
update_profile "$install_dir" &&
info "Finished" 'installation. Open a new terminal to start using Wasmtime!'
fi
}
# parse the 'version = "X.Y.Z"' line from the input Cargo.toml contents
# and return the version string
parse_cargo_version() {
local contents="$1"
while read -r line
do
if [[ "$line" =~ ^version\ =\ \"(.*)\" ]]
then
echo "${BASH_REMATCH[1]}"
return 0
fi
done <<< "$contents"
error "Could not determine the current version from Cargo.toml"
return 1
}
install_release() {
local version="$1"
local install_dir="$2"
local is_dev_install="false"
info 'Checking' "for existing Wasmtime installation"
if upgrade_is_ok "$version" "$install_dir" "$is_dev_install"
then
download_archive="$(download_release "$version"; exit "$?")"
exit_status="$?"
if [ "$exit_status" != 0 ]
then
error "Could not download Wasmtime version '$version'. See $(release_url) for a list of available releases"
return "$exit_status"
fi
install_from_file "$download_archive" "$install_dir"
else
# existing legacy install, or upgrade problem
return 1
fi
}
install_local() {
local dev_or_release="$1"
local install_dir="$2"
# this is a local install, so skip the version equality check
local is_dev_install="true"
info 'Checking' "for existing Wasmtime installation"
install_version="$(parse_cargo_version "$(<Cargo.toml)" )" || return 1
if no_legacy_install && upgrade_is_ok "$install_version" "$install_dir" "$is_dev_install"
then
# compile and package the binaries, then install from that local archive
compiled_archive="$(compile_and_package "$dev_or_release")" &&
install_from_file "$compiled_archive" "$install_dir"
else
# existing legacy install, or upgrade problem
return 1
fi
}
compile_and_package() {
local dev_or_release="$1"
local release_output
# get the directory of this script
# (from https://stackoverflow.com/a/246128)
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
# call the release script to create the packaged archive file
# '2> >(tee /dev/stderr)' copies stderr to stdout, to collect it and parse the filename
release_output="$( "$DIR/release.sh" "--$dev_or_release" 2> >(tee /dev/stderr) )"
[ "$?" != 0 ] && return 1
# parse the release filename and return that
if [[ "$release_output" =~ release\ in\ file\ (target[^\ ]+) ]]; then
echo "${BASH_REMATCH[1]}"
else
error "Could not determine output filename"
return 1
fi
}
download_release() {
local version="$1"
local arch="$(get_architecture)"
local uname_str="$(uname -s)"
local os_info
os_info="$(parse_os_info "$uname_str")"
if [ "$?" != 0 ]; then
error "The current operating system ($uname_str) does not appear to be supported by Wasmtime."
return 1
fi
local pretty_os_name="$(parse_os_pretty "$uname_str")"
info 'Fetching' "archive for $pretty_os_name, version $version"
# store the downloaded archive in a temporary directory
local download_dir="$(mktemp -d)"
download_release_from_repo "$version" "$arch" "$os_info" "$download_dir"
}
install_from_file() {
local archive="$1"
local copy_to="$2"
local extract_to="$(dirname $archive)"
local extracted_path="$extract_to/$(basename $archive .tar.xz)"
create_tree "$copy_to"
info 'Extracting' "Wasmtime binaries"
# extract the files to the temp directory
tar -xvf "$archive" -C "$extract_to"
# copy the files to the specified directory
# binaries go into the bin folder
cp "$extracted_path/wasmtime" "$copy_to/bin"
# the others directly into the specified folder
cp "$extracted_path/LICENSE" "$extracted_path/README.md" "$copy_to"
}
get_architecture() {
local arch="$(uname -m)"
case "$arch" in
# macOS on aarch64 says "arm64" instead.
arm64)
arch=aarch64
;;
esac
echo "$arch"
}
check_architecture() {
local version="$1"
local arch="$2"
local os="$3"
# Local version always allowed.
if [[ "$version" == "local"* ]]; then
return 0
fi
# Otherwise, check the matrix of OS/architecture support.
case "$arch/$os" in
aarch64/Linux)
return 0
;;
aarch64/Darwin)
return 0
;;
s390x/Linux)
return 0
;;
x86_64/*)
return 0
;;
esac
error "Sorry! Wasmtime currently only provides pre-built binaries for x86_64 (Linux, macOS, Windows), aarch64 (Linux, macOS), and s390x (Linux)."
return 1
}
# return if sourced (for testing the functions above)
return 0 2>/dev/null
# default to installing the latest available version
version_to_install="latest"
# install to WASMTIME_HOME, defaulting to ~/.wasmtime
install_dir="${WASMTIME_HOME:-"$HOME/.wasmtime"}"
# parse command line options
while [ $# -gt 0 ]
do
arg="$1"
case "$arg" in
-h|--help)
usage
exit 0
;;
--dev)
shift # shift off the argument
version_to_install="local-dev"
;;
--release)
shift # shift off the argument
version_to_install="local-release"
;;
--version)
shift # shift off the argument
version_to_install="$1"
shift # shift off the value
;;
*)
error "unknown option: '$arg'"
usage
exit 1
;;
esac
done
check_architecture "$version_to_install" "$(get_architecture)" "$(uname)" || exit 1
install_version "$version_to_install" "$install_dir"