# ww — Plan 9-organised toolchain. POSIX make.
#
# Build a small static frontend library libwcc.a and the user-facing
# driver `ww`. Per-target tools (w6c, w6a, w6l) and the runtime are added
# in their own phases. Everything lands under out/.

PREFIX	?= /usr/local
CC	?= cc
AR	?= ar

# Fixture-cell concurrency is separate from caller-owned Make scheduling.
JOBS	?= 1

# Wrap the C compiler with ccache when present — content-addressed, so
# byte-identical to plain cc; just skips recompiling unchanged TUs.
# Falls back cleanly to bare $(CC) when ccache is absent. A command-line
# CC= override wins and is left unwrapped.
CC	:= $(shell command -v ccache >/dev/null 2>&1 && echo "ccache $(CC)" || echo "$(CC)")
CFLAGS	?= -O0 -g -Wall -Wextra -Wpedantic -Wno-unused-parameter
CFLAGS	+= -std=c99 -fno-strict-aliasing -D_POSIX_C_SOURCE=200809L
INCS	= -Icmd/wcc

OUT	= out
BIN	= $(OUT)/bin
LIB	= $(OUT)/lib
OBJ	= $(OUT)/obj

WCC_SRC	= cmd/wcc/mem.c cmd/wcc/err.c cmd/wcc/tok.c cmd/wcc/lex.c \
	  cmd/wcc/ast.c cmd/wcc/parse.c cmd/wcc/sym.c cmd/wcc/type.c \
	  cmd/wcc/check.c
WCC_OBJ	= $(WCC_SRC:cmd/wcc/%.c=$(OBJ)/wcc/%.o)

WW_SRC	= cmd/ww/main.c
WW_OBJ	= $(WW_SRC:cmd/ww/%.c=$(OBJ)/ww/%.o)

WD_SRC	= cmd/wwdump/main.c
WD_OBJ	= $(WD_SRC:cmd/wwdump/%.c=$(OBJ)/wwdump/%.o)

W6C_SRC	= cmd/w6c/main.c cmd/w6c/cgen.c cmd/w6c/txt.c cmd/w6c/swt.c \
	  cmd/w6c/peep.c cmd/w6c/reg.c cmd/w6c/wwi.c
W6C_OBJ	= $(W6C_SRC:cmd/w6c/%.c=$(OBJ)/w6c/%.o)

W6A_SRC	= cmd/w6a/main.c cmd/w6a/lex.c cmd/w6a/parse.c cmd/w6a/asm.c cmd/w6a/obj.c
W6A_OBJ	= $(W6A_SRC:cmd/w6a/%.c=$(OBJ)/w6a/%.o)

W6L_SRC	= cmd/w6l/main.c cmd/w6l/obj.c cmd/w6l/sym.c cmd/w6l/pass.c cmd/w6l/out.c \
	  cmd/w6l/dyn.c cmd/w6l/dynout.c
W6L_OBJ	= $(W6L_SRC:cmd/w6l/%.c=$(OBJ)/w6l/%.o)

RT_S	= rt/start.s rt/syscall.s rt/alloc.s rt/streq.s rt/abort.s
RT_WW	= rt/ensure.ww rt/malloc.ww
RT_OBJ	= $(RT_S:rt/%.s=$(OBJ)/rt/%.o) $(RT_WW:rt/%.ww=$(OBJ)/rt/%.o)

WWTEST_BIN	= $(BIN)/wwtest
WWTEST_SRC	= cmd/wwtest/wwtest.ww \
	  internal/wwpackage/package.ww lib/os/exec/exec.ww \
	  lib/ascii/ascii.ww lib/bytes/bytes.ww \
	  lib/encoding/utf8/utf8.ww lib/os/os.ww lib/rt/malloc.ww \
	  lib/strconv/strconv.ww \
	  lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \
	  lib/types/types.ww

WWFIXTURE_BIN	= $(BIN)/wwfixture
WWFIXTURE_SRC	= cmd/wwfixture/wwfixture.ww \
	  internal/wwfixture/command.ww internal/wwfixture/corpus.ww \
	  internal/wwfixture/protocol.ww internal/wwfixture/run.ww \
	  internal/wwfixture/types.ww lib/os/exec/exec.ww \
	  lib/ascii/ascii.ww lib/bytes/bytes.ww \
	  lib/crypto/math/math.ww lib/crypto/sha256/sha256.ww \
	  lib/encoding/hex/hex.ww lib/encoding/utf8/utf8.ww \
	  lib/endian/endian.ww lib/errors/errors.ww lib/fmt/fmt.ww \
	  lib/fnmatch/fnmatch.ww lib/hash/hash.ww \
	  lib/io/io.ww lib/io/stream.ww lib/io/types.ww \
	  lib/math/floats.ww lib/math/math.ww lib/memio/memio.ww \
	  lib/os/os.ww lib/rt/malloc.ww \
	  lib/strconv/decimal.ww lib/strconv/ftos.ww \
	  lib/strconv/ftos_data.ww lib/strconv/stof.ww \
	  lib/strconv/stof_data.ww lib/strconv/strconv.ww \
	  lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \
	  lib/types/types.ww

WWFIXTURE_BUILD_TOOLS = $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
	  $(LIB)/libwwrt.a
WWFIXTURE_TOOLS = $(WWFIXTURE_BUILD_TOOLS) $(BIN)/w6c_ww \
	  $(BIN)/w6a_ww $(BIN)/w6l_ww $(BIN)/ww_ww

EXEC_TEST_SRC = test/wwfixture/process/main.ww \
	  lib/os/exec/exec.ww lib/os/os.ww lib/rt/malloc.ww \
	  lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww \
	  lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \
	  lib/types/types.ww
EXEC_TEST_C = $(BIN)/test_exec
EXEC_TEST_WW = $(BIN)/test_exec_ww

PACKAGE_TEST_BIN = $(BIN)/test_package
PACKAGE_TEST_SRC = test/package/package_test.ww lib/os/exec/exec.ww \
	  lib/test/run.ww lib/fnmatch/fnmatch.ww \
	  lib/ascii/ascii.ww lib/bytes/bytes.ww \
	  lib/encoding/utf8/utf8.ww lib/os/os.ww lib/rt/malloc.ww \
	  lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \
	  lib/types/types.ww

# Cstage: one-time C bootstrap (see BOOTSTRAP.md). Built by `cc`.
# Goes away at v1.0, replaced by a checked-in stage-0 binary.
CSTAGE_BINS	= $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(BIN)/wwdump

# Wwstage: ww-rewritten toolchain. Built by Cstage today; will be the
# only toolchain after Phase 10 closeout. Byte-identical to Cstage
# (tests 990-994).
WWSTAGE_BINS	= $(BIN)/wwdump_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww $(BIN)/ww_ww

BINS	= $(CSTAGE_BINS) $(WWSTAGE_BINS) $(WWTEST_BIN)
LIBS	= $(LIB)/libwcc.a $(LIB)/libwwrt.a

all: cstage wwstage $(WWTEST_BIN)

cstage: $(CSTAGE_BINS) $(LIBS)
wwstage: $(WWSTAGE_BINS)

# ---- libwcc.a ----------------------------------------------------------
$(LIB)/libwcc.a: $(WCC_OBJ) | $(LIB)
	$(AR) rcs $@ $(WCC_OBJ)

$(OBJ)/wcc/%.o: cmd/wcc/%.c cmd/wcc/ww.h | $(OBJ)/wcc
	$(CC) $(CFLAGS) $(INCS) -c -o $@ $<

# ---- ww driver ---------------------------------------------------------
$(BIN)/ww: $(WW_OBJ) $(LIB)/libwcc.a | $(BIN)
	$(CC) $(CFLAGS) -o $@ $(WW_OBJ) -L$(LIB) -lwcc

$(OBJ)/ww/%.o: cmd/ww/%.c cmd/wcc/ww.h | $(OBJ)/ww
	$(CC) $(CFLAGS) $(INCS) -c -o $@ $<

# ---- wwdump (lex/AST diff anchor) --------------------------------------
$(BIN)/wwdump: $(WD_OBJ) $(LIB)/libwcc.a | $(BIN)
	$(CC) $(CFLAGS) -o $@ $(WD_OBJ) -L$(LIB) -lwcc

$(OBJ)/wwdump/%.o: cmd/wwdump/%.c cmd/wcc/ww.h | $(OBJ)/wwdump
	$(CC) $(CFLAGS) $(INCS) -c -o $@ $<

# ---- w6c amd64 compiler ------------------------------------------------
$(BIN)/w6c: $(W6C_OBJ) $(LIB)/libwcc.a | $(BIN)
	$(CC) $(CFLAGS) -o $@ $(W6C_OBJ) -L$(LIB) -lwcc

$(OBJ)/w6c/%.o: cmd/w6c/%.c cmd/w6c/gc.h cmd/w6c/6.out.h cmd/wcc/ww.h | $(OBJ)/w6c
	$(CC) $(CFLAGS) $(INCS) -Icmd/w6c -c -o $@ $<

# ---- w6a amd64 assembler -----------------------------------------------
$(BIN)/w6a: $(W6A_OBJ) | $(BIN)
	$(CC) $(CFLAGS) -o $@ $(W6A_OBJ)

$(OBJ)/w6a/%.o: cmd/w6a/%.c cmd/w6a/a.h cmd/w6c/6.out.h | $(OBJ)/w6a
	$(CC) $(CFLAGS) -Icmd/w6a -Icmd/w6c -c -o $@ $<

# ---- w6l amd64 linker --------------------------------------------------
$(BIN)/w6l: $(W6L_OBJ) | $(BIN)
	$(CC) $(CFLAGS) -o $@ $(W6L_OBJ)

$(OBJ)/w6l/%.o: cmd/w6l/%.c cmd/w6l/l.h | $(OBJ)/w6l
	$(CC) $(CFLAGS) -Icmd/w6l -c -o $@ $<

# ---- ww-side wwdump (the frontend dump tool; oracle for 950/994) -------
# Built via the user-facing ww driver. The frontend (lex, tok, ast,
# parse, typ, sym) is the `syntax` package in lib/ww/syntax/; the
# compiler-only bits (check, cgen*) stay in selfhost/cmd/wcc/. Output named wwdump_ww
# to avoid colliding with the C-side wwdump in $(BIN).
$(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \
		lib/ww/syntax/lex.ww lib/ww/syntax/tok.ww lib/ww/syntax/ast.ww \
		lib/ww/syntax/parse.ww lib/ww/syntax/expr.ww lib/ww/syntax/stmt.ww lib/ww/syntax/decl.ww lib/ww/syntax/typ.ww lib/ww/syntax/sym.ww \
		selfhost/cmd/wcc/check.ww \
		selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
		selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
		selfhost/cmd/wcc/cgendecl.ww \
		lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/errors/errors.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww lib/memio/memio.ww lib/fmt/fmt.ww \
		$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
		$(LIB)/libwwrt.a | $(BIN)
	@mkdir -p $(BIN)/wwdump_ww.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/wwdump_ww.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \
			-I $(CURDIR)/lib/ww -I $(CURDIR)/selfhost/cmd/wcc \
			$(CURDIR)/selfhost/cmd/wwdump/main.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

# ---- ww-side w6c (compiler port, exercised by 994_w6c_ww) -------------
# Thin driver: parse + cgen. The frontend (lex/parse/ast/...) is the
# `syntax` package in lib/ww/syntax/; cgen + check live in selfhost/cmd/wcc/.
$(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \
		lib/ww/syntax/lex.ww lib/ww/syntax/tok.ww lib/ww/syntax/ast.ww \
		lib/ww/syntax/parse.ww lib/ww/syntax/expr.ww lib/ww/syntax/stmt.ww lib/ww/syntax/decl.ww lib/ww/syntax/typ.ww lib/ww/syntax/sym.ww \
		selfhost/cmd/wcc/check.ww selfhost/cmd/wcc/wwi.ww \
		selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
		selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
		selfhost/cmd/wcc/cgendecl.ww \
		lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/errors/errors.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww lib/memio/memio.ww lib/fmt/fmt.ww \
		$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
		$(LIB)/libwwrt.a | $(BIN)
	@mkdir -p $(BIN)/w6c_ww.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/w6c_ww.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \
			-I $(CURDIR)/lib/ww -I $(CURDIR)/selfhost/cmd/wcc \
			$(CURDIR)/selfhost/cmd/w6c/main.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

# ---- ww-side w6a (assembler port, exercised by 991_w6a_ww) ------------
# Built like wwdump_ww. Needs -I selfhost/cmd/w6a for the local types/lex/
# parse/asm/obj modules.
$(BIN)/w6a_ww: selfhost/cmd/w6a/main.ww selfhost/cmd/w6a/opcodes.ww \
		selfhost/cmd/w6a/lex.ww selfhost/cmd/w6a/parse.ww \
		selfhost/cmd/w6a/asm.ww selfhost/cmd/w6a/obj.ww \
		lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww \
		lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww \
		$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
		$(LIB)/libwwrt.a | $(BIN)
	@mkdir -p $(BIN)/w6a_ww.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/w6a_ww.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \
			-I $(CURDIR)/selfhost/cmd/w6a \
			$(CURDIR)/selfhost/cmd/w6a/main.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

# ---- ww-side w6l (linker port, exercised by 992_w6l_ww) ---------------
# Built like 6a_ww. Needs -I selfhost/cmd/w6l for the local sym/obj/pass/
# out modules.
$(BIN)/w6l_ww: selfhost/cmd/w6l/main.ww selfhost/cmd/w6l/sym.ww \
		selfhost/cmd/w6l/obj.ww selfhost/cmd/w6l/dyn.ww \
		selfhost/cmd/w6l/pass.ww selfhost/cmd/w6l/dynout.ww \
		selfhost/cmd/w6l/out.ww \
		lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww \
		lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww \
		$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
		$(LIB)/libwwrt.a | $(BIN)
	@mkdir -p $(BIN)/w6l_ww.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/w6l_ww.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \
			-I $(CURDIR)/selfhost/cmd/w6l \
			$(CURDIR)/selfhost/cmd/w6l/main.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

# ---- ww-side ww driver (exercised by 993_ww_ww) ------------------------
# The driver pulls in lib/os (default search path) and orchestrates
# w6c/w6a/w6l like the C driver.
$(BIN)/ww_ww: selfhost/cmd/ww/main.ww lib/os/exec/exec.ww \
		lib/os/os.ww lib/rt/malloc.ww \
		lib/time/time.ww lib/strconv/strconv.ww \
		lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww \
		$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
		$(LIB)/libwwrt.a | $(BIN)
	@mkdir -p $(BIN)/ww_ww.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/ww_ww.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \
			$(CURDIR)/selfhost/cmd/ww/main.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

# ---- runtime (libwwrt.a, assembled by our own w6a) ---------------------
$(OBJ)/rt/%.o: rt/%.s $(BIN)/w6a | $(OBJ)/rt
	$(BIN)/w6a -o $@ $<

# ww-side runtime helpers (rt/*.ww): compile via w6c to .s, then w6a.
# Each .ww module is standalone — uses @symbol FFI for rt_alloc/rt_free
# rather than `use os;` so no bundling is required.
$(OBJ)/rt/%.s: rt/%.ww $(BIN)/w6c | $(OBJ)/rt
	$(BIN)/w6c $< > $@
$(OBJ)/rt/%.o: $(OBJ)/rt/%.s $(BIN)/w6a | $(OBJ)/rt
	$(BIN)/w6a -o $@ $<

$(LIB)/libwwrt.a: $(RT_OBJ) | $(LIB)
	$(AR) rcs $@ $(RT_OBJ)

$(WWTEST_BIN): $(WWTEST_SRC) $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \
		$(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
	@mkdir -p $(BIN)/wwtest.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/wwtest.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \
			-I $(CURDIR)/internal $(CURDIR)/cmd/wwtest/wwtest.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

wwfixture: $(WWFIXTURE_BIN)

test-wwfixture-process: $(EXEC_TEST_C) $(EXEC_TEST_WW)
	$(CURDIR)/$(EXEC_TEST_C)
	$(CURDIR)/$(EXEC_TEST_WW)

test-wwfixture: wwfixture test-wwfixture-process
	sh test/wwfixture/integration.sh

$(WWFIXTURE_BIN): $(WWFIXTURE_SRC) $(WWFIXTURE_BUILD_TOOLS) | $(BIN)
	@mkdir -p $(BIN)/wwfixture.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/wwfixture.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \
			-I $(CURDIR)/internal $(CURDIR)/cmd/wwfixture/wwfixture.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

$(EXEC_TEST_C): $(EXEC_TEST_SRC) $(WWFIXTURE_TOOLS) | $(BIN)
	@mkdir -p $(BIN)/test_exec.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/test_exec.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \
			-I $(CURDIR)/internal $(CURDIR)/test/wwfixture/process/main.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

$(EXEC_TEST_WW): $(EXEC_TEST_SRC) $(WWFIXTURE_TOOLS) | $(BIN)
	@mkdir -p $(BIN)/test_exec_ww.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/test_exec_ww.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww_ww build -o main \
			-I $(CURDIR)/internal $(CURDIR)/test/wwfixture/process/main.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

$(PACKAGE_TEST_BIN): $(PACKAGE_TEST_SRC) $(WWTEST_BIN) \
		$(BIN)/ww $(BIN)/ww_ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
		$(LIB)/libwwrt.a | $(BIN)
	@mkdir -p $(BIN)/test_package.d
	@d=$$(mktemp -d "$(CURDIR)/$(BIN)/test_package.d/build.XXXXXX") || exit 1; \
		rc=0; published=0; \
		(cd "$$d" && $(CURDIR)/$(BIN)/ww test -c -o main \
			-I $(CURDIR)/internal $(CURDIR)/test/package/package_test.ww) || rc=$$?; \
		rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \
		if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \
		if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \
		if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \
		exit $$rc

test-package: all $(PACKAGE_TEST_BIN)
	@WW_PACKAGE_REPO=$(CURDIR) $(CURDIR)/$(PACKAGE_TEST_BIN)

# ---- directories -------------------------------------------------------
$(BIN) $(LIB) $(OBJ)/wcc $(OBJ)/ww $(OBJ)/wwdump $(OBJ)/w6c $(OBJ)/w6a $(OBJ)/w6l $(OBJ)/rt:
	mkdir -p $@

# ---- tests -------------------------------------------------------------

# Five in-process C compiler units.  Every other surviving C gate has a named
# artifact owner below and is built by one generic rule; source presence is
# the registration, so adding a case never requires a TESTS edit and a second
# per-case Make rule.
UNIT_SOURCES = test/wcc/000_smoke.c test/wcc/100_lex.c \
	  test/wcc/200_parse.c test/wcc/300_check.c test/wcc/400_w6c.c
# Byte/artifact residuals that the blanket test-data-byteid comparator
# cannot subsume: asm-pattern/symbol/frame observers, .wwi round-trips,
# inline sources with no corpus twin, and the wwstage-driver-leg gates.
# 61 pure data-fixture byte-id carriers were retired into test-data-byteid
# (fold-4 migrate-and-retire; audit 2026-08-07).
BYTEID_WRAPPER_SOURCES = test/wcc/631_def_neg_global.c \
	  test/wcc/753_convwrap_audit.c \
	  test/wcc/754_slice_of_slice_index.c test/wcc/755_amp_dot_idx.c \
	  test/wcc/756_alias_chain_unwrap.c \
	  test/wcc/758_cgalloc_str_field.c \
	  test/wcc/785_structvariant_largeunion_return.c \
	  test/wcc/786_narrow_alias_deref_store.c \
	  test/wcc/789_named_ptr_alias_variant_widen.c \
	  test/wcc/790_single_field_struct_zeroinit.c \
	  test/wcc/795_xmod_valglobal_run.c \
	  test/wcc/796_xmod_valglobal_dot_run.c \
	  test/wcc/798_tuple_sret_callee.c \
	  test/wcc/844_size_untyped_int.c \
	  test/wcc/926_tagscr_sizes_run.c test/wcc/930_free_noop_run.c \
	  test/wcc/946_structparam_run.c \
	  test/wcc/946_structret_run.c \
	  test/wcc/989_m1mangle_sym.c test/wcc/989_m1union_run.c \
	  test/wcc/989_m2wwi_run.c test/wcc/989_structlocal_frame.c \
	  test/wcc/989_wwileaf_run.c test/wcc/989_lib_byteid.c
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
	  test/wcc/991_w6a_ww.c \
	  test/wcc/992_w6l_ww.c test/wcc/993_ww_ww.c \
	  test/wcc/994_w6c_ww.c test/wcc/995_self_rebuild.c
PLATFORM_WRAPPER_SOURCES = test/wcc/996_dyn_ww.c
ALL_WRAPPER_SOURCES = $(wildcard test/wcc/*.c)
COMPILER_WRAPPER_SOURCES = $(filter-out $(UNIT_SOURCES) \
	  $(BYTEID_WRAPPER_SOURCES) $(BOOTSTRAP_WRAPPER_SOURCES) \
	  $(PLATFORM_WRAPPER_SOURCES),$(ALL_WRAPPER_SOURCES))

UNIT_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(UNIT_SOURCES))
BYTEID_WRAPPER_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(BYTEID_WRAPPER_SOURCES))
BOOTSTRAP_WRAPPER_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(BOOTSTRAP_WRAPPER_SOURCES))
PLATFORM_WRAPPER_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(PLATFORM_WRAPPER_SOURCES))
COMPILER_WRAPPER_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(COMPILER_WRAPPER_SOURCES))
WRAPPER_TOOLS = $(CSTAGE_BINS) $(WWSTAGE_BINS) $(WWTEST_BIN) $(LIBS)

$(filter-out $(BIN)/test_400_w6c,$(UNIT_BINS)): $(BIN)/test_%: \
		test/wcc/%.c $(LIB)/libwcc.a | $(BIN)
	$(CC) $(CFLAGS) $(INCS) -o $@ $< $(LIB)/libwcc.a

$(BIN)/test_200_parse $(BIN)/test_300_check: test/wcc/wwtestpkg.h

$(BIN)/test_400_w6c: test/wcc/400_w6c.c \
		test/wcc/wwtestpkg.h $(OBJ)/w6c/cgen.o $(OBJ)/w6c/txt.o \
		$(LIB)/libwcc.a | $(BIN)
	$(CC) $(CFLAGS) $(INCS) -Icmd/w6c -o $@ $< \
		$(OBJ)/w6c/cgen.o $(OBJ)/w6c/txt.o $(LIB)/libwcc.a

$(BIN)/test_%: test/wcc/%.c test/wcc/wwtestpkg.h $(WRAPPER_TOOLS) | $(BIN)
	$(CC) $(CFLAGS) $(INCS) -o $@ $< $(LIB)/libwcc.a

# Native library tests execute their source owner directly.  The retired C
# launchers added no assertion beyond this exit status.
LIBRARY_TESTS = lib/errors/errnotest.ww lib/ascii/asciitest.ww \
	  lib/ww/syntax/toktest.ww lib/ww/syntax/asttest.ww \
	  lib/ww/syntax/symtest.ww \
	  lib/strconv/test/ftostest.ww lib/strconv/test/stoftest.ww \
	  lib/strconv/test/inttest.ww lib/strings/stringstest.ww \
	  lib/bytes/bytestest.ww lib/encoding/utf8/utf8test.ww \
	  lib/bufio/bufiotest.ww lib/math/random/random_test.ww \
	  lib/math/checked/checked_test.ww lib/fmt/fmttest.ww \
	  lib/log/logtest.ww lib/fnmatch/fnmatchtest.ww \
	  lib/shlex/shlextest.ww lib/time/timetest.ww \
	  lib/encoding/hex/hextest.ww lib/memio/memiotest.ww \
	  lib/temp/temptest.ww lib/getopt/getopttest.ww \
	  lib/encoding/base32/base32_test.ww \
	  lib/encoding/base64/base64_test.ww \
	  lib/hash/adler32/adler32_test.ww lib/hash/crc16/crc16_test.ww \
	  lib/hash/crc32/crc32_test.ww lib/hash/crc64/crc64_test.ww \
	  lib/path/pathtest.ww lib/crypto/sha256/sha256_test.ww \
	  lib/hash/siphash/siphash_test.ww

# These real sources have a standalone-compilation contract that is not
# subsumed by a library @test import owner.
LIBRARY_STANDALONE_SOURCES = lib/io/io.ww lib/strconv/strconv.ww \
	  lib/hash/fnv/fnv.ww lib/net/net.ww

test-unit: $(UNIT_BINS)
	@set -e; for t in $(UNIT_BINS); do \
		echo "unit $$t"; $(CURDIR)/$$t; \
	done

# One compile-only declarative fixture is the ordinary C/WW smoke slice.
test-compiler-smoke: $(WWFIXTURE_BIN) $(BIN)/w6c_ww
	@$(CURDIR)/$(WWFIXTURE_BIN) -j 1 \
		-run compiler.r78_strict_package_main_accept

# Complete declarative corpus plus the residual native artifact/integration
# gates that cannot be represented by a single compiler fixture.
test-compiler: $(WWFIXTURE_BIN) $(WWFIXTURE_TOOLS) $(COMPILER_WRAPPER_BINS)
	@$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS)
	@set -e; for t in $(COMPILER_WRAPPER_BINS); do \
		echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \
	done

test-lang: cstage
	@set -e; for f in $(wildcard test/lang/*_test.ww); do \
		echo "ww test $$f"; $(BIN)/ww test $$f; \
	done

test-library: cstage $(BIN)/w6c_ww $(LIBRARY_STANDALONE_SOURCES)
	@set -e; for f in $(LIBRARY_TESTS); do \
		echo "ww test $$f"; \
		$(BIN)/ww test -I $(CURDIR)/lib/ww $$f; \
	done
	@set -e; for f in $(LIBRARY_STANDALONE_SOURCES); do \
		echo "library compile c $$f"; \
		$(CURDIR)/$(BIN)/w6c $(CURDIR)/$$f > /dev/null; \
		echo "library compile ww $$f"; \
		$(CURDIR)/$(BIN)/w6c_ww $(CURDIR)/$$f > /dev/null; \
	done

test-native-byteid: $(BYTEID_WRAPPER_BINS)
	@set -e; for t in $(BYTEID_WRAPPER_BINS); do \
		echo "byte-id $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \
	done

test-bootstrap-native: $(BOOTSTRAP_WRAPPER_BINS)
	@set -e; for t in $(BOOTSTRAP_WRAPPER_BINS); do \
		echo "bootstrap $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \
	done

test-platform: $(PLATFORM_WRAPPER_BINS)
	@set -e; for t in $(PLATFORM_WRAPPER_BINS); do \
		echo "platform $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \
	done

# Public composition.  The default developer gate is intentionally small;
# byte identity and self-host fixed points are never hidden prerequisites.
test: test-unit test-compiler-smoke

test-commit: test-unit test-compiler test-package test-lang test-library

# Compiler-output identity stops after w6c has emitted every package `.s`.
# Driver -S still performs discovery, unit composition, dependency ordering,
# and `.wwi` production; it never invokes w6a, archive creation, or w6l.
LANGBYTEID_DIR = $(OUT)/langbyteid
LANGBYTEID_FILES = $(filter-out %_runonly_test.ww,$(wildcard test/lang/*_test.ww))
LANGBYTEID_EXPECTED_MIN = 158
$(if $(LANGBYTEID_FILES),,$(error test-byteid: empty language corpus))
test-lang-byteid: $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww
	@work=$$(mktemp -d "$(CURDIR)/$(LANGBYTEID_DIR).XXXXXX") || exit 1; \
	rc=0; n=0; base=; \
	for f in $(LANGBYTEID_FILES); do \
		base=$$work/`basename $$f .ww`; \
		WW_W6C=$(BIN)/w6c $(BIN)/ww test -S -o $$base.cs $$f \
			|| { rc=$$?; break; }; \
		WW_W6C=$(BIN)/w6c_ww $(BIN)/ww test -S -o $$base.ww $$f \
			|| { rc=$$?; break; }; \
		test -d $$base.cs.sepwork && test -d $$base.ww.sepwork || { \
			echo "test-byteid: FAIL $$f: missing .sepwork"; rc=1; break; }; \
		cs_set=`cd $$base.cs.sepwork && find . -maxdepth 1 -type f -name '*.s' -printf '%f\n' | sort`; \
		ww_set=`cd $$base.ww.sepwork && find . -maxdepth 1 -type f -name '*.s' -printf '%f\n' | sort`; \
		if [ "$$cs_set" != "$$ww_set" ]; then \
			echo "test-byteid: FAIL $$f: .s set differs"; rc=1; break; \
		fi; \
		echo "$$cs_set" | grep -qx __root.s || { \
			echo "test-byteid: FAIL $$f: no __root.s"; rc=1; break; }; \
		ns=0; \
		for s in $$cs_set; do \
			cmp -s $$base.cs.sepwork/$$s $$base.ww.sepwork/$$s || { \
				echo "test-byteid: FAIL $$f: $$s differs"; rc=1; break; }; \
			ns=`expr $$ns + 1`; \
		done; \
		if [ $$rc -ne 0 ]; then break; fi; \
		test $$ns -ge 1 || { rc=1; break; }; \
		rm -rf -- $$base.cs.sepwork $$base.ww.sepwork || { \
			echo "test-byteid: FAIL $$f: scratch cleanup"; rc=1; break; }; \
		base=; n=`expr $$n + 1`; \
	done; \
	if [ -n "$$base" ]; then \
		rm -rf -- $$base.cs.sepwork $$base.ww.sepwork || { \
			echo "test-byteid: scratch cleanup failed"; \
			if [ $$rc -eq 0 ]; then rc=1; fi; }; \
	fi; \
	if [ $$rc -eq 0 ] && [ $$n -lt $(LANGBYTEID_EXPECTED_MIN) ]; then \
		echo "test-byteid: corpus shrank ($$n < $(LANGBYTEID_EXPECTED_MIN))"; \
		rc=1; \
	fi; \
	rmdir "$$work" || { echo "test-byteid: workspace cleanup failed"; \
		if [ $$rc -eq 0 ]; then rc=1; fi; }; \
	if [ $$rc -eq 0 ]; then \
		echo "test-byteid: $$n language files compared at compiler output"; \
	fi; \
	exit $$rc

# Blanket compiler-output identity over the declarative fixture corpus:
# every non-error case.ww builds through the fixed cstage driver twice,
# swapping only the frontend (WW_W6C), and every emitted per-package .s
# must be byte-identical.  This is the fold-4 end-state comparator that
# retired the per-snippet byte-id C carriers; //ww:error fixtures have no
# .s and their both-stage reject parity is owned by the fixture corpus.
DATABYTEID_DIR = $(OUT)/databyteid
DATABYTEID_FILES = $(wildcard test/wcc/data/*/case.ww)
DATABYTEID_EXPECTED_MIN = 915
# Known cs!=ww divergences (loud over blind, the 989_lib_byteid DIVERGE
# discipline): each entry must still build on both stages AND still differ.
# When a compiler fix lands the entry fails demanding graduation out of
# this list rather than silently widening coverage.  Currently empty: the
# 2026-08 drain closed all eight original entries.
DATABYTEID_DIVERGED =
$(if $(DATABYTEID_FILES),,$(error test-byteid: empty data corpus))
test-data-byteid: $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww
	@work=$$(mktemp -d "$(CURDIR)/$(DATABYTEID_DIR).XXXXXX") || exit 1; \
	rc=0; n=0; pn=0; base=; \
	for f in $(DATABYTEID_FILES); do \
		head -1 $$f | grep -q '^//ww:error' && continue; \
		d=`basename \`dirname $$f\``; \
		base=$$work/$$d; \
		WW_W6C=$(BIN)/w6c $(BIN)/ww build -S -o $$base.cs $$f \
			|| { rc=$$?; break; }; \
		WW_W6C=$(BIN)/w6c_ww $(BIN)/ww build -S -o $$base.ww $$f \
			|| { rc=$$?; break; }; \
		test -d $$base.cs.sepwork && test -d $$base.ww.sepwork || { \
			echo "test-byteid: FAIL $$f: missing .sepwork"; rc=1; break; }; \
		cs_set=`cd $$base.cs.sepwork && find . -maxdepth 1 -type f -name '*.s' -printf '%f\n' | sort`; \
		ww_set=`cd $$base.ww.sepwork && find . -maxdepth 1 -type f -name '*.s' -printf '%f\n' | sort`; \
		if [ "$$cs_set" != "$$ww_set" ]; then \
			echo "test-byteid: FAIL $$f: .s set differs"; rc=1; break; \
		fi; \
		echo "$$cs_set" | grep -qx __root.s || { \
			echo "test-byteid: FAIL $$f: no __root.s"; rc=1; break; }; \
		ns=0; diffs=0; firstdiff=; \
		for s in $$cs_set; do \
			cmp -s $$base.cs.sepwork/$$s $$base.ww.sepwork/$$s || { \
				diffs=`expr $$diffs + 1`; \
				if [ -z "$$firstdiff" ]; then firstdiff=$$s; fi; }; \
			ns=`expr $$ns + 1`; \
		done; \
		case " $(DATABYTEID_DIVERGED) " in \
		*" $$d "*) \
			if [ $$diffs -eq 0 ]; then \
				echo "test-byteid: $$f: now byte-identical — graduate out of DATABYTEID_DIVERGED"; \
				rc=1; break; \
			fi; \
			pn=`expr $$pn + 1`;; \
		*) \
			if [ $$diffs -ne 0 ]; then \
				echo "test-byteid: FAIL $$f: $$firstdiff differs"; \
				rc=1; break; \
			fi;; \
		esac; \
		test $$ns -ge 1 || { rc=1; break; }; \
		rm -rf -- $$base.cs.sepwork $$base.ww.sepwork || { \
			echo "test-byteid: FAIL $$f: scratch cleanup"; rc=1; break; }; \
		base=; n=`expr $$n + 1`; \
	done; \
	if [ -n "$$base" ]; then \
		rm -rf -- $$base.cs.sepwork $$base.ww.sepwork || { \
			echo "test-byteid: scratch cleanup failed"; \
			if [ $$rc -eq 0 ]; then rc=1; fi; }; \
	fi; \
	if [ $$rc -eq 0 ] && [ $$n -lt $(DATABYTEID_EXPECTED_MIN) ]; then \
		echo "test-byteid: data corpus shrank ($$n < $(DATABYTEID_EXPECTED_MIN))"; \
		rc=1; \
	fi; \
	rmdir "$$work" || { echo "test-byteid: workspace cleanup failed"; \
		if [ $$rc -eq 0 ]; then rc=1; fi; }; \
	if [ $$rc -eq 0 ]; then \
		echo "test-byteid: $$n data fixtures compared at compiler output ($$pn pinned-divergent)"; \
	fi; \
	exit $$rc

test-byteid: test-lang-byteid test-data-byteid test-native-byteid

test-bootstrap: bootstrap test-bootstrap-native

test-all: test-commit test-byteid test-bootstrap test-platform test-wwfixture

install: all
	mkdir -p $(PREFIX)/bin $(PREFIX)/lib
	cp $(BIN)/ww $(PREFIX)/bin/
	cp $(WWTEST_BIN) $(PREFIX)/bin/
	cp $(LIB)/libwcc.a $(PREFIX)/lib/

clean:
	rm -rf $(OUT)

# ---- explicit bootstrap ------------------------------------------------
# Four-stage self-host on the SEPARATE-COMPILATION path: Cstage (these C
# tools) builds the wwstage tools, then the wwstage compiler reproduces
# itself three times with cmp ww2 == ww3 == ww4.
#
# Orchestration vs. proof — drawn explicitly so this demo claims no more
# self-host than it delivers (#66):
#   - The cstage `ww` driver runs the sep GLUE only: dep-discovery,
#     per-package `w6c -c -I`, archiving, and the final link. It is a
#     fixed C orchestrator and is NOT part of the fixed point.
#   - The fixed point ww2 == ww3 == ww4 proves the wwstage COMPILER
#     (w6c, genuinely chained — stage N's frontend is stage N-1's output,
#     injected via WW_W6C) plus the wwstage ASSEMBLER (w6a_ww) and LINKER
#     (w6l_ww) reach a byte-stable fixed point: the load-bearing
#     compiler-reproduces-itself proof. wwdump is unchainable under sep
#     (it takes no -o/-I and writes asm to stdout), so the chainable sep
#     frontend w6c is the bootstrapped target (test 994 pins w6c ==
#     wwdump -c).
#   - The wwstage DRIVER (`ww_ww`) is NOT yet in this loop: it cannot
#     swap its frontend (it hardcodes <selfdir>/w6c_ww and honors no
#     WW_W6C override). Closing that — so `ww_ww` itself carries the
#     glue — is task #66. Until then cstage `ww` orchestrates and this
#     bootstrap does NOT claim a fully wwstage-driven self-host.
#
# Why a fourth stage? Stage 3 proves stage-1 w6c_ww and ww2 (which differ
# in how they were built) emit the same machine code. Stage 4 proves ww3 is
# byte-stable when used as a compiler — i.e. the fixed point is truly
# fixed, not merely a coincidental two-stage equilibrium.
BS	= $(OUT)/bootstrap
W6CSRC	= selfhost/cmd/w6c/main.ww
W6CINC	= -I lib/ww -I selfhost/cmd/wcc
bootstrap: all
	@echo "=== stage 0 (Cstage): C-built tools ==="
	@echo "  $(BIN)/ww (sep orchestrator) $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l"
	@echo
	@echo "=== stage 1: wwstage tools the fixed point chains ==="
	@ls -l $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww
	@rm -rf $(BS) && mkdir -p $(BS)
	@echo
	@echo "=== stage 2: ww2 = stage-1 w6c_ww sep-compiles w6c ==="
	@WW_W6C=$(BIN)/w6c_ww WW_W6A=$(BIN)/w6a_ww WW_W6L=$(BIN)/w6l_ww \
		$(BIN)/ww build -o $(BS)/ww2 $(W6CINC) $(W6CSRC)
	@ls -l $(BS)/ww2
	@echo
	@echo "=== stage 3: ww3 = ww2 sep-compiles w6c ==="
	@WW_W6C=$(BS)/ww2 WW_W6A=$(BIN)/w6a_ww WW_W6L=$(BIN)/w6l_ww \
		$(BIN)/ww build -o $(BS)/ww3 $(W6CINC) $(W6CSRC)
	@ls -l $(BS)/ww3
	@echo
	@echo "=== stage 4: ww4 = ww3 sep-compiles w6c ==="
	@WW_W6C=$(BS)/ww3 WW_W6A=$(BIN)/w6a_ww WW_W6L=$(BIN)/w6l_ww \
		$(BIN)/ww build -o $(BS)/ww4 $(W6CINC) $(W6CSRC)
	@ls -l $(BS)/ww4
	@echo
	@echo "=== fixed-point gates ==="
	@cmp $(BS)/ww2 $(BS)/ww3 && echo "  ww2 == ww3 ✓ (byte-identical exe)"
	@cmp $(BS)/ww3 $(BS)/ww4 && echo "  ww3 == ww4 ✓ (byte-identical exe, ww3 is byte-stable as a compiler)"
	@echo
	@echo "BOOTSTRAP OK: ww-cgen (w6c) + w6a_ww + w6l_ww reach a fixed point that holds across two iterations (sep path)."
	@echo
	@echo "Phase 10 remaining work:"
	@echo "  - delete cmd/wcc/ cmd/w6c/ cmd/w6a/ cmd/w6l/ cmd/ww/ in the final commit"
	@echo
	@echo "Already in selfhost/:"
	@echo "  - cmd/wcc/ : ww-cgen  (lex/parse/check/cgen) — bootstrap fixed point"
	@echo "  - cmd/w6c/ : ww-w6c   (lex/parse/cgen front-end) — matches wwdump_ww -c (test 994)"
	@echo "  - cmd/w6a/ : ww-w6a   (lex/parse/asm/obj) — byte-identical to C w6a (test 991)"
	@echo "  - cmd/w6l/ : ww-w6l   (linker w/ archive support) — byte-identical to C w6l (test 992)"
	@echo "  - cmd/ww/  : ww-driver (build/run/version) — byte-identical to C ww (test 993)"

# ---- nocc: bootstrap from a checked-in stage-0 binary, no cc -----------
# A fresh-checkout flow that never invokes cc. Mirrors `make bootstrap`'s
# fixed-point check but uses `bootstrap/$(ARCH)/{ww,w6c,w6a,w6l}` as the
# starting point and lands in $(OUT)/nocc/ so the cstage build under
# $(OUT)/ stays untouched. See bootstrap/README.md and BOOTSTRAP.md.
#
# `make bootstrap-snapshot` populates bootstrap/$(ARCH)/ from the
# currently-built wwstage so you can verify locally before deciding
# to commit the binaries.
ARCH		?= amd64
STAGE0		= bootstrap/$(ARCH)
NOCC_OUT	= $(OUT)/nocc
NOCC_BIN	= $(NOCC_OUT)/bin
NOCC_LIB	= $(NOCC_OUT)/lib
NOCC_OBJ	= $(NOCC_OUT)/obj

bootstrap-snapshot: wwstage
	@mkdir -p $(STAGE0)
	@cp $(BIN)/ww_ww $(STAGE0)/ww
	@cp $(BIN)/w6c_ww $(STAGE0)/w6c
	@cp $(BIN)/w6a_ww $(STAGE0)/w6a
	@cp $(BIN)/w6l_ww $(STAGE0)/w6l
	@echo "snapshot: $(STAGE0)/{ww,w6c,w6a,w6l} populated from $(BIN)/*_ww"
	@echo "         (gitignored — 'git add -f bootstrap/$(ARCH)/*' to ship)"

nocc:
	@for f in ww w6c w6a w6l; do \
		test -x $(STAGE0)/$$f || { \
			echo "$@: missing $(STAGE0)/$$f"; \
			echo "$@: run 'make bootstrap-snapshot' first, or read bootstrap/README.md"; \
			exit 1; \
		}; \
	done
	@echo "=== nocc: starting from $(STAGE0) (no cc invoked) ==="
	@rm -rf $(NOCC_OUT)
	@mkdir -p $(NOCC_BIN) $(NOCC_LIB) $(NOCC_OBJ)
	@# Stage 0: drop bootstrap binaries under both natural names and
	@# the _ww-suffixed names the driver expects when shelling out.
	@for f in ww w6c w6a w6l; do \
		cp $(STAGE0)/$$f $(NOCC_BIN)/$$f; \
		cp $(STAGE0)/$$f $(NOCC_BIN)/$${f}_ww; \
	done
	@# Assemble libwwrt.a using stage-0 w6a — the linker needs it for
	@# every wwstage-built binary in the next stage. Member order
	@# follows $(RT_S) so the embedded symbol table matches the
	@# cstage-built libwwrt.a (cstage uses ar rcs $(RT_OBJ), same
	@# order).
	@objs=""; for s in $(RT_S); do \
		b=`basename $$s .s`; \
		$(NOCC_BIN)/w6a -o $(NOCC_OBJ)/$$b.o $$s; \
		objs="$$objs $(NOCC_OBJ)/$$b.o"; \
	done; $(AR) rcs $(NOCC_LIB)/libwwrt.a $$objs
	@echo "=== stage 1: stage-0 rebuilds the wwstage tools from source ==="
	@# The driver's default lib path is $self_dir/../../lib — under
	@# $(NOCC_BIN) that resolves to $(NOCC_OUT)/lib (libwwrt only).
	@# Pass -I $(CURDIR)/lib so `use os` etc. resolve to source.
	@cd $(NOCC_BIN) && ./ww build -o main \
		-I $(CURDIR)/lib/ww \
		-I $(CURDIR)/selfhost/cmd/wcc \
		-I $(CURDIR)/lib $(CURDIR)/selfhost/cmd/w6c/main.ww && mv main w6c_ww1
	@cd $(NOCC_BIN) && ./ww build -o main -I $(CURDIR)/selfhost/cmd/w6a \
		-I $(CURDIR)/selfhost/cmd/wcc -I $(CURDIR)/lib \
		$(CURDIR)/selfhost/cmd/w6a/main.ww && mv main w6a_ww1
	@cd $(NOCC_BIN) && ./ww build -o main -I $(CURDIR)/selfhost/cmd/w6l \
		-I $(CURDIR)/selfhost/cmd/wcc -I $(CURDIR)/lib \
		$(CURDIR)/selfhost/cmd/w6l/main.ww && mv main w6l_ww1
	@cd $(NOCC_BIN) && ./ww build -o main -I $(CURDIR)/selfhost/cmd/wcc \
		-I $(CURDIR)/lib $(CURDIR)/selfhost/cmd/ww/main.ww && mv main ww_ww1
	@echo "=== fixed-point gates: rebuilt tools == stage-0 binaries? ==="
	@cmp $(NOCC_BIN)/w6c_ww1 $(STAGE0)/w6c && echo "  w6c ✓"
	@cmp $(NOCC_BIN)/w6a_ww1 $(STAGE0)/w6a && echo "  w6a ✓"
	@cmp $(NOCC_BIN)/w6l_ww1 $(STAGE0)/w6l && echo "  w6l ✓"
	@cmp $(NOCC_BIN)/ww_ww1 $(STAGE0)/ww && echo "  ww ✓"
	@echo
	@echo "NOCC OK: $(STAGE0)/* reproduces itself from source. cc not invoked."

.PHONY: all cstage wwstage wwfixture test-wwfixture-process test-wwfixture \
	test test-unit test-compiler-smoke test-compiler test-package \
	test-lang test-library test-commit test-lang-byteid \
	test-data-byteid test-native-byteid test-byteid \
	test-bootstrap-native test-bootstrap \
	test-platform test-all install clean bootstrap nocc bootstrap-snapshot
