#!/usr/bin/env bash # Build a portable rcalc AppImage. # # The release binary is compiled inside Ubuntu 22.04 (glibc 2.35) so the # AppImage runs on most modern distros — not only on bleeding-edge hosts. # AppImage itself does NOT bundle glibc; the Docker base sets the floor. set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" ARCH="$(uname -m)" OUT_DIR="${ROOT}/dist" APPDIR="${OUT_DIR}/rcalc.AppDir" TOOLS_DIR="${ROOT}/.cache/appimage-tools" VERSION="$(grep -m1 '^version' "${ROOT}/Cargo.toml" | sed 's/.*"\(.*\)".*/\1/')" IMAGE_NAME="rcalc-${VERSION}-${ARCH}.AppImage" DOCKER_IMAGE="${DOCKER_IMAGE:-ubuntu:22.04}" # Set SKIP_DOCKER=1 to package the already-built host binary (same glibc floor as host). SKIP_DOCKER="${SKIP_DOCKER:-0}" mkdir -p "${OUT_DIR}" "${TOOLS_DIR}" need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "error: required command not found: $1" >&2 exit 1 } } download_tool() { local url="$1" dest="$2" if [[ -x "$dest" ]]; then return 0 fi echo "Downloading $(basename "$dest")…" curl -fsSL -o "$dest" "$url" chmod +x "$dest" } build_binary_docker() { need_cmd docker echo "Building release binary in ${DOCKER_IMAGE} (portable glibc)…" mkdir -p "${HOME}/.cargo/registry" "${HOME}/.cargo/git" docker run --rm \ -v "${ROOT}:/src" \ -v "${HOME}/.cargo/registry:/root/.cargo/registry" \ -v "${HOME}/.cargo/git:/root/.cargo/git" \ -w /src \ "${DOCKER_IMAGE}" \ bash -lc ' set -euo pipefail export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq curl build-essential pkg-config libfontconfig1-dev ca-certificates curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal # shellcheck disable=SC1091 source "$HOME/.cargo/env" export CARGO_TARGET_DIR=/src/target-appimage cargo build --release install -D -m755 /src/target-appimage/release/rcalc /src/dist/rcalc ldd --version | head -1 > /src/dist/.glibc-build.txt # Make artifacts writable by the host user chown -R '"$(id -u):$(id -g)"' /src/dist/rcalc /src/dist/.glibc-build.txt /src/target-appimage || true ' } ensure_binary() { if [[ "$SKIP_DOCKER" == "1" ]]; then if [[ ! -x "${OUT_DIR}/rcalc" ]]; then echo "Building on host (SKIP_DOCKER=1)…" (cd "${ROOT}" && cargo build --release) install -D -m755 "${ROOT}/target/release/rcalc" "${OUT_DIR}/rcalc" fi ldd --version | head -1 > "${OUT_DIR}/.glibc-build.txt" || true else build_binary_docker fi if [[ ! -x "${OUT_DIR}/rcalc" ]]; then echo "error: dist/rcalc missing after build" >&2 exit 1 fi } package_appimage() { need_cmd curl local linuxdeploy="${TOOLS_DIR}/linuxdeploy-${ARCH}.AppImage" download_tool \ "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${ARCH}.AppImage" \ "${linuxdeploy}" rm -rf "${APPDIR}" mkdir -p "${APPDIR}/usr/bin" "${APPDIR}/usr/share/icons/hicolor/256x256/apps" install -m755 "${OUT_DIR}/rcalc" "${APPDIR}/usr/bin/rcalc" install -m644 "${ROOT}/assets/icon-256.png" "${APPDIR}/usr/share/icons/hicolor/256x256/apps/rcalc.png" install -m644 "${ROOT}/assets/icon-256.png" "${APPDIR}/rcalc.png" install -m644 "${ROOT}/assets/rcalc-appimage.desktop" "${APPDIR}/rcalc.desktop" echo "Bundling libraries with linuxdeploy…" export APPIMAGE_EXTRACT_AND_RUN=1 export OUTPUT="${IMAGE_NAME}" # Marker: mtime boundary to find the FRESH linuxdeploy output. local marker="${OUT_DIR}/.pack-start" touch "${marker}" ( cd "${OUT_DIR}" "${linuxdeploy}" \ --appdir "${APPDIR}" \ --executable "${APPDIR}/usr/bin/rcalc" \ --desktop-file "${APPDIR}/rcalc.desktop" \ --icon-file "${APPDIR}/rcalc.png" \ --output appimage ) # linuxdeploy drops the AppImage next to AppDir (dist/). Only consider files # NEWER than the marker: a blind `find | head -1` with several older # *.AppImage in dist/ renamed an OLD image OVER the freshly built one # (rcalc-0.1.28 got shipped with a 0.1.25 binary from the first build). local built built="$(find "${OUT_DIR}" -maxdepth 1 -name '*.AppImage' ! -name 'linuxdeploy*' -newer "${marker}" | head -1)" rm -f "${marker}" if [[ -z "$built" ]]; then echo "error: AppImage was not produced (linuxdeploy failed?)" >&2 exit 1 fi if [[ "$(basename "$built")" != "${IMAGE_NAME}" ]]; then mv -f "$built" "${OUT_DIR}/${IMAGE_NAME}" fi chmod +x "${OUT_DIR}/${IMAGE_NAME}" } ensure_binary package_appimage echo echo "OK: ${OUT_DIR}/${IMAGE_NAME}" if [[ -f "${OUT_DIR}/.glibc-build.txt" ]]; then echo "Built against: $(cat "${OUT_DIR}/.glibc-build.txt")" fi echo "Run: ${OUT_DIR}/${IMAGE_NAME}" echo "Tip: on the other PC, chmod +x and execute — no install needed."