feat(editor): add --gen-makefile for incremental zone-output rebuilds

Generates a Makefile that rebuilds every derived output for a zone
with proper dependency tracking. Designers can `make` to refresh
glb/obj/stl/html/csv/md from sources after editing creatures.json
or terrain — without remembering which wowee_editor flag does what,
and without rebuilding outputs that haven't changed:

  wowee_editor --gen-makefile custom_zones/MyZone
  cd custom_zones/MyZone && make

  make all       # rebuild everything that's stale
  make glb       # just the glTF bake
  make clean     # nuke derived outputs (calls --strip-zone)
  make validate  # run --validate-all on the zone

Targets generated:
  glb obj stl html docs csv graph all clean validate

Dependency tracking:
  - terrain bakes (.glb/.obj/.stl) depend on zone.json + WHM tiles
  - HTML viewer depends on the .glb (forces glb rebuild first)
  - docs (ZONE.md/DEPS.md) depend on content JSONs
  - csv/graph use '-' prefix so missing-content failures don't
    block 'make all' (zone with no quests still bakes terrain)

Uses /proc/self/exe absolute path so the Makefile works from any
cwd (run via `make -C custom_zones/MyZone` from anywhere). Falls
back to PATH lookup if /proc not available.

Verified end-to-end: scaffolded zone, generated Makefile, ran
`make all` from inside the zone dir — all derived outputs (.glb,
.obj, .stl, .html, ZONE.md, DEPS.md) generated; csv+graph
gracefully skipped due to no content; make exit 0.
This commit is contained in:
Kelsi 2026-05-06 15:17:53 -07:00
parent 5ebd04a953
commit 1f20d0c5a2

View file

@ -477,6 +477,8 @@ static void printUsage(const char* argv0) {
std::printf(" Wipe one or more content files (terrain + manifest preserved)\n");
std::printf(" --strip-zone <zoneDir> [--dry-run]\n");
std::printf(" Remove derived outputs (.glb/.obj/.stl/.html/.dot/.csv/ZONE.md/DEPS.md)\n");
std::printf(" --gen-makefile <zoneDir> [out.mk]\n");
std::printf(" Generate a Makefile that rebuilds every derived output for a zone\n");
std::printf(" --repair-zone <zoneDir> [--dry-run]\n");
std::printf(" Auto-fix manifest/disk drift (missing tiles in manifest, hasCreatures flag)\n");
std::printf(" --build-woc <wot-base> Generate a WOC collision mesh from WHM/WOT and exit\n");
@ -688,7 +690,7 @@ int main(int argc, char* argv[]) {
"--clone-object",
"--remove-creature", "--remove-object", "--remove-quest",
"--copy-zone", "--rename-zone", "--clear-zone-content", "--strip-zone",
"--repair-zone",
"--repair-zone", "--gen-makefile",
"--build-woc", "--regen-collision", "--fix-zone",
"--export-png", "--export-obj", "--import-obj",
"--export-wob-obj", "--import-wob-obj",
@ -9588,6 +9590,98 @@ int main(int argc, char* argv[]) {
std::printf(" re-run without --dry-run to apply\n");
}
return 0;
} else if (std::strcmp(argv[i], "--gen-makefile") == 0 && i + 1 < argc) {
// Generate a Makefile that rebuilds every derived output for
// a zone. With this in place, designers can `make` to refresh
// glb/obj/stl/html/csv/md from sources after editing
// creatures.json or terrain — without remembering which
// wowee_editor flag does what. The Makefile uses dependency
// tracking so only stale outputs get rebuilt.
std::string zoneDir = argv[++i];
std::string outPath;
if (i + 1 < argc && argv[i + 1][0] != '-') outPath = argv[++i];
namespace fs = std::filesystem;
std::string manifestPath = zoneDir + "/zone.json";
if (!fs::exists(manifestPath)) {
std::fprintf(stderr,
"gen-makefile: %s has no zone.json\n", zoneDir.c_str());
return 1;
}
wowee::editor::ZoneManifest zm;
if (!zm.load(manifestPath)) {
std::fprintf(stderr, "gen-makefile: parse failed\n");
return 1;
}
if (outPath.empty()) outPath = zoneDir + "/Makefile";
std::ofstream out(outPath);
if (!out) {
std::fprintf(stderr,
"gen-makefile: cannot write %s\n", outPath.c_str());
return 1;
}
// Use a single absolute editor path so the Makefile works
// from any cwd (running `make -C custom_zones/MyZone` etc.).
std::error_code ec;
std::string editorBin = fs::canonical("/proc/self/exe", ec).string();
if (ec || editorBin.empty()) editorBin = "wowee_editor";
// Per-tile WHM/WOT inputs feed the bake targets. Compose the
// list once so all targets share the same dep set.
std::string tileWhmDeps;
for (const auto& [tx, ty] : zm.tiles) {
tileWhmDeps += " " + zm.mapName + "_" +
std::to_string(tx) + "_" + std::to_string(ty) +
".whm";
}
std::string slug = zm.mapName;
out << "# Generated by wowee_editor --gen-makefile\n"
"# Zone: " << slug << "\n"
"# Run from this directory: `make` to rebuild all\n"
"# derived outputs from sources, `make clean` to wipe.\n\n";
out << "EDITOR := " << editorBin << "\n";
out << "ZONE := .\n\n";
// Source dep aggregations for content-derived outputs.
out << "CONTENT_SRCS := zone.json $(wildcard creatures.json) "
"$(wildcard objects.json) $(wildcard quests.json)\n";
out << "TERRAIN_SRCS := zone.json" << tileWhmDeps << "\n\n";
out << ".PHONY: all clean glb obj stl html docs csv graph\n\n";
out << "all: glb obj stl html docs csv graph\n\n";
// Each target lists its dependencies so make can skip already-
// up-to-date outputs.
out << "glb: " << slug << ".glb\n"
<< slug << ".glb: $(TERRAIN_SRCS)\n"
<< "\t$(EDITOR) --bake-zone-glb $(ZONE)\n\n";
out << "obj: " << slug << ".obj\n"
<< slug << ".obj: $(TERRAIN_SRCS)\n"
<< "\t$(EDITOR) --bake-zone-obj $(ZONE)\n\n";
out << "stl: " << slug << ".stl\n"
<< slug << ".stl: $(TERRAIN_SRCS)\n"
<< "\t$(EDITOR) --bake-zone-stl $(ZONE)\n\n";
out << "html: " << slug << ".html\n"
<< slug << ".html: " << slug << ".glb\n"
<< "\t$(EDITOR) --export-zone-html $(ZONE)\n\n";
out << "docs: ZONE.md DEPS.md\n";
out << "ZONE.md: $(CONTENT_SRCS)\n"
<< "\t$(EDITOR) --export-zone-summary-md $(ZONE)\n";
out << "DEPS.md: zone.json $(wildcard objects.json) $(wildcard *.wob)\n"
<< "\t$(EDITOR) --export-zone-deps-md $(ZONE)\n\n";
// CSV + graph targets use '-' prefix so missing-content
// (zone without creatures/quests) doesn't fail the whole
// 'make all'. The editor prints the error to stderr; make
// continues with the next target.
out << "csv:\n"
<< "\t-$(EDITOR) --export-zone-csv $(ZONE)\n\n";
out << "graph:\n"
<< "\t-$(EDITOR) --export-quest-graph $(ZONE)\n\n";
out << "clean:\n"
<< "\t$(EDITOR) --strip-zone $(ZONE)\n\n";
out << "validate:\n"
<< "\t$(EDITOR) --validate-all $(ZONE)\n";
out.close();
std::printf("Wrote %s\n", outPath.c_str());
std::printf(" zone : %s\n", slug.c_str());
std::printf(" tiles : %zu (terrain dep)\n", zm.tiles.size());
std::printf(" next : cd %s && make\n", zoneDir.c_str());
return 0;
} else if (std::strcmp(argv[i], "--pack-wcp") == 0 && i + 1 < argc) {
// Pack a zone directory into a .wcp archive.
// Usage: --pack-wcp <zoneDirOrName> [destPath]