159 lines
5.4 KiB
Bash
Executable File
159 lines
5.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
cd "$ROOT"
|
|
|
|
DIST="$ROOT/dist"
|
|
CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$ROOT/target}"
|
|
export CARGO_TARGET_DIR
|
|
|
|
fail() {
|
|
printf 'error: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
need() {
|
|
command -v "$1" >/dev/null 2>&1 || fail "$1 is required"
|
|
}
|
|
|
|
need cargo
|
|
need rustc
|
|
need python3
|
|
need sha256sum
|
|
need tar
|
|
need git
|
|
|
|
python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3,10) else 1)' \
|
|
|| fail 'Python 3.10+ is required'
|
|
|
|
cargo fmt --version >/dev/null 2>&1 \
|
|
|| fail 'rustfmt component is required (rustup component add rustfmt)'
|
|
cargo clippy --version >/dev/null 2>&1 \
|
|
|| fail 'clippy component is required (rustup component add clippy)'
|
|
|
|
[[ -f Cargo.toml ]] || fail 'run this script from the RustPad project root'
|
|
[[ -f Cargo.lock ]] || fail 'Cargo.lock is required for a reproducible release'
|
|
[[ -f make_zip.py ]] || fail 'make_zip.py is missing'
|
|
[[ -f scripts/update_browser_libs.py ]] || fail 'scripts/update_browser_libs.py is missing'
|
|
[[ -f scripts/browser-libs.lock.json ]] || fail 'scripts/browser-libs.lock.json is missing'
|
|
[[ -d static ]] || fail 'static directory is missing'
|
|
[[ -d migrations ]] || fail 'migrations directory is missing'
|
|
|
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
|
|| fail 'release source archive requires a Git working tree'
|
|
|
|
METADATA="$(cargo metadata --locked --no-deps --format-version 1)"
|
|
read -r PACKAGE VERSION REQUIRED_RUST < <(
|
|
printf '%s' "$METADATA" | python3 -c '
|
|
import json, sys
|
|
data = json.load(sys.stdin)
|
|
packages = data.get("packages", [])
|
|
pkg = next((p for p in packages if p.get("name") == "rustpad"), None)
|
|
if pkg is None:
|
|
raise SystemExit("rustpad package not found in Cargo metadata")
|
|
print(pkg["name"], pkg["version"], pkg.get("rust_version") or "0")
|
|
'
|
|
)
|
|
|
|
[[ "$PACKAGE" == "rustpad" ]] || fail 'Cargo package rustpad was not found'
|
|
[[ -n "$VERSION" ]] || fail 'could not determine project version'
|
|
|
|
RUSTC_VERSION="$(rustc -V | awk '{print $2}')"
|
|
HOST="$(rustc -vV | sed -n 's/^host: //p')"
|
|
[[ -n "$RUSTC_VERSION" ]] || fail 'could not determine rustc version'
|
|
[[ -n "$HOST" ]] || fail 'could not determine Rust host target'
|
|
|
|
python3 - "$RUSTC_VERSION" "$REQUIRED_RUST" <<'PY' \
|
|
|| fail "Rust $REQUIRED_RUST+ is required (found $RUSTC_VERSION)"
|
|
import re
|
|
import sys
|
|
|
|
def version_tuple(value: str) -> tuple[int, int, int]:
|
|
match = re.match(r"^(\d+)\.(\d+)(?:\.(\d+))?", value)
|
|
if not match:
|
|
raise SystemExit(1)
|
|
major, minor, patch = match.groups(default="0")
|
|
return int(major), int(minor), int(patch)
|
|
|
|
raise SystemExit(0 if version_tuple(sys.argv[1]) >= version_tuple(sys.argv[2]) else 1)
|
|
PY
|
|
|
|
printf 'RustPad %s release build for %s\n' "$VERSION" "$HOST"
|
|
printf 'Preparing locked browser libraries...\n'
|
|
python3 scripts/update_browser_libs.py --locked --strict
|
|
|
|
printf '\nChecking formatting...\n'
|
|
cargo fmt --all -- --check
|
|
|
|
printf '\nRunning tests...\n'
|
|
cargo test --locked --all-targets
|
|
|
|
printf '\nRunning clippy...\n'
|
|
cargo clippy --locked --all-targets -- -D warnings
|
|
|
|
printf '\nBuilding release binary...\n'
|
|
rm -rf "$CARGO_TARGET_DIR/release"
|
|
cargo build --locked --release --bin rustpad
|
|
|
|
BIN="$CARGO_TARGET_DIR/release/rustpad"
|
|
[[ -x "$BIN" ]] || fail "release binary was not created: $BIN"
|
|
|
|
ACTUAL_VERSION="$("$BIN" --version)"
|
|
EXPECTED_VERSION="rustpad $VERSION"
|
|
[[ "$ACTUAL_VERSION" == "$EXPECTED_VERSION" ]] \
|
|
|| fail "built binary reports '$ACTUAL_VERSION', expected '$EXPECTED_VERSION'"
|
|
printf 'Built: %s\n' "$ACTUAL_VERSION"
|
|
|
|
rm -rf "$DIST"
|
|
mkdir -p "$DIST"
|
|
|
|
# Source archive. dist/ is ignored by Git, so generated release files are not included.
|
|
SOURCE_ZIP="$DIST/RustPad-${VERSION}-source.zip"
|
|
python3 make_zip.py "$SOURCE_ZIP"
|
|
[[ -s "$SOURCE_ZIP" ]] || fail "source archive was not created: $SOURCE_ZIP"
|
|
sha256sum "$SOURCE_ZIP" > "$SOURCE_ZIP.sha256"
|
|
|
|
BINARY_ARTIFACT="rustpad-${VERSION}-${HOST}"
|
|
cp "$BIN" "$DIST/$BINARY_ARTIFACT"
|
|
chmod 0755 "$DIST/$BINARY_ARTIFACT"
|
|
sha256sum "$DIST/$BINARY_ARTIFACT" > "$DIST/$BINARY_ARTIFACT.sha256"
|
|
|
|
# RustPad needs static assets and SQL migrations at runtime.
|
|
BUNDLE_NAME="RustPad-${VERSION}-${HOST}"
|
|
BUNDLE_DIR="$DIST/$BUNDLE_NAME"
|
|
BUNDLE_ARCHIVE="$DIST/$BUNDLE_NAME.tar.gz"
|
|
|
|
mkdir -p "$BUNDLE_DIR/data/db" "$BUNDLE_DIR/data/files"
|
|
cp "$BIN" "$BUNDLE_DIR/rustpad"
|
|
chmod 0755 "$BUNDLE_DIR/rustpad"
|
|
cp -a static "$BUNDLE_DIR/static"
|
|
cp -a migrations "$BUNDLE_DIR/migrations"
|
|
|
|
[[ -d systemd ]] && cp -a systemd "$BUNDLE_DIR/systemd"
|
|
[[ -f README.md ]] && cp README.md "$BUNDLE_DIR/README.md"
|
|
[[ -f LICENSE.md ]] && cp LICENSE.md "$BUNDLE_DIR/LICENSE.md"
|
|
[[ -f .env.example ]] && cp .env.example "$BUNDLE_DIR/.env.example"
|
|
|
|
cat > "$BUNDLE_DIR/RELEASE.txt" <<EOF_RELEASE
|
|
RustPad $VERSION
|
|
Target: $HOST
|
|
Rust: $RUSTC_VERSION
|
|
|
|
Run RustPad from this directory, or configure STATIC_DIR, FILES_DIR and DATABASE_URL
|
|
with absolute paths appropriate for the installation.
|
|
EOF_RELEASE
|
|
|
|
tar -C "$DIST" -czf "$BUNDLE_ARCHIVE" "$BUNDLE_NAME"
|
|
[[ -s "$BUNDLE_ARCHIVE" ]] || fail "runtime archive was not created: $BUNDLE_ARCHIVE"
|
|
sha256sum "$BUNDLE_ARCHIVE" > "$BUNDLE_ARCHIVE.sha256"
|
|
rm -rf "$BUNDLE_DIR"
|
|
|
|
printf '\nRelease ready:\n'
|
|
printf ' binary: %s\n' "$DIST/$BINARY_ARTIFACT"
|
|
printf ' checksum: %s\n' "$DIST/$BINARY_ARTIFACT.sha256"
|
|
printf ' runtime: %s\n' "$BUNDLE_ARCHIVE"
|
|
printf ' checksum: %s\n' "$BUNDLE_ARCHIVE.sha256"
|
|
printf ' source zip: %s\n' "$SOURCE_ZIP"
|
|
printf ' checksum: %s\n' "$SOURCE_ZIP.sha256"
|