#!/usr/bin/env bash set -euo pipefail # Ensure cargo is on PATH export PATH="${HOME}/.cargo/bin:${PATH}" # ============================================================ # Release configuration # ============================================================ REPO="https://gitea.dcglab.co.uk/steve/mouth" FORGE="gitea" # "gitea" (uses tea CLI) or "github" (uses gh CLI) # ============================================================ # Derived variables # ============================================================ VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') RELEASE_DIR="release/v${VERSION}" BINARY_NAME="mouth" TAG="v${VERSION}" echo "=== Mouth Release Build ${TAG} ===" echo "Forge: ${FORGE} (${REPO})" echo "" # Ensure we're in the project root if [ ! -f Cargo.toml ]; then echo "ERROR: Must be run from the project root (where Cargo.toml is)" exit 1 fi # Check CLI tools if [ "${FORGE}" = "gitea" ]; then if ! command -v tea &>/dev/null; then echo "ERROR: 'tea' CLI not found. Install: https://gitea.com/gitea/tea" exit 1 fi elif [ "${FORGE}" = "github" ]; then if ! command -v gh &>/dev/null; then echo "ERROR: 'gh' CLI not found. Install: https://cli.github.com" exit 1 fi else echo "ERROR: Unknown forge '${FORGE}'. Must be 'gitea' or 'github'." exit 1 fi # Clean previous release artifacts for this version rm -rf "${RELEASE_DIR}" mkdir -p "${RELEASE_DIR}" # Track build results BUILT=() FAILED=() build_target() { local target="$1" local label="$2" local ext="${3:-}" echo "--- Building ${label} (${target}) ---" if cargo build --release --target "${target}" 2>&1; then local binary="target/${target}/release/${BINARY_NAME}${ext}" if [ -f "${binary}" ]; then local archive="${RELEASE_DIR}/${BINARY_NAME}-${TAG}-${target}" if [ -n "${ext}" ]; then # Windows: ship the exe directly cp "${binary}" "${archive}${ext}" echo " -> ${archive}${ext}" BUILT+=("${archive}${ext}") else # Linux/macOS: tar.gz local tar_name="${archive}.tar.gz" tar -czf "${tar_name}" -C "target/${target}/release" "${BINARY_NAME}" echo " -> ${tar_name}" BUILT+=("${tar_name}") fi else echo " WARN: Binary not found at ${binary}" FAILED+=("${label}") fi else echo " FAILED: ${label}" FAILED+=("${label}") fi echo "" } # ============================================================ # Build targets # ============================================================ # Linux x86_64 (native — always available) build_target "x86_64-unknown-linux-gnu" "Linux x86_64" # Windows x86_64 (MSVC target via cargo-xwin) if command -v cargo-xwin &>/dev/null && rustup target list --installed | grep -q x86_64-pc-windows-msvc; then echo "--- Building Windows x86_64 (x86_64-pc-windows-msvc via cargo-xwin) ---" if cargo xwin build --release --target x86_64-pc-windows-msvc 2>&1; then local_binary="target/x86_64-pc-windows-msvc/release/${BINARY_NAME}.exe" if [ -f "${local_binary}" ]; then archive="${RELEASE_DIR}/${BINARY_NAME}-${TAG}-x86_64-pc-windows-msvc.exe" cp "${local_binary}" "${archive}" echo " -> ${archive}" BUILT+=("${archive}") else echo " WARN: Binary not found" FAILED+=("Windows x86_64 (MSVC)") fi else echo " FAILED: Windows x86_64 (MSVC)" FAILED+=("Windows x86_64 (MSVC)") fi echo "" else echo "--- Skipping Windows x86_64 ---" echo " cargo-xwin not installed or x86_64-pc-windows-msvc target missing." echo " To enable Windows builds from Linux:" echo " cargo install cargo-xwin" echo " rustup target add x86_64-pc-windows-msvc" echo "" FAILED+=("Windows x86_64 (not configured)") fi # ============================================================ # Summary # ============================================================ echo "=== Build Summary ===" echo "" if [ ${#BUILT[@]} -gt 0 ]; then echo "Successful builds:" for b in "${BUILT[@]}"; do local_size=$(du -h "${b}" | cut -f1) echo " ${b} (${local_size})" done fi if [ ${#FAILED[@]} -gt 0 ]; then echo "" echo "Failed / skipped builds:" for f in "${FAILED[@]}"; do echo " ${f}" done fi echo "" echo "Release artifacts in: ${RELEASE_DIR}/" echo "" # Generate checksums if [ ${#BUILT[@]} -gt 0 ]; then echo "--- Checksums (SHA256) ---" cd "${RELEASE_DIR}" sha256sum * > checksums-sha256.txt 2>/dev/null || shasum -a 256 * > checksums-sha256.txt cat checksums-sha256.txt cd - > /dev/null fi # ============================================================ # Publish release # ============================================================ if [ ${#BUILT[@]} -eq 0 ]; then echo "" echo "No successful builds — skipping release publish." exit 1 fi echo "" read -rp "Publish release ${TAG} to ${FORGE}? [y/N] " confirm if [[ ! "${confirm}" =~ ^[Yy]$ ]]; then echo "Skipped. Artifacts are in ${RELEASE_DIR}/" exit 0 fi # Ensure the git tag exists if ! git rev-parse "${TAG}" &>/dev/null; then echo "Creating git tag ${TAG}..." git tag -a "${TAG}" -m "Release ${TAG}" git push origin "${TAG}" fi # Collect all release files (artifacts + checksums) RELEASE_FILES=() for b in "${BUILT[@]}"; do RELEASE_FILES+=("${b}") done RELEASE_FILES+=("${RELEASE_DIR}/checksums-sha256.txt") RELEASE_TITLE="Mouth ${TAG}" RELEASE_BODY="## Mouth ${TAG} ### Downloads $(for b in "${BUILT[@]}"; do echo "- $(basename "${b}")"; done) ### Checksums (SHA256) \`\`\` $(cat "${RELEASE_DIR}/checksums-sha256.txt") \`\`\` " if [ "${FORGE}" = "gitea" ]; then echo "Publishing to Gitea via tea..." # Extract host and owner/repo from REPO URL REPO_OWNER_NAME=$(echo "${REPO}" | sed 's|.*://[^/]*/||') # Create the release tea release create \ --repo "${REPO_OWNER_NAME}" \ --tag "${TAG}" \ --title "${RELEASE_TITLE}" \ --note "${RELEASE_BODY}" # Upload assets for f in "${RELEASE_FILES[@]}"; do echo " Uploading $(basename "${f}")..." tea release asset create \ --repo "${REPO_OWNER_NAME}" \ --tag "${TAG}" \ --name "$(basename "${f}")" \ --file "${f}" done elif [ "${FORGE}" = "github" ]; then echo "Publishing to GitHub via gh..." gh release create "${TAG}" \ --repo "${REPO}" \ --title "${RELEASE_TITLE}" \ --notes "${RELEASE_BODY}" \ "${RELEASE_FILES[@]}" fi echo "" echo "=== Release ${TAG} published to ${FORGE}! ==="