Skip to content

Commit

Permalink
Add a generic install.sh and update README.md. (#88)
Browse files Browse the repository at this point in the history
* Adds a generic `install.sh` utility.
* Updates `README.md` to use the new installer.
* Adds tests.

OSX install example:

```
om:~ kw$ curl -LSsf https://raw.githubusercontent.com/kwlzn/lift/kwlzn/installer/install.sh | /bin/bash
Download URL is: https://github.com/a-scie/lift/releases/latest/download/science-fat-macos-aarch64
######################################################################## 100.0%
######################################################################## 100.0%
Download completed successfully
Download matched it's expected sha256 fingerprint, proceeding
Installed https://github.com/a-scie/lift/releases/latest/download/science-fat-macos-aarch64 to /Users/kw/.local/bin/science
WARNING: /Users/kw/.local/bin is not detected on $PATH
You'll either need to invoke /Users/kw/.local/bin/science explicitly or else add /Users/kw/.local/bin to your shell's PATH.
om:~ kw$ /Users/kw/.local/bin/science -V
0.7.0
```
  • Loading branch information
kwlzn committed Sep 7, 2024
1 parent bf8edd1 commit 351ff9d
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 7 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,12 @@ a result. The "thin" varieties have the CPython 3.12 distribution gouged out and
result. In its place a [`ptex`](https://github.com/a-scie/ptex) binary is included that fills in the
CPython 3.12 distribution by fetching it when the "thin" `science` binary is first run.

I run on Linux x86_64; so I install a stable release like so:
You can install the latest `science` release using the `install.sh` script like so:

```
curl -fLO \
https://github.com/a-scie/lift/releases/download/v0.1.0/science-linux-x86_64
curl -fL \
https://github.com/a-scie/lift/releases/download/v0.1.0/science-linux-x86_64.sha256 \
| sha256sum -c -
chmod +x science-linux-x86_64 && mv science-linux-x86_64 ~/bin/science
$ curl --proto '=https' --tlsv1.2 -LSsf https://raw.githubusercontent.com/a-scie/lift/main/install.sh | bash
...
$ science -V
```

The high level documentation is currently thin! The command line help is pretty decent though; so
Expand Down
212 changes: 212 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#!/usr/bin/env bash
# Copyright 2024 Science project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).

set -euo pipefail

COLOR_RED="\x1b[31m"
COLOR_GREEN="\x1b[32m"
COLOR_YELLOW="\x1b[33m"
COLOR_RESET="\x1b[0m"

function log() {
echo -e "$@" 1>&2
}

function die() {
(($# > 0)) && log "${COLOR_RED}$*${COLOR_RESET}"
exit 1
}

function green() {
(($# > 0)) && log "${COLOR_GREEN}$*${COLOR_RESET}"
}

function warn() {
(($# > 0)) && log "${COLOR_YELLOW}$*${COLOR_RESET}"
}

function ensure_cmd() {
local cmd="$1"
command -v "$cmd" > /dev/null || die "This script requires the ${cmd} binary to be on \$PATH."
}

ISSUES_URL="https://github.com/a-scie/lift/issues"
_GC=()

ensure_cmd rm
function gc() {
if (($# > 0)); then
_GC+=("$@")
else
# Check if $_GC has members to avoid "unbound variable" warnings if gc w/ arguments is never called.
if ! [ ${#_GC[@]} -eq 0 ]; then
rm -rf "${_GC[@]}"
fi
fi
}

trap gc EXIT

ensure_cmd uname
function determine_os() {
local os

os="$(uname -s)"
if [[ "${os}" =~ [Ll]inux ]]; then
echo linux
elif [[ "${os}" =~ [Dd]arwin ]]; then
echo macos
elif [[ "${os}" =~ [Ww]indow|[Mm][Ii][Nn][Gg] ]]; then
# Powershell reports something like: Windows_NT
# Git bash reports something like: MINGW64_NT-10.0-22621
echo windows
else
die "Science is not supported on this OS (${os}). Please reach out at ${ISSUES_URL} for help."
fi
}

OS="$(determine_os)"

function determine_arch() {
# Map platform architecture to released binary architecture.
read_arch="$(uname -m)"
case "$read_arch" in
x86_64*) echo "x86_64" ;;
amd64*) echo "x86_64" ;;
arm64*) echo "aarch64" ;;
aarch64*) echo "aarch64" ;;
*) die "unknown arch: ${read_arch}" ;;
esac
}

ensure_cmd basename
ensure_cmd curl
function fetch() {
local url="$1"
local dest_dir="$2"

local dest
dest="${dest_dir}/$(basename "${url}")"

# N.B. Curl is included on Windows 10+: https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/
curl --proto '=https' --tlsv1.2 -SfL --progress-bar -o "${dest}" "${url}"
}

ensure_cmd $([[ "${OS}" == "macos" ]] && echo "shasum" || echo "sha256sum")
function sha256() {
if [[ "${OS}" == "macos" ]]; then
shasum --algorithm 256 "$@"
else
sha256sum "$@"
fi
}

ensure_cmd dirname
ensure_cmd mktemp
ensure_cmd install
ensure_cmd tr
ensure_cmd mv
function install_from_url() {
local url="$1"
local dest="$2"

local workdir
workdir="$(mktemp -d)"
gc "${workdir}"

fetch "${url}.sha256" "${workdir}"
fetch "${url}" "${workdir}" && green "Download completed successfully"
(
cd "${workdir}"

if [[ "${OS}" == "windows" ]]; then
# N.B. Windows sha256sum is sensitive to trailing \r in files.
cat *.sha256 | tr -d "\r" > out.sanitized
mv -f out.sanitized *.sha256
fi

sha256 -c --status ./*.sha256 &&
green "Download matched it's expected sha256 fingerprint, proceeding" ||
die "Download from ${url} did not match the fingerprint at ${url}.sha256"
)
rm "${workdir}/"*.sha256

if [[ "${OS}" == "macos" ]]; then
mkdir -p "$(dirname "${dest}")"
install -m 755 "${workdir}/"* "${dest}"
else
install -D -m 755 "${workdir}/"* "${dest}"
fi

green "Installed ${url} to ${dest}"
}

ensure_cmd cat
function usage() {
cat << __EOF__
Usage: $0
Installs the \`science\` binary.
-h | --help: Print this help message.
-d | --bin-dir:
The directory to install the science binary in, "~/.local/bin" by default.
-b | --base-name:
The name to use for the science binary, "science" by default.
-V | --version:
The version of the science binary to install, the latest version by default.
The available versions can be seen at:
https://github.com/a-scie/lift/releases
__EOF__
}

INSTALL_PREFIX="${HOME}/.local/bin"
INSTALL_FILE="science"
VERSION="latest/download"

# Parse arguments.
while (($# > 0)); do
case "$1" in
--help | -h)
usage
exit 0
;;
--bin-dir | -d)
INSTALL_PREFIX="$2"
shift
;;
--base-name | -b)
INSTALL_FILE="$2"
shift
;;
--version | -V)
VERSION="download/v${2}"
shift
;;
*)
usage
die "Unexpected argument: ${1}\n"
;;
esac
shift
done

ARCH="$(determine_arch)"
DIRSEP=$([[ "${OS}" == "windows" ]] && echo "\\" || echo "/")
INSTALL_DEST="${INSTALL_PREFIX}${DIRSEP}${INSTALL_FILE}"
DL_EXT=$([[ "${OS}" == "windows" ]] && echo ".exe" || echo "")
DL_URL="https://github.com/a-scie/lift/releases/${VERSION}/science-fat-${OS}-${ARCH}${DL_EXT}"

green "Download URL is: ${DL_URL}"
install_from_url "${DL_URL}" "${INSTALL_DEST}"

# Warn if the install prefix is not on $PATH.
if ! [[ ":$PATH:" == *":${INSTALL_PREFIX}:"* ]]; then
warn "WARNING: ${INSTALL_PREFIX} is not detected on \$PATH"
warn "You'll either need to invoke ${INSTALL_DEST} explicitly or else add ${INSTALL_PREFIX} to your shell's PATH."
fi
61 changes: 61 additions & 0 deletions tests/test_installer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright 2024 Science project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import subprocess
from pathlib import Path

import pytest
from _pytest.tmpdir import TempPathFactory

from science.os import IS_WINDOWS


@pytest.fixture(scope="module")
def installer(build_root: Path) -> list:
installer = build_root / "install.sh"
return [Path(r"C:\Program Files\Git\bin\bash.EXE"), installer] if IS_WINDOWS else [installer]


def run_captured(cmd: list):
return subprocess.run(cmd, capture_output=True, text=True)


def test_installer_help(installer: list):
"""Validates -h|--help in the installer."""
for tested_flag in ("-h", "--help"):
assert (result := run_captured(installer + [tested_flag])).returncode == 0
assert "--help" in result.stdout, "Expected '--help' in tool output"


def test_installer_fetch_latest(tmp_path_factory: TempPathFactory, installer: list):
"""Invokes install.sh to fetch the latest science release binary, then invokes it."""
test_dir = tmp_path_factory.mktemp("install-test-default")
bin_dir = test_dir / "bin"

assert (result := run_captured(installer + ["-d", bin_dir])).returncode == 0
assert "success" in result.stderr, "Expected 'success' in tool stderr logging"

assert (result := run_captured([bin_dir / "science", "-V"])).returncode == 0
assert result.stdout.strip(), "Expected version output in tool stdout"


def test_installer_fetch_argtest(tmp_path_factory: TempPathFactory, installer: list):
"""Exercises all the options in the installer."""
test_dir = tmp_path_factory.mktemp("install-test")
test_ver = "0.7.0"
bin_dir = test_dir / "bin"
bin_file = f"science{test_ver}"

assert (
result := run_captured(installer + ["-V", test_ver, "-b", bin_file, "-d", bin_dir])
).returncode == 0
assert "success" in result.stderr, "Expected 'success' in tool stderr logging"

# Ensure missing $PATH entry warning (assumes our temp dir by nature is not on $PATH).
assert "is not detected on $PATH" in result.stderr, "Expected missing $PATH entry warning"

# Check expected versioned binary exists.
assert (result := run_captured([bin_dir / bin_file, "-V"])).returncode == 0
assert (
result.stdout.strip() == test_ver
), f"Expected version output in tool stdout to be {test_ver}"

0 comments on commit 351ff9d

Please sign in to comment.