#!/usr/bin/env bash # Remove generated build output from this checkout. # # Targets are limited to directories this project generates. Nothing tracked by # git is removed, and Source/, third_party/, .git and the other project # directories are refused even if they are named as a target. set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$REPO_ROOT" usage() { cat <<'EOF' Usage: scripts/clean.sh [options] Removes generated build output. With no options it removes the build trees under build/. Options: --staging also remove the staged Windows bundle, SerumAlt_Windows/ --legacy also remove build trees left in the repository root by earlier versions of the build scripts (build_*, cmake-build-*) --all --staging and --legacy together --dry-run print every path and its size, and remove nothing -h, --help print this text EOF } DRY_RUN=false STAGING=false LEGACY=false for arg in "$@"; do case "$arg" in --dry-run) DRY_RUN=true ;; --staging) STAGING=true ;; --legacy) LEGACY=true ;; --all) STAGING=true; LEGACY=true ;; -h|--help) usage; exit 0 ;; *) echo "ERROR: unknown option: $arg" >&2; usage >&2; exit 2 ;; esac done if ! command -v git >/dev/null 2>&1; then echo "ERROR: git is required to confirm that no tracked file is removed." >&2 exit 2 fi # A build directory outside the repository belongs to some other checkout, and # this script has no business deciding whether it is disposable. for var in BUILD_DIR OUTPUT_DIR; do value="${!var:-}" [ -n "$value" ] || continue case "$value" in /*) abs="$value" ;; *) abs="$REPO_ROOT/$value" ;; esac abs="$(realpath -m -- "$abs" 2>/dev/null || printf '%s' "$abs")" case "$abs" in "$REPO_ROOT"/*) ;; *) echo "ERROR: $var=$value resolves outside $REPO_ROOT." >&2 echo " Unset $var to clean this repository." >&2 exit 2 ;; esac done PROTECTED=(Source third_party .git scripts cmake docs graft) refused=0 removed=0 remove_target() { local path="$1" [ -e "$path" ] || return 0 local abs rel size abs="$(realpath -m -- "$path")" rel="${abs#"$REPO_ROOT"/}" case "$abs" in "$REPO_ROOT"/*) ;; *) echo "refusing, outside the repository: $abs" >&2 refused=$((refused + 1)) return 1 ;; esac local deny for deny in "${PROTECTED[@]}"; do if [ "$rel" = "$deny" ] || [ "${rel#"$deny"/}" != "$rel" ]; then echo "refusing, protected path: $rel" >&2 refused=$((refused + 1)) return 1 fi done if [ -n "$(git ls-files -- "$rel")" ]; then echo "refusing, tracked by git: $rel" >&2 refused=$((refused + 1)) return 1 fi size="$(du -sh -- "$rel" 2>/dev/null | cut -f1)" if [ "$DRY_RUN" = true ]; then printf 'would remove %6s %s\n' "${size:-?}" "$rel" else rm -rf -- "$abs" printf 'removed %6s %s\n' "${size:-?}" "$rel" fi removed=$((removed + 1)) } TARGETS=("$REPO_ROOT/build") if [ "$STAGING" = true ]; then TARGETS+=("$REPO_ROOT/SerumAlt_Windows") fi if [ "$LEGACY" = true ]; then shopt -s nullglob for dir in "$REPO_ROOT"/build_* "$REPO_ROOT"/cmake-build-*; do TARGETS+=("$dir") done shopt -u nullglob fi for target in "${TARGETS[@]}"; do remove_target "$target" || true done if [ "$removed" -eq 0 ] && [ "$refused" -eq 0 ]; then echo "Nothing to remove." elif [ "$DRY_RUN" = true ]; then echo "Dry run: $removed path(s) listed, nothing removed." fi [ "$refused" -eq 0 ]