#!/usr/bin/env bash
# prune-data.sh deletes from the MegaMek tree at image build. Two ways that goes
# wrong are invisible until a match runs, and both are what the guards exist for:
#
# - a MegaMek bump renames what a prune line names, the glob matches nothing,
# and the build quietly ships the bytes the line was written to remove
# - a glob grows too broad and takes a file the game needs with it
#
# So the test is not "does rm work". It is that both failures stop the build.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
pass=0; fail=0
check() { if "${@:2}"; then echo "ok $1"; pass=$((pass+1)); else echo "FAIL $1"; fail=$((fail+1)); fi; }
# A MegaMek tree carrying one file per path the script names, plus one file it
# must not touch in each directory it prunes from.
stage_tree() { #
local mm="$1"
rm -rf "$mm"
mkdir -p "$mm/data/images/portraits/Male/MekWarrior" \
"$mm/data/images/portraits/Female/MekWarrior" \
"$mm/data/images/portraits/Male/Astech" \
"$mm/data/images/portraits/Female/Astech" \
"$mm/data/images/portraits/Black Widow Company" \
"$mm/data/images/hexes/Ahne" \
"$mm/data/images/hexes/largeTextures" \
"$mm/data/images/misc" \
"$mm/data/images/widgets/kiro" \
"$mm/data/boards/unofficial/UlyssesMaps/MegaMek Invitational Map Pack" \
"$mm/data/universe/planetary_systems" \
"$mm/lib"
touch "$mm/data/images/portraits/default.gif" \
"$mm/data/images/portraits/Male/MekWarrior/MW_M_999.png" \
"$mm/data/images/portraits/Female/MekWarrior/MW_F_999.png" \
"$mm/data/images/portraits/Male/Astech/AST_M_1.png" \
"$mm/data/images/portraits/Female/Astech/AST_F_1.png" \
"$mm/data/images/portraits/Black Widow Company/Francine.jpg" \
"$mm/data/images/hexes/saxarba.tileset" \
"$mm/data/images/hexes/defaulthexset.txt" \
"$mm/data/images/hexes/largeTextures.tileset" \
"$mm/data/images/hexes/bw_atmospheric.tileset" \
"$mm/data/images/hexes/Ahne/hex.png" \
"$mm/data/images/hexes/largeTextures/hex.png" \
"$mm/data/images/misc/MekHQ.png" \
"$mm/data/images/misc/MekHQ Load_uhd.png" \
"$mm/data/images/misc/MHQcamo1.png" \
"$mm/data/images/misc/mml-background.png" \
"$mm/data/images/misc/mml-splash.jpg" \
"$mm/data/images/misc/mml_logo.jpg" \
"$mm/data/images/misc/megamek_splash_hd.png" \
"$mm/data/images/misc/camo_overlay0.png" \
"$mm/data/images/misc/camo_overlay5.png" \
"$mm/data/images/widgets/kiro/01.gif" \
"$mm/data/images/widgets/kiro/border.psd" \
"$mm/MegaMek.jar" \
"$mm/lib/MegaMek.jar" \
"$mm/lib/commons-io-2.21.0.jar" \
"$mm/data/universe/eras.xml" \
"$mm/data/universe/planetary_systems/canon_systems.zip"
# Directories with spaces in them, which the .docx prune has to glob through.
touch "$mm/data/boards/unofficial/UlyssesMaps/MegaMek Invitational Map Pack/MegaMek Invitational - Map Pack Rules.docx" \
"$mm/data/boards/unofficial/UlyssesMaps/MegaMek Invitational Map Pack/20x20 Marik Arena - Factory.board"
# The portraits the shipped scenarios name, read out of the script rather
# than repeated here: whether that list is the right one is settled against
# the real tree at build time, by keep. What the fixture is for is that
# everything else around them goes.
local portrait
for portrait in $(portraits_kept); do
touch "$(portrait_path "$mm" "$portrait")"
done
}
portraits_kept() {
grep -oE 'MekWarrior/MW_[MF]_[0-9]+\.png' "$ROOT/megamek/prune-data.sh" \
| sed 's#.*/##' | sort -u
}
portrait_path() { #
case "$2" in
MW_M_*) echo "$1/data/images/portraits/Male/MekWarrior/$2" ;;
*) echo "$1/data/images/portraits/Female/MekWarrior/$2" ;;
esac
}
run_prune() { #
MM_HOME="$1" bash "$ROOT/megamek/prune-data.sh" 2>&1
}
# --- the happy path ---------------------------------------------------------
MM="$TMP/mm"
stage_tree "$MM"
out="$(run_prune "$MM")"; status=$?
check "runs clean against a complete tree" test "$status" -eq 0
[ "$status" -eq 0 ] || printf '%s\n' "$out" | sed 's/^/ /'
check "unreferenced portraits removed" \
test ! -e "$MM/data/images/portraits/Male/MekWarrior/MW_M_999.png" \
-a ! -e "$MM/data/images/portraits/Female/MekWarrior/MW_F_999.png"
# A whole portrait category nothing names. The files go, and so does the
# directory they leave behind - an empty tree in the image is still inodes.
check "an emptied portrait category removed" \
test ! -e "$MM/data/images/portraits/Male/Astech" \
-a ! -e "$MM/data/images/portraits/Female/Astech"
check "unreachable hex sets removed" test ! -e "$MM/data/images/hexes/Ahne"
check "orphaned tilesets removed" \
test ! -e "$MM/data/images/hexes/largeTextures.tileset"
check "foreign-project art removed" test ! -e "$MM/data/images/misc/MekHQ.png"
check "art with a space in the name removed" \
test ! -e "$MM/data/images/misc/MekHQ Load_uhd.png"
check "Photoshop source art removed" \
test ! -e "$MM/data/images/widgets/kiro/border.psd"
# The pattern globs through two directories whose names contain spaces. A prune
# line that names such a path literally would be word-split and match nothing.
check "a document under directories with spaces removed" \
test ! -e "$MM/data/boards/unofficial/UlyssesMaps/MegaMek Invitational Map Pack/MegaMek Invitational - Map Pack Rules.docx"
# The first prune outside data/, and the one glob that could reach a file the
# build stages after this one still compile against.
check "the duplicate stock jar removed" test ! -e "$MM/lib/MegaMek.jar"
check "MekHQ's planetary systems removed" \
test ! -e "$MM/data/universe/planetary_systems"
check "the portrait fallback survives" test -e "$MM/data/images/portraits/default.gif"
# An MMSVersion 2 scenario whose messages: block names one of these does not
# load without it - MessageDeserializer throws rather than falling back.
missing=0
for portrait in $(portraits_kept); do
[ -e "$(portrait_path "$MM" "$portrait")" ] || missing=$((missing+1))
done
check "every portrait a shipped scenario names survives" test "$missing" -eq 0
check "portrait categories the script never names survive" \
test -e "$MM/data/images/portraits/Black Widow Company/Francine.jpg"
check "the kiro art the stock skins name survives" \
test -e "$MM/data/images/widgets/kiro/01.gif"
check "boards beside the pruned document survive" \
test -e "$MM/data/boards/unofficial/UlyssesMaps/MegaMek Invitational Map Pack/20x20 Marik Arena - Factory.board"
check "the stock jar the build compiles against survives" test -e "$MM/MegaMek.jar"
check "the other jars in lib/ survive" test -e "$MM/lib/commons-io-2.21.0.jar"
check "the universe data MegaMek does read survives" test -e "$MM/data/universe/eras.xml"
check "the default hex set survives" test -e "$MM/data/images/hexes/saxarba.tileset"
check "the fallback hex set survives" test -e "$MM/data/images/hexes/defaulthexset.txt"
check "MegaMek's own splash art survives" \
test -e "$MM/data/images/misc/megamek_splash_hd.png"
# Same directory as the foreign-project art, and no file names them - a glob
# there that grew would take a facing's camo shading with it.
check "the facing camo overlays survive" \
test -e "$MM/data/images/misc/camo_overlay0.png" -a -e "$MM/data/images/misc/camo_overlay5.png"
check "reports what it removed" grep -q "^pruned " <<<"$out"
# --- a prune line that no longer matches anything ---------------------------
# What an upstream rename looks like. Shipping the bytes silently is the failure
# this has to prevent, so the build must stop.
MM="$TMP/mm-moved"
stage_tree "$MM"
rm -rf "$MM/data/images/hexes/Ahne"
out="$(run_prune "$MM")"; status=$?
check "a prune that matches nothing fails the build" test "$status" -ne 0
check "and names the pattern that missed" grep -q "Ahne" <<<"$out"
# --- a kept file that is not there ------------------------------------------
# The prune next to it was written on the assumption that it exists, so its
# absence means the assumption needs re-checking, not that the prune is fine.
MM="$TMP/mm-nofallback"
stage_tree "$MM"
rm -f "$MM/data/images/portraits/default.gif"
out="$(run_prune "$MM")"; status=$?
check "a missing kept file fails the build" test "$status" -ne 0
check "and says which one" grep -q "default.gif" <<<"$out"
# --- nothing is pruned before the guards have run ---------------------------
# The failure above must leave the tree alone: a build that half-prunes and then
# exits non-zero is a cache entry nobody can reason about.
check "a failed guard prunes nothing" \
test -e "$MM/data/images/portraits/Male/MekWarrior/MW_M_999.png"
# --- a portrait the prune keeps that upstream dropped -----------------------
# The kept list was read off the scenarios; if a name is no longer in the
# release the two have drifted and the scan has to be rerun. Carrying on would
# put a scenario back on tests/scenarios-known-bad.txt, which is where this
# started.
MM="$TMP/mm-noportrait"
stage_tree "$MM"
victim="$(portraits_kept | head -1)"
rm -f "$(portrait_path "$MM" "$victim")"
out="$(run_prune "$MM")"; status=$?
check "a portrait upstream dropped fails the build" test "$status" -ne 0
check "and names the portrait" grep -q "$victim" <<<"$out"
echo "$pass passed, $fail failed"
[ "$fail" -eq 0 ]