#!/usr/bin/env bash # Build the vibescrobble CLI for every Linux target web/install.sh offers, # from one commit, into a directory of its own. It publishes nothing. # # scripts/release-cli.sh # HEAD # scripts/release-cli.sh v0.1.0 # any commit-ish # scripts/release-cli.sh --name v0.1.0 # print and build nothing # # What it leaves in dist/cli// is exactly what install.sh reads from # /releases/cli//: # # vibescrobble--x86_64-unknown-linux-musl.tar.gz # vibescrobble--aarch64-unknown-linux-musl.tar.gz # SHA256SUMS # # is `v` when the commit is the one tag v names, # and `v-dev.` for any other commit, so a build of a branch can # never be mistaken for the release it is not. The binaries are static musl, # built by cli.Dockerfile from `git archive` of the commit, so a dirty working # tree cannot leak into them and the machine needs Docker and nothing else: # no Rust, no Zig, no credentials. set -euo pipefail cd "$(dirname "$0")/.." usage() { echo "usage: scripts/release-cli.sh [--name] []" } rev="" name_only="" for arg in "$@"; do case "$arg" in --name) name_only=1 ;; -h | --help) usage exit 0 ;; -*) usage >&2 exit 2 ;; *) if [ -n "$rev" ]; then usage >&2 exit 2 fi rev="$arg" ;; esac done rev="${rev:-HEAD}" say() { printf '\033[1m==> %s\033[0m\n' "$*"; } for tool in git docker sha256sum tar; do command -v "$tool" >/dev/null || { echo "release-cli: $tool is required and was not found on PATH" >&2 exit 1 } done sha="$(git rev-parse --verify --quiet --short=12 "$rev^{commit}")" || { echo "release-cli: $rev is not a commit" >&2 exit 1 } git cat-file -e "$sha:infra/files/cli.Dockerfile" 2>/dev/null || { echo "release-cli: infra/files/cli.Dockerfile is not in $sha" >&2 exit 1 } # The workspace's one version, as that commit has it. version="$(git show "$sha:Cargo.toml" | sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"/\1/p')" [ -n "$version" ] || { echo "release-cli: no [workspace.package] version in $sha's Cargo.toml" >&2 exit 1 } tagged="$(git rev-parse --verify --quiet "refs/tags/v$version^{commit}" || true)" if [ -n "$tagged" ] && [ "$(git rev-parse "$sha^{commit}")" = "$tagged" ]; then release="v$version" else release="v$version-dev.$sha" fi if [ -n "$name_only" ]; then echo "$release" exit 0 fi out="dist/cli/$release" if [ -f "$out/SHA256SUMS" ]; then say "$out is already built" exit 0 fi work="$(mktemp -d -t vibescrobble-release-cli-XXXXXX)" trap 'rm -rf "$work"' EXIT # The builder release.sh uses, named so that a build never lands on whatever # is somebody's default. Its cache mounts are what make a second build quick. builder=vibescrobble docker buildx inspect "$builder" >/dev/null 2>&1 || docker buildx create --name "$builder" >/dev/null say "building $release from $sha" mkdir "$work/context" git archive "$sha" | tar -x -C "$work/context" docker buildx build \ --builder "$builder" \ --file "$work/context/infra/files/cli.Dockerfile" \ --build-arg CARGO_BUILD_JOBS \ --build-arg "RELEASE=$release" \ --build-arg "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct "$sha")" \ --output "type=local,dest=$work/out" \ "$work/context" # The sums are checked here once before anything is kept, and the binary # this machine can run is asked its version: a tarball that unpacks to the # wrong program is caught before anybody downloads it. (cd "$work/out" && sha256sum --quiet -c SHA256SUMS) case "$(uname -m)" in x86_64) native=x86_64-unknown-linux-musl ;; aarch64 | arm64) native=aarch64-unknown-linux-musl ;; *) native="" ;; esac if [ -n "$native" ]; then tar -xzf "$work/out/vibescrobble-$release-$native.tar.gz" -C "$work" said="$("$work/vibescrobble-$release-$native/vibescrobble" --version)" [ "$said" = "vibescrobble $version" ] || { echo "release-cli: the $native binary says '$said', not 'vibescrobble $version'" >&2 exit 1 } fi mkdir -p "$(dirname "$out")" rm -rf "$out.partial" mv "$work/out" "$out.partial" mv "$out.partial" "$out" say "built $out:" sed 's/^/ /' "$out/SHA256SUMS"