#!/usr/bin/env bash # Builds Vectrum Code in release mode and writes two artifacts to ./build: # build/vectrum-code plain binary # build/Vectrum_Code--.AppImage # # appimagetool is used from $PATH if present, otherwise downloaded once into # build/.tools. Set APPIMAGETOOL=/path/to/appimagetool to use a specific one. # # ./build.sh --release (or -r) then publishes them as a release on Vectrum: # it tags the commit with the version in Cargo.toml, pushes the tag, and # uploads the AppImage, the binary and SHA256SUMS (the first upload # publishes the release). Bump the version in Cargo.toml for each release. # VECTRUM_TOKEN a read & write API token (vec_…); otherwise read from # ~/.config/vectrum/token (or $VECTRUM_TOKEN_FILE) # VECTRUM_URL the server, https://vectrum.cc by default # VECTRUM_REPO owner/repo, taken from the origin remote by default set -euo pipefail RELEASE=0 for arg in "$@"; do case "$arg" in -r|--release) RELEASE=1 ;; -h|--help) sed -n '2,/^set -euo/p' "$0" | sed '$d; s/^# \{0,1\}//'; exit 0 ;; *) printf 'Unknown option: %s (try --help)\n' "$arg" >&2; exit 2 ;; esac done ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BUILD="$ROOT/build" TOOLS="$BUILD/.tools" APPDIR="$BUILD/AppDir" NAME="vectrum-code" PRETTY="Vectrum Code" VERSION="$(sed -n 's/^version *= *"\(.*\)"/\1/p' "$ROOT/Cargo.toml" | head -n1)" ARCH="$(uname -m)" log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } die() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; } # ── 0. Release checks, before spending time on a build ─────────────────────── if (( RELEASE )); then TAG="$VERSION" SERVER="${VECTRUM_URL:-https://vectrum.cc}" SERVER="${SERVER%/}" TOKEN_FILE="${VECTRUM_TOKEN_FILE:-$HOME/.config/vectrum/token}" TOKEN="${VECTRUM_TOKEN:-}" if [[ -z "$TOKEN" && -r "$TOKEN_FILE" ]]; then TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")" fi [[ -n "$TOKEN" ]] || die "No API token: set VECTRUM_TOKEN or save a read & write token to $TOKEN_FILE" # ssh://git@git.vectrum.cc:2222/vectrum/code.git, https://…/vectrum/code → vectrum/code REPO="${VECTRUM_REPO:-$(git -C "$ROOT" remote get-url origin | sed -E 's#\.git$##; s#^.*[:/]([^/:]+/[^/:]+)$#\1#')}" [[ "$REPO" == */* ]] || die "Couldn't tell the repository from the origin remote; set VECTRUM_REPO=owner/repo" [[ -z "$(git -C "$ROOT" status --porcelain)" ]] || die "Commit or stash your changes first: a release is built from a commit" HEAD="$(git -C "$ROOT" rev-parse HEAD)" REMOTE_TAG="$(git -C "$ROOT" ls-remote origin "refs/tags/$TAG^{}" "refs/tags/$TAG" | awk 'NR==1 || /\^\{\}$/ {sha=$1} END {print sha}')" LOCAL_TAG="$(git -C "$ROOT" rev-parse -q --verify "refs/tags/$TAG^{commit}" || true)" for tagged in "$REMOTE_TAG" "$LOCAL_TAG"; do [[ -z "$tagged" || "$tagged" == "$HEAD" ]] || die "$TAG is already tagged at ${tagged:0:7}; bump the version in Cargo.toml for a new release" done log "Releasing $TAG of $REPO on $SERVER from ${HEAD:0:7}" fi # ── 1. Binary ──────────────────────────────────────────────────────────────── log "Building $NAME $VERSION (release, $ARCH)" cargo build --release --manifest-path "$ROOT/Cargo.toml" TARGET_DIR="$(cargo metadata --format-version 1 --no-deps --manifest-path "$ROOT/Cargo.toml" | sed -n 's/.*"target_directory":"\([^"]*\)".*/\1/p')" mkdir -p "$BUILD" install -m755 "$TARGET_DIR/release/$NAME" "$BUILD/$NAME" log "Binary: build/$NAME" # ── 2. AppDir ──────────────────────────────────────────────────────────────── rm -rf "$APPDIR" mkdir -p "$APPDIR/usr/bin" \ "$APPDIR/usr/share/applications" \ "$APPDIR/usr/share/icons/hicolor/scalable/apps" install -m755 "$BUILD/$NAME" "$APPDIR/usr/bin/$NAME" cat > "$APPDIR/usr/share/applications/$NAME.desktop" < "$APPDIR/AppRun" <<'EOF' #!/bin/sh HERE="$(dirname "$(readlink -f "$0")")" exec "$HERE/usr/bin/vectrum-code" "$@" EOF chmod +x "$APPDIR/AppRun" ln -sf "usr/share/applications/$NAME.desktop" "$APPDIR/$NAME.desktop" ln -sf "usr/share/icons/hicolor/scalable/apps/$NAME.svg" "$APPDIR/$NAME.svg" ln -sf "$NAME.svg" "$APPDIR/.DirIcon" # ── 3. appimagetool ────────────────────────────────────────────────────────── APPIMAGETOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}" if [[ -z "$APPIMAGETOOL" ]]; then APPIMAGETOOL="$TOOLS/appimagetool-$ARCH.AppImage" if [[ ! -x "$APPIMAGETOOL" ]]; then log "Downloading appimagetool" mkdir -p "$TOOLS" curl -fL --progress-bar -o "$APPIMAGETOOL.part" \ "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$ARCH.AppImage" chmod +x "$APPIMAGETOOL.part" mv "$APPIMAGETOOL.part" "$APPIMAGETOOL" fi fi # ── 4. AppImage ────────────────────────────────────────────────────────────── OUT="$BUILD/${PRETTY// /_}-$VERSION-$ARCH.AppImage" log "Packaging AppImage" # Extract-and-run so this works on systems without FUSE (containers, CI). APPIMAGE_EXTRACT_AND_RUN=1 ARCH="$ARCH" "$APPIMAGETOOL" --no-appstream "$APPDIR" "$OUT" rm -rf "$APPDIR" log "AppImage: ${OUT#"$ROOT"/}" # ── 5. Release ─────────────────────────────────────────────────────────────── if (( RELEASE )); then STAGE="$BUILD/release-$TAG" rm -rf "$STAGE" mkdir -p "$STAGE" cp "$OUT" "$STAGE/" cp "$BUILD/$NAME" "$STAGE/$NAME-$VERSION-$ARCH" (cd "$STAGE" && sha256sum -- * > SHA256SUMS) if [[ -z "$LOCAL_TAG" ]]; then git -C "$ROOT" tag -a "$TAG" -m "$PRETTY $VERSION" "$HEAD" fi if [[ -z "$REMOTE_TAG" ]]; then log "Pushing tag $TAG" git -C "$ROOT" push -q origin "refs/tags/$TAG" fi for file in "$STAGE"/*; do name="$(basename "$file")" log "Uploading $name" # The token goes in through a file descriptor, so it never shows in ps. if ! reply="$(curl -sS --fail-with-body -T "$file" -H @<(printf 'Authorization: Bearer %s\n' "$TOKEN") \ "$SERVER/$REPO/releases/download/$TAG/$name")"; then die "Uploading $name failed: $reply" fi done log "Released: $SERVER/$REPO/releases/tag/$TAG" fi