# RiscOS-C build.
#
#   make            build everything
#   make verify     check built output against a reference ROM
#   make hdrcheck   hold the owned C-form headers to the ObjAsm originals
#   make regen-headers  rewrite the owned C-form headers from those originals
#   make clean

include mk/toolchain.mk

ifeq ($(CROSS_ARCH),aarch64)
include mk/aarch64.mk
else

BUILD    ?= build
$(eval $(call check-build-target,$(BUILD)))

# The C library's archive. Named here, before first use, because
# Sources/Lib/CLib/clib.mk, which also assigns it, is included after rules that name
# it as a prerequisite; unset there, it expands to nothing and a parallel
# build fails.
CLIB := $(BUILD)/lib/libclib.a
# Likewise for Sources/Lib/Desktop/desktop.mk's archive, and clib's float-free
# formatter.
DESKTOP_LIB := $(BUILD)/lib/libdesktop.a
CLIB_NOFLOAT := $(BUILD)/Sources/Lib/CLib/stdio/nofloat/format_float.o
LEGACY   ?= ../legacy
REF_ROM  ?= $(LEGACY)/rpcemu/roms/riscos

HDRSRC := $(LEGACY)/riscos-src/RiscOS/Sources/Programmer/HdrSrc/hdr
KERNEL := $(LEGACY)/riscos-src/RiscOS/Sources/Kernel/hdr
HALSRC := $(LEGACY)/riscos-src/RiscOS/Sources/HAL/HAL_IOMD/hdr

# The IOMD ROM is 4MB. The size is written into the HAL header at &60.
ROM_SIZE ?= 0x400000

# The size riscos-full, riscos-replaced and riscos-ours are built at. They
# carry the reference chain with our larger kernel under it, and every
# replacement bigger than its slot twice (a placeholder in the chain and the
# real module past it); that does not fit in 4M.
#
# ROM_SIZE reaches the HAL header, the kernel and every module, so each target
# that wants one of these ROMs recurses with ROM_SIZE=$(BIG_ROM_SIZE). Keep the
# recursion spelt $(MAKE) literally in those recipes: make recognises a
# recursive line before expanding it, and behind a variable --dry-run stops
# descending and -j stops sharing job tokens.
BIG_ROM_SIZE ?= 0x800000

# The HAL occupies a fixed-size region at the front of the ROM, and the RISC OS
# image follows it. Both the code and the tools need to agree on how big it is.
HAL_SIZE ?= 0x8000

HAL_IOMD_SRC := Sources/Hardware/IOMD/top.S \
                Sources/Hardware/IOMD/kbdscan.S \
                Sources/Hardware/IOMD/nvram.S \
                Sources/Hardware/IOMD/iic.S \
                Sources/Hardware/IOMD/iicentries.S \
                Sources/Hardware/IOMD/timers.S \
                Sources/Hardware/IOMD/machineid.S \
                Sources/Hardware/IOMD/video.S \
                Sources/Hardware/IOMD/rtc.S \
                Sources/Hardware/IOMD/ata.S \
                Sources/Hardware/IOMD/debug.S \
                Sources/Hardware/IOMD/interrupts.S \
                Sources/Hardware/IOMD/post.S \
                Sources/Hardware/IOMD/boot.S \
                Sources/Hardware/IOMD/dma.S \
                Sources/Hardware/IOMD/videoentries.S

HAL_IOMD_CSRC := Sources/Hardware/IOMD/cbits.c Sources/Hardware/IOMD/haliic.c

# The HAL_Video* entries: C built for a static base, like a module's, as
# their workspace is a block the HAL reserves (see hal_video_ws.h). They link
# the same VIDC20 core object, with the same flags, as the VIDC20Video module.
# Recursive, since the core's list is given with the module below.
HAL_VIDEO_CSRC = Sources/Hardware/IOMD/halvideo.c $(VIDC20_CORE_CSRC) Sources/Lib/Compiler/arm32/aeabi_divide.c
HAL_VIDEO_OBJ  = $(patsubst %.c,$(BUILD)/%.o,$(HAL_VIDEO_CSRC))
HAL_VIDEO_WHY  := the original left every video entry null and the display \
                  to a module; this HAL fills them in, and drives VIDC20 \
                  through them when the emulator asks it to
VIDC20_WS_SIZE := $(shell sed -n 's/^\#define VIDC20_WS_SIZE\s*//p' \
                    include/vidc20/vidc20_ws.h)

# Routines that assemble differently from the original; reported rather than
# treated as errors. ObjAsm's ADRL is two instructions: the port uses one
# where the distance is known at assembly time, and a displacement word
# (three) where only the linker knows it.
SHORTER_WHY := one ADD reaches it, where ObjAsm's ADRL always emitted two
ADRFAR_WHY := the distance is not known until link time, so it takes a word
POOL_WHY := a branch offset, shifted by the pool words the above needs

DIFFERS_SHORTER := IDEDevice_Init
DIFFERS_ADRFAR  := start Find_WS_Page_In_Bank Find_WS_Page AddBlock Add_VRAM \
                   Power_On_Self_Test HAL_Reset IOMD21DMA_Init
DIFFERS_POOL    := init7500cpu_common Relocate_Forwards_2 Relocate_Backwards

# Routines rewritten rather than reassembled; each file's header comment
# gives the reason.
DEBUGTX_WHY := the debug channel goes to the emulator's telemetry port; the \
               serial port the original drove does not exist on this machine
DIFFERS_DEBUGTX := HAL_DebugTX
# The bus type: a constant in the original, asked of Sources/Hardware/IOMD/haliic.c here.
IIC_WHY := the emulator can ask the HAL to present a high-level IIC bus, so \
           the type is asked of Sources/Hardware/IOMD/haliic.c rather than being a constant
DIFFERS_IIC := HAL_IICType
HAL_IIC_WHY := the original had no high-level bus; Sources/Hardware/IOMD/haliic.c drives \
               the lines whole when the emulator asks, and this is its entry

# What the video entries change: the descriptor's workspace size, the entry
# table, HAL_Init, and VideoDevice_Init (also on the ADRL list above).
DESC_WHY := the workspace size, grown by the VIDC20 driver's block
TABLE_WHY := fourteen video entries where the original had HAL_Null
HALINIT_WHY := one more call, to set the VIDC20 driver's workspace up
VIDEODEV_WHY := registers no VIDC20 device while the HAL drives the chip \
                itself, and one ADD reaches the template where ObjAsm's \
                ADRL emitted two
DIFFERS_DESC     := HALdescriptor
DIFFERS_TABLE    := HAL_EntryTable
DIFFERS_HALINIT  := HAL_Init
DIFFERS_VIDEODEV := VideoDevice_Init
# A deliberate correction in Sources/Hardware/IOMD/ata.S.
IDEINFO_WHY := the copy loop tests the length first, so nought copies nothing
DIFFERS_IDEINFO := IDE_ControllerInfo
HAL_IOMD_OBJ = $(patsubst %.S,$(BUILD)/%.o,$(HAL_IOMD_SRC)) \
               $(patsubst %.c,$(BUILD)/%.o,$(HAL_IOMD_CSRC)) \
               $(HAL_VIDEO_OBJ)
HAL_IOMD_ELF := $(BUILD)/hal-iomd.elf
HAL_IOMD_BIN := $(BUILD)/hal-iomd.bin

INCLUDES := -Iinclude -Iinclude/freestanding -ISources/Hardware/IOMD/include
DEFINES  := -DROM_SIZE=$(ROM_SIZE) -DOSROM_HALSize=$(HAL_SIZE)

# ---------------------------------------------------------------------------
# The kernel
#
# C and GAS, compiled and assembled by clang.

include mk/kernel.mk
# The optional reference sources, for the checks and header regeneration
# that read them. Nothing the build makes comes from here.
RO_SRC     := $(abspath $(LEGACY)/riscos-src/RiscOS/Sources)

# The build this kernel is: a StrongARM Risc PC, 4MB ROM, 32K of it the HAL.
# These are parameters because the build matrix (see `matrix` below) builds
# the same kernel for other machines from the same recipe.
#
#  - MACHINE is what the sources ask about: it picks the page tables, the cache
#    and TLB maintenance, and which arm of s/ARMops the build takes.
#  - ARCH_MACHINE is the same fact for the compiler: the kernel, the modules
#    and the libraries follow it. The IOMD HAL, IOMD-only modules and the
#    machine tests stay on ARCH_iomd.
MACHINE_NAME   ?= IOMD
# Which row of the build matrix this is. Two rows are the same machine and
# differ only by a predefine - CortexA53 with and without long descriptors -
# so a layout keyed on the machine's name alone cannot tell them apart.
CONFIG_NAME    ?= $(MACHINE_NAME)
ARCH_MACHINE   ?= $(ARCH_iomd)

# The abort trap: kernel C that decodes an aborting instruction so the kernel
# can say what memory it was reaching for. atcontext is a separate assembly
# unit (GetAll does not GET it), linked alongside.
#
# The decoder, atarm, is generated by tools/build/atgen.py from
# aborttrap/encodings, aborttrap/actions and aborttrap/cache. The action set
# is the machine's: IOMD's here, and each matrix row carries its own. The ids
# file keeps the IOMD output identical to the original decoder's.
#
# atinstr and atpre build to empty objects: they hold only static functions
# and are #included (atpre.c includes atinstr.c, and so does the generated
# atarm.c). They are listed so that they are still compiled standalone.
ABORTTRAP_ACTIONS := ARMv3 ARMv4 FPA
ABORTTRAP_OBJ     := $(BUILD)/Sources/Kernel/atcontext.o \
                     $(patsubst %,$(BUILD)/Sources/Kernel/%.o,$(ABORTTRAP_CNAMES))


premmu: $(PREMMU_CNAMES:%=$(BUILD)/Sources/Kernel/%.o) $(PRERW_CNAMES:%=$(BUILD)/Sources/Kernel/%.o)
	@for o in $(PREMMU_CNAMES:%=$(BUILD)/Sources/Kernel/%.o); do \
	  if llvm-readelf -S $$o | awk '$$2 ~ /^\.(data|bss|rodata)/ && $$6 !~ /^0+$$/ {print "premmu: '$$o' carries " $$2; f=1} END {exit f}'; then :; else exit 1; fi; \
	  if llvm-readelf -r $$o | awk '/^Relocation section/ {skip = ($$3 ~ /debug|eh_frame/)} !skip && /R_ARM_ABS32/ {print "premmu: '$$o' has an absolute relocation: " $$0; f=1} END {exit f}'; then :; else exit 1; fi; \
	done
	@for o in $(PRERW_CNAMES:%=$(BUILD)/Sources/Kernel/%.o); do \
	  if llvm-readelf -S $$o | awk '$$2 ~ /^\.(data|bss)/ && $$6 !~ /^0+$$/ {print "premmu: '$$o' carries " $$2 " and runs before the RW area is mapped"; f=1} END {exit f}'; then :; else exit 1; fi; \
	done
	@echo "premmu: $(if $(PREMMU_CNAMES),$(words $(PREMMU_CNAMES)) units hold the discipline,no pre-MMU units yet - the rails are up)$(if $(PRERW_CNAMES), and $(words $(PRERW_CNAMES)) the pre-RW half)"

.PHONY: premmu

# Bump a component's VersionNum with vmanage: make vbump C=Sources/Desktop/Free
# (V=x.yy sets that version instead of incrementing; U=1 only refreshes the date).
.PHONY: vbump
vbump:
	@test -n "$(C)" -a -f "$(C)/VersionNum" || { echo "usage: make vbump C=Sources/<Category>/<Component> [V=x.yy|U=1]"; exit 1; }
	cd $(C) && $(CURDIR)/tools/vmanage/vmanage $(if $(V),set $(V),$(if $(U),update,inc))


# Named as targets so make knows it can build them. A pattern rule's
# prerequisite with no stem must exist or be a named target; otherwise, in a
# fresh build tree, the kernel's rules below are passed over for the generic
# $(BUILD)/%.o rules, which lack the kernel's include path. The recipe still
# comes from the pattern rule.
$(KERNEL_GLOBALS):


# The C needs a division, and a StrongARM has no instruction for one. This is
# the same helper the modules use, built for the kernel.
KERNEL_LIBOBJ := $(BUILD)/Sources/Kernel/aeabi_divide.o

# Nothing in the kernel image is assembled by asasm. The marker labels
# s/GetAll emitted are defined in Sources/Kernel/kernel.ld.
KERNEL_OBJ := $(ABORTTRAP_OBJ) $(KERNEL_COBJ) \
              $(KERNEL_LIBOBJ)
KERNEL_ELF := $(BUILD)/kernel.elf
KERNEL_BIN := $(BUILD)/kernel.bin

kernel: $(KERNEL_BIN)

# Whole reference module chains under either kernel, with our HAL.
# The control keeps the reference kernel's 4 MB ROM mapping; the larger
# kernel moves the chain as one unit. Relocation is heuristic, as for riscos-full.
KERNEL_ROM     := $(BUILD)/riscos-c-kernel
KERNEL_CONTROL := $(BUILD)/riscos-control-kernel

kernelrom:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) \
	  $(KERNEL_ROM) $(KERNEL_CONTROL)

$(KERNEL_ROM): $(HAL_IOMD_BIN) $(KERNEL_BIN) $(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  --reference-modules --relocate

kernelcontrolhal:
	@$(MAKE) --no-print-directory BUILD=$(BUILD)/kernel-control ROM_SIZE=0x400000 \
	  $(BUILD)/kernel-control/hal-iomd.bin

$(KERNEL_CONTROL): kernelcontrolhal $(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(BUILD)/kernel-control/hal-iomd.bin $@ \
	  --reference $(REF_ROM) --hal-size $(HAL_SIZE)

kernelboot: kernelrom
	$(LEGACY)/tools/try-rom.sh $(abspath $(KERNEL_ROM)) $(abspath $(BUILD))/kernelboot.png 18
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/kernelboot.png

.PHONY: kernelrom kernelboot kernelcontrolhal

# GetAll GETs every other source, so the whole tree is the prerequisite.
KERNEL_ASM_SRC := $(sort $(wildcard $(KERNEL_DIR)/s/* $(KERNEL_DIR)/s/*/* \
                             $(KERNEL_DIR)/hdr/*))


# The build matrix: the same kernel built for other processor families, each
# row this Makefile's `kernel` target with the machine parameters overridden,
# into its own build tree, with the original's abort-trap action set for that
# machine.
#
#   Tungsten     XScale class; selects the ARMv5TE arms
#   RPi          ARM11 class, the three-architectures-in-one-ROM build;
#                selects the ARM11 errata arms and the ARM6support else-arms
#                in s/HAL
#   CortexA53    VMSAv6 class
#   CortexA53LD  CortexA53 with LongDesc predefined {TRUE}; the only build
#                that assembles s/LongDesc and s/VMSAv6Long
#   Virt         QEMU's virt machine: a Cortex-A72 in AArch32, CortexA53's
#                configuration under its own name
#
# Each row must link, not just assemble: the assembler leaves an unknown name
# undefined without comment, and only the linker reports a call to a routine
# that no longer exists. None of these boots; there is no HAL for them.
MATRIX := Tungsten RPi CortexA53 CortexA53LD Virt

MATRIX_MACHINE_Tungsten    := Tungsten
MATRIX_ARCH_Tungsten       := -mcpu=xscale
MATRIX_ACTIONS_Tungsten    := ARMv3 ARMv4 ARMv5TE

MATRIX_MACHINE_RPi         := RPi
MATRIX_ARCH_RPi            := -mcpu=arm1176jzf-s
MATRIX_ACTIONS_RPi         := ARMv3 ARMv4 ARMv5TE ARMv6 ARMv6K ARMv6T2 ARMv8 VFP ASIMD
# The 1176 has VFPv2, but this row's action set carries ASIMD for the Cortex
# cores the same ROM serves, and the abort trap's context save uses D16 to
# D31, which the assembler accepts only on the wider unit.
MATRIX_FPU_RPi             := VFPv3

MATRIX_MACHINE_CortexA53   := CortexA53
# The C gets no FPU (clang would vectorise into NEON, which is off until a
# context asks for it); the kernel's own assembly gets the one the core has.
MATRIX_ARCH_CortexA53      := -mcpu=cortex-a53 -mfpu=none
MATRIX_FPU_CortexA53       := NEON-FP-ARMv8
MATRIX_ACTIONS_CortexA53   := ARMv3 ARMv4 ARMv5TE ARMv6 ARMv6K ARMv6T2 ARMv8 VFP ASIMD

MATRIX_MACHINE_CortexA53LD := CortexA53
MATRIX_ARCH_CortexA53LD    := -mcpu=cortex-a53 -mfpu=none
MATRIX_FPU_CortexA53LD     := NEON-FP-ARMv8
MATRIX_ACTIONS_CortexA53LD := ARMv3 ARMv4 ARMv5TE ARMv6 ARMv6K ARMv6T2 ARMv8 VFP ASIMD

MATRIX_MACHINE_Virt        := Virt
MATRIX_ARCH_Virt           := $(ARCH_virt)
MATRIX_FPU_Virt            := NEON-FP-ARMv8
MATRIX_ACTIONS_Virt        := ARMv3 ARMv4 ARMv5TE ARMv6 ARMv6K ARMv6T2 ARMv8 VFP ASIMD

# Each row's output goes to a log; a failure prints it.
matrix: $(MATRIX:%=matrix-%)

$(MATRIX:%=matrix-%): matrix-%:
	@mkdir -p $(BUILD)/$*
	@$(MAKE) --no-print-directory kernel \
	   MACHINE_NAME=$(MATRIX_MACHINE_$*) \
	   ARCH_MACHINE="$(MATRIX_ARCH_$*)" BUILD=$(BUILD)/$* \
	   ABORTTRAP_ACTIONS="$(MATRIX_ACTIONS_$*)" CONFIG_NAME=$* \
	   KERNEL_FPU='$(MATRIX_FPU_$*)' \
	   > $(BUILD)/$*/build.log 2>&1 \
	   || { cat $(BUILD)/$*/build.log; exit 1; }

# Alias for matrix.
secondarch: matrix

.PHONY: matrix secondarch $(MATRIX:%=matrix-%)

# Rewritten units, linked in the order the original s/GetAll GET'd their
# origins (a file GET'd from inside another sits where that put it). Add a
# unit where its origin stood, never at the end. The order matters: the
# utility module's header holds offsets to labels in the supervisor and
# messages units, and the module handler reads their sign, so those units
# must link after it.
KERNEL_GAS_UNITS := os/swispine os/swiveneer arch/armops os/interrupts \
                    mem/heapentry \
                    os/vectors os/exceptions \
                    arch/mmujoins \
                    boot/hal \
                    cli/sysvars cli/utilitymod \
                    os/handlers \
                    cli/supervisorentry os/taskcontrol thread/switch \
                    vdu/vduswi vdu/graphicsv \
                    io/key \
                    mem/amblazy veneers
ifneq ($(words $(KERNEL_GAS_UNITS)),$(words $(sort $(KERNEL_GAS_UNITS))))
$(error KERNEL_GAS_UNITS names a unit twice)
endif
KERNEL_GAS_OBJ := $(patsubst %,$(BUILD)/Sources/Kernel/s/%.o,$(KERNEL_GAS_UNITS))

# The rewritten units, assembled by clang with the machine named as
# Sources/Kernel/hdr/machine.h reads it, so the matrix rows use the same recipe.
# -Werror=undef makes a switch missing from machine.h an error at its first
# #if. The include path is owned headers only: Sources/Kernel/hdr, include/, and
# $(BUILD)/Sources/Kernel for generated headers. The FPU is the row's: the abort
# trap's context save reaches D16 to D31.
KERNEL_GAS_FPU = $(if $(KERNEL_FPU),-mfpu=$(shell echo '$(KERNEL_FPU)' | tr 'A-Z' 'a-z'))
KERNEL_GAS_FLAGS = $(ASFLAGS) -g $(ARCH_MACHINE) $(KERNEL_GAS_FPU) \
                   -I$(KERNEL_DIR)/hdr -I$(KERNEL_DIR)/c/arch -Iinclude \
                   -I$(BUILD)/Sources/Kernel -DMACHINE_$(MACHINE_NAME) -DCONFIG_$(CONFIG_NAME) \
                   -DROM_SIZE=$(ROM_SIZE) -Werror=undef

$(BUILD)/Sources/Kernel/s/%.o: $(KERNEL_DIR)/s/%.S $(KERNEL_GLOBALS) $(BUILD)/defines.stamp \
                       $(KERNEL_DIR)/hdr/zeropage.h $(KERNEL_DIR)/hdr/kernel-ws.h \
                       $(KERNEL_DIR)/hdr/vduws.h $(BUILD)/Sources/Kernel/kernel-version.h
	@mkdir -p $(dir $@)
	$(AS) $(KERNEL_GAS_FLAGS) -c $< -o $@

# The generated veneers, the same way. The AArch64 kernel has its own C
# for these entries and does not assemble them.
$(BUILD)/Sources/Kernel/s/veneers.o: $(KERNEL_VENEERS) $(KERNEL_GLOBALS) $(BUILD)/defines.stamp
	@mkdir -p $(dir $@)
	$(AS) $(KERNEL_GAS_FLAGS) -c $< -o $@

# The assembly links ahead of the C. The units reach each other with ADRL,
# two ADDs whose immediates the linker fills from two eight-bit groups of the
# distance; with the C in between, lld refused some of those distances.
KERNEL_OBJ := $(KERNEL_GAS_OBJ) $(KERNEL_OBJ)

# The architecture and processor numbers Sources/Kernel/hdr/ARMops declares, which
# c/cpufeature.c and Sources/Kernel/test/cpu compare against.
KERNEL_WS_ARMOPS_SYMS := ARMv3 ARMv4 ARMv4T ARMv5 ARMv5T ARMv5TE ARMv5TEJ \
	 ARMv6 ARMvF \
	 ARM600 ARM610 ARM700 ARM710 ARM710a SA110_preRevT SA110 \
	 ARM7500 ARM7500FE SA1100 SA1110 ARM720T ARM920T ARM922T \
	 X80200 X80321 ARM1176JZF_S \
	 Cortex_A5 Cortex_A7 Cortex_A8 Cortex_A9 Cortex_A12 Cortex_A15 \
	 Cortex_A17 Cortex_A53 Cortex_A57 Cortex_A72 ARMunk

# Symbols wanted for their size (`?name`) rather than their address, which
# come through as `<name>_size`. SoundDMABuffers is the length s/HAL maps as
# uncacheable space after the cursor chunk.
KERNEL_WS_SIZE_SYMS := SoundDMABuffers

# The same, for symbols hdr/KeyWS declares register-relative (^ 0, R11): the
# assembler will not call those absolute, so the offset is taken with :INDEX:.
KERNEL_WS_REG_SYMS := UserKeyWorkSpace \
	 MouseX MouseY MouseXMult MouseYMult MouseBounds \
	 MouseAltX MouseAltY MouseButtons MouseType MouseReporting \
	 CurrKey OldKey KbId LastKbId AutoRepeatCount Debouncing \
	 PendingAltType LastLED NoDebounce InkeyCounter SoftKeyPtr KeyVec \
	 KeysDown SoftKeyName SoftKeyExpand \
	 KeyHandler_KeyTranSize KeyHandler_Flags \
	 AMBNhandles AMBNtasks AMBMappedInNode AMBNodeHandles \
	 AMBAnchorNode AMBFlags AMBMappedInRegister

# The assembly's logical build switches, exported as one or nought for the
# C (OS_ReadSysInfo 8 reports three of them).
KERNEL_WS_BOOL_SYMS := NoUnaligned NoARMv6 SupportARMv6 LongDesc ShortDesc \
	 EmulateAP1 \
	 XScaleMiniCache \
	 SupportARMV SupportARMV32 \
	 ROMatTop SASTMhatbroken DebugHALTX \
	 InterruptDelay \
	 NoARMv3 NoARMv4 NoARMv5 NoARMv7 NoARMv8 NoARMK NoARMT2 \
	 SupportARMT SupportARMv4 SupportARMv7 StrongARM_MSR_bug \
	 No26bitCode No32bitCode HAL E2ROMSupport HWInterlace HiResTTX \
	 Only13500kHzTV STB TTX256 \
	 International DebugROMInit ARM6support MEMM_VMSAv6 CompatibilityPage \
	 CacheablePageTables DebugHALTX DebugTerminal CacheCleanerHack \
	 MachineNoHalfwords MachinePreV6T2IFAR SMP \
	 HiProcVecs CacheOff XScaleJTAGDebug XScaleMiniCache AMB_LazyMapIn \
	 PMPRAMFS ChocolateOSMod ChocolateService PMPDebug \
	 ChocolateSysVars CacheCommonErrors Oscli_HashedCommands

# Symbols only some configurations declare (a descriptor format's page
# tables, VMSAv6's temporary-uncache bit). Each is its value where declared
# and nought elsewhere, so c/pagetable.c can carry every family's arithmetic
# and take a table's address only under that family's guard.
KERNEL_WS_IFDEF_SYMS := L1PT L2PT LL1PT LL2PT LL3PT XCB_TU LLAttr_Nrm_NC \
	 L2_S \
	 L2_XN AP_PROM VMSAv6_Cache_NC L2_X \
	 L2_SO_S L2_Dev_S L2_Nrm_NC L2_Nrm_WBWA L2_Nrm_WBRA L2_Nrm_WT L2_Nrm_WT_WBWA \
	 CCSIDR_Associativity_mask CCSIDR_Associativity_pos CCSIDR_LineSize_mask CCSIDR_LineSize_pos CCSIDR_NumSets_mask CCSIDR_NumSets_pos \
	 LLAttr_Dev LLAttr_Nrm_WBRA LLAttr_Nrm_WBWA LLAttr_Nrm_WT LLAttr_SO \
	 LL_HighAttr_Start LL_LowAttr_Size LL_LowAttr_Start \
	 LL_Page_HighAttr_PXN LL_Page_HighAttr_SW0 LL_Page_HighAttr_XN \
	 LL_Page_LowAttr_AP1 LL_Page_LowAttr_AP2 LL_Page_LowAttr_AttrIndx0_bit \
	 LL_Page_LowAttr_SH0 LL_Page_LowAttr_SH0_bit LL_Page_LowAttr_SH1 \
	 PPLTrans_ARM6Step PPLTrans_ShareableStep \
	 TempUncache_L2PTMask TempUncache_L3PTMask

# The tests' copy of Sources/Kernel/hdr/kernel-ws.h, on their own include path.
$(BUILD)/tests/kernel-ws.h: $(KERNEL_DIR)/hdr/kernel-ws.h
	@mkdir -p $(dir $@)
	@cp $< $@





# The abort trap's assembly. It has its own recipe only because it lives
# outside Sources/Kernel/s.
$(BUILD)/Sources/Kernel/atcontext.o: $(ABORTTRAP_DIR)/s/atcontext.S \
                             $(KERNEL_GLOBALS) $(KERNEL_DIR)/hdr/kernel-ws.h \
                             $(KERNEL_DIR)/hdr/zeropage.h
	@mkdir -p $(dir $@)
	$(AS) $(KERNEL_GAS_FLAGS) -c $< -o $@

# RISC OS names a C file c/thing rather than thing.c, so the compiler has to be
# told what it is being handed.
$(BUILD)/Sources/Kernel/%.o: $(ABORTTRAP_DIR)/c/% $(KERNEL_GLOBALS) $(KERNEL_INC)/.stamp
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_MACHINE) $(KERNEL_CFLAGS) -x c -c $< -o $@

# The decoder, generated for this build's action set. The explicit rule wins
# over the pattern above, which is what lets every other aborttrap C keep
# coming from the tree while this one comes from the generator.
$(BUILD)/Sources/Kernel/atarm.c: tools/build/atgen.py \
                         $(wildcard $(ABORTTRAP_DIR)/encodings/*) \
                         $(wildcard $(ABORTTRAP_DIR)/actions/*) \
                         $(wildcard $(ABORTTRAP_DIR)/cache/*) \
                         $(wildcard $(ABORTTRAP_DIR)/ids/*)
	@mkdir -p $(dir $@)
	python3 tools/build/atgen.py $(ABORTTRAP_ACTIONS) -o $@

$(BUILD)/Sources/Kernel/atarm.o: $(BUILD)/Sources/Kernel/atarm.c $(KERNEL_GLOBALS) \
                         $(KERNEL_INC)/.stamp
	$(CC) $(CFLAGS) $(ARCH_MACHINE) $(KERNEL_CFLAGS) -x c -c $< -o $@

$(BUILD)/Sources/Kernel/aeabi_divide.o: Sources/Lib/Compiler/arm32/aeabi_divide.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_MACHINE) $(KERNEL_CFLAGS) -c $< -o $@



$(BUILD)/Sources/Kernel/%.o: $(KERNEL_DIR)/c/%.c $(KERNEL_DIR)/hdr/vduws.h $(BUILD)/defines.stamp \
                     $(KERNEL_DIR)/hdr/zeropage.h $(BUILD)/Sources/Kernel/kernel-version.h $(KERNEL_BLOCK_HDRS) \
                     $(KERNEL_DIR)/hdr/kernel-ws.h $(KERNEL_GLOBALS) $(KERNEL_INC)/.stamp
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_MACHINE) $(KERNEL_CFLAGS) -c $< -o $@




$(KERNEL_ELF): $(KERNEL_OBJ) $(KERNEL_DIR)/kernel.ld tools/build/kernelsize.py
	$(LD) $(LDFLAGS) -T $(KERNEL_DIR)/kernel.ld -o $@ $(KERNEL_OBJ)
	@python3 tools/build/kernelsize.py $@

$(KERNEL_BIN): $(KERNEL_ELF)
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes)"

.PHONY: kernel

# ---------------------------------------------------------------------------
# Tests that run on the emulated machine
#
# A test is a set of C cases run on the emulated machine, reporting what the
# OS did with each. Run it against a reference ROM and against this one and
# diff the two reports; the port is right when they match.
#
# Each test is built two ways from the same cases:
#  - as a ROM module, run while the OS initialises it and reporting through
#    the emulator's telemetry port. It needs no filing system, keyboard or
#    desktop, so it runs in a ROM of this HAL, a kernel and the test.
#  - as an absolute file loaded at &8000 from the desktop, reporting to
#    HostFS. It runs the same cases from user mode with the whole OS up.

TESTS := convert heapsort heap ticker chocolate dynarea memmap pmp \
         sparse readargs subargs swiname eval gstrans sysvars modhand vdu \
         oscli environ swi ramdisc ramfs fcfs dahandlers meminfo amb osbyte osword \
         buffer wrch mouse key realtime cmos eeprom eeprom2k tick super \
         confirm halvideo iic iicnack1 iicnack2 iicnack3 iicarb iicstretch iichl readline hardware exception \
         exceptalign cpu cpuv3 cpu610

# Tests with named checks on the build machine; each wants
# tests/<name>/checks.c. starcmd is checks-only: its commands are reached on
# the machine through Sources/Kernel/test/oscli, so it has no ROM and no cases.c.
CHECKED_TESTS := convert heapsort heap tickevents chocolate dynarea \
                 memmap pmp sparse readargs subargs eval vdu gstrans \
                 swiname sysvars sysvarcode modhand dacreate dahandler \
                 daareas dagrow dagrowpages damovepages dainit \
                 amb oscli osbyte osword buffer starcmd \
                 starfile starconf help prettyprint callback vectors swi \
                 environ meminfo handlers sysinfo utility unsqueeze \
                 supervisor wrch mouse key realtime cmos iic readline hardware pagetable pagemap exception reset \
                 confirm libkern cpufeature bootram mmuctl armid postmmu bootmem \
                 divide modcopy codelevel thread

# These exercise the real outward-call policies with their own SWI doubles,
# rather than the shared kernel mocks used by CHECKED_TESTS and coverage.
POLICY_TESTS := rmapolicy osfilepolicy

# Machine-only tests about the ROM chain rather than kernel C. One ROM is
# built; the cases' own checks decide, and the rule reads the report's tally.
CHAIN_TESTS := messages heapirq swiirq cmosirq fontmanager

# Tests whose cases also run on the build machine. Each SWI a test calls needs
# a host veneer; heapsort's OS_HeapSort has none, so its C is covered by
# heapsortchecks instead.
HOST_TESTS := convert

# A pattern rule cannot see its own stem while its prerequisites are being
# worked out, so the modules a particular test wants in its chain are named
# with $$(TEST_MODS_$$*) and resolved on the second pass.
.SECONDEXPANSION:

# Keep intermediate files: a failing test's objects and ELF are what is
# needed to investigate it.
.SECONDARY:

TEST_LIB := tests/lib

# What each test's module calls itself in the chain
TEST_TITLE_convert := ConvertTest
TEST_TITLE_drawfilemod := DrawFileTest
TEST_AT_SERVICE_drawfilemod := Service_Reset
TEST_MOD_EXTRA_drawfilemod := $(BUILD)/tests/drawfilemod/fixtures.o
$(BUILD)/tests/drawfilemod/fixtures.o: Sources/Video/Render/DrawFile/test/drawfilemod/fixtures.S \
 $(wildcard ../legacy/riscos-src/RiscOS/Sources/Video/Render/DrawFile/TestFiles/*,aff)
	@mkdir -p $(dir $@)
	$(CC) --target=arm-none-eabi -c $< -o $@
TEST_TITLE_heapsort := HeapSortTest
TEST_TITLE_messages := MessagesTest
TEST_TITLE_fontmanager := FontManagerTest
TEST_TITLE_heap := HeapTest
TEST_TITLE_heapirq := HeapIRQTest
TEST_TITLE_cmosirq := CMOSIRQTest
TEST_TITLE_swiirq := SWIIRQTest
TEST_TITLE_ticker := TickerTest
TEST_TITLE_chocolate := ChocolateTest
TEST_TITLE_dynarea := DynAreaTest
TEST_TITLE_memmap := MemMapTest
TEST_TITLE_pmp := PMPTest
TEST_TITLE_sparse := SparseTest
TEST_TITLE_readargs := ReadArgsTest
TEST_TITLE_subargs := SubArgsTest
TEST_TITLE_swiname := SWINameTest
# A second module carried by Sources/Kernel/test/swiname: decode code and no name table,
# the only header shape that reaches a module's own name code.
TEST_TITLE_swiname2 := NameCode
TEST_TITLE_eval := EvalTest
TEST_TITLE_gstrans := GSTransTest
TEST_TITLE_sysvars := SysVarsTest
TEST_TITLE_modhand := ModHandTest
TEST_TITLE_vdu := VDUTest
TEST_TITLE_drawmod := DrawModTest
TEST_TITLE_sprxmod := SprxModTest
TEST_TITLE_fpemod := FPEModTest
TEST_TITLE_iicmod := IICModTest
TEST_TITLE_podulemod := PoduleTest
TEST_TITLE_soundchannelsmod := SoundChTest
TEST_TITLE_bootnetmod := BootNetTest
TEST_TITLE_bootcmdsmod := BootCmdsTest
TEST_TITLE_spriteutilsmod := SprUtilTest
TEST_TITLE_sermousemod := SerMouseTest
TEST_TITLE_ps2drvmod := PS2DrvTest
TEST_TITLE_supersamplemod := SuperTest
TEST_TITLE_hourglassmod := HourTest
TEST_TITLE_itablemod := ITableTest
TEST_TITLE_scrblankmod := ScrBlankTest
TEST_TITLE_buffersmod := BufferManTest
TEST_TITLE_mbufmod := MbufManTest
TEST_TITLE_devicefsmod := DeviceFSTest
TEST_TITLE_serialsptmod := SerialSptTest
TEST_TITLE_systemdevsmod := SysDevsTest
TEST_TITLE_pipefsmod := PipeFSTest
TEST_TITLE_fslockmod := FSLockTest
TEST_TITLE_romfontsmod := ROMFontsTest
TEST_TITLE_cdfsdrivermod := CDDriverTest
TEST_TITLE_cdatapimod := CDATAPITest
TEST_TITLE_cdfsmod := CDFSTest
TEST_TITLE_dosfsmod := DOSFSModTest
TEST_TITLE_resolvermod := ResolverTest
TEST_TITLE_dhcpmod := DHCPModTest
TEST_TITLE_netmod := NetModTest
TEST_TITLE_oscli := TestMod
TEST_TITLE_ramdisc := RamDiscTest
TEST_TITLE_ramfs := RamFSTest
TEST_TITLE_fcfs := FCFSTest
TEST_TITLE_fcprobe := FCProbeTest
TEST_TITLE_ideprobe := IDEProbeTest
TEST_TITLE_adfsmod := ADFSModTest
TEST_TITLE_adfsbg := ADFSBgTest
TEST_TITLE_adfsfloppy := ADFSFloppyTest
TEST_AT_SERVICE_adfsfloppy := Service_Reset
TEST_CHAIN_adfsfloppy := 002-FileSwitch 003-ResourceFS 004-TerritoryManager 005-Messages 006-MessageTrans 007-UK
TEST_MODS_adfsfloppy = $(MOD_KRES_MOD) $(FC_MOD) $(ADFS_MOD)
# ADFS's two lower interfaces: a 765 command from a caller, and a packet
# command to an IDE device. Uses the floppy test's chain.
TEST_TITLE_adfsdcb := ADFSDCBTest
TEST_AT_SERVICE_adfsdcb := Service_Reset
TEST_CHAIN_adfsdcb := 002-FileSwitch 003-ResourceFS 004-TerritoryManager 005-Messages 006-MessageTrans 007-UK
TEST_MODS_adfsdcb = $(MOD_KRES_MOD) $(FC_MOD) $(ADFS_MOD)
TEST_EXTRA_CFLAGS_adfsdcb := -ISources/FileSys/ADFS/c -ISources/FileSys/FileCore/c
TEST_MOD_EXTRA_adfsdcb := $(BUILD)/tests/adfsdcb/mod/post.o
TEST_TITLE_dahandlers := DAHandlersTest
TEST_TITLE_meminfo := MemInfoTest
TEST_TITLE_amb := AMBTest
TEST_TITLE_osbyte := OSByteTest
TEST_TITLE_osword := OSWordTest
TEST_TITLE_buffer := BufferTest
TEST_TITLE_wrch := WrchTest
TEST_TITLE_mouse := MouseTest
TEST_TITLE_key := KeyTest
TEST_TITLE_realtime := RealTimeTest
TEST_TITLE_cmos := CMOSTest
TEST_TITLE_iic := IICTest
TEST_TITLE_iicnack1 := IICNack1Test
TEST_TITLE_iicnack2 := IICNack2Test
TEST_TITLE_iicnack3 := IICNack3Test
TEST_TITLE_iicarb := IICArbTest
TEST_TITLE_iicstretch := IICStretchTest
TEST_TITLE_iichl := IICHLTest
TEST_TITLE_tick := TickTest
TEST_TITLE_eeprom := EEPROMTest
TEST_TITLE_eeprom2k := EEPROM2KTest
TEST_TITLE_halvideo := HALVideoTest
TEST_TITLE_confirm := ConfirmTest
TEST_TITLE_readline := ReadLineTest
TEST_TITLE_hardware := HardwareTest
TEST_TITLE_exception := ExceptionTest
# The same cases under a different fault status and under three different
# processors. Each is a module of its own, so each carries a name of its own.
TEST_TITLE_exceptalign := ExcAlignTest
TEST_TITLE_cpu := CPUTest
TEST_TITLE_cpuv3 := CPUv3Test
TEST_TITLE_cpu610 := CPU610Test

# When a test does not run as its module initialises, the service call that
# runs it instead. The kernel opens its own message file after
# Service_PostInit, so anything wanting the OS to be finished with itself has
# to wait for a service issued after that, and Service_Reset is the first.
TEST_AT_SERVICE_messages := Service_Reset
TEST_AT_SERVICE_fontmanager := Service_Reset
TEST_AT_SERVICE_swiname := Service_Reset
TEST_AT_SERVICE_vdu := Service_Reset
# Draw's test needs the display driver up and Draw's DrawV claim made.
TEST_AT_SERVICE_drawmod := Service_Reset
# SpriteExtend's test needs the display driver up and SpriteV claimed.
TEST_AT_SERVICE_sprxmod := Service_Reset
# FPEmulator's test needs the undefined instruction vector claimed.
TEST_AT_SERVICE_fpemod := Service_Reset
# The IIC module's test claims WrchV after the chain is up and before the
# prompt starts, and needs MessageTrans for the module's messages.
TEST_AT_SERVICE_iicmod := Service_Reset
# Podule's test claims WrchV for *Podules and needs MessageTrans.
TEST_AT_SERVICE_podulemod := Service_Reset
# SoundChannels' test claims WrchV and needs MessageTrans for its errors.
TEST_AT_SERVICE_soundchannelsmod := Service_Reset
# BootNet's test claims WrchV, and its `*`Configure needs the chain finished.
TEST_AT_SERVICE_bootnetmod := Service_Reset
# BootCommands's test claims WrchV, grows a dynamic area and registers a
# ResourceFS directory.
TEST_AT_SERVICE_bootcmdsmod := Service_Reset
# SpriteUtils's test claims WrchV and grows a dynamic area.
TEST_AT_SERVICE_spriteutilsmod := Service_Reset
# SerialMouse's test calls PointerV and sets the pointer type, which wants
# the chain up and the kernel's own claimant on the vector.
TEST_AT_SERVICE_sermousemod := Service_Reset
# PS2Driver's test waits for the keyboard to be found and for keys to be
# typed at it, which wants the machine's interrupts running and the chain up.
TEST_AT_SERVICE_ps2drvmod := Service_Reset
# Hourglass's test waits on the centisecond clock for the ticker to have
# drawn, which wants the machine's own interrupts running and the chain up.
TEST_AT_SERVICE_hourglassmod := Service_Reset
# InverseTable's test needs ColourTrans reading a palette from an initialised
# display driver and mode, and the module under test to have seen
# Service_PostInit, issued as the chain finishes.
TEST_AT_SERVICE_itablemod := Service_Reset
# ScreenBlanker's test claims WrchV, inserts a module of its own and drops to
# user mode to let a callback through.
TEST_AT_SERVICE_scrblankmod := Service_Reset
# Needs the chain finished: during initialisation the modules after the test
# are not there yet.
TEST_AT_SERVICE_modhand := Service_Reset
# The supervisor test claims WrchV after the chain is up and before the
# prompt starts.
TEST_AT_SERVICE_super := Service_Reset
# And the confirm test claims the same vector in the same gap, for the same
# reason - its command is then typed at the prompt the way super's script is.
TEST_AT_SERVICE_confirm := Service_Reset
TEST_CONFIRM_confirm := 1
# And the readline test: OS_ReadLine waits on the keyboard exactly as
# OS_Confirm does, so its commands are typed at the same prompt.
TEST_AT_SERVICE_readline := Service_Reset
TEST_READLINE_readline := 1
# The command line wants the rest of the machine standing up: aliases are
# system variables, *Help reads a message file, and the module that owns a
# command has to be in the chain before the command can reach it.
TEST_AT_SERVICE_oscli := Service_Reset
# Growing the RAM disc reinitialises RamFS, and a module being reinitialised
# wants the chain it lives in finished first.
TEST_AT_SERVICE_ramdisc := Service_Reset
# Growing the disc reinitialises RamFS, which is in the chain behind this
# test.
TEST_AT_SERVICE_ramfs := Service_Reset
TEST_AT_SERVICE_fcfs := Service_Reset
# The probe makes a FileCore incarnation of its own and lays a filing system
# out on it, both of which want FileSwitch and the chain behind it already up.
TEST_AT_SERVICE_fcprobe := Service_Reset
# Resets the IDE controller, which nothing else in the chain uses, and words
# a command's refusals through MessageTrans.
TEST_AT_SERVICE_ideprobe := Service_Reset
# ADFS's test catalogues discs and saves a file, so it needs FileSwitch, the
# territory modules and ADFS up; it also claims WrchV.
TEST_AT_SERVICE_adfsmod := Service_Reset
# And the background test, which reads files and so wants the chain up.
TEST_AT_SERVICE_adfsbg := Service_Reset
# The areas this tests are made by InitHostedDAs, after the first pass of
# module initialisation; before it, three of the six do not exist.
TEST_AT_SERVICE_dahandlers := Service_Reset
# The compatibility page and the HAL's uncacheable workspace are settled after
# the reset sequence rather than during it, and OS_Memory 16 is asked about
# both - so this waits for the same moment the areas test does.
TEST_AT_SERVICE_meminfo := Service_Reset
# Empties application space; at Service_Reset nothing else wants &8000.
TEST_AT_SERVICE_amb := Service_Reset
# Allocates its own application slot, and needs the kernel's message file
# (opened after Service_PostInit) for the text of exception errors.
TEST_AT_SERVICE_exception := Service_Reset
# The sweep drives the VDU, keyboard, buffers and CMOS, and empties every
# buffer.
TEST_AT_SERVICE_osbyte := Service_Reset
# As OS_Byte; also four of its reasons are the clock, which goes through a
# territory and its message file.
TEST_AT_SERVICE_osword := Service_Reset
# Claims EventV and empties every buffer.
TEST_AT_SERVICE_buffer := Service_Reset
# Where a written character goes is decided by the write destination and by
# three streams, two of which are files - so this one wants the filing system
# in the chain and the chain finished.
TEST_AT_SERVICE_wrch := Service_Reset
TEST_AT_SERVICE_mouse := Service_Reset
TEST_AT_SERVICE_key := Service_Reset
TEST_AT_SERVICE_realtime := Service_Reset
TEST_AT_SERVICE_cmos := Service_Reset
TEST_AT_SERVICE_iic := Service_Reset
TEST_AT_SERVICE_iicnack1 := Service_Reset
TEST_AT_SERVICE_iicnack2 := Service_Reset
TEST_AT_SERVICE_iicnack3 := Service_Reset
TEST_AT_SERVICE_iicarb := Service_Reset
TEST_AT_SERVICE_iicstretch := Service_Reset
TEST_AT_SERVICE_iichl := Service_Reset
TEST_AT_SERVICE_tick := Service_Reset
TEST_AT_SERVICE_halvideo := Service_Reset
TEST_AT_SERVICE_eeprom := Service_Reset
TEST_AT_SERVICE_eeprom2k := Service_Reset

# A test module that declares SWIs of its own, so that the half of the SWI
# naming that asks a module can be reached at all. The chunk is one nothing in
# the reference ROM's chain uses.
TEST_SWIS_swiname := 0x88000
TEST_SWIS_swiname2 := 0x88100
TEST_SWIS_NO_TABLE_swiname2 := 1
TEST_MODS_swiname = $(BUILD)/tests/swiname2.mod

# A test module that declares commands of its own, which is the only way to
# see what a command line was turned into: what the kernel hands a command
# entry is not visible from anywhere else.
TEST_COMMANDS_oscli := 1

# Reference ROM modules a test needs in its chain. convert needs all of
# these: the punctuated types ask the territory for the digit-group
# separator, and the date SWIs need a territory that can name the months,
# hence the message file and the filing system under it. Both ROMs get the
# same modules.
TEST_CHAIN_convert := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK

# Deliberately not the Messages module, which supplies the file in the
# reference ROM; the test's own file must be the only source.
TEST_CHAIN_messages := 002-FileSwitch 003-ResourceFS 006-MessageTrans
# The exception test needs the message file; without it both kernels answer
# the token's default and the translation goes untested.
TEST_CHAIN_exception := 002-FileSwitch 003-ResourceFS 005-Messages 006-MessageTrans
TEST_MODS_messages = $(MOD_KRES_MOD)
# InverseTable is there for blending in eight bits per pixel.
TEST_MODS_fontmanager = $(FSW_MOD) $(RFS_MOD) $(MOD_KRES_MOD) $(MSG_MOD) $(ROMFONTS_MOD) $(FM_MOD) $(MOD_VIDC20_MOD) $(CTM_MOD) $(ITAB_MOD)

# The VDU test's chain: the display driver; ColourTrans, since above eight
# bits per pixel a colour number goes through it; PipeFS, for OS_SpriteOp's
# five file reason codes; and FileCore with RamFS, since ScreenLoad reads with
# OS_GBPB 3, which a pipe cannot serve.
# The Draw test needs the display driver too, and MessageTrans so errors are
# looked up rather than all reported as "SWI &%0 not known". The Draw module
# is not named here: the oracle rules below choose it through
# DRAWMOD_UNDER_TEST.
TEST_CHAIN_drawmod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK
TEST_MODS_drawmod = $(MOD_VIDC20_MOD) $(DRAWMOD_UNDER_TEST)
DRAWMOD_UNDER_TEST ?= $(DRAW_MOD)

# SpriteExtend. MessageTrans and its chain, because both refusals are
# tokens; ColourTrans, because a plot in a format other than the screen's asks
# it for a translation table; VIDC20Video, so there is a frame buffer to plot
# into; Draw, because a transformed plot works out its corners with
# Draw_TransformPath. All are the same in both ROMs.
TEST_CHAIN_sprxmod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK 021-ColourTrans \
                      028-Draw 043-InverseTable
TEST_MODS_sprxmod = $(MOD_VIDC20_MOD) $(SPRXMOD_UNDER_TEST)
SPRXMOD_UNDER_TEST ?= $(SPRX_MOD)

# FPEmulator. MessageTrans and its chain, because an exception with its trap
# enabled is raised as an error looked up through MessageTrans_ErrorLookup;
# without it all five exceptions answer the same "no such SWI".
#
# $(MOD_KRES_MOD) is in both ROMs for the message file: it registers
# Sources/Programmer/FPEmulator/Resources/UK/Messages at the reference's path,
# with the reference's tokens and texts, so the lookup succeeds for both
# modules.
TEST_CHAIN_fpemod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                     005-Messages 006-MessageTrans 007-UK
TEST_MODS_fpemod = $(MOD_KRES_MOD) $(FPEMOD_UNDER_TEST)
FPEMOD_UNDER_TEST ?= $(FPE_MOD)
# No TEST_WAIT: the whole report takes about four seconds.

# IIC. MessageTrans and its chain, because the listing's heading, device
# names and both errors are tokens. The IIC module is not named here: the two
# ROM rules below name one each.
TEST_CHAIN_iicmod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                     005-Messages 006-MessageTrans 007-UK
TEST_MODS_iicmod = $(IICMOD_UNDER_TEST)
IICMOD_UNDER_TEST ?= $(IIC_MOD)

# Podule. It goes first, before the reference chain's next six, in both
# ROMs: the kernel starts the first module early and asks it for the
# modules on the expansion cards, so the reset scan goes through it.
TEST_CHAIN_podulemod :=
TEST_MODS_podulemod = $(PODULEMOD_UNDER_TEST) \
                      $(foreach m,002-FileSwitch 003-ResourceFS \
                        004-TerritoryManager 005-Messages 006-MessageTrans \
                        007-UK,$(LEGACY)/romparts/$(m).mod)
PODULEMOD_UNDER_TEST ?= $(BUILD)/modules/Podule.mod

# SoundChannels, with our SoundDMA under it in both ROMs and no voices, so
# that SoundChannels is the one thing that differs. MessageTrans and its
# chain carry the errors and the command help.
TEST_CHAIN_soundchannelsmod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                               005-Messages 006-MessageTrans 007-UK
TEST_MODS_soundchannelsmod = $(SOUNDDMA_MOD) $(SOUNDCHANNELSMOD_UNDER_TEST)
SOUNDCHANNELSMOD_UNDER_TEST ?= $(SOUNDCHANNELS_MOD)

# MessageTrans. `005-Messages` carries the kernel's messages compressed
# against a dictionary, and the dictionary, so the escape expansion is
# exercised against real data; an all-ours ROM never reaches it, because
# Sources/SystemRes/KernelRes writes its messages out in full. FileSwitch and
# ResourceFS are how a message file is reached.
# The Territory Manager, against the reference's own.
#
# `005-Messages` supplies `Resources.TerrMgr.Messages`, so both ROMs read the
# same file; the reference MessageTrans is needed because that file is
# compressed against its dictionary. `007-UK` is in TEST_MODS rather than the
# chain so that it comes after the manager it registers with.
#
# The reference UK territory answers Territory_ReadTimeZones and
# Territory_DaylightRules, so the report's time zone and daylight saving rules
# are a real territory's.
TEST_TITLE_territorymod := TerrMgrTest
TEST_AT_SERVICE_territorymod := Service_Reset
TEST_CHAIN_territorymod := 002-FileSwitch 003-ResourceFS 005-Messages \
                           006-MessageTrans
TEST_MODS_territorymod = $(TERRMOD_UNDER_TEST) $(LEGACY)/romparts/007-UK.mod
TERRMOD_UNDER_TEST ?= $(TERR_MOD)

# Our Territory Manager in both ROMs, with the UK territory beneath it as the
# module under test; every SWI the test asks is a territory SWI the manager
# only routes.
#
# Same chain as above: `005-Messages` supplies `Resources.UK.Messages`, which
# is compressed against the reference MessageTrans's dictionary. Both ROMs
# read the reference's message file, not ours, so a difference in the file
# cannot look like a difference in the module.
TEST_TITLE_ukmod := UKTerrTest
TEST_AT_SERVICE_ukmod := Service_Reset
TEST_CHAIN_ukmod := 002-FileSwitch 003-ResourceFS 005-Messages \
                    006-MessageTrans
TEST_MODS_ukmod = $(TERR_MOD) $(UKMOD_UNDER_TEST)
UKMOD_UNDER_TEST ?= $(UKT_MOD)

TEST_TITLE_msgtransmod := MsgTransModTest
TEST_AT_SERVICE_msgtransmod := Service_Reset
TEST_CHAIN_msgtransmod := 002-FileSwitch 003-ResourceFS 005-Messages
TEST_MODS_msgtransmod = $(MSGTRANSMOD_UNDER_TEST)
MSGTRANSMOD_UNDER_TEST ?= $(MSG_MOD)

# BootNet. The same chain: its error and its keyword's help are tokens.
TEST_CHAIN_bootnetmod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                         005-Messages 006-MessageTrans 007-UK
TEST_MODS_bootnetmod = $(BOOTNETMOD_UNDER_TEST)
BOOTNETMOD_UNDER_TEST ?= $(BNET_MOD)

# BootCommands. The same chain: every word the thirteen commands print, and
# every syntax error, is a token. `*AddApp` registers into ResourceFS, and
# the files `*IfThere` and `*LoadCMOS` are pointed at live in it.
TEST_CHAIN_bootcmdsmod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                          005-Messages 006-MessageTrans 007-UK
TEST_MODS_bootcmdsmod = $(BOOTCMDSMOD_UNDER_TEST)
BOOTCMDSMOD_UNDER_TEST ?= $(BCMD_MOD)

# SpriteUtils. The same chain: every word it prints and every syntax it
# refuses with is a token.
TEST_CHAIN_spriteutilsmod := 002-FileSwitch 003-ResourceFS \
                             004-TerritoryManager \
                             005-Messages 006-MessageTrans 007-UK
TEST_MODS_spriteutilsmod = $(SPRITEUTILSMOD_UNDER_TEST)
SPRITEUTILSMOD_UNDER_TEST ?= $(SPU_MOD)

# SerialMouse. The same chain: the two names it puts in the device list are
# tokens in its own message file, which the resources module delivers.
TEST_CHAIN_sermousemod := 002-FileSwitch 003-ResourceFS \
                          004-TerritoryManager \
                          005-Messages 006-MessageTrans 007-UK
TEST_MODS_sermousemod = $(MOD_KRES_MOD) $(BUFS_MOD) $(DEVFS_MOD) \
                        $(SERSPT_MOD) $(SERMOUSEMOD_UNDER_TEST)
# The stand-in serial driver, registered as Serial for the ticker to read.
TEST_MOD_EXTRA_sermousemod := $(BUILD)/tests/lib/mod/standeventry.o \
                              $(BUILD)/tests/lib/mod/standev.o
SERMOUSEMOD_UNDER_TEST ?= $(SERM_MOD)

# PS2Driver. The same chain, for its message file, and InternationalKeyboard,
# which installs the key handler the typed keys reach (the reference kernel
# has none). KeyV and PointerV are watched by recorders that pass each call
# on.
TEST_CHAIN_ps2drvmod := 002-FileSwitch 003-ResourceFS \
                        004-TerritoryManager \
                        005-Messages 006-MessageTrans 007-UK \
                        042-InternationalKeyboard
TEST_MODS_ps2drvmod = $(PS2DRVMOD_UNDER_TEST)
PS2DRVMOD_UNDER_TEST ?= $(PS2_MOD)
TEST_MOD_EXTRA_ps2drvmod := $(BUILD)/tests/lib/mod/eventvec.o \
                            $(BUILD)/tests/lib/mod/bytevec.o

# SuperSample. The same chain: both its errors are tokens in the global
# message file. The test runs at module initialisation, so there is no
# TEST_AT_SERVICE.
TEST_CHAIN_supersamplemod := 002-FileSwitch 003-ResourceFS \
                             004-TerritoryManager \
                             005-Messages 006-MessageTrans 007-UK
TEST_MODS_supersamplemod = $(SUPERSAMPLEMOD_UNDER_TEST)
SUPERSAMPLEMOD_UNDER_TEST ?= $(SUPS_MOD)

# Hourglass. The same chain, because its one error and its two commands'
# help are tokens; the display driver is not needed, since what is checked
# is the pointer number and its palette entries rather than any pixels.
TEST_CHAIN_hourglassmod := 002-FileSwitch 003-ResourceFS \
                           004-TerritoryManager \
                           005-Messages 006-MessageTrans 007-UK
TEST_MODS_hourglassmod = $(HOURGLASSMOD_UNDER_TEST)
HOURGLASSMOD_UNDER_TEST ?= $(HOUR_MOD)

# InverseTable. The usual chain plus four: ColourTrans supplies the palettes
# and the two ready-made tables (GenerateTable's 32K one and the 8desktop
# file); Hourglass is shown while the cube is built; KernelResources carries
# this module's Messages; VIDC20Video provides a mode to read a palette for.
# All four are the same in both ROMs.
TEST_CHAIN_itablemod := 002-FileSwitch 003-ResourceFS \
                        004-TerritoryManager \
                        005-Messages 006-MessageTrans 007-UK \
                        021-ColourTrans 039-Hourglass
TEST_MODS_itablemod = $(MOD_KRES_MOD) $(MOD_VIDC20_MOD) \
                      $(ITABLEMOD_UNDER_TEST)
ITABLEMOD_UNDER_TEST ?= $(ITAB_MOD)

# ScreenBlanker. MessageTrans and its chain, because the command's output and
# both errors are tokens; KernelResources, for this module's message file;
# and a display driver, because blanking is a palette call.
#
# ScrSaver is left out: it claims Service_ScreenBlanking and would take the
# blanking over from the module under test.
# BufferManager. The usual chain and KernelResources, for the module's
# message file.
TEST_CHAIN_buffersmod := 002-FileSwitch 003-ResourceFS \
                         004-TerritoryManager \
                         005-Messages 006-MessageTrans 007-UK
TEST_MODS_buffersmod = $(MOD_KRES_MOD) $(BUFSMOD_UNDER_TEST)

# The mbuf manager needs its own message file, so KernelResources goes in
# both ROMs beside it.
TEST_CHAIN_mbufmod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK
TEST_MODS_mbufmod = $(MOD_KRES_MOD) $(MBUFMOD_UNDER_TEST)
MBUFMOD_UNDER_TEST ?= $(MBUF_MOD)
TEST_AT_SERVICE_mbufmod := Service_Reset
BUFSMOD_UNDER_TEST ?= $(BUFS_MOD)
# The two events a ring raises are caught on EventV, passed on rather than
# claimed so the kernel still sees them.
TEST_MOD_EXTRA_buffersmod := $(BUILD)/tests/lib/mod/eventvec.o
TEST_AT_SERVICE_buffersmod := Service_Reset

# DeviceFS: the same stand-in and our BufferManager in both oracle ROMs.
# Only DeviceFS itself changes. Build these two test ROMs without rebuilding
# riscos-ours or riscos-replaced: those carry the shared Wimp session.
TEST_CHAIN_devicefsmod := 002-FileSwitch 003-ResourceFS \
                          004-TerritoryManager \
                          005-Messages 006-MessageTrans 007-UK
TEST_MODS_devicefsmod = $(MOD_KRES_MOD) $(BUFS_MOD) $(DEVFSMOD_UNDER_TEST)
DEVFSMOD_UNDER_TEST ?= $(DEVFS_MOD)
# The stand-in driver's entry point, registered in place of the deferred
# drivers.
TEST_MOD_EXTRA_devicefsmod := $(BUILD)/tests/lib/mod/deventry.o
TEST_AT_SERVICE_devicefsmod := Service_Reset

# SerialDeviceSupport: the same stand-in, BufferManager and DeviceFS in both
# ROMs. Only the support module changes.
#
# KernelResources and 005-Messages both hold `Resources.Serial.Messages`.
# This is harmless: Sources/HWSupport/SerialSpt's file is a transcription of
# the reference's, so either copy gives the same error text.
TEST_CHAIN_serialsptmod := 002-FileSwitch 003-ResourceFS \
                           004-TerritoryManager \
                           005-Messages 006-MessageTrans 007-UK
TEST_MODS_serialsptmod = $(MOD_KRES_MOD) $(BUFS_MOD) $(DEVFS_MOD) \
                         $(SERSPTMOD_UNDER_TEST)
SERSPTMOD_UNDER_TEST ?= $(SERSPT_MOD)
# The stand-in serial driver: its ARM entry and its body. SerialDeviceDriver
# is deferred with the rest of IOMD, so without these the module under test
# has no driver to call.
TEST_MOD_EXTRA_serialsptmod := $(BUILD)/tests/lib/mod/standeventry.o \
                               $(BUILD)/tests/lib/mod/standev.o
TEST_AT_SERVICE_serialsptmod := Service_Reset

# SystemDevices. Its seven devices are filing systems, so it needs only
# FileSwitch. KernelResources supplies `Resources.SystemDevs.Messages` and the
# global file holding "BadFSOp", "FSInUse" and "Escape"; 005-Messages carries
# the same tokens and text, so either ResourceFS copy gives the same report.
TEST_CHAIN_systemdevsmod := 002-FileSwitch 003-ResourceFS \
                            004-TerritoryManager \
                            005-Messages 006-MessageTrans 007-UK
TEST_MODS_systemdevsmod = $(MOD_KRES_MOD) $(SYSDEVMOD_UNDER_TEST)
SYSDEVMOD_UNDER_TEST ?= $(SYSDEV_MOD)
TEST_AT_SERVICE_systemdevsmod := Service_Reset

# PipeFS, which also needs only FileSwitch. KernelResources carries its
# Messages and the global file holding "BadFSOp", "Escape" and "IsAFil";
# 005-Messages carries the same tokens and text.
TEST_CHAIN_pipefsmod := 002-FileSwitch 003-ResourceFS \
                        004-TerritoryManager \
                        005-Messages 006-MessageTrans 007-UK
TEST_MODS_pipefsmod = $(MOD_KRES_MOD) $(PIPEMOD_UNDER_TEST)
PIPEMOD_UNDER_TEST ?= $(PIPE_MOD)
TEST_AT_SERVICE_pipefsmod := Service_Reset

# FSLock needs a hard disc. It protects drives 4 to 7 of one filing system,
# found by canonicalising `<FSName>::4.$`, and without a disc none of its
# interception can be reached. So FileCore and the reference's ADFS are in
# both ROMs, with the two IDE images Sources/FileSys/FileCore/test/filecore lays out: `Laid4` in
# drive 4 and `Laid5` in drive 5. ADFS is the reference's so that FSLock is
# the only difference between the ROMs.
#
# $(MOD_KRES_MOD) supplies `Resources.FSLock.Messages`: the eleven errors,
# three status lines and the three paths the lock leaves writable.
# 005-Messages carries the same tokens.
FSLOCKMOD_UNDER_TEST ?= $(FSLK_MOD)
TEST_CHAIN_fslockmod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                        005-Messages 006-MessageTrans 007-UK 030-FileCore \
                        031-ADFS
TEST_MODS_fslockmod = $(MOD_KRES_MOD) $(FSLOCKMOD_UNDER_TEST)
TEST_AT_SERVICE_fslockmod := Service_Reset
# The four commands print, and so does `*Help`, so all of it comes off WrchV.
TEST_MOD_EXTRA_fslockmod := $(BUILD)/tests/lib/mod/vectorclaim.o

# ROMFonts, against the reference's own.
#
# `romfontschecks` compares the packed block on the host; this checks what
# ResourceFS and FileSwitch make of it: that every entry is reachable under
# its registered name, what a directory read answers for type, length and
# attributes, whether a leafname containing `/` opens, and the two
# variables. The test walks the tree, so an extra or missing file shows in
# the report.
#
# FileSwitch and ResourceFS are what the module stands on; 004 to 007 give
# both ROMs the same error text.
ROMFONTSMOD_UNDER_TEST ?= $(ROMFONTS_MOD)
TEST_CHAIN_romfontsmod := 002-FileSwitch 003-ResourceFS \
                          004-TerritoryManager \
                          005-Messages 006-MessageTrans 007-UK
TEST_MODS_romfontsmod = $(ROMFONTSMOD_UNDER_TEST)
TEST_AT_SERVICE_romfontsmod := Service_Reset
# Forty-two files read a chunk at a time, each one a telemetry write.
TEST_WAIT_romfontsmod := 600

# CDFSDriver, against the reference's own. The same stand-in soft driver is
# linked into the test module in both ROMs, since there is no CD driver in
# this tree yet (CDFSSoftATAPI is a separate module). See tests/lib/standcd.h.
#
# 005-Messages carries `Resources.CDFSDriver.CDFSDriver.Messages`, where both
# modules look their errors up.
CDDMOD_UNDER_TEST ?= $(CDD_MOD)
TEST_CHAIN_cdfsdrivermod := 002-FileSwitch 003-ResourceFS \
                            004-TerritoryManager \
                            005-Messages 006-MessageTrans 007-UK
TEST_MODS_cdfsdrivermod = $(CDDMOD_UNDER_TEST)
TEST_AT_SERVICE_cdfsdrivermod := Service_Reset
TEST_MOD_EXTRA_cdfsdrivermod := $(BUILD)/tests/lib/mod/standcdentry.o \
                                $(BUILD)/tests/lib/mod/standcd.o

# CDFSSoftATAPI, against the reference's own. RPCEMU_CDROM puts an ISO
# behind the second IDE unit and the emulator answers it as a packet device.
#
# FileCore and CDFSDriver are the reference's; ADFS is ours in both ROMs. The
# reference's ADFS hands CDFSSoftATAPI a drive it cannot reach ("No CD-ROM
# drive present"); the control run is described in the header of
# Sources/FileSys/CDFS/CDFSATAPI. The driver reaches the bus through ADFS_ATAPIOp and
# is reached through the CD_ chunk, so both must be constant.
CDATAPIMOD_UNDER_TEST ?= $(ATAPI_MOD)
TEST_CHAIN_cdatapimod := 002-FileSwitch 003-ResourceFS \
                         004-TerritoryManager \
                         005-Messages 006-MessageTrans 007-UK 030-FileCore \
                         116-CDFSDriver
TEST_MODS_cdatapimod = $(ADFS_MOD) $(CDATAPIMOD_UNDER_TEST)
TEST_AT_SERVICE_cdatapimod := Service_Reset

# CDFS, on ADFS and both CD drivers.
#
# ADFS is ours in both ROMs, for the reason given for CDFSSoftATAPI above.
# The two CD drivers are the reference's in both. 030-FileCore is there
# because ADFS is an incarnation of it; 005 and 006 give both modules the
# reference's `Resources.CDFS.Messages`.
# CDFSSoftATAPI must initialise after ADFS: it looks for a packet device via
# ADFS_IDEDeviceInfo at initialisation. So it is in TEST_MODS, which follow
# the chain.
CDFSMOD_UNDER_TEST ?= $(CDFS_MOD)
TEST_CHAIN_cdfsmod := 002-FileSwitch 003-ResourceFS \
                      004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK 030-FileCore \
                      116-CDFSDriver
TEST_MODS_cdfsmod = $(ADFS_MOD) \
                    $(LEGACY)/romparts/117-CDFSSoftATAPI.mod \
                    $(CDFSMOD_UNDER_TEST)
TEST_AT_SERVICE_cdfsmod := Service_Reset
# The commands write through OS_WriteC, so the report catches them off WrchV.
TEST_MOD_EXTRA_cdfsmod := $(BUILD)/tests/lib/mod/vectorclaim.o

# DOSFS. No TEST_CHAIN: both ROMs are the whole reference ROM, because the
# reference's DOSFS is C that calls SharedCLibrary by plain BL instructions
# and only works at the distance the ROM build gave it. See
# `reference-chain-oracle` below.
#
# Both ROMs read `Resources.DOSFS.Messages` from the reference's Messages
# module.
TEST_AT_SERVICE_dosfsmod := Service_Reset
# *DOSMap prints its listing and Service_DisplayFormatHelp prints the nine
# formats, so both come off WrchV.
TEST_MOD_EXTRA_dosfsmod := $(BUILD)/tests/lib/mod/vectorclaim.o
# A SWI of the test's own: DOSFS_DiscFormat hands the format block to a SWI
# the caller names. tests/lib/module.S says what its three offsets answer.
TEST_SWIS_dosfsmod := 0x88200
TEST_SWI_CALLBACK_dosfsmod := 1

# The Resolver's test runs at Service_Reset: a lookup needs MessageTrans, the
# Internet module and the boot's Inet$ variables in place.
TEST_AT_SERVICE_resolvermod := Service_Reset

# DHCP's runs there too: two of its cases run a command, which must not run
# before the module that answers it is on the chain.
TEST_AT_SERVICE_dhcpmod := Service_Reset
# Both commands print, so the characters come off WrchV.
TEST_MOD_EXTRA_dhcpmod := $(BUILD)/tests/lib/mod/vectorclaim.o

# Net's runs there too: it starts with `*RMReInit Net`, which cannot run
# while the chain is still being built.
TEST_AT_SERVICE_netmod := Service_Reset
# All four commands print, and so does Econet_PrintBanner, so the characters
# come off WrchV.
TEST_MOD_EXTRA_netmod := $(BUILD)/tests/lib/mod/vectorclaim.o

# The Display Manager's command prints through WrchV and its refusal is a
# MessageTrans lookup, so the test waits until the chain is built.
TEST_AT_SERVICE_displaymanagermod := Service_Reset
TEST_TITLE_displaymanagermod := DispManTest
TEST_CHAIN_displaymanagermod := 002-FileSwitch 003-ResourceFS \
                                004-TerritoryManager \
                                005-Messages 006-MessageTrans 007-UK
# Named as a path because Sources/Desktop/DisplayManager/module.mk is
# included at the end of this file, so `$(DM_MOD)` is empty here.
TEST_MODS_displaymanagermod = $(MOD_KRES_MOD) $(DMMOD_UNDER_TEST)
DMMOD_UNDER_TEST ?= $(BUILD)/modules/DisplayManager.mod
# The command's output is captured off WrchV.
TEST_MOD_EXTRA_displaymanagermod := $(BUILD)/tests/lib/mod/vectorclaim.o

TEST_CHAIN_scrblankmod := 002-FileSwitch 003-ResourceFS \
                          004-TerritoryManager \
                          005-Messages 006-MessageTrans 007-UK
TEST_MODS_scrblankmod = $(MOD_KRES_MOD) $(MOD_VIDC20_MOD) \
                        $(SCRBLANKMOD_UNDER_TEST)
SCRBLANKMOD_UNDER_TEST ?= $(BLNK_MOD)
# The command's output comes off WrchV, the service calls from a module this
# test inserts, and the callback that carries them needs user mode.
TEST_MOD_EXTRA_scrblankmod := $(BUILD)/tests/lib/mod/vectorclaim.o \
                              $(BUILD)/tests/lib/mod/servicewatch.o \
                              $(BUILD)/tests/lib/mod/usrmode.o

TEST_MODS_vdu = $(MOD_VIDC20_MOD) \
                $(LEGACY)/romparts/002-FileSwitch.mod \
                $(LEGACY)/romparts/003-ResourceFS.mod \
                $(LEGACY)/romparts/004-TerritoryManager.mod \
                $(LEGACY)/romparts/005-Messages.mod \
                $(LEGACY)/romparts/006-MessageTrans.mod \
                $(LEGACY)/romparts/007-UK.mod \
                $(LEGACY)/romparts/021-ColourTrans.mod \
                $(LEGACY)/romparts/030-FileCore.mod \
                $(LEGACY)/romparts/032-RamFS.mod \
                $(LEGACY)/romparts/052-PipeFS.mod

# SWI naming asks every module in the chain, so one module besides the test
# is needed: the last module in a ROM chain is preceded by a zero rather than
# its size, and the name lookup skips a module whose size is implausible.
TEST_CHAIN_swiname := 002-FileSwitch

# Three of the five kernel-preset variables are the clock: reading one is
# Territory_ConvertDateAndTime, writing one is OS_Word 15 through
# Territory_ConvertTimeStringToOrdinals. Without a territory all three answer
# "SWI not known".
TEST_CHAIN_sysvars := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK

# The heap under interrupt needs nothing but a kernel: OS_Heap, OS_Claim and
# the 100Hz tick are all the kernel's own.
TEST_CHAIN_heapirq :=
TEST_CHAIN_cmosirq :=
TEST_CHAIN_swiirq :=

# The module handler test needs a chain: modules before and after the test,
# one with more than one incarnation. The same set as the convert test.
# 001-Podule is here so that Podule_EnumerateChunksWithInfo exists and the
# emulator's card is walked; without it every slot is "no such podule".
TEST_CHAIN_modhand := 001-Podule 002-FileSwitch 003-ResourceFS \
                      004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK
TEST_CHAIN_oscli := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                    005-Messages 006-MessageTrans 007-UK 052-PipeFS

# A display driver, because *Dump takes its line width from
# VduExt_WindowWidth. With no driver that width is zero and the derived line
# length is most of the address space.
TEST_MODS_oscli = $(MOD_VIDC20_MOD)

# The OS_Byte sweep asks the VDU six questions, one of which reads the
# screen, so the display driver goes in.
TEST_MODS_osbyte = $(MOD_VIDC20_MOD)

# OS_Word's clock reasons go through the territory
# (Territory_ConvertDateAndTime, Territory_ConvertTimeStringToOrdinals), which
# needs its message file, ResourceFS and a filing system. Seven reasons are
# the VDU's (palette, pixel read, character definitions, graphics cursors,
# pointer, screen start), so the display driver goes in too.
TEST_CHAIN_osword := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                     005-Messages 006-MessageTrans 007-UK
TEST_MODS_osword = $(MOD_VIDC20_MOD)

# Tests that type need a keyboard: PS2Driver registers one and
# InternationalKeyboard installs the key handler the kernel lacks. There is
# no filing system on purpose: the command walk behaves differently on a
# machine with none.
TEST_MODS_super = $(MOD_VIDC20_MOD)
TEST_CHAIN_super := 015-PS2Driver 042-InternationalKeyboard
TEST_MODS_confirm = $(MOD_VIDC20_MOD)
TEST_CHAIN_confirm := 015-PS2Driver 042-InternationalKeyboard
TEST_MODS_readline = $(MOD_VIDC20_MOD)
TEST_CHAIN_readline := 015-PS2Driver 042-InternationalKeyboard

# The RAM disc test needs FileCore, RamFS, FileSwitch and the message chain
# for FileCore's errors.
# The spool file and printer stream are both pipes: PipeFS needs no disc or
# hardware, and a pipe can be read back. The territory chain is for PipeFS's
# errors; the display driver for what the VDU did with the character.
TEST_CHAIN_wrch := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                   005-Messages 006-MessageTrans 007-UK 052-PipeFS
TEST_MODS_wrch = $(MOD_VIDC20_MOD)

# The display driver, because the mouse position is relative to the graphics
# origin and bounded by the screen. The buffer manager, because the nine-byte
# packet a button change makes is a block insertion, which only that module's
# extension to the vector accepts.
TEST_CHAIN_mouse := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                    005-Messages 006-MessageTrans 007-UK 020-BufferManager
TEST_MODS_mouse = $(MOD_VIDC20_MOD)

# The keyboard module, because without one the kernel has no table to turn a
# key number into a character. It needs the territory chain and
# International beside it.
TEST_CHAIN_key := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                  005-Messages 006-MessageTrans 007-UK 020-BufferManager \
                  041-International 042-InternationalKeyboard 052-PipeFS
TEST_MODS_key = $(MOD_VIDC20_MOD)

# The territory chain, because OS_Word 15 asks it to take a five-byte time
# apart.
TEST_CHAIN_realtime := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                       005-Messages 006-MessageTrans 007-UK
TEST_MODS_realtime = $(MOD_VIDC20_MOD)

# The message chain, so that IIC refusals are looked up through MessageTrans
# rather than being bare numbers.
TEST_CHAIN_cmos := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                   005-Messages 006-MessageTrans 007-UK
TEST_MODS_cmos = $(MOD_VIDC20_MOD)

TEST_CHAIN_eeprom := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                     005-Messages 006-MessageTrans 007-UK
TEST_MODS_eeprom = $(MOD_VIDC20_MOD)

# The bus driven by hand, and the same cases under each of the emulator's
# injected faults. One set of cases: the five fault tests include Sources/HWSupport/IIC/test/iic's.
IIC_TESTS := iic iicnack1 iicnack2 iicnack3 iicarb iicstretch iichl
$(foreach t,$(IIC_TESTS),$(eval TEST_CHAIN_$(t) := 002-FileSwitch 003-ResourceFS 004-TerritoryManager 005-Messages 006-MessageTrans 007-UK))
$(foreach t,$(IIC_TESTS),$(eval TEST_MODS_$(t) = $$(MOD_VIDC20_MOD)))

TEST_CHAIN_eeprom2k := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                       005-Messages 006-MessageTrans 007-UK
TEST_MODS_eeprom2k = $(MOD_VIDC20_MOD)

TEST_CHAIN_ramdisc := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK 030-FileCore \
                      032-RamFS

# The same chain with the reference RamFS replaced by ours, so a difference
# in the report is a difference in RamFS. `ramfsmodtest` runs the same cases
# with the reference module.
TEST_CHAIN_ramfs := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                    005-Messages 006-MessageTrans 007-UK 030-FileCore
TEST_MODS_ramfs = $(RAMFS_MOD)

# Disc images written by the reference kernel, FileCore and ADFS, for the
# FileCore port to read. Not in TESTS: there is nothing to compare against,
# and the transcript is what Sources/FileSys/FileCore/test/filecore checks. The target is
# `filecoreimages`, below the FileCore module's checks.
TEST_TITLE_fcimages := FCImages
TEST_AT_SERVICE_fcimages := Service_Reset
TEST_CHAIN_fcimages := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                       005-Messages 006-MessageTrans 007-UK 030-FileCore \
                       031-ADFS
TEST_EXTRA_CFLAGS_fcimages := -ISources/FileSys/FileCore/c -ISources/FileSys/RAMFS/c
TEST_MOD_EXTRA_fcimages := $(BUILD)/tests/fcimages/mod/discrec.o $(CLIB)

# The whole reference machine, HAL included, so nothing of ours is under
# what is written. An explicit rule, which make prefers to the pattern.
$(BUILD)/tests/fcimages-ref.rom: $(HAL_IOMD_BIN) $(BUILD)/tests/fcimages.mod \
                                 tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-hal --reference-kernel \
	  $(addprefix --module ,$(call test_chain,fcimages)) \
	  --module $(BUILD)/tests/fcimages.mod

# The hard discs' shape comes from RamFS's arithmetic, built as a module
# object like the cases.
$(BUILD)/tests/fcimages/mod/discrec.o: Sources/FileSys/RAMFS/c/discrec.c \
                                        Sources/FileSys/RAMFS/c/ramfs.h \
                                        Sources/FileSys/FileCore/c/fcdisc.h
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(MOD_PIFLAGS) $(TEST_CFLAGS) \
	  -ISources/FileSys/FileCore/c -ISources/FileSys/RAMFS/c -c $< -o $@

# The display driven through the HAL: no VIDC20Video in the chain, and the
# machine set to let the HAL drive, so the kernel's VduGrafHAL is the display
# driver in both ROMs. The territory chain is there because a mode change
# looks a message up.
TEST_CHAIN_halvideo := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                       005-Messages 006-MessageTrans 007-UK
TEST_HALVIDEO_halvideo := 1
TEST_VIDCTRACE_halvideo := 1

test_chain = $(foreach m,$(TEST_CHAIN_$(1)),$(LEGACY)/romparts/$(m).mod) \
             $(TEST_MODS_$(1))

# Every recipe below pairs these with $(ARCH_iomd): machine suites are built
# for the emulated machine whatever the matrix row. Sources/Kernel/hdr is on the path
# because the kernel's layout headers live there, not in the build tree.
TEST_CFLAGS := -I$(BUILD)/Sources/Kernel -I$(BUILD)/tests -Iinclude \
               -I$(KERNEL_DIR)/hdr -DCONFIG_IOMD -DROM_SIZE=$(ROM_SIZE) \
               -Iinclude/freestanding -I$(TEST_LIB) -Os

# The shared half of every test: the two report backends, the formatting and
# the run-time SWI call. Built twice: an absolute file loads at a fixed
# address, a module must be position independent (MOD_PIFLAGS, in the Modules
# section below).
# What each test needs beyond the shared set.
TEST_MOD_EXTRA_heapirq := $(BUILD)/tests/lib/mod/vector.o
TEST_MOD_EXTRA_sprxmod := $(BUILD)/tests/sprxmod/callbacks.o

$(BUILD)/tests/sprxmod/callbacks.S: Sources/Video/Render/SprExtend/test/sprxmod/s/boundaries \
                                 Sources/Video/Render/SprExtend/test/sprxmod/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/SprExtend/test/sprxmod --asm $@

$(BUILD)/tests/sprxmod/callbacks.o: $(BUILD)/tests/sprxmod/callbacks.S
	$(CC) --target=arm-none-eabi $(ARCH_iomd) -c $< -o $@
TEST_MOD_EXTRA_cmosirq := $(BUILD)/tests/lib/mod/vector.o
TEST_MOD_EXTRA_swiirq := $(BUILD)/tests/lib/mod/vector.o
TEST_MOD_EXTRA_ticker := $(BUILD)/tests/lib/mod/ticker.o
TEST_MOD_EXTRA_chocolate := $(BUILD)/tests/lib/mod/ticker.o
TEST_MOD_EXTRA_dynarea := $(BUILD)/tests/lib/mod/dahandler.o \
                          $(BUILD)/tests/lib/mod/upcall.o \
                          $(BUILD)/tests/lib/mod/servicewatch.o
TEST_MOD_EXTRA_environ := $(BUILD)/tests/lib/mod/upcall.o \
                          $(BUILD)/tests/lib/mod/envwatch.o
TEST_MOD_EXTRA_exception := $(BUILD)/tests/lib/mod/faults.o
# FPEmulator's: an exception with its trap enabled is raised with
# OS_GenerateError, which would leave the machine at a prompt; the error
# handler in faults.o brings it back. See Sources/Programmer/FPEmulator/test/fpemod/cases.c.
TEST_MOD_EXTRA_fpemod := $(BUILD)/tests/lib/mod/faults.o
TEST_MOD_EXTRA_exceptalign := $(BUILD)/tests/lib/mod/faults.o
TEST_MOD_EXTRA_super := $(BUILD)/tests/lib/mod/telemetryvec.o
TEST_MOD_EXTRA_confirm := $(BUILD)/tests/lib/mod/telemetryvec.o
TEST_MOD_EXTRA_readline := $(BUILD)/tests/lib/mod/telemetryvec.o
TEST_MOD_EXTRA_swi := $(BUILD)/tests/lib/mod/usrmode.o
TEST_MOD_EXTRA_pmp := $(BUILD)/tests/lib/mod/dahandler.o
# A physical memory pool needs a handler, and only a pool's pages can each
# carry their own access privileges, which is the arm of OS_Memory 24 tested
# here.
TEST_MOD_EXTRA_meminfo := $(BUILD)/tests/lib/mod/dahandler.o \
                          $(BUILD)/tests/lib/mod/dmaprep.o
TEST_MOD_EXTRA_modhand := $(BUILD)/tests/lib/mod/modsubject.o
# OS_Word 0 reads a line, and so does the reason one past the end of its
# table. There is no keyboard, so the test supplies the line.
TEST_MOD_EXTRA_osword := $(BUILD)/tests/lib/mod/readlinevec.o

# The two events a buffer raises are the only sign an insertion overflowed.
# tests/lib/eventvec.S claims EventV and records them.
TEST_MOD_EXTRA_buffer := $(BUILD)/tests/lib/mod/eventvec.o
TEST_MOD_EXTRA_tick := $(BUILD)/tests/lib/mod/eventvec.o

# The vector standing in for the VDU is claimed rather than passed on, so the
# VDU does not see the characters.
TEST_MOD_EXTRA_wrch := $(BUILD)/tests/lib/mod/vectorclaim.o
TEST_MOD_EXTRA_fcfs := $(BUILD)/tests/lib/mod/vectorclaim.o \
                       $(BUILD)/tests/fileswitch/unbuffered.o

$(BUILD)/tests/fileswitch/unbuffered.o: Sources/FileSys/FileSwitch/test/fileswitch/unbuffered.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) -c $< -o $@
TEST_MOD_EXTRA_iicmod := $(BUILD)/tests/lib/mod/vectorclaim.o
TEST_MOD_EXTRA_podulemod := $(BUILD)/tests/lib/mod/vectorclaim.o
TEST_MOD_EXTRA_soundchannelsmod := $(BUILD)/tests/lib/mod/vectorclaim.o
TEST_MOD_EXTRA_territorymod := $(BUILD)/tests/lib/mod/vectorclaim.o
TEST_MOD_EXTRA_bootnetmod := $(BUILD)/tests/lib/mod/vectorclaim.o
TEST_MOD_EXTRA_bootcmdsmod := $(BUILD)/tests/lib/mod/vectorclaim.o
TEST_MOD_EXTRA_spriteutilsmod := $(BUILD)/tests/lib/mod/vectorclaim.o

TEST_MOD_EXTRA_oscli := $(BUILD)/tests/lib/mod/vectorclaim.o \
                        $(BUILD)/tests/lib/mod/bytevec.o \
                        $(BUILD)/tests/lib/mod/readlinevec.o \
                        $(BUILD)/tests/lib/mod/modsubject.o
TEST_MOD_EXTRA_halvideo := $(BUILD)/tests/lib/mod/graphicsvvec.o
# The device registry issues service calls when a device comes or goes; the
# watcher records them.
TEST_MOD_EXTRA_hardware := $(BUILD)/tests/lib/mod/servicewatch.o
TEST_MOD_EXTRA_vdu := $(BUILD)/tests/lib/mod/eventvec.o \
                      $(BUILD)/tests/lib/mod/plotvec.o \
                      $(BUILD)/tests/lib/mod/vectorclaim.o \
                      $(BUILD)/tests/lib/mod/vdu23vec.o \
                      $(BUILD)/tests/lib/mod/spritevec.o \
                      $(BUILD)/tests/lib/mod/graphicsvvec.o \
                      $(BUILD)/tests/lib/mod/servicewatch.o

TEST_ABS_LIB := $(addprefix $(BUILD)/tests/lib/abs/,format.o report_hostfs.o swi.o)
TEST_MOD_LIB := $(addprefix $(BUILD)/tests/lib/mod/,format.o report_telemetry.o \
                                                    swi.o check_report.o)

$(BUILD)/tests/lib/abs/%.o: $(TEST_LIB)/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(TEST_CFLAGS) -c $< -o $@

$(BUILD)/tests/lib/mod/%.o: $(TEST_LIB)/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(MOD_PIFLAGS) $(TEST_CFLAGS) -c $< -o $@

$(BUILD)/tests/lib/%/swi.o: $(TEST_LIB)/swi.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(TEST_CFLAGS) -c $< -o $@

$(BUILD)/tests/lib/mod/%.o: $(TEST_LIB)/%.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(MOD_PIFLAGS) $(TEST_CFLAGS) -c $< -o $@

# The cases, which are the same file whichever way the test is delivered.
#
# Sources/Kernel/test/cpu and its two ARMv3 twins are the same cases, differing only in the
# processor they expect. That is passed in here, as the kernel's name for the
# part and the architecture, so a run on the wrong machine fails.
test_cpu_flags = $(if $(TEST_CPU_TYPE_$(1)),-DTEST_CPU_TYPE=$(TEST_CPU_TYPE_$(1)) \
                        -DTEST_CPU_ARCH=$(TEST_CPU_ARCH_$(1)))

$(BUILD)/tests/%/abs/cases.o: $$(call testdir,$$*)/cases.c $(KERNEL_GLOBALS) \
                              $(BUILD)/tests/kernel-ws.h
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(TEST_EXTRA_CFLAGS_$*) $(TEST_CFLAGS) \
	  $(call test_cpu_flags,$*) -c $< -o $@

$(BUILD)/tests/%/mod/cases.o: $$(call testdir,$$*)/cases.c $(KERNEL_GLOBALS) \
                              $(BUILD)/tests/kernel-ws.h \
                              $(KERNEL_DIR)/hdr/vduws.h
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(MOD_PIFLAGS) $(TEST_EXTRA_CFLAGS_$*) \
	  $(TEST_CFLAGS) $(call test_cpu_flags,$*) -c $< -o $@

$(BUILD)/tests/%/abs/start.o: $$(call testdir,$$*)/start.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(TEST_CFLAGS) -c $< -o $@

# The module header and init veneer are the same for every test, but the header
# carries the test's name and says when the test runs, so this is assembled
# once per test.
$(BUILD)/tests/%/mod/module.o: $(TEST_LIB)/module.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(TEST_CFLAGS) \
	  -DMODULE_TITLE='"$(TEST_TITLE_$*)"' \
	  $(if $(TEST_AT_SERVICE_$*),-DTEST_AT_SERVICE=$(TEST_AT_SERVICE_$*)) \
	  $(if $(TEST_SWIS_$*),-DTEST_SWIS=$(TEST_SWIS_$*)) \
	  $(if $(TEST_SWIS_NO_TABLE_$*),-DTEST_SWIS_NO_TABLE=1) \
	  $(if $(TEST_SWI_CALLBACK_$*),-DTEST_SWI_CALLBACK=1) \
	  $(if $(TEST_COMMANDS_$*),-DTEST_COMMANDS=1) \
	  $(if $(TEST_CONFIRM_$*),-DTEST_CONFIRM=1) \
	  $(if $(TEST_READLINE_$*),-DTEST_READLINE=1) \
	  -c $< -o $@

$(BUILD)/tests/%-mod.elf: $(BUILD)/tests/%/mod/module.o \
                          $(BUILD)/tests/%/mod/cases.o \
                          $$(TEST_MOD_EXTRA_$$*) \
                          $(TEST_MOD_LIB) $(TEST_LIB)/module.ld
	$(LD) $(LDFLAGS) --no-check-sections -T $(TEST_LIB)/module.ld -o $@ \
	  $(BUILD)/tests/$*/mod/module.o $(BUILD)/tests/$*/mod/cases.o \
	  $(TEST_MOD_EXTRA_$*) $(TEST_MOD_LIB)

$(BUILD)/tests/%.mod: $(BUILD)/tests/%-mod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes)"

$(BUILD)/tests/%-abs.elf: $(BUILD)/tests/%/abs/start.o \
                          $(BUILD)/tests/%/abs/cases.o \
                          $$(TEST_ABS_EXTRA_$$*) \
                          $(TEST_ABS_LIB) $$(call testdir,$$*)/test.ld
	$(LD) $(LDFLAGS) -T $(call testdir,$*)/test.ld -o $@ \
	  $(BUILD)/tests/$*/abs/start.o $(BUILD)/tests/$*/abs/cases.o \
	  $(TEST_ABS_LIB) $(TEST_ABS_EXTRA_$*)

# The one test of a library of ours rather than the OS. It links Sources/Lib/Desktop
# and Sources/Lib/CLib, so needs their headers and archives, and the named-check
# reporter. See tests/desktoplive/cases.c.
TEST_EXTRA_CFLAGS_desktoplive := -ISources/Lib/Desktop/include -ISources/Lib/CLib/include
TEST_ABS_EXTRA_desktoplive := $(BUILD)/tests/lib/abs/check_report.o \
                              $(BUILD)/lib/libdesktop.a $(BUILD)/lib/libclib.a

$(BUILD)/tests/%-file: $(BUILD)/tests/%-abs.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes)"

# The two ROMs a test module runs in: this HAL, a kernel and the test. One
# has the kernel built here, the other the reference ROM's; that is the only
# difference between them.
$(BUILD)/tests/%-ours.rom: $(HAL_IOMD_BIN) $(KERNEL_BIN) $(BUILD)/tests/%.mod \
                          $$(TEST_MODS_$$*)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  $(addprefix --module ,$(call test_chain,$*)) \
	  --module $(BUILD)/tests/$*.mod

$(BUILD)/tests/%-ref.rom: $(HAL_IOMD_BIN) $(BUILD)/tests/%.mod \
                          $$(TEST_MODS_$$*)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  $(addprefix --module ,$(call test_chain,$*)) \
	  --module $(BUILD)/tests/$*.mod

# The module oracle: the reference kernel with the reference's module in it,
# taken from `legacy/romparts`, compared against `%-ref.rom` (the reference
# kernel with our module). The module under test is the only difference.
#
#   $(call module-oracle,<test>,<reference slot>,<ours>,<report>,<differs>)
#
#   <test>            the test directory, and the module image it builds -
#                     `buffersmod` for Sources/HWSupport/Buffers/test/buffersmod.
#   <reference slot>  what it is measured against, named as the file in
#                     `legacy/romparts` is - `020-BufferManager`.
#   <ours>            the NAME of the variable holding our own module, e.g.
#                     `BUFSMOD_UNDER_TEST`, and not its value. It has to be a
#                     name because the template edits `TEST_MODS_<test>`
#                     rather than writing a second list: see below.
#   <report>          what the report calls itself, where that is not the
#                     test's own name. Sources/FileSys/RAMFS/test/ramfs reports as `ramfsmod`,
#                     because `ramfstest` is already the kernel comparison and
#                     this is the module one. The run target is
#                     `<report>test`.
#   <differs>         a file of case tags whose two reports are expected to
#                     disagree, where a port corrects something the reference
#                     gets wrong. Usually absent. See reporttest.sh.
#
# `TEST_WAIT_<test>` is how long a run may take (default two minutes). A run
# that times out has not finished, and a truncated report can look like a
# pass; set it only from the measured time of a run that finished.
#
# The reference ROM's module list is `TEST_MODS_<test>` with `<ours>`
# replaced in place by the reference slot, so the two lists differ in exactly
# one entry. The recipe refuses to build if `<ours>` is not in
# `TEST_MODS_<test>`, which would otherwise compare ours against ours.
#
# reporttest.sh already fails on any FAIL line in a report, so no separate
# check is needed here. A `differs` file goes in `tests/<test>/differs` and is
# named in the fifth argument; reporttest.sh ignores a file that does not
# exist.
define module-oracle

$$(BUILD)/tests/$(1)-refmod.rom: $$(HAL_IOMD_BIN) $$(BUILD)/tests/$(1).mod \
                                 $$$$(filter-out $$$$($(3)),$$$$(TEST_MODS_$(1))) \
                                 $$(REF_ROM) tools/build/mkrom.py Makefile
	@$$(if $$(filter $$($(3)),$$(TEST_MODS_$(1))),,$$(error \
	  module-oracle $(1): $(3) is not one of TEST_MODS_$(1), so the \
	  reference ROM would carry no reference module))
	@python3 tools/build/mkrom.py $$(HAL_IOMD_BIN) $$@ --reference $$(REF_ROM) \
	  --hal-size $$(HAL_SIZE) --reference-kernel \
	  $$(addprefix --module ,$$(foreach m,$$(TEST_CHAIN_$(1)),\
	                            $$(LEGACY)/romparts/$$(m).mod)) \
	  $$(addprefix --module ,$$(patsubst $$($(3)),\
	     $$(LEGACY)/romparts/$(2).mod,$$(TEST_MODS_$(1)))) \
	  --module $$(BUILD)/tests/$(1).mod

$(or $(4),$(1))test: $$(BUILD)/tests/$(1)-ref.rom \
                     $$(BUILD)/tests/$(1)-refmod.rom \
                     tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference module:"
	@tools/build/rom-test.sh \
	  $$(abspath $$(BUILD))/tests/$(1)-refmod.rom \
	  $$(abspath $$(BUILD))/tests/$(1)-refmod.txt $$(call wait_for,$(1))
	@echo "this module:"
	@tools/build/rom-test.sh \
	  $$(abspath $$(BUILD))/tests/$(1)-ref.rom \
	  $$(abspath $$(BUILD))/tests/$(1)-ref.txt $$(call wait_for,$(1))
	@echo
	@tools/build/reporttest.sh $(or $(4),$(1)) \
	  $$(BUILD)/tests/$(1)-refmod.txt \
	  $$(BUILD)/tests/$(1)-ref.txt \
	  $$(BUILD)/tests/$(or $(4),$(1)).diff $(5)
.PHONY: $(or $(4),$(1))test

endef

# RamFS: a third ROM. The `-ours`/`-ref` pair compares kernels with our RamFS
# in both; this compares the module with the reference kernel in both. Hence
# the report name, since `ramfstest` is the kernel comparison.
$(eval $(call module-oracle,ramfs,032-RamFS,RAMFS_MOD,ramfsmod))

# Draw, against the reference's own Draw. Sources/Video/Render/Draw/test/draw runs the C against
# stand-ins with no frame buffer; this draws in a ROM with a display driver,
# and the report is a screen checksum per case, with a picture of the corner
# the paths are in.
$(eval $(call module-oracle,drawmod,028-Draw,DRAWMOD_UNDER_TEST))

# SpriteExtend, against the reference's own. Sources/Video/Render/SprExtend/test/spriteextend runs the C
# and its generated blitters on an interpreter, with no frame buffer. This
# plots sprites in a ROM with a display driver; the report is a screen
# checksum per case with a picture of the corner the sprite landed in. It
# targets the bit block move at one, two, four and eight bits, from every
# pixel offset within a word.
$(eval $(call module-oracle,sprxmod,066-SpriteExtend,SPRXMOD_UNDER_TEST,,\
  Sources/Video/Render/SprExtend/test/sprxmod/differs))

# FPEmulator, against the reference's own. The trap, decode, register
# transfers, contexts and ten SWIs are entered by the processor, so can only
# be tested on the machine. Every case is a floating point instruction
# executed for real and the bits that came back. One line is expected to
# differ: Sources/Programmer/FPEmulator/test/fpemod/differs says which and why.
$(eval $(call module-oracle,fpemod,037-FPEmulator,FPEMOD_UNDER_TEST,,\
  Sources/Programmer/FPEmulator/test/fpemod/differs))

# IIC, against the reference's own IIC.
$(eval $(call module-oracle,iicmod,040-IIC,IICMOD_UNDER_TEST))

# Podule, against the reference's own, over the emulator's card.
$(eval $(call module-oracle,podulemod,001-Podule,PODULEMOD_UNDER_TEST))
$(eval $(call module-oracle,soundchannelsmod,064-SoundChannels,SOUNDCHANNELSMOD_UNDER_TEST,,Sources/HWSupport/Sound/Sound1/test/soundchannelsmod/differs))

# ROMFonts, against the reference's own. One declared difference, which is
# the datestamp on every entry: see Sources/Video/Render/Fonts/ROMFonts/test/romfontsmod/differs.
$(eval $(call module-oracle,romfontsmod,055-ROMFonts,ROMFONTSMOD_UNDER_TEST,,\
  Sources/Video/Render/Fonts/ROMFonts/test/romfontsmod/differs))

$(eval $(call module-oracle,msgtransmod,006-MessageTrans,MSGTRANSMOD_UNDER_TEST,,\
  Sources/Internat/MsgTrans/test/msgtransmod/differs-reference))

# TerritoryManager, against the reference's own. `territorychecks` cannot
# reach a CMOS, a clock or a territory.
$(eval $(call module-oracle,territorymod,004-TerritoryManager,TERRMOD_UNDER_TEST))

# The UK territory, against the reference's own 007-UK, with our
# TerritoryManager above it in both ROMs.
$(eval $(call module-oracle,ukmod,007-UK,UKMOD_UNDER_TEST))

# BootNet, against the reference's own BootNet.
$(eval $(call module-oracle,bootnetmod,088-BootNet,BOOTNETMOD_UNDER_TEST,,\
  Sources/Networking/Econet/BootNet/test/bootnetmod/differs))

# SpriteUtils, against the reference's own SpriteUtils.
$(eval $(call module-oracle,spriteutilsmod,067-SpriteUtils,SPRITEUTILSMOD_UNDER_TEST))

# SerialMouse, against the reference's own SerialMouse.
$(eval $(call module-oracle,sermousemod,061-SerialMouse,SERMOUSEMOD_UNDER_TEST))

# SuperSample, against the reference's own SuperSample. The module is
# arithmetic, so the two reports must be identical.
$(eval $(call module-oracle,supersamplemod,069-SuperSample,SUPERSAMPLEMOD_UNDER_TEST))

# Hourglass, against the reference's own Hourglass.
$(eval $(call module-oracle,hourglassmod,039-Hourglass,HOURGLASSMOD_UNDER_TEST))

# InverseTable, against the reference's own, with ColourTrans, Hourglass,
# this tree's resources and display driver in front of it in both ROMs. The
# chain and extra modules are declared with the test, above.
$(eval $(call module-oracle,itablemod,043-InverseTable,ITABLEMOD_UNDER_TEST))

# BufferManager, against the reference's own, with the same resources under
# both.
$(eval $(call module-oracle,buffersmod,020-BufferManager,BUFSMOD_UNDER_TEST))

# MbufManager. The usual chain: this module stands only on the kernel and
# MessageTrans.
$(eval $(call module-oracle,mbufmod,084-MbufManager,MBUFMOD_UNDER_TEST,,\
  Sources/Networking/MManager/test/mbufmod/differs))

# ScreenBlanker, against the reference's own, with the same resources and the
# same display driver under both.
$(eval $(call module-oracle,scrblankmod,057-ScreenBlanker,SCRBLANKMOD_UNDER_TEST))

# DeviceFS, against the reference's own: registration and stream behaviour
# through a driver shared by both ROMs.
$(eval $(call module-oracle,devicefsmod,023-DeviceFS,DEVFSMOD_UNDER_TEST))

# SerialDeviceSupport, against the reference's own. It has no SWIs or
# commands; its interface is OS_SerialOp and two OS_Bytes arriving on
# vectors. `make serialsptchecks` covers the arithmetic.
$(eval $(call module-oracle,serialsptmod,060-SerialDeviceSupport,SERSPTMOD_UNDER_TEST,,Sources/HWSupport/SerialSpt/test/serialsptmod/differs))

# SystemDevices, against the reference's own. It has no SWIs or commands, so
# it is reached only through FileSwitch.
$(eval $(call module-oracle,systemdevsmod,070-SystemDevices,SYSDEVMOD_UNDER_TEST,,Sources/Utilities/SystemDevs/test/systemdevsmod/differs))

# PipeFS, against the reference's own.
$(eval $(call module-oracle,pipefsmod,052-PipeFS,PIPEMOD_UNDER_TEST))

# FSLock, against the reference's own. Not `module-oracle`, because each boot
# needs two freshly laid IDE images (as in `adfstest`): the test writes to
# the disc, so a reused image would make the reports differ.
$(BUILD)/tests/fslockmod-refmod.rom: $(HAL_IOMD_BIN) \
                                     $(BUILD)/tests/fslockmod.mod \
                                     $(MOD_KRES_MOD) $(REF_ROM) \
                                     tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  $(addprefix --module ,$(foreach m,$(TEST_CHAIN_fslockmod),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(MOD_KRES_MOD) \
	  --module $(LEGACY)/romparts/035-FSLock.mod \
	  --module $(BUILD)/tests/fslockmod.mod

FSLOCK_TEST_IMAGES := $(BUILD)/tests/fslockmod

# One boot over freshly laid discs.
fslock_run = rm -f $(FSLOCK_TEST_IMAGES)/hd4.hdf $(FSLOCK_TEST_IMAGES)/hd5.hdf; \
             $(BUILD)/tests/filecore-layimages $(FSLOCK_TEST_IMAGES) winnies filled \
               > $(FSLOCK_TEST_IMAGES)/laid.txt \
               || { cat $(FSLOCK_TEST_IMAGES)/laid.txt; exit 1; }; \
             RPCEMU_HD4=$(abspath $(FSLOCK_TEST_IMAGES))/hd4.hdf \
             RPCEMU_HD5=$(abspath $(FSLOCK_TEST_IMAGES))/hd5.hdf \
             tools/build/rom-test.sh $(abspath $(BUILD))/tests/$(1) \
               $(abspath $(BUILD))/tests/$(2) 300

fslockmodtest: $(BUILD)/tests/fslockmod-ref.rom \
               $(BUILD)/tests/fslockmod-refmod.rom \
               $(BUILD)/tests/filecore-layimages \
               tools/build/rom-test.sh tools/build/reporttest.sh
	@mkdir -p $(FSLOCK_TEST_IMAGES)
	@echo "the reference module:"
	@$(call fslock_run,fslockmod-refmod.rom,fslockmod-refmod.txt)
	@echo "this module:"
	@$(call fslock_run,fslockmod-ref.rom,fslockmod-ref.txt)
	@echo
	@tools/build/reporttest.sh fslockmod \
	  $(BUILD)/tests/fslockmod-refmod.txt \
	  $(BUILD)/tests/fslockmod-ref.txt \
	  $(BUILD)/tests/fslockmod.diff Sources/FileSys/FSLock/test/fslockmod/differs
.PHONY: fslockmodtest

# CDFSDriver, against the reference's own 116-CDFSDriver.
$(eval $(call module-oracle,cdfsdrivermod,116-CDFSDriver,CDDMOD_UNDER_TEST,,\
  Sources/FileSys/CDFS/CDFSDriver/test/cdfsdrivermod/differs))

# PS2Driver, against the reference's own 015-PS2Driver, on two machines.
#
# Not `module-oracle`, because two things are compared. The report is what
# the driver told the machine: every KeyV call, the ports' devices, the device
# list, the wheel. The other is what it told the devices: the emulator traces
# every IOMD register write (RPCEMU_IOMDTRACE), and the lines for each port's
# data and control registers and interrupt masks must match port by port.
# They are compared per port because the interleaving of the two ports'
# writes depends on each driver's timing.
#
# The emulator types the keys and moves the mouse from Sources/HWSupport/PS2Driver/test/ps2drvmod/typed
# and Sources/HWSupport/PS2Driver/test/ps2drvmod/mouse, late enough that the test is waiting. The Risc
# PC has one port and a quadrature mouse, so its run reaches the keyboard; the
# A7000's ARM7500 has a second port with a PS/2 mouse (an IntelliMouse
# Explorer once the driver has knocked). The A7000 is ARMv3, and
# RPCEMU_ARCH_V4 lends it the halfword loads every ROM built here uses; see
# Sources/Kernel/test/cpu.
$(BUILD)/tests/ps2drvmod-refmod.rom: $(HAL_IOMD_BIN) \
                                     $(BUILD)/tests/ps2drvmod.mod \
                                     $(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  $(addprefix --module ,$(foreach m,$(TEST_CHAIN_ps2drvmod),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(LEGACY)/romparts/015-PS2Driver.mod \
	  --module $(BUILD)/tests/ps2drvmod.mod

# The registers a PS/2 driver writes, per port: its data and control
# registers and its interrupt mask (IRQ B for the keyboard, which is IOC's
# serial one; IRQ D for the mouse). The rest of the trace, notably the timer,
# is excluded.
PS2DRV_KBD := ^(004|008|028) 
PS2DRV_MSE := ^(078|0a8|0ac) 

# One boot: <rom>, <what to call its report>, <model, or nothing for the
# Risc PC>.
ps2drv_run = RPCEMU_AUTOTYPE=$(abspath Sources/HWSupport/PS2Driver/test/ps2drvmod/typed) \
             RPCEMU_AUTOTYPE_DELAY=15000 \
             RPCEMU_AUTOMOUSE=$(abspath Sources/HWSupport/PS2Driver/test/ps2drvmod/mouse) \
             RPCEMU_AUTOMOUSE_DELAY=22000 \
             RPCEMU_IOMDTRACE=$(abspath $(BUILD))/tests/$(2).iomd \
             $(if $(3),RPCEMU_MODEL=$(3) RPCEMU_ARCH_V4=1) \
             tools/build/rom-test.sh $(abspath $(BUILD))/tests/$(1) \
               $(abspath $(BUILD))/tests/$(2).txt 180 \
             && { grep -E '$(PS2DRV_KBD)' $(BUILD)/tests/$(2).iomd \
                    > $(BUILD)/tests/$(2).kbd || true; } \
             && { grep -E '$(PS2DRV_MSE)' $(BUILD)/tests/$(2).iomd \
                    > $(BUILD)/tests/$(2).mse || true; }

# The two traces of one port, <test> and <kbd or mse>, which must be the
# same. A port with nothing on it writes nothing, so two empty traces match.
ps2drv_port = if diff $(BUILD)/tests/$(1)-refmod.$(2) $(BUILD)/tests/$(1)-ref.$(2) \
                   > $(BUILD)/tests/$(1)-$(2).diff; then \
                echo "$(1): what each module sent the $(2) port is the same" \
                     "($$(wc -l < $(BUILD)/tests/$(1)-ref.$(2)) register writes)"; \
              else \
                echo "$(1): the two modules sent the $(2) port different" \
                     "things - $(BUILD)/tests/$(1)-$(2).diff:"; \
                head -40 $(BUILD)/tests/$(1)-$(2).diff; exit 1; \
              fi
ps2drv_traces = $(call ps2drv_port,$(1),kbd) && $(call ps2drv_port,$(1),mse)

ps2drvmodtest: $(BUILD)/tests/ps2drvmod-ref.rom \
               $(BUILD)/tests/ps2drvmod-refmod.rom \
               tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference module:"
	@$(call ps2drv_run,ps2drvmod-refmod.rom,ps2drvmod-refmod,)
	@echo "this module:"
	@$(call ps2drv_run,ps2drvmod-ref.rom,ps2drvmod-ref,)
	@echo
	@tools/build/reporttest.sh ps2drvmod \
	  $(BUILD)/tests/ps2drvmod-refmod.txt \
	  $(BUILD)/tests/ps2drvmod-ref.txt \
	  $(BUILD)/tests/ps2drvmod.diff Sources/HWSupport/PS2Driver/test/ps2drvmod/differs
	@$(call ps2drv_traces,ps2drvmod)

# The same two ROMs on the A7000. Its reports are named for the machine so
# both runs' reports can coexist.
ps2drvmoda7000test: $(BUILD)/tests/ps2drvmod-ref.rom \
                    $(BUILD)/tests/ps2drvmod-refmod.rom \
                    tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference module, on the A7000:"
	@$(call ps2drv_run,ps2drvmod-refmod.rom,ps2drva7000-refmod,A7000)
	@echo "this module, on the A7000:"
	@$(call ps2drv_run,ps2drvmod-ref.rom,ps2drva7000-ref,A7000)
	@echo
	@tools/build/reporttest.sh ps2drva7000 \
	  $(BUILD)/tests/ps2drva7000-refmod.txt \
	  $(BUILD)/tests/ps2drva7000-ref.txt \
	  $(BUILD)/tests/ps2drva7000.diff Sources/HWSupport/PS2Driver/test/ps2drvmod/differs
	@$(call ps2drv_traces,ps2drva7000)
.PHONY: ps2drvmodtest ps2drvmoda7000test

# CDFSSoftATAPI, against the reference's own 117-CDFSSoftATAPI. Hand-written
# rather than `module-oracle`, because both boots need a CD image and an
# environment variable.
$(BUILD)/tests/cdatapimod-refmod.rom: $(HAL_IOMD_BIN) \
                                      $(BUILD)/tests/cdatapimod.mod \
                                      $(ADFS_MOD) \
                                      $(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  $(addprefix --module ,$(foreach m,$(TEST_CHAIN_cdatapimod),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(ADFS_MOD) \
	  --module $(LEGACY)/romparts/117-CDFSSoftATAPI.mod \
	  --module $(BUILD)/tests/cdatapimod.mod

# The ISO is generated here, so sectors read off the CD are known bytes:
# thirty-two blocks of 2,048, each filled with a pattern carrying its block
# number.
CDATAPI_DIR := $(BUILD)/tests/cdatapimod
CDATAPI_ISO := $(CDATAPI_DIR)/test.iso

$(CDATAPI_ISO):
	@mkdir -p $(dir $@)
	@python3 -c "import sys; \
	  open(sys.argv[1],'wb').write(bytes((b*13+i*7+3)&255 \
	    for b in range(32) for i in range(2048)))" $@
	@echo "wrote $@ (32 sectors of 2048, each patterned by its number)"

cdatapi_run = RPCEMU_CDROM=$(abspath $(CDATAPI_ISO)) \
              tools/build/rom-test.sh $(abspath $(BUILD))/tests/$(1) \
                $(abspath $(BUILD))/tests/$(2) 180

cdatapimodtest: $(BUILD)/tests/cdatapimod-ref.rom \
                $(BUILD)/tests/cdatapimod-refmod.rom $(CDATAPI_ISO) \
                tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference module:"
	@$(call cdatapi_run,cdatapimod-refmod.rom,cdatapimod-refmod.txt)
	@echo "this module:"
	@$(call cdatapi_run,cdatapimod-ref.rom,cdatapimod-ref.txt)
	@echo
	@tools/build/reporttest.sh cdatapimod \
	  $(BUILD)/tests/cdatapimod-refmod.txt \
	  $(BUILD)/tests/cdatapimod-ref.txt \
	  $(BUILD)/tests/cdatapimod.diff Sources/FileSys/CDFS/CDFSATAPI/test/cdatapimod/differs
.PHONY: cdatapimodtest

# CDFS, against the reference's own 118-CDFS. Hand-written for the same
# reason as CDFSSoftATAPI's: both boots need a CD image and an environment
# variable.
$(BUILD)/tests/cdfsmod-refmod.rom: $(HAL_IOMD_BIN) \
                                   $(BUILD)/tests/cdfsmod.mod \
                                   $(ADFS_MOD) \
                                   $(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  $(addprefix --module ,$(foreach m,$(TEST_CHAIN_cdfsmod),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(ADFS_MOD) \
	  --module $(LEGACY)/romparts/117-CDFSSoftATAPI.mod \
	  --module $(LEGACY)/romparts/118-CDFS.mod \
	  --module $(BUILD)/tests/cdfsmod.mod

# The image is generated here: a primary volume descriptor, a root
# directory, a subdirectory and five files, laid out by tools/build/mkiso.py
# to ECMA-119. Sources/FileSys/CDFS/CDFSATAPI/test/cdatapimod's patterned sectors test sector delivery;
# this tests what is decoded from them.
CDFS_ISO_DIR := $(BUILD)/tests/cdfsmod
CDFS_ISO := $(CDFS_ISO_DIR)/test.iso

$(CDFS_ISO): tools/build/mkiso.py
	@mkdir -p $(dir $@)
	@python3 tools/build/mkiso.py $@

.PHONY: cdfsiso
cdfsiso: $(CDFS_ISO)

# CMOS &8A: the low five bits are the drive count, the top three the
# directory cache size; 21 is one drive and an eight-kilobyte cache. With
# zero, only drive zero is reachable and nothing is cached, so two paths
# under test would never run.
CDFS_CMOS ?= 8A=21

# Twenty minutes rather than the usual three: the report is about 350
# telemetry lines, many of them from commands that spin the disc up.
cdfs_run = RPCEMU_CDROM=$(abspath $(CDFS_ISO)) RPCEMU_CMOS='$(CDFS_CMOS)' \
           tools/build/rom-test.sh $(abspath $(BUILD))/tests/$(1) \
             $(abspath $(BUILD))/tests/$(2) 1200

cdfsmodtest: $(BUILD)/tests/cdfsmod-ref.rom \
             $(BUILD)/tests/cdfsmod-refmod.rom $(CDFS_ISO) \
             tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference module:"
	@$(call cdfs_run,cdfsmod-refmod.rom,cdfsmod-refmod.txt)
	@echo "this module:"
	@$(call cdfs_run,cdfsmod-ref.rom,cdfsmod-ref.txt)
	@echo
	@tools/build/reporttest.sh cdfsmod \
	  $(BUILD)/tests/cdfsmod-refmod.txt \
	  $(BUILD)/tests/cdfsmod-ref.txt \
	  $(BUILD)/tests/cdfsmod.diff Sources/FileSys/CDFS/CDFS/test/cdfsmod/differs
.PHONY: cdfsmodtest

# The stand-in CD driver has a string and a table of reasons: ROPI cannot
# relocate an absolute string pointer in a test's ROM data.
.PHONY: cdfsdriverfixturechecks
cdfsdriverfixturechecks: $(BUILD)/tests/cdfsdrivermod/mod/cases.o \
                         $(BUILD)/tests/lib/mod/standcd.o \
                         Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py
	@python3 Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py \
	  $(BUILD)/tests/cdfsdrivermod/mod/cases.o CDFSDriver
	@python3 Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py \
	  $(BUILD)/tests/lib/mod/standcd.o "the stand-in CD driver"

$(BUILD)/tests/cdfsdrivermod-mod.elf: | cdfsdriverfixturechecks
cdfsdrivermodtest: cdfsdriverfixturechecks

# CDFS's test module has tables of names: a braced initialiser holding
# string pointers needs a relocation, and a test module linked at zero has
# none applied.
.PHONY: cdfsfixturechecks
cdfsfixturechecks: $(BUILD)/tests/cdfsmod/mod/cases.o \
                   Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py
	@python3 Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py \
	  $(BUILD)/tests/cdfsmod/mod/cases.o CDFS

$(BUILD)/tests/cdfsmod-mod.elf: | cdfsfixturechecks
cdfsmodtest: cdfsfixturechecks
hostchecks: cdfsfixturechecks

# The Display Manager, against the reference's own: the module only, not the
# task.
#
# A Wimp task polls from user mode, and this test runs at a service call in
# supervisor mode with no desktop. So this measures the command, its help,
# the path variable, the seven services and the message file; the dialogue is
# covered by `displaymanagerdesktoptest` below.
#
# The command's reply is a token in the module's message file, which is in
# the kernel resources block in both ROMs.
$(eval $(call module-oracle,displaymanagermod,024-DisplayManager,DMMOD_UNDER_TEST,,\
  Sources/Desktop/DisplayManager/test/displaymanagermod/differs))

# A reference module written in C cannot be lifted out of its ROM, which is
# what this second template is for.
#
# `module-oracle` builds a chain from parts, which works for self-contained
# assembly modules. The twenty-three reference modules written in C call
# SharedCLibrary with BL instructions, which encode a distance, so moved out
# of the ROM they were linked in they branch to the wrong place.
#
# So both ROMs are the whole reference chain, with our kernel under both and
# `--relocate` over both: every slot keeps its reference length, so internal
# distances are preserved, and only the module's own slot differs. The test
# module is appended to the chain, where it initialises last.
#
# Our kernel, not the reference's, is required: a replacement too big for its
# slot is carried past the chain behind a placeholder, which only our kernel
# follows. mkrom.py refuses the combination. The kernel is the same in both
# ROMs, so it is not a second variable.
#
#   $(call reference-chain-oracle,<test>,<the name the module registers>,
#                                 <ours>,<report>,<differs>)
#
# The arguments mean what they mean above, with one difference: the second is
# the name the module CALLS ITSELF, because that is what --replace matches on,
# and not the name of a file in legacy/romparts.
#
# A full reference boot is slow, so a test for this template reports at
# Service_Reset rather than waiting for a desktop.
define reference-chain-oracle

$$(BUILD)/tests/$(1)-refchain.rom: $$(HAL_IOMD_BIN) $$(KERNEL_BIN) \
                                   $$(BUILD)/tests/$(1).mod \
                                   $$(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $$(HAL_IOMD_BIN) $$@ --reference $$(REF_ROM) \
	  --hal-size $$(HAL_SIZE) --kernel $$(KERNEL_BIN) \
	  --reference-modules --relocate \
	  --module $$(BUILD)/tests/$(1).mod

$$(BUILD)/tests/$(1)-refchain-ours.rom: $$(HAL_IOMD_BIN) $$(KERNEL_BIN) \
                                        $$(BUILD)/tests/$(1).mod \
                                        $$$$($(3)) \
                                        $$(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $$(HAL_IOMD_BIN) $$@ --reference $$(REF_ROM) \
	  --hal-size $$(HAL_SIZE) --kernel $$(KERNEL_BIN) \
	  --reference-modules --relocate \
	  --replace $(2)=$$($(3)) \
	  --module $$(BUILD)/tests/$(1).mod

# The run rebuilds the HAL and kernel at BIG_ROM_SIZE under this make, which
# has already decided they are up to date; they are put back at this make's
# size afterwards, or what it builds next (verify, the kernel oracles) takes
# the 8M ones.
$(or $(4),$(1))test:
	@$$(MAKE) --no-print-directory ROM_SIZE=$$(BIG_ROM_SIZE) \
	  $(or $(4),$(1))test-run; status=$$$$?; \
	  $$(MAKE) --no-print-directory $$(HAL_IOMD_BIN) $$(KERNEL_BIN) \
	  && exit $$$$status

.PHONY: $(or $(4),$(1))test

$(or $(4),$(1))test-run: $$(BUILD)/tests/$(1)-refchain.rom \
                         $$(BUILD)/tests/$(1)-refchain-ours.rom \
                         tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference module:"
	@tools/build/rom-test.sh \
	  $$(abspath $$(BUILD))/tests/$(1)-refchain.rom \
	  $$(abspath $$(BUILD))/tests/$(1)-refchain.txt $$(call wait_for,$(1))
	@echo "this module:"
	@tools/build/rom-test.sh \
	  $$(abspath $$(BUILD))/tests/$(1)-refchain-ours.rom \
	  $$(abspath $$(BUILD))/tests/$(1)-refchain-ours.txt \
	  $$(call wait_for,$(1))
	@echo
	@tools/build/reporttest.sh $(or $(4),$(1)) \
	  $$(BUILD)/tests/$(1)-refchain.txt \
	  $$(BUILD)/tests/$(1)-refchain-ours.txt \
	  $$(BUILD)/tests/$(or $(4),$(1)).diff $(5)
.PHONY: $(or $(4),$(1))test-run

endef

# BootCommands uses SharedCLibrary at its linked addresses.
$(eval $(call reference-chain-oracle,bootcmdsmod,BootCommands,BCMD_MOD,,\
  Sources/Utilities/BootCmds/test/bootcmdsmod/differs))

bootcmdsrepeattest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) bootcmdsrepeat-run

$(BUILD)/tests/bootcmds-repeat-control.rom: $(HAL_IOMD_BIN) $(KERNEL_BIN) \
    $(REF_ROM) tools/build/mkrom.py
	python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) --reference-modules --relocate

$(BUILD)/tests/bootcmds-repeat-ours.rom: $(HAL_IOMD_BIN) $(KERNEL_BIN) \
    $(REF_ROM) $$(BCMD_MOD) tools/build/mkrom.py
	python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) --reference-modules --relocate \
	  --replace BootCommands=$(BCMD_MOD)

bootcmdsrepeat-run: $(BUILD)/tests/bootcmds-repeat-control.rom \
    $(BUILD)/tests/bootcmds-repeat-ours.rom $(BUILD)/tests/filecore-putimage
	python3 Sources/Utilities/BootCmds/test/bootcmds/machine/repeat.py --build $(BUILD) \
	  $(BUILD)/tests/bootcmds-repeat-control.rom \
	  $(BUILD)/tests/bootcmds-repeat-ours.rom
.PHONY: bootcmdsrepeattest bootcmdsrepeat-run

# DOSFS, against the reference's own, in the reference ROM it was linked in.
# The module makes its own fixture: a 720K file on the RAM disc laid out by
# DOSFS_LayoutStructure, then read back through FileSwitch as an image.
$(eval $(call reference-chain-oracle,dosfsmod,DOSFS,DOSFS_MOD,,\
  Sources/FileSys/DOSFS/test/dosfsmod/differs))

# A DOS floppy in drive 0, reference ROM against ours: a disc whose root is
# an image is a directory to *Cat and *Dir. See Sources/FileSys/DOSFS/test/dosfs/machine/run.py.
dosfloppytest: oursrom
	LEGACY=$(abspath $(LEGACY)) python3 Sources/FileSys/DOSFS/test/dosfs/machine/run.py $(REF_ROM) \
	  $(BUILD)/riscos-ours \
	  $(BUILD)/dosfloppy-rpcemu
.PHONY: dosfloppytest

# The Resolver, against ANT's own, in the reference ROM it was linked in.
#
# No network: the cases cover what needs none - names refused before a query
# is built, what a failed lookup leaves in r1 and r2, the cache failures fill
# and the masks that flush it, and the error for a SWI outside the chunk. See
# Sources/Networking/Inet/Resolver/test/resolvermod/cases.c.
$(eval $(call reference-chain-oracle,resolvermod,Resolver,RESOLV_MOD,,\
  Sources/Networking/Inet/Resolver/test/resolvermod/differs))

# DHCP, against Element 14's own, in the reference ROM it was linked in.
#
# No DHCP server: two boots cannot be given identical server responses. The
# cases cover what needs none: all six SWIs, every refusal's number and text,
# both commands and their output, and the two option calls against a
# caller-owned packet. `make dhcpchecks` and `make dhcpmachinetest` cover the
# rest; see Sources/Networking/Inet/DHCP/test/dhcpmod/cases.c.
$(eval $(call reference-chain-oracle,dhcpmod,DHCP,DHCP_MOD,,\
  Sources/Networking/Inet/DHCP/test/dhcpmod/differs))

# Net, against Acorn's own, in the reference ROM it was linked in.
#
# No second AUN station: nothing beyond the emulator's NAT answers AUN. The
# cases cover the four commands and their output, all thirty-four SWIs,
# every error's number and text, the station-number parser, the ports, the
# control blocks and what a service call leaves behind. See
# Sources/Networking/Econet/Net/test/netmod/cases.c.
$(eval $(call reference-chain-oracle,netmod,Net,NET_MOD,,\
  Sources/Networking/Econet/Net/test/netmod/differs))
$(eval $(call reference-chain-oracle,drawfilemod,DrawFile,DRAWFILE_MOD))

# ROPI cannot relocate absolute string pointers in the test's ROM data.
# Run this before linking even when building only the oracle ROMs.
.PHONY: devicefsfixturechecks
devicefsfixturechecks: $(BUILD)/tests/devicefsmod/mod/cases.o Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py
	@python3 Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py $<

$(BUILD)/tests/devicefsmod-mod.elf: | devicefsfixturechecks
devicefsmodtest: devicefsfixturechecks

# The same check for SerialDeviceSupport's oracle, which shares the
# stand-in.
.PHONY: serialsptfixturechecks
serialsptfixturechecks: $(BUILD)/tests/serialsptmod/mod/cases.o \
                        $(BUILD)/tests/lib/mod/standev.o \
                        Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py
	@python3 Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py \
	  $(BUILD)/tests/serialsptmod/mod/cases.o SerialDeviceSupport
	@python3 Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py \
	  $(BUILD)/tests/lib/mod/standev.o "the stand-in device"

$(BUILD)/tests/serialsptmod-mod.elf: | serialsptfixturechecks
serialsptmodtest: serialsptfixturechecks

# SerialMouse's, which registers the same stand-in.
.PHONY: sermousefixturechecks
hostchecks: sermousefixturechecks
sermousefixturechecks: $(BUILD)/tests/sermousemod/mod/cases.o \
                       Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py
	@python3 Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py \
	  $(BUILD)/tests/sermousemod/mod/cases.o SerialMouse

$(BUILD)/tests/sermousemod-mod.elf: | sermousefixturechecks
sermousemodtest: sermousefixturechecks

# And SystemDevices', which has a table of names.
.PHONY: systemdevsfixturechecks
systemdevsfixturechecks: $(BUILD)/tests/systemdevsmod/mod/cases.o \
                         Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py
	@python3 Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py $< SystemDevices

$(BUILD)/tests/systemdevsmod-mod.elf: | systemdevsfixturechecks
systemdevsmodtest: systemdevsfixturechecks

# And PipeFS', which has a table of names.
.PHONY: pipefsfixturechecks
pipefsfixturechecks: $(BUILD)/tests/pipefsmod/mod/cases.o \
                     Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py
	@python3 Sources/FileSys/DeviceFS/test/devicefs/checkfixture.py $< PipeFS

$(BUILD)/tests/pipefsmod-mod.elf: | pipefsfixturechecks
pipefsmodtest: pipefsfixturechecks

# DeviceFS is in OURS_MODULES only with its stream oracle in the test gate.
test: devicefsmodtest

# SerialDeviceSupport has no other test on the machine.
test: fslockmodtest
test: serialsptmodtest

# Nor has SystemDevices.
test: systemdevsmodtest

# And PipeFS.
test: pipefsmodtest

# Nor has DOSFS.
test: dosfsmodtest
test: resolvermodtest
test: netmodtest
test: dhcpmodtest

# The Display Manager is in OURS_MODULES and REPLACED_MODULES, and no other
# machine test reaches its task.
test: displaymanagermodtest


# Three oracles are still hand-written, all FileCore's.
#
# `filecoremodtest` compares two `-refmod` ROMs: the reference RamFS in both,
# with FileCore the only difference. `fcfsfswtest` is a third ROM, for
# FileSwitch. Neither is this template's shape.
#
# `fcfsmodtest` is, and
# `$(call module-oracle,fcfs,030-FileCore,FC_MOD,fcfsmod)` produces an
# identical ROM to the hand-written rule. Converting it changes only the run's
# wording ("the reference module" for "the reference FileCore").

# Which tests are typed at, and the script each is typed. A test with one
# waits for the machine to reach its prompt before the first key, so it is
# given longer than the rest.
TEST_TYPED_super := Sources/Kernel/test/super/typed
TEST_TYPED_confirm := Sources/Kernel/test/confirm/typed
TEST_TYPED_readline := Sources/Kernel/test/readline/typed
typed_for = $(if $(TEST_TYPED_$(1)),RPCEMU_AUTOTYPE=$(abspath $(TEST_TYPED_$(1))) $(if $(TEST_TYPED_DELAY_$(1)),RPCEMU_AUTOTYPE_DELAY=$(TEST_TYPED_DELAY_$(1))) $(if $(TEST_TYPED_GAP_$(1)),RPCEMU_AUTOTYPE_GAP=$(TEST_TYPED_GAP_$(1))))

# Which tests want a machine other than the default.
#
# The kernel offers AMBControl's lazy task swapping only if the CPU ID says
# the processor restarts an aborted instruction correctly. It decides at
# reset, and no SWI can clear the disable bit afterwards. RPCEmu's default
# StrongARM revision 2 matches SA110_preRevT (CPUFlag_AbortRestartBroken), so
# the lazy paths are unreachable on it. rpcemu/src/cp15.c takes the revision
# from RPCEMU_SA_REVISION; 4 is the first rev T part. Other tests keep the
# default.
TEST_REVISION_amb := 4
# Sources/Kernel/test/exception needs the same machine: only an abort taken while a slot is
# lazily mapped makes the kernel map the whole slot in first.
TEST_REVISION_exception := 4
# Sources/Kernel/test/exceptalign is Sources/Kernel/test/exception's cases under an alignment fault, and
# needs the same machine.
TEST_REVISION_exceptalign := 4
machine_for = $(if $(TEST_REVISION_$(1)),RPCEMU_SA_REVISION=$(TEST_REVISION_$(1)))

# Which processor the machine has; RPCEMU_MODEL overrides rpc.cfg for a run.
#
# The kernel identifies the part from its MIDR, derives the cache geometry
# from the matching table row, builds ProcessorFlags from what the processor
# does, and installs one of six ARMop sets. The default StrongARM reaches only
# Analyse_WB_Crd; the ARMv3 models reach Analyse_ARMv3. All three models are
# IOMD with the same SuperIO and IIC clock, so only the processor differs.
#
#   cpu     RPCSA   StrongARM, ARMv4,  write-back clean-by-read
#   cpuv3   RPC710  ARM710,    ARMv3,  write-through, 8K/4-way/8-byte lines
#   cpu610  RPC610  ARM610,    ARMv3,  write-through, 4K/64-way/4-byte lines
#
# ARM810 is left out: its MIDR &41018100 matches no row of the kernel's table,
# so the kernel hangs in its "Cache unknown" panic loop. The A7000 models have
# a different IOMD and are left out too.
#
# RPCEMU_ARCH_V4 lets the ARMv3 models execute LDRH, STRH, LDRSB and LDRSH:
# clang has no ARMv3 target, so every ROM built here contains some. The MIDR,
# cache type register and abort behaviour stay the emulated part's. See
# rpcemu/src/telemetry.h.
TEST_MODEL_cpu := RPCSA
TEST_MODEL_cpuv3 := RPC710
TEST_MODEL_cpu610 := RPC610
TEST_ARCHV4_cpuv3 := 1
TEST_ARCHV4_cpu610 := 1
model_for = $(if $(TEST_MODEL_$(1)),RPCEMU_MODEL=$(TEST_MODEL_$(1))) \
            $(if $(TEST_ARCHV4_$(1)),RPCEMU_ARCH_V4=1)

# The part and architecture each CPU test must find itself on, by the kernel's
# own names (declared in Sources/Kernel/hdr/ARMops, lifted into kernel-ws.h by
# KERNEL_WS_ARMOPS_SYMS). An unknown model name leaves the emulator on
# rpc.cfg's model with only a warning on stderr, so each test checks.
# SA110_preRevT because the emulated StrongARM is revision 2 unless
# RPCEMU_SA_REVISION says otherwise.
TEST_CPU_TYPE_cpu := KernelWS_SA110_preRevT
TEST_CPU_ARCH_cpu := KernelWS_ARMv4
TEST_CPU_TYPE_cpuv3 := KernelWS_ARM710
TEST_CPU_ARCH_cpuv3 := KernelWS_ARMv3
TEST_CPU_TYPE_cpu610 := KernelWS_ARM610
TEST_CPU_ARCH_cpu610 := KernelWS_ARMv3

# The data address that aborts and the instruction fetch that is undefined.
# A guest cannot provoke an external abort or choose a fault status:
# RPCEMU_FAULT (address, fault status, count of accesses) makes
# rpcemu/src/telemetry.c abort accesses to that address whatever the page
# tables say, and RPCEMU_UNDEF makes the word fetched from its address
# undefined once. Both are in a page the test maps at an address it chooses,
# so both kernels are asked about the same word; Sources/Kernel/test/exception/cases.c
# uses the same numbers. Count 2: one abort for the test's own handler, one
# for the kernel's default handler.
TEST_FAULT_exception := 0xA0000000,8,2
TEST_UNDEF_exception := 0xA0000010
# The same cases with fault status 1 (alignment) instead of 8.
#
# No guest here can raise an alignment fault: the check is an ARMv6 control
# register bit this build does not set, and ARMv3/v4 parts rotate a
# misaligned load instead. aborttrap_handler's is_relevant_abort_* does not
# list this status, so the trap declines it and the kernel's own report
# answers. The count is two, as for the external abort, so the two reports
# are comparable line for line.
TEST_FAULT_exceptalign := 0xA0000000,1,2
TEST_UNDEF_exceptalign := 0xA0000010
fault_for = $(if $(TEST_FAULT_$(1)),RPCEMU_FAULT=$(TEST_FAULT_$(1)))
undef_for = $(if $(TEST_UNDEF_$(1)),RPCEMU_UNDEF=$(TEST_UNDEF_$(1)))

# Which part is on the IIC bus. The default machine has only the clock's 256
# bytes of CMOS; rpcemu/src/cmos.c attaches a separate EEPROM (address,size)
# where RPCEMU_I2C_EEPROM is set. A8 is the address the kernel's header gives
# a 24C128.
TEST_EEPROM_eeprom := A8,16384
TEST_EEPROM_eeprom2k := E0,2048
eeprom_for = $(if $(TEST_EEPROM_$(1)),RPCEMU_I2C_EEPROM=$(TEST_EEPROM_$(1)))

# Tests that keep the emulator's decoded IIC bus trace (one line per
# transaction) beside the report; tools/build/i2ccmp.py compares the two
# kernels' traces after the reports. See rpcemu/src/cmos.c.
TEST_I2CTRACE_cmos := 1
TEST_I2CTRACE_eeprom := 1
TEST_I2CTRACE_eeprom2k := 1
$(foreach t,$(IIC_TESTS),$(eval TEST_I2CTRACE_$(t) := 1))
i2ctrace_for = $(if $(TEST_I2CTRACE_$(1)),RPCEMU_I2CTRACE=$(abspath $(BUILD))/tests/$(1)-$(2).i2c)

# The IIC fault the emulator injects, and into which transaction. The count
# starts after the first transaction to &B2, which nothing answers and which
# Sources/HWSupport/IIC/test/iic/cases.c addresses first; the numbers match that file's comments.
# nack 1-3 refuse a byte on a write, on the offset before a read, and on a
# bare read's address; arb and stretch act as another master on the wire.
TEST_I2CFAULT_iicnack1 := nack 1
TEST_I2CFAULT_iicnack2 := nack 2
TEST_I2CFAULT_iicnack3 := nack 3
TEST_I2CFAULT_iicarb := arb 2
TEST_I2CFAULT_iicstretch := stretch 2
i2cfault_for = $(if $(TEST_I2CFAULT_$(1)),RPCEMU_I2C_FAULT=$(word 1,$(TEST_I2CFAULT_$(1))) RPCEMU_I2C_FAULT_AT=$(word 2,$(TEST_I2CFAULT_$(1))) RPCEMU_I2C_FAULT_AFTER=b2)

# Whether the HAL drives the display (the facilities bit Sources/Hardware/IOMD/halvideo.c
# reads at HAL_Init), and whether the emulator keeps the run's VIDC trace
# beside the report for tools/check/vidccmp.py. See rpcemu/src/telemetry.h.
halvideo_for = $(if $(TEST_HALVIDEO_$(1)),RPCEMU_HAL_VIDEO=1)

# Whether the HAL drives the IIC bus itself (Sources/Hardware/IOMD/haliic.c's facility
# bit), which reaches the kernel's high-level bus path. Sources/HWSupport/IIC/test/iichl is
# Sources/HWSupport/IIC/test/iic's cases over that bus.
TEST_HALIIC_iichl := 1
haliic_for = $(if $(TEST_HALIIC_$(1)),RPCEMU_HAL_IIC=1)
vidctrace_for = $(if $(TEST_VIDCTRACE_$(1)),RPCEMU_VIDCTRACE=$(abspath $(BUILD))/tests/$(1)-$(2).vidc)

# Which tests want an expansion card in the machine.
#
# The kernel's reset scan makes a module node for each relocatable-module
# chunk on every podule and extension ROM. rpcemu/src/podulerom.c builds a
# card from the directory in RPCEMU_PODULEROMS: a description chunk, then one
# &81 chunk per file in sorted name order. Every test that asks gets the same
# card.
TEST_PODULES_modhand := 1
podules_for = $(if $(TEST_PODULES_$(1)),RPCEMU_PODULEROMS=$(abspath $(BUILD))/tests/poduleroms)

# What is on it: the same source twice over, with its own name and version
# each time. The file names decide the chunk order, so they are numbered.
POD_MODULES  := 1-podfirst 2-podsecond
POD_TITLE_1-podfirst  := PodFirst
POD_HELP_1-podfirst   := PodFirst\t\t1.23 The first module on the card
POD_TITLE_2-podsecond := PodSecond
POD_HELP_2-podsecond  := PodSecond\t\t4.56 The second one

POD_FILES := $(POD_MODULES:%=$(BUILD)/tests/poduleroms/%)

# Assembled, linked and taken out as a bare image, because that is what a
# card chunk is. The object and ELF are built outside the card directory: the
# emulator takes every ordinary file there as a chunk.
$(BUILD)/tests/pod/%.o: $(TEST_LIB)/podmodule.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(TEST_CFLAGS) \
	  -DPODMODULE_TITLE='"$(POD_TITLE_$*)"' \
	  -DPODMODULE_HELP='"$(POD_HELP_$*)"' \
	  -c $< -o $@

$(BUILD)/tests/poduleroms/%: $(BUILD)/tests/pod/%.o
	@mkdir -p $(dir $@)
	@$(LD) $(LDFLAGS) --image-base=0 -Ttext=0 --entry=0 -o $(BUILD)/tests/pod/$*.elf $<
	@$(OBJCOPY) -O binary $(BUILD)/tests/pod/$*.elf $@
	@echo "built $@ ($$(stat -c%s $@) bytes)"

# `super` is shorthand for supertest.
#
# Timeouts in seconds for tests that need more than wait_for's default of
# 120. The interrupt tests' run time depends on load on the host.
super: supertest
.PHONY: super

TEST_WAIT_heapirq := 300
TEST_WAIT_cmosirq := 300
TEST_WAIT_super := 180
TEST_WAIT_confirm := 180
TEST_WAIT_readline := 180
TEST_WAIT_swiirq := 120
# DOSFS writes a 720K file on the RAM disc and runs a filing system over it,
# every transfer through FileCore.
TEST_WAIT_dosfsmod := 400
# Nine lookups that each time out: with no name server configured, the
# reference waits its configured delay over its configured retries.
TEST_WAIT_resolvermod := 900
# Two of DHCP's cases reach the Internet module, and one sends a datagram
# that has nowhere to go.
TEST_WAIT_dhcpmod := 300
# Net's report is long, and the fixture reinitialises the module first.
TEST_WAIT_netmod := 900
wait_for = $(or $(TEST_WAIT_$(1)),120)

# Each test as a module, in the smallest ROM that runs it, under both
# kernels. Only the last line of output is the result.
$(TESTS:%=%test): %test: $(BUILD)/tests/%-ref.rom $(BUILD)/tests/%-ours.rom \
                         $$(if $$(TEST_PODULES_$$*),$$(POD_FILES)) \
                         tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference kernel:"
	@$(call typed_for,$*) $(call machine_for,$*) $(call eeprom_for,$*) \
	  $(call podules_for,$*) $(call halvideo_for,$*) $(call haliic_for,$*) \
	  $(call fault_for,$*) $(call undef_for,$*) $(call model_for,$*) \
	  $(call vidctrace_for,$*,ref) $(call i2ctrace_for,$*,ref) \
	  $(call i2cfault_for,$*) tools/build/rom-test.sh \
	  $(abspath $(BUILD))/tests/$*-ref.rom \
	  $(abspath $(BUILD))/tests/$*-ref.txt $(call wait_for,$*)
	@echo "this kernel:"
	@$(call typed_for,$*) $(call machine_for,$*) $(call eeprom_for,$*) \
	  $(call podules_for,$*) $(call halvideo_for,$*) $(call haliic_for,$*) \
	  $(call fault_for,$*) $(call undef_for,$*) $(call model_for,$*) \
	  $(call vidctrace_for,$*,ours) $(call i2ctrace_for,$*,ours) \
	  $(call i2cfault_for,$*) tools/build/rom-test.sh \
	  $(abspath $(BUILD))/tests/$*-ours.rom \
	  $(abspath $(BUILD))/tests/$*-ours.txt $(call wait_for,$*)
	@echo
	@tools/build/reporttest.sh $* $(BUILD)/tests/$*-ref.txt \
	  $(BUILD)/tests/$*-ours.txt $(BUILD)/tests/$*.diff $(call testdir,$*)/differs
	$(if $(TEST_I2CTRACE_$*),@python3 tools/build/i2ccmp.py \
	  $(BUILD)/tests/$*-ref.i2c $(BUILD)/tests/$*-ours.i2c)
	@if grep -aq '^  FAIL  ' $(BUILD)/tests/$*-ours.txt; then \
	  echo "$*test: our report carries FAILed checks, which an identical"; \
	  echo "  diff cannot excuse - both kernels failing alike is still a"; \
	  echo "  failure:"; \
	  grep -a '^  FAIL  ' $(BUILD)/tests/$*-ours.txt; \
	  exit 1; \
	fi

# The halvideo test with the VIDC20Video module in the chain and the HAL's
# display gate shut, so the module drives the display. tools/check/vidccmp.py
# compares this run's VIDC trace with the one halvideotest writes.
$(BUILD)/tests/halvideo-module.rom: $(HAL_IOMD_BIN) $(KERNEL_BIN) \
                                    $(BUILD)/tests/halvideo.mod $(MOD_VIDC20_MOD)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  --module $(MOD_VIDC20_MOD) \
	  $(addprefix --module ,$(call test_chain,halvideo)) \
	  --module $(BUILD)/tests/halvideo.mod

$(BUILD)/tests/halvideo-module.vidc: $(BUILD)/tests/halvideo-module.rom \
                                     tools/build/rom-test.sh
	@echo "this kernel, the module driving:"
	@RPCEMU_VIDCTRACE=$(abspath $@) tools/build/rom-test.sh $(abspath $<) \
	  $(abspath $(BUILD))/tests/halvideo-module.txt $(call wait_for,halvideo)

halvideotrace: halvideotest $(BUILD)/tests/halvideo-module.vidc tools/check/vidccmp.py
	@python3 tools/check/vidccmp.py --hal $(BUILD)/tests/halvideo-ours.vidc \
	  --module $(BUILD)/tests/halvideo-module.vidc \
	  --report $(BUILD)/tests/halvideo-ours.txt --hal-only 10
.PHONY: halvideotrace

vidccmpchecks:
	@python3 Sources/Video/HWVideo/VIDC20Video/test/vidccmp/check.py
hostchecks: vidccmpchecks
.PHONY: vidccmpchecks

# A machine-only test: one ROM, and the report judges itself. It fails unless
# the report carries a "checks, all passed" tally.
$(CHAIN_TESTS:%=%test): %test: $(BUILD)/tests/%-ours.rom tools/build/rom-test.sh
	@tools/build/rom-test.sh $(abspath $(BUILD))/tests/$*-ours.rom \
	  $(abspath $(BUILD))/tests/$*-ours.txt $(call wait_for,$*)
	@sed -n 's/^  //p' $(BUILD)/tests/$*-ours.txt
	@if grep -q 'checks, all passed$$' $(BUILD)/tests/$*-ours.txt; then \
	  grep 'checks, all passed$$' $(BUILD)/tests/$*-ours.txt; \
	else \
	  echo "$*test: the report carries no passing tally"; \
	  exit 1; \
	fi

# The same cases as an absolute file under a whole desktop: the reference ROM,
# then the reference ROM with this kernel spliced into its chain.
#
# Only tests with a test.ld in their directory ($(call testdir,<name>)) are listed; that name means "links
# as an absolute file". A probe with a link script of its own must call it
# probe.ld, or it joins this list and breaks `everything`.
FILE_TESTS := $(patsubst tests/%/test.ld,%,$(wildcard tests/*/test.ld)) \
              $(foreach t,$(TEST_DIR_NAMES),$(if $(wildcard $(call testdir,$(t))/test.ld),$(t)))

convertfilecomparechecks:
	@python3 Sources/Kernel/test/convert/filecompare_test.py
hostchecks: convertfilecomparechecks
.PHONY: convertfilecomparechecks

$(FILE_TESTS:%=%filetest): %filetest: $(BUILD)/tests/%-file
	@$(MAKE) --no-print-directory kernelrom
	@echo "the reference ROM:"
	@$(if $(filter toolboxlive,$*),GUEST_RUN_PREFIX='WimpTask ') \
	  tools/build/guest-test.sh $(abspath $(KERNEL_CONTROL)) \
	  $(abspath $(BUILD))/tests/$*-file \
	  $(abspath $(BUILD))/tests/$*-file-ref.txt
	@echo "this kernel in it:"
	@$(if $(filter toolboxlive,$*),GUEST_RUN_PREFIX='WimpTask ') \
	  tools/build/guest-test.sh $(abspath $(KERNEL_ROM)) \
	  $(abspath $(BUILD))/tests/$*-file \
	  $(abspath $(BUILD))/tests/$*-file-ours.txt
	@echo
	@$(if $(filter convert,$*),python3 Sources/Kernel/test/convert/filecompare.py,tools/build/reporttest.sh $*file) $(BUILD)/tests/$*-file-ref.txt \
	  $(BUILD)/tests/$*-file-ours.txt $(BUILD)/tests/$*-file.diff


# Sources/Lib/Desktop's own test on the machine. Not a comparison: the report names
# every check, and this fails on any that did not pass.
# `make desktoplivefiletest` runs the same program under both kernels and
# diffs the reports.
desktoplivetest: $(BUILD)/tests/desktoplive-file
	@tools/build/guest-test.sh $(abspath $(REF_ROM)) \
	  $(abspath $(BUILD))/tests/desktoplive-file \
	  $(abspath $(BUILD))/tests/desktoplive.txt
	@if grep -q '^  FAIL' $(BUILD)/tests/desktoplive.txt; then \
	  grep -n -A3 '^  FAIL' $(BUILD)/tests/desktoplive.txt; \
	  echo "  full report in $(BUILD)/tests/desktoplive.txt"; exit 1; \
	fi
	@grep '^desktoplive:' $(BUILD)/tests/desktoplive.txt

.PHONY: desktoplivetest

# ---------------------------------------------------------------------------
# The Toolbox on the machine.
#
# The test is an absolute file, not a module in the chain: a Toolbox
# application is a Wimp task, and a task polls from user mode. See
# tests/toolboxlive/cases.c.
#
# Both ROMs are the reference chain on this kernel (--reference-modules needs
# --kernel; --relocate because the reference modules carry absolute
# addresses), one with the seventeen Toolbox modules replaced. Both keep the
# reference's Res and Messages files, so only the module code differs.
TOOLBOX_ORACLE_MODULES := \
    Toolbox=$(BUILD)/modules/Toolbox.mod \
    Window=$(BUILD)/modules/Window.mod \
    Menu=$(BUILD)/modules/Menu.mod \
    ToolAction=$(BUILD)/modules/ToolAction.mod \
    TextGadgets=$(BUILD)/modules/TextGadgets.mod \
    Iconbar=$(BUILD)/modules/Iconbar.mod \
    ColourDbox=$(BUILD)/modules/ColourDbox.mod \
    ColourMenu=$(BUILD)/modules/ColourMenu.mod \
    DCS=$(BUILD)/modules/DCS.mod \
    FileInfo=$(BUILD)/modules/FileInfo.mod \
    FontDbox=$(BUILD)/modules/FontDbox.mod \
    FontMenu=$(BUILD)/modules/FontMenu.mod \
    PrintDbox=$(BUILD)/modules/PrintDbox.mod \
    ProgInfo=$(BUILD)/modules/ProgInfo.mod \
    SaveAs=$(BUILD)/modules/SaveAs.mod \
    Scale=$(BUILD)/modules/Scale.mod \
    ColourPicker=$(BUILD)/modules/ColourPicker.mod

TOOLBOX_ORACLE_MODS := $(foreach m,$(TOOLBOX_ORACLE_MODULES),\
                         $(word 2,$(subst =, ,$(m))))

# The control: the reference chain on this kernel, nothing replaced.
$(BUILD)/tests/toolbox-ref.rom: $(HAL_IOMD_BIN) $(KERNEL_BIN) $(REF_ROM) \
                                tools/build/mkrom.py
	@mkdir -p $(dir $@)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  --reference-modules --relocate

# And the same ROM with the seventeen replaced.
$(BUILD)/tests/toolbox-ours.rom: $(HAL_IOMD_BIN) $(KERNEL_BIN) $(REF_ROM) \
                                 $(TOOLBOX_ORACLE_MODS) tools/build/mkrom.py
	@mkdir -p $(dir $@)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  --reference-modules --relocate \
	  $(addprefix --replace ,$(TOOLBOX_ORACLE_MODULES))

# Both ROMs are built at BIG_ROM_SIZE: most replacements are larger than the
# modules they replace and go past the chain, and the reference chain on this
# kernel does not fit four megabytes. The control is the same size so every
# module lands at the same address.
toolboxlivetest: $(BUILD)/tests/toolboxlive-file tools/build/guest-test.sh
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) \
	  $(BUILD)/tests/toolbox-ref.rom $(BUILD)/tests/toolbox-ours.rom
	@echo "the reference's Toolbox:"
	@GUEST_RUN_PREFIX='WimpTask ' \
	tools/build/guest-test.sh $(abspath $(BUILD))/tests/toolbox-ref.rom \
	  $(abspath $(BUILD))/tests/toolboxlive-file \
	  $(abspath $(BUILD))/tests/toolboxlive-ref.txt
	@echo "ours:"
	@GUEST_RUN_PREFIX='WimpTask ' \
	tools/build/guest-test.sh $(abspath $(BUILD))/tests/toolbox-ours.rom \
	  $(abspath $(BUILD))/tests/toolboxlive-file \
	  $(abspath $(BUILD))/tests/toolboxlive-ours.txt
	@python3 tests/toolboxlive/compare.py \
	  $(BUILD)/tests/toolboxlive-ref.txt $(BUILD)/tests/toolboxlive-ours.txt \
	  tests/toolboxlive/differs $(BUILD)/tests/toolboxlive.diff
.PHONY: toolboxlivetest


# ---------------------------------------------------------------------------
# The third delivery: the host
#
# Ported C has no registers, processor modes or error blocks, so it compiles
# and runs here: tests/lib/hostswi.c stands in for the assembly veneer, and
# the same cases run with no emulator, ROM or ARM. The veneer, SWI dispatch
# and other modules are tested only on the machine; guard pages, sanitisers
# and coverage only here.

HOSTCC ?= cc

# Linked without PIE because every address handed to the conversions passes
# through a 32-bit register block; hostswi.c checks that it fits.
# include/freestanding is left off the path: the host has a real C library.
HOST_SANITISE ?= -fsanitize=undefined,address -fno-sanitize-recover=all
# -MMD -MP so a changed header rebuilds what includes it; otherwise a test and
# its stand-in can disagree about the size of a shared struct.
HOST_BASE_CFLAGS := -std=gnu17 -Wall -Wextra -Wno-sign-compare -g -Og -no-pie \
                    -MMD -MP \
                    -I$(BUILD)/Sources/Kernel -I$(KERNEL_DIR)/hdr -DCONFIG_$(CONFIG_NAME) \
                    -DROM_SIZE=$(ROM_SIZE) \
                    -Iinclude -I$(TEST_LIB) -I$(KERNEL_DIR)/c \
                    -I$(KERNEL_DIR)/c/arch -I$(KERNEL_DIR)/c/boot -I$(KERNEL_DIR)/c/mem -I$(KERNEL_DIR)/c/module -I$(KERNEL_DIR)/c/io -I$(KERNEL_DIR)/c/cli -I$(KERNEL_DIR)/c/vdu -I$(KERNEL_DIR)/c/os -I$(KERNEL_DIR)/c/lib -I$(KERNEL_DIR)/c/thread
HOST_CFLAGS := $(HOST_BASE_CFLAGS) $(HOST_SANITISE)

# The kernel's C, built for the host. Every host test links all of it: these
# are pure functions with nothing to initialise.
HOST_KERNEL_OBJ := $(patsubst %,$(BUILD)/tests/lib/host/kernel/%.o,$(KERNEL_CNAMES) \
                     $(KERNEL_64BIT_CNAMES))

# Except that Sources/Kernel/test/postmmu does not link c/bootmem.c: its stand-ins answer
# c/postmmu.c's allocation calls and record the sequence asked for, while
# Sources/Kernel/test/bootmem covers c/bootmem.c itself. Those stand-ins carry real kernel
# names, so they live in Sources/Kernel/test/postmmu/checks.c, not the shared stand-in
# library, and tests/lib/hostboot.c must name nothing c/bootmem.c defines.
# The two halves share struct init_alloc in c/bootmem.h; `make minboot`
# checks that they agree on values.
STANDS_IN_FOR_postmmu := boot/bootmem

host_kernel_obj = $(patsubst %,$(BUILD)/tests/lib/host/kernel/%.o,\
                    $(filter-out $(STANDS_IN_FOR_$(1)),$(KERNEL_CNAMES) \
                    $(KERNEL_64BIT_CNAMES)))

# Every host stand-in, listed once and shared by the two object lists and the
# two coverage recipes. Each adds its own: hostmain and the report backend for
# cases, check for checks.
HOST_STUBS := hostswi hostheap hostmmu hostvdu hostthread \
              hostswiname hostvectors hostsysvars hostsysvarcode \
              hostsysheap hostmodhand hostdacreate hostdahandler \
              hostdaareas hostmeminfo hostdagrow hostdamovepages hostamb \
              hostoscli hostwrite hostosfile hostosswi hostservice \
              hosthelp hostconfig hostsysinfo hostosbyte hostosword \
              hostbuffer hostmouse hostkey hostcmos hostrealtime \
              hostzeropage squeeze hostsupervisor hostiic hostreadline hosthal \
              hostreset hostcpufeature hostarmid hostpostmmu hostboot \
              hostiocalls hostvducalls hostspritecalls \
              hostclicalls hostmemcalls hostoscalls

HOST_STUB_SRC := $(HOST_STUBS:%=$(TEST_LIB)/%.c)

HOST_REPORT_OBJ := $(addprefix $(BUILD)/tests/lib/host/,\
                     hostmain.o $(HOST_STUBS:%=%.o) report_stdout.o format.o)
HOST_CHECK_OBJ  := $(addprefix $(BUILD)/tests/lib/host/,\
                     $(HOST_STUBS:%=%.o) check.o)

$(BUILD)/tests/lib/host/%.o: $(TEST_LIB)/%.c $(KERNEL_GLOBALS) \
                             $(KERNEL_DIR)/hdr/vduws.h $(KERNEL_DIR)/hdr/zeropage.h \
                             $(BUILD)/Sources/Kernel/kernel-version.h $(KERNEL_BLOCK_HDRS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -c $< -o $@


$(BUILD)/tests/lib/host/kernel/%.o: $(KERNEL_DIR)/c/%.c $(KERNEL_GLOBALS) \
                                    $(KERNEL_DIR)/hdr/vduws.h $(KERNEL_BLOCK_HDRS) \
                                    $(BUILD)/Sources/Kernel/kernel-version.h \
                                    $(KERNEL_DIR)/hdr/zeropage.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -c $< -o $@

$(BUILD)/tests/%/host/cases.o: $$(call testdir,$$*)/cases.c $(KERNEL_GLOBALS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -c $< -o $@

$(BUILD)/tests/%/host/checks.o: $$(call testdir,$$*)/checks.c $(KERNEL_GLOBALS) \
                                $(KERNEL_DIR)/hdr/vduws.h $(KERNEL_BLOCK_HDRS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -c $< -o $@

# The thread SWI policy uses named stack-allocation and switch doubles.
$(BUILD)/tests/thread-swi-checks: Sources/Kernel/test/thread/swichecks.c \
    $(KERNEL_DIR)/c/thread/thread.c $(KERNEL_DIR)/c/thread/thread.h \
    $(KERNEL_DIR)/c/thread/sched.c tests/lib/check.c $(KERNEL_BLOCK_HDRS) \
    $(BUILD)/Sources/Kernel/kernel_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) Sources/Kernel/test/thread/swichecks.c \
	  $(KERNEL_DIR)/c/thread/sched.c tests/lib/check.c -o $@

threadchecks: threadswichecks
threadswichecks: $(BUILD)/tests/thread-swi-checks
	@$<
.PHONY: threadswichecks

# RTSupport's pure half (Sources/Programmer/RTSupport/c/rtcalc.c). The module
# itself is built for the AArch64 ROMs only (mk/a64modules.mk).
RTS_CHECKS := $(BUILD)/tests/rtsupport-checks
$(RTS_CHECKS): Sources/Programmer/RTSupport/test/rtsupport/checks.c Sources/Programmer/RTSupport/c/rtcalc.c \
                Sources/Programmer/RTSupport/c/rtcalc.h tests/lib/check.c
	@mkdir -p $(dir $@)
	$(HOSTCC) -m32 -funsigned-char $(HOST_CFLAGS) -ISources/Programmer/RTSupport/c -Itests/lib \
	  $(filter %.c,$^) -o $@

rtsupportchecks: $(RTS_CHECKS)
	@$<

.PHONY: rtsupportchecks
hostchecks: threadchecks rtsupportchecks
# The division checks compile Sources/Lib/Compiler/arm32/aeabi_divide.c and
# aeabi_divide64.c into themselves, so the source the modules use is what is
# checked; that needs Sources/Lib/Compiler/arm32 on the include path.
$(BUILD)/tests/divide/host/checks.o: Sources/Lib/Compiler/test/divide/checks.c \
                                     Sources/Lib/Compiler/arm32/aeabi_divide.c Sources/Lib/Compiler/arm32/aeabi_divide64.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Lib/Compiler/arm32 -c $< -o $@

$(BUILD)/tests/%-host: $(BUILD)/tests/%/host/cases.o $(HOST_REPORT_OBJ) \
                       $(HOST_KERNEL_OBJ)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/%-checks: $(BUILD)/tests/%/host/checks.o $(HOST_CHECK_OBJ) \
                         $(HOST_KERNEL_OBJ)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Exercise the real RMA policy, not the module tests' allocation stand-in.
$(BUILD)/tests/rmapolicy-checks: $(BUILD)/tests/rmapolicy/host/checks.o \
                               $(BUILD)/tests/lib/host/check.o \
                               $(BUILD)/tests/lib/host/kernel/mem/rma.o
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Link only the bounded-read wrapper from the real filing-system veneer.
$(BUILD)/tests/osfilepolicy/osfile.o: $(KERNEL_DIR)/c/cli/osfile.c $(KERNEL_GLOBALS) \
                                   $(KERNEL_BLOCK_HDRS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ffunction-sections -c $< -o $@

$(BUILD)/tests/osfilepolicy-checks: $(BUILD)/tests/osfilepolicy/host/checks.o \
                                  $(BUILD)/tests/lib/host/check.o \
                                  $(BUILD)/tests/osfilepolicy/osfile.o
	$(HOSTCC) $(HOST_CFLAGS) -Wl,--gc-sections -o $@ $^

# The one suite with a kernel file excluded, which needs its own rule because
# the exclusion is in the prerequisites.
$(BUILD)/tests/postmmu-checks: $(BUILD)/tests/postmmu/host/checks.o \
                               $(HOST_CHECK_OBJ) \
                               $(call host_kernel_obj,postmmu)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The reference ROM's report on the same cases, which the host is checked
# against. Not committed: it derives from a ROM this repository does not
# redistribute.
$(BUILD)/tests/%-ref.txt: $(BUILD)/tests/%-ref.rom tools/build/rom-test.sh
	@echo "the reference kernel:"
	@tools/build/rom-test.sh $(abspath $<) $(abspath $@)

# The cases the host has nothing to run: calls that are a shuffle into another
# module, with no C of ours underneath them.
TEST_HOSTONLY_convert := date datefmt shortdate shortdatefmt

comma := ,
empty :=
space := $(empty) $(empty)
# "a b c" -> "a|b|c", with a word that cannot occur so that an empty list
# still makes a pattern that matches nothing.
host_only = $(subst $(space),|,$(strip $(TEST_HOSTONLY_$(1)) ~none~))

# Cases a test declares in tests/<name>/differs are held out of the host
# comparison too, since the host runs the ported C.
differs_tags = $(shell if [ -f $(call testdir,$(1))/differs ]; then \
  sed 's/[ \t].*//' $(call testdir,$(1))/differs | awk 'NF && substr($$0, 1, 1) != "\043"' \
    | paste -sd'|' -; else echo '~none~'; fi)

# The same cases, run against the same C, on this machine.
$(HOST_TESTS:%=%hosttest): %hosttest: $(BUILD)/tests/%-host $(BUILD)/tests/%-ref.txt
	@$(BUILD)/tests/$*-host > $(BUILD)/tests/$*-host.txt
	@grep -Ev '^($(call host_only,$*)|$(call differs_tags,$*)) ' \
	  $(BUILD)/tests/$*-ref.txt > $(BUILD)/tests/$*-oracle.txt
	@grep -Ev '^($(call differs_tags,$*)) ' $(BUILD)/tests/$*-host.txt \
	  > $(BUILD)/tests/$*-hostcmp.txt
	@if diff -u $(BUILD)/tests/$*-oracle.txt $(BUILD)/tests/$*-hostcmp.txt \
	            > $(BUILD)/tests/$*-host.diff; \
	then \
	  echo "$*hosttest: identical to the reference ROM ($$(($$(wc -l < $(BUILD)/tests/$*-hostcmp.txt) - 2)) cases)"; \
	else \
	  echo "$*hosttest: $$(grep -c '^-[^-]' $(BUILD)/tests/$*-host.diff) cases differ"; \
	  head -40 $(BUILD)/tests/$*-host.diff; \
	  echo "  full difference in $(BUILD)/tests/$*-host.diff"; \
	  exit 1; \
	fi

# Which lines of the C the cases reach. Rebuilt with --coverage rather than
# reusing the sanitised objects, since the two instrumentations do not mix.
# Everything it produces goes in the recipe's own directory; the terminal
# gets the summary for the files under test and any lines never reached.
$(HOST_TESTS:%=%coverage): %coverage: $(BUILD)/tests/%-host $(BUILD)/tests/%-ref.txt
	@rm -rf $(BUILD)/tests/cov/$*-host
	@mkdir -p $(BUILD)/tests/cov/$*-host
	@$(HOSTCC) $(HOST_BASE_CFLAGS) --coverage \
	  -o $(BUILD)/tests/cov/$*-host/$*-host \
	  $(call testdir,$*)/cases.c $(TEST_LIB)/hostmain.c $(HOST_STUB_SRC) \
	  $(TEST_LIB)/report_stdout.c $(TEST_LIB)/format.c \
	  $(patsubst %,$(KERNEL_DIR)/c/%.c,\
	     $(filter-out $(STANDS_IN_FOR_$*),$(KERNEL_CNAMES))) \
	  2> $(BUILD)/tests/cov/$*-host/cc.log \
	  || { cat $(BUILD)/tests/cov/$*-host/cc.log; exit 1; }
	@cd $(BUILD)/tests/cov/$*-host && ./$*-host > /dev/null
	@gcov -b -o $(BUILD)/tests/cov/$*-host \
	  $(patsubst %,$(BUILD)/tests/cov/$*-host/$*-host-%.gcda,\
	     $(filter-out $(STANDS_IN_FOR_$*),$(KERNEL_CNAMES))) \
	  > $(BUILD)/tests/cov/$*-host/gcov.log
	@awk -v tag=$*coverage -f tools/build/covsum.awk \
	  $(BUILD)/tests/cov/$*-host/gcov.log > $(BUILD)/tests/cov/$*-host/summary
	@mv $(patsubst %,%.c.gcov,\
	      $(filter-out $(STANDS_IN_FOR_$*),$(KERNEL_CNAMES))) \
	    $(BUILD)/tests/cov/$*-host/
	@rm -f *.h.gcov
	@grep -E "/($(subst $(space),|,$(strip $(call covers,$*))))\.c:" \
	  $(BUILD)/tests/cov/$*-host/summary
	@for c in $(call covers,$*); do \
	  if grep -Fq '#####' $(BUILD)/tests/cov/$*-host/$$c.c.gcov; then \
	    echo "  lines the cases never reach in $$c.c:"; \
	    grep -Fn '#####' $(BUILD)/tests/cov/$*-host/$$c.c.gcov \
	      | sed 's/^/    /'; \
	  fi; \
	done

# What the conversions do, asserted directly with no ROM or oracle, including
# where the original is wrong and is matched on purpose.
$(CHECKED_TESTS:%=%checks) $(POLICY_TESTS:%=%checks): %checks: $(BUILD)/tests/%-checks
	@$<

hostchecks test: $(POLICY_TESTS:%=%checks)
everything: $(POLICY_TESTS:%=$(BUILD)/tests/%-checks)
.PHONY: $(POLICY_TESTS:%=%checks)

# The same, measured for line and branch coverage. The checks can reach
# branches no sequence of calls produces, such as a corrupt free list.
# Which files a test's checks cover, where not the test's own name.
COVERS_convert := convert convertswi
# The RAM bank table is shared by the memory estates; the memory map suite
# drives its two walks.
COVERS_memmap := memmap physram
# c/message.c is the kernel's message file, exercised by every command that
# writes a heading.
COVERS_starcmd := starcmd message
# A device vector is a vector whose head is a node, so the two are one test.
# c/modinit.c is the reset's pass over the ROM; Sources/Kernel/test/modhand checks it.
COVERS_modhand := modhand modinit modinitswi
COVERS_vectors := vectors devvector
COVERS_dagrow := dagrow dagrowswi dapages
# Each estate whose SWI has a register layer of its own is one test over the
# estate and that layer - see "The families" in c/swi.h.
COVERS_amb := amb ambswi
COVERS_sysinfo := sysinfo sysinfoswi
# The calls a SWI makes with its caller's registers are c/arch/regcall.c.
COVERS_swi := swi regcall
COVERS_hardware := hardware hardwareswi
COVERS_meminfo := meminfo meminfoswi
COVERS_dynarea := dynarea dynareaswi
COVERS_heap := heap heapswi
COVERS_wrch := oswrch
COVERS_mouse := mouse
COVERS_key := key
COVERS_readline := readline readlineswi
COVERS_realtime := realtime
COVERS_cmos := cmos
COVERS_iic := iic iicswi
# OS_PlatformFeatures 34's register layer is in c/swi.c and is covered by
# Sources/Kernel/test/swi.
COVERS_cpufeature := cpufeature
COVERS_mmuctl := mmuctl mmuctlswi
COVERS_vdu := vducursor vducursoft vducolour vduclear vduplot vducontrol vduttx vduwrch vduinit vduvars vdumode vduscreen vduclip vduspec vdugraf vducurve vduline vdufill vdupalette vdu5 vdulegacy vduchange vdusprite vdusave vducopy vduscreenmode vduscreenmodeswi vdupointer vduptrscale
COVERS_armid := armid
covers = $(or $(COVERS_$(1)),$(1))

$(CHECKED_TESTS:%=%checkcoverage): %checkcoverage: $$(call testdir,$$*)/checks.c \
                                                  $(BUILD)/Sources/Kernel/kernel-version.h
	@rm -rf $(BUILD)/tests/cov/$*-checks
	@mkdir -p $(BUILD)/tests/cov/$*-checks
	@$(HOSTCC) $(HOST_BASE_CFLAGS) --coverage \
	  -o $(BUILD)/tests/cov/$*-checks/$*-checks $(call testdir,$*)/checks.c \
	  $(TEST_LIB)/check.c $(HOST_STUB_SRC) \
	  $(TEST_LIB)/format.c $(TEST_LIB)/report_stdout.c \
	  $(patsubst %,$(KERNEL_DIR)/c/%.c,\
	     $(filter-out $(STANDS_IN_FOR_$*),$(KERNEL_CNAMES))) \
	  2> $(BUILD)/tests/cov/$*-checks/cc.log \
	  || { cat $(BUILD)/tests/cov/$*-checks/cc.log; exit 1; }
	@cd $(BUILD)/tests/cov/$*-checks && ./$*-checks > /dev/null
	@for c in $(call covers,$*); do \
	  gcov -b -o $(BUILD)/tests/cov/$*-checks \
	    $(BUILD)/tests/cov/$*-checks/$*-checks-$$c.gcda; \
	  mv $$c.c.gcov $(BUILD)/tests/cov/$*-checks/; \
	done > $(BUILD)/tests/cov/$*-checks/gcov.log
	@rm -f *.h.gcov
	@awk -v tag=$*checkcoverage -f tools/build/covsum.awk \
	  $(BUILD)/tests/cov/$*-checks/gcov.log \
	  > $(BUILD)/tests/cov/$*-checks/summary
	@cat $(BUILD)/tests/cov/$*-checks/summary
	@for c in $(call covers,$*); do \
	  if grep -Fq '#####' $(BUILD)/tests/cov/$*-checks/$$c.c.gcov; then \
	    echo "  lines the checks never reach in $$c.c:"; \
	    grep -Fn '#####' $(BUILD)/tests/cov/$*-checks/$$c.c.gcov \
	      | sed 's/^/    /'; \
	  fi; \
	done

# The configurations machinecheck covers.
MACHINECHECK_ROWS := IOMD $(MATRIX)
# machinecheck holds Sources/Kernel/hdr/machine.h's switches (NoARMv6 and so on) to
# the KernelWS_ names in Sources/Kernel/hdr/kernel-ws.h, under each row's arm.
#
# The Wimp's SWI numbers, against the Wimp's own table.
swischeck:
	@python3 tools/check/swischeck.py

# Each module's CMHG service list, against the services its handler answers
# (switch case labels, or `on` values in s/boundaries). Naming any service
# enables the kernel's fast table, so an unlisted service never arrives.
servicecheck:
	@python3 tools/check/servicecheck.py Sources/Desktop/Wimp/cmhg/modhead \
	  Sources/Desktop/Wimp/c/wimp_module.c
	@python3 tools/check/servicecheck.py Sources/Desktop/RAMFSFiler/cmhg/modhead \
	  Sources/Desktop/RAMFSFiler/c/rfsmod.c
	@python3 tools/check/servicecheck.py Sources/Desktop/ResourceFiler/cmhg/modhead \
	  Sources/Desktop/ResourceFiler/c/rsfmod.c
	@python3 tools/check/servicecheck.py Sources/Desktop/Switcher/cmhg/modhead \
	  Sources/Desktop/Switcher/c/swmod.c
	@python3 tools/check/servicecheck.py Sources/Desktop/TaskWindow/cmhg/modhead \
	  Sources/Desktop/TaskWindow/c/twmod.c
	@python3 tools/check/servicecheck.py Sources/Desktop/Filer/cmhg/modhead \
	  Sources/Desktop/Filer/c/module.c
	@python3 tools/check/servicecheck.py Sources/Desktop/Free/cmhg/modhead \
	  Sources/Desktop/Free/c/freemod.c
	@python3 tools/check/servicecheck.py Sources/Desktop/ShellCLI/cmhg/ShellCLIHdr \
	  Sources/Desktop/ShellCLI/c/shellcli.c
	@python3 tools/check/servicecheck.py Sources/Desktop/WindowScroll/cmhg/modhead \
	  Sources/Desktop/WindowScroll/c/windowscroll.c
	@python3 tools/check/servicecheck.py Sources/Desktop/FilerAct/cmhg/modhead \
	  Sources/Desktop/FilerAct/c/fileract.c
	@python3 tools/check/servicecheck.py Sources/Video/Render/Colours/cmhg/modhead \
	  Sources/Video/Render/Colours/c/ctmodule.c
	@python3 tools/check/servicecheck.py Sources/HWSupport/RTC/cmhg/modhead \
	  Sources/HWSupport/RTC/c/rtcmodule.c
	@python3 tools/check/servicecheck.py Sources/Programmer/FPEmulator/cmhg/modhead \
	  Sources/Programmer/FPEmulator/c/arch/aarch32/fparm.c
	@python3 tools/check/servicecheck.py Sources/Video/Render/Fonts/FontManager500/cmhg/modhead \
	  Sources/Video/Render/Fonts/FontManager500/c/fmmodule.c
	@python3 tools/check/servicecheck.py Sources/Video/Render/Draw/cmhg/modhead \
	  Sources/Video/Render/Draw/c/draw_module.c
	@python3 tools/check/servicecheck.py Sources/Video/Render/SprExtend/cmhg/modhead \
	  Sources/Video/Render/SprExtend/c/sprx_module.c
	@python3 tools/check/servicecheck.py Sources/Networking/Econet/NetStatus/cmhg/modhead \
	  Sources/Networking/Econet/NetStatus/c/nsmodule.c
	@python3 tools/check/servicecheck.py Sources/HWSupport/SerialSpt/cmhg/modhead \
	  Sources/HWSupport/SerialSpt/s/boundaries
	@python3 tools/check/servicecheck.py Sources/Utilities/SystemDevs/cmhg/modhead \
	  Sources/Utilities/SystemDevs/s/boundaries
	@python3 tools/check/servicecheck.py Sources/FileSys/PipeFS/cmhg/modhead \
	  Sources/FileSys/PipeFS/s/boundaries
	@python3 tools/check/servicecheck.py Sources/Desktop/DisplayManager/cmhg/modhead \
	  Sources/Desktop/DisplayManager/s/boundaries
	@python3 tools/check/servicecheck.py Sources/Networking/MManager/cmhg/modhead \
	  Sources/Networking/MManager/s/boundaries
	@python3 tools/check/servicecheck.py Sources/Networking/Econet/Net/cmhg/modhead \
	  Sources/Networking/Econet/Net/s/boundaries
.PHONY: servicecheck

# The library's compiled-in message defaults, against the ROM's own copies.
# The legacy tree's header of defaults is a different revision from the ROM.
msgcheck:
	@python3 tools/check/msgcheck.py $(REF_ROM)

# Every error SpriteExtend raises, against its message file: each token must
# exist there, and a message with `%0` must be given a parameter.
# tools/build/sprite_shrink.py on every kind of sprite it meets: coverage
# and mean colour kept, everything not 37x37 32bpp (or 1555) untouched.
spriteshrinkcheck:
	@python3 Sources/Video/Render/SprExtend/test/spriteshrink/check.py
.PHONY: spriteshrinkcheck

sprxerrorcheck:
	@python3 tools/check/sprxerrors.py

# The generated BASIC keyword table the desktop library detokenises with,
# against BASIC's own two statements of it.
basictokencheck:
	@python3 tools/check/basictokens.py

# include/riscos/deskswi.h against the SWI numbers: every wrapper compiled,
# and each SVC checked to be the immediate it should be.
$(BUILD)/tests/deskswi-probe.o: tests/deskswi/probe.c include/riscos/deskswi.h \
                                include/riscos/swis.h
	@mkdir -p $(dir $@)
	$(CC) $(MOD_CFLAGS) -c $< -o $@

deskswichecks: $(BUILD)/tests/deskswi-probe.o
	@python3 tests/deskswi/check.py $<

# The command tables cmunge writes, against the ones CMunge wrote. Header
# offsets change in a port; what a command says about its arguments does not,
# so the reference ROM is the oracle for that.
cmungechecks:
	@python3 tests/cmunge/check.py

# Where mkrom puts a replaced module, and what modulecheck requires of a
# module header. Getting either wrong fails at boot, not in a check.
mkromchecks:
	@python3 tests/mkrom/check.py

modulecheckchecks:
	@python3 tests/modulecheck/check.py

# The AArch64 kernel's lazy floating-point state (Sources/Kernel/c/arch/aarch64/
# fp64.c) on a model of the registers; Sources/Kernel/test/fp64/run.py is the machine.
FP64_CHECKS := $(BUILD)/tests/fp64-checks

$(FP64_CHECKS): Sources/Kernel/test/fp64/checks.c $(KERNEL_DIR)/c/arch/aarch64/fp64.c \
                $(KERNEL_DIR)/c/arch/aarch64/fp64.h tests/lib/check.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(KERNEL_DIR)/c/arch/aarch64 -Itests/lib $(filter %.c,$^) -o $@

fp64checks: $(FP64_CHECKS)
	@$<

hostchecks: fp64checks
.PHONY: fp64checks

# The module code area's link: tools/build/copyrelocs.py on copied modules.
copyrelocschecks:
	@python3 tests/copyrelocs/check.py

# WindowScroll's scroll arithmetic, run against the original's.
#
# The emulator has no scroll wheel, so this is the only test of how wheel
# ticks become a distance and how the speed setting is honoured. extract.py
# lifts each implementation out of its own source at build time; a source
# that no longer holds what is asked for fails here.
WSSCROLL_SRC := $(LEGACY)/riscos-src/RiscOS/Sources/Desktop/WindowScroll
WSSCROLL_GEN := $(BUILD)/tests/wsscroll
WSSCROLL_EXTRACT := python3 Sources/Desktop/WindowScroll/test/wsscroll/extract.py

$(WSSCROLL_GEN)/ref-globals.h: $(WSSCROLL_SRC)/h/globals Sources/Desktop/WindowScroll/test/wsscroll/extract.py
	@mkdir -p $(dir $@)
	@$(WSSCROLL_EXTRACT) $< -o $@ \
	  --endblock '\}\s*scroll_type\s*;' --endblock '\}\s*settings_t\s*;'

$(WSSCROLL_GEN)/ref-utils.h: $(WSSCROLL_SRC)/h/utils Sources/Desktop/WindowScroll/test/wsscroll/extract.py
	@mkdir -p $(dir $@)
	@$(WSSCROLL_EXTRACT) $< -o $@ \
	  --endblock '\}\s*scroll_mode\s*;' --endblock '\}\s*scroll_target\s*;'

$(WSSCROLL_GEN)/ref-body.h: $(WSSCROLL_SRC)/c/task Sources/Desktop/WindowScroll/test/wsscroll/extract.py
	@mkdir -p $(dir $@)
	@$(WSSCROLL_EXTRACT) $< -o $@ --define SIGN \
	  --endblock '\}\s*scroll_vals\s*;' \
	  --func measure_distance --func scroll_vals_update

$(WSSCROLL_GEN)/port-types.h: Sources/Desktop/WindowScroll/c/windowscroll.h \
                              Sources/Desktop/WindowScroll/test/wsscroll/extract.py
	@mkdir -p $(dir $@)
	@$(WSSCROLL_EXTRACT) $< -o $@ \
	  --block 'enum scroll_type\s*\{' \
	  --block 'struct ws_settings\s*\{' \
	  --block 'enum scroll_mode\s*\{' \
	  --block 'struct scroll_target\s*\{' \
	  --block 'struct scroll_vals\s*\{'

$(WSSCROLL_GEN)/port-body.h: Sources/Desktop/WindowScroll/c/wstask.c \
                             Sources/Desktop/WindowScroll/test/wsscroll/extract.py
	@mkdir -p $(dir $@)
	@$(WSSCROLL_EXTRACT) $< -o $@ --define SIGN \
	  --func measure_distance --func scroll_vals_update

WSSCROLL_HDRS := $(addprefix $(WSSCROLL_GEN)/,ref-globals.h ref-utils.h \
                   ref-body.h port-types.h port-body.h)

$(WSSCROLL_GEN)/%.o: Sources/Desktop/WindowScroll/test/wsscroll/%.c Sources/Desktop/WindowScroll/test/wsscroll/wsscroll.h \
                     $(WSSCROLL_HDRS) $(KERNEL_GLOBALS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Desktop/WindowScroll/test/wsscroll -I$(WSSCROLL_GEN) -c $< -o $@

$(BUILD)/tests/wsscroll-checks: $(addprefix $(WSSCROLL_GEN)/,checks.o \
                                  reference.o ported.o) \
                                $(HOST_CHECK_OBJ) $(HOST_KERNEL_OBJ)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

wsscrollchecks: $(BUILD)/tests/wsscroll-checks
	@$<

.PHONY: wsscrollchecks

# The cached screen's dirty tiles (kernel/c/arch/aarch64/screendirty.c): the
# marks, their merging and taking, and a race of writer threads against a
# cleaner thread in which nothing written may be left uncleaned.
$(BUILD)/tests/screendirty-checks: tests/screendirty/checks.c \
                                   $(KERNEL_DIR)/c/arch/aarch64/screendirty.c \
                                   $(KERNEL_DIR)/c/arch/aarch64/screendirty.h \
                                   $(TEST_LIB)/check.c
	@mkdir -p $(dir $@)
	$(HOSTCC) -std=gnu17 -Wall -Wextra -Werror -g -O1 $(HOST_SANITISE) \
	  -I$(TEST_LIB) -I$(KERNEL_DIR)/c/arch/aarch64 -o $@ \
	  tests/screendirty/checks.c $(KERNEL_DIR)/c/arch/aarch64/screendirty.c \
	  $(TEST_LIB)/check.c -lpthread

screendirtychecks: $(BUILD)/tests/screendirty-checks
	@$<

hostchecks: screendirtychecks
.PHONY: screendirtychecks

# The parts of shared work (kernel/c/arch/aarch64/smpparts.h, smp_run): each
# part run exactly once, the caller back only when all are done, and a
# helper that arrives after the caller's time limit never running the job.
$(BUILD)/tests/smpparts-checks: tests/smpparts/checks.c \
                                $(KERNEL_DIR)/c/arch/aarch64/smpparts.h \
                                $(TEST_LIB)/check.c
	@mkdir -p $(dir $@)
	$(HOSTCC) -std=gnu17 -Wall -Wextra -Werror -g -O1 $(HOST_SANITISE) \
	  -I$(TEST_LIB) -I$(KERNEL_DIR)/c/arch/aarch64 -o $@ \
	  tests/smpparts/checks.c $(TEST_LIB)/check.c -lpthread

smppartschecks: $(BUILD)/tests/smpparts-checks
	@$<

hostchecks: smppartschecks
.PHONY: smppartschecks

# The pattern search, run against the original's.
#
# RISC_OSLib's txtfind.c and txtregexp.c compile straight from the legacy
# tree; Sources/Desktop/FilerFind/test/find/shim supplies the five text-access calls both sides make,
# over one flat array. -m32 and -funsigned-char are required: the original
# assumes 32-bit pointers (a twelve-word handle holds its own struct) and an
# unsigned plain char (characters index a set).
FIND_RLIB := $(LEGACY)/riscos-src/RiscOS/Sources/Lib/RISC_OSLib/rlib
FIND_GEN  := $(BUILD)/tests/find
# The original includes its own headers the way its own build laid them out.
FIND_INC  := $(FIND_GEN)/inc
# Sources/Lib/CLib/include is for the library's own two files only: its stdio.h and
# ctype.h describe the module's library, and a test that reached them would
# lose printf.
FIND_FLAGS := -m32 -funsigned-char -w -O1 -ISources/Desktop/FilerFind/test/find/shim -I$(FIND_INC) \
              -ISources/Lib/Desktop/include
FIND_PORT_FLAGS := $(FIND_FLAGS) -ISources/Lib/CLib/include -std=gnu17 -ISources/Lib/Desktop/text
FIND_REF_FLAGS := $(FIND_FLAGS) -std=gnu89 -DFIELDNUM -DALLOW_OLD_PATTERNS

$(FIND_INC)/EditIntern/txtregexp.h: $(FIND_RLIB)/EditIntern/h/txtregexp
	@mkdir -p $(dir $@)
	@ln -sf $(abspath $<) $@
$(FIND_INC)/EditIntern/txtfind.h: $(FIND_RLIB)/EditIntern/h/txtfind
	@mkdir -p $(dir $@)
	@ln -sf $(abspath $<) $@

FIND_HDRS := $(FIND_INC)/EditIntern/txtregexp.h $(FIND_INC)/EditIntern/txtfind.h

$(FIND_GEN)/txtregexp.o: $(FIND_RLIB)/c/txtregexp $(FIND_HDRS) \
                         $(wildcard Sources/Desktop/FilerFind/test/find/shim/*.h)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FIND_REF_FLAGS) -x c -c $< -o $@
$(FIND_GEN)/txtfind.o: $(FIND_RLIB)/c/txtfind $(FIND_HDRS) \
                       $(wildcard Sources/Desktop/FilerFind/test/find/shim/*.h)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FIND_REF_FLAGS) -x c -c $< -o $@

$(FIND_GEN)/%-port.o: Sources/Lib/Desktop/text/%.c Sources/Lib/Desktop/text/regexp.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FIND_PORT_FLAGS) -c $< -o $@

$(FIND_GEN)/shim.o: Sources/Desktop/FilerFind/test/find/shim.c $(wildcard Sources/Desktop/FilerFind/test/find/shim/*.h)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FIND_FLAGS) -std=gnu17 -c $< -o $@

$(FIND_GEN)/checks.o: Sources/Desktop/FilerFind/test/find/checks.c $(FIND_HDRS) \
                      $(wildcard Sources/Desktop/FilerFind/test/find/shim/*.h)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FIND_REF_FLAGS) -std=gnu17 -Itests/lib -c $< -o $@

$(FIND_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FIND_FLAGS) -Itests/lib -c $< -o $@

$(BUILD)/tests/find-checks: $(addprefix $(FIND_GEN)/,checks.o shim.o \
                              txtregexp.o txtfind.o regexp-port.o find-port.o \
                              check.o)
	$(HOSTCC) -m32 -o $@ $^

findchecks: $(BUILD)/tests/find-checks
	@$<

.PHONY: findchecks

machinecheck:
	@python3 tools/check/machinecheck.py $(KERNEL_DIR)/hdr/machine.h --cc "$(CC)" \
	  --define ROM_SIZE=$(ROM_SIZE) \
	  IOMD=$(MACHINE_NAME)=$(KERNEL_DIR)/hdr/kernel-ws.h \
	  $(foreach r,$(MATRIX),$(r)=$(MATRIX_MACHINE_$(r))=$(KERNEL_DIR)/hdr/kernel-ws.h)

hostchecks: a64checks ukcharscheck ukterritorychecks toolboxlibchecks windowchecks adfschecks adfsfilerchecks bufferschecks screenmodeschecks bcmvideochecks scrnsetupchecks dosfschecks toolboxchecks devicefschecks residuechecks modetableschecks msgtranschecks resourcefschecks territorychecks fileswitchchecks obeychecks mousemodchecks scrsaverchecks blendtablechecks colourtranschecks mimemapchecks mbufmanagerchecks dhcpchecks netchecks netfschecks netprintchecks rtcchecks squashchecks hdrcheck machinecheck swischeck servicecheck msgcheck basictokencheck deskswichecks desktopcheck cmungechecks marshalcheck mkromchecks modulecheckchecks copyrelocschecks wsscrollchecks findchecks fileractchecks filerswischecks dragobjchecks desktopmodchecks dragsprchecks filterchecks freechecks taskwinchecks switcherchecks pinboardchecks filerchecks thumbjpegchecks thumbdcchecks wimpchecks ramfschecks filecorechecks fontmanagerchecks unicodechecks drawchecks drawappchecks sprxchecks netstatuschecks iicscanchecks bootnetchecks romfontschecks bootcmdschecks fslockchecks cdfsdriverchecks cdatapichecks cdfschecks cdfsfilerchecks netfilerchecks spriteutilschecks sermousechecks ps2driverchecks serialsptchecks systemdevschecks pipefschecks supersamplechecks hourglasschecks hourshapecheck itablechecks resfilerchecks scrblankchecks basicchecks basic64checks basictranschecks internetchecks rumpcheck $(CHECKED_TESTS:%=%checks)
.PHONY: hostchecks

# The targets under `test` that start the emulator wait for `everything`
# through an order-only prerequisite, without being made out of date by it.
$(TESTS:%=%test) $(CHAIN_TESTS:%=%test) $(HOST_TESTS:%=%hosttest) \
$(FILE_TESTS:%=%filetest) halvideotrace: | everything

test: residuechecks residueromchecks everything interfaces-check joins verify hdrcheck machinecheck msgcheck kreschecks basictokencheck premmu secondarch wsscrollchecks $(CHECKED_TESTS:%=%checks) $(HOST_TESTS:%=%hosttest) \
      $(TESTS:%=%test) $(CHAIN_TESTS:%=%test) halvideotrace

# All coverage measurements, joined into one summary file. Not part of `test`:
# it rebuilds every ported file with coverage instrumentation and takes
# minutes. Run it after anything that moves a boundary.
coverage: $(CHECKED_TESTS:%=%checkcoverage) $(HOST_TESTS:%=%coverage)
	@cat $(CHECKED_TESTS:%=$(BUILD)/tests/cov/%-checks/summary) \
	     $(HOST_TESTS:%=$(BUILD)/tests/cov/%-host/summary) \
	     > $(BUILD)/tests/cov/summary
	@echo "every file of every recipe: $(BUILD)/tests/cov/summary"

# gcov writes its .gcov files into the working directory before each recipe
# moves them, so the coverage recipes must not run in parallel.
# .NOTPARALLEL: coverage serialises coverage's prerequisites.
.NOTPARALLEL: coverage

# Test recipes invoke recursive makes in the same BUILD directory. Finish
# each prerequisite before another can rebuild or replace its outputs.
.NOTPARALLEL: test

# Every place a ported module's C still names a register. MODULE= narrows it
# to one module; REGCENSUS_ARGS takes --list (every site), --vars (register
# frames resolved) and --kinds (boundaries found). See
# tools/check/regcensus.py.
regcensus:
	@python3 tools/check/regcensus.py $(REGCENSUS_ROOTS) \
		$(if $(MODULE),--module $(MODULE)) $(REGCENSUS_ARGS)
.PHONY: regcensus

# The ratchet over the same count: no file may name more registers than
# tools/check/regcensus.baseline says, outside the code
# tools/check/regcensus.allow lists as modelling registers on purpose. When a
# count falls, `make regbaseline` holds it there.
REGCENSUS_ROOTS = modules Desktop kernel lib hal include tests
regcheck:
	@python3 tools/check/regcensus.py $(REGCENSUS_ROOTS) \
		--check tools/check/regcensus.baseline
regbaseline:
	@python3 tools/check/regcensus.py $(REGCENSUS_ROOTS) \
		--write-baseline tools/check/regcensus.baseline
hostchecks: regcheck
.PHONY: regcheck regbaseline

# Veneers generated by tools/build/marshal.py from each module's
# s/boundaries declaration, compared instruction for instruction with the
# hand-written ones. MARSHAL_MODULES finds declarations in every component
# (mk/components.mk) and the directories one level inside it.
MARSHAL_MODULES = $(patsubst %/s/boundaries,%,\
                    $(sort $(wildcard $(addsuffix /s/boundaries,$(COMPONENT_DIRS)) \
                               $(addsuffix /*/s/boundaries,$(COMPONENT_DIRS)))))

marshalcheck:
	@python3 tests/marshal/check.py
	@python3 tests/marshal/exits.py
	@mkdir -p $(BUILD)/marshal
	@python3 tools/build/marshal.py --check $(MARSHAL_MODULES) \
	  --build $(BUILD)/marshal --cc "$(CC) $(MOD_CFLAGS)"
	@python3 tools/build/marshal.py --check-swi $(SWI_DECLS)

# The SWI wrappers modules call each other through. Generated into the tree,
# not the build directory, because a header included everywhere would
# otherwise have to be built before every object; marshalcheck keeps the
# committed file matching its declaration.
SWI_DECLS = $(wildcard include/riscos/*.swi)

# marshal.py says where each declaration's header goes. It is loaded by path:
# `import marshal` finds Python's builtin module whatever sys.path says.
swiheaders: $(SWI_DECLS)
	@for d in $(SWI_DECLS); do \
	  python3 tools/build/marshal.py --swi $$d \
	    --header $$(python3 -c "import importlib.util as u;\
	      s=u.spec_from_file_location('marshal_tool','tools/build/marshal.py');\
	      m=u.module_from_spec(s);s.loader.exec_module(m);\
	      print(m.header_named('$$d'))"); \
	done
.PHONY: swiheaders
.PHONY: marshalcheck

# The AArch64 veneers, clib's AArch64 parts, the kernel's UtilityModule SWI
# entry and TaskWindow's task switch, run under qemu-aarch64.
a64checks:
	@python3 tests/marshal/a64.py $(BUILD)/marshala64
	@python3 Sources/Lib/CLib/test/clib/a64.py $(BUILD)/clib-a64
	@python3 Sources/Lib/RWPI/test/rwpi/a64.py $(BUILD)/rwpi-a64
	@python3 Sources/Kernel/test/utility/a64.py $(BUILD)/utility-a64
	@python3 Sources/Desktop/TaskWindow/test/taskwin/a64.py $(BUILD)/taskwin-a64
	@python3 Sources/Networking/Inet/Internet/test/internet/a64.py $(BUILD)/internet-a64
	@python3 Sources/Apps/Alarm/test/alarm/a64.py $(BUILD)/alarm-a64
	@python3 Sources/Programmer/ARM32/test/arm32/a64.py $(BUILD)/arm32-a64
.PHONY: a64checks

# Which public kernel entry points are implemented in C, assembly, or a
# deliberate mixture of the two. The report is parsed from the same SWI and
# vector tables that build the image; interfaces-check keeps their names and
# targets readable by the tool as those tables change.
interfaces:
	@python3 tools/check/interfaces.py --summary

interfaces-check:
	@python3 tools/check/interfaces.py --check --format json > /dev/null

# Join faults between assembly and C that can be found by reading: each is
# about instruction order, not meaning. See tools/check/joins.py.
JOINS_ARGS = $(KERNEL_ASM_SRC) $(KERNEL_VENEERS) \
	  $(addprefix --macros ,$(wildcard $(HDRSRC)/Macros $(KERNEL_DIR)/hdr/*)) \
	  $(addprefix --c ,$(sort $(wildcard $(KERNEL_DIR)/c/*/*.c $(KERNEL_DIR)/c/*/*/*.c))) \
	  $(addprefix --c ,$(sort $(wildcard $(ABORTTRAP_DIR)/c/*))) \
	  --ld $(KERNEL_DIR)/kernel.ld \
	  --estate hal --estate tests --estate lib --estate include \
	  --estate $(KERNEL_DIR)/c --estate $(KERNEL_DIR)/aborttrap \
	  --ld $(KERNEL_DIR)/kernel.ld \
	  --pd "FreezeDevRel SETL {FALSE}" \
	  --pd "RISCOS_KERNEL SETL {TRUE}" \
	  --gas-flags '$(AS) $(KERNEL_GAS_FLAGS)'

# It preprocesses the kernel's assembly, so it needs what assembling it needs.
JOINS_DEPS = $(KERNEL_GLOBALS) $(BUILD)/defines.stamp $(BUILD)/Sources/Kernel/kernel-version.h \
             $(KERNEL_VENEERS)

joins: $(JOINS_DEPS)
	@python3 tools/check/joins.py $(JOINS_ARGS)

.PHONY: test coverage joins secondarch regcensus marshalcheck swiheaders interfaces interfaces-check kreschecks $(TESTS:%=%test) $(CHAIN_TESTS:%=%test) $(FILE_TESTS:%=%filetest) \
        $(HOST_TESTS:%=%hosttest) $(CHECKED_TESTS:%=%checks) \
        $(CHECKED_TESTS:%=%checkcoverage) $(HOST_TESTS:%=%coverage)

# ---------------------------------------------------------------------------
# Modules
#
# A RISC OS module is a flat, position independent image that runs from ROM
# or anywhere in the RMA. Its C is built with -fropi -frwpi: read-only data
# PC-relative, writable statics through sb, in workspace claimed at run time.

MOD_VIDC20_DIR  := Sources/Video/HWVideo/VIDC20Video
# The driver core is shared with the IOMD HAL: see include/vidc20/vidc20_core.h
VIDC20_CORE_CSRC := Sources/Lib/VIDC20/modes.c \
                    Sources/Lib/VIDC20/palette.c \
                    Sources/Lib/VIDC20/display.c
MOD_VIDC20_CSRC := $(MOD_VIDC20_DIR)/init.c \
                   $(MOD_VIDC20_DIR)/graphicsv.c \
                   $(MOD_VIDC20_DIR)/monitorid.c \
                   $(VIDC20_CORE_CSRC) \
                   Sources/Lib/Compiler/arm32/aeabi_divide.c
# The header from cmhg/modhead and every entry from s/boundaries; the
# rules are in its module.mk, after Sources/Lib/RWPI.
MOD_VIDC20_GEN  := $(BUILD)/modules/vidc20video
MOD_VIDC20_OBJ  := $(MOD_VIDC20_GEN)/modhead.o \
                   $(MOD_VIDC20_GEN)/veneers.o \
                   $(patsubst %.c,$(BUILD)/%.o,$(MOD_VIDC20_CSRC))
MOD_VIDC20_ELF  := $(BUILD)/vidc20video.elf
MOD_VIDC20_MOD  := $(BUILD)/vidc20video.mod

# Sources/Kernel/hdr is last on the path so the typed SWI wrappers in
# riscos/deskswi.h can find the kernel's Global headers; being last, it
# shadows nothing.
MOD_INCLUDES := -Iinclude -Iinclude/freestanding -ISources/Hardware/IOMD/include \
                -I$(MOD_VIDC20_DIR)/include -ISources/Kernel/hdr
# The version GraphicsV_ReadInfo gives, as cmhg/modhead's in BCD.
MOD_VIDC20_BCD := 0x09
MOD_DEFINES  := -DMODULE_VERSION_BCD=$(MOD_VIDC20_BCD) $(MOD_TRACE)

# Position independent both ways round: see Sources/Lib/RWPI. Optimised for size,
# because a module has to fit in the space the ROM chain leaves for it.
MOD_PIFLAGS := -fropi -frwpi -Os

# The interpreters are named here because OURS_MODULES is simply expanded
# and has to see them.
BASIC_MOD    := $(BUILD)/modules/BASIC.mod
BASIC64_MOD  := $(BUILD)/modules/BASIC64.mod
include mk/kernelres.mk

# ShellCLI: header from tools/build/cmunge.py, C compiled by clang, veneers
# for the four entry conventions C cannot be written to. It reaches the OS
# through SWIs alone, links Sources/Lib/CLib, and keeps all mutable state in
# workspace reached by the private word.
# -fpie and hidden address-taken externs keep its references relative. Final
# relocations are retained so modulecheck can reject absolute pointers, GOT
# storage and external C dependencies before the flat image is made.
# Not compared with the reference ROM's ShellCLI.
SHELL_DIR  := Sources/Desktop/ShellCLI
SHELL_OBJS := $(BUILD)/Sources/Desktop/ShellCLI/modhead.o \
              $(BUILD)/Sources/Desktop/ShellCLI/shellcli.o \
              $(BUILD)/Sources/Desktop/ShellCLI/veneers.o \
              $(BUILD)/Sources/Desktop/ShellCLI/enter.o
SHELL_MOD  := $(BUILD)/Desktop/ShellCLI.mod

# -fno-jump-tables is required: a switch lowered to a table of addresses
# needs load-time relocation, which a ROM module cannot have. The symptom is
# "a ROM module allocates its own workspace" and a .data.rel.ro section.
MOD_CFLAGS := --target=arm-none-eabi -ffreestanding -fno-builtin \
              -fpie -fvisibility=hidden -fno-jump-tables \
              -fno-stack-protector -fno-common -mno-unaligned-access \
              $(ARCH_MACHINE) -Os -g -std=gnu17 \
              -fno-unwind-tables -fno-asynchronous-unwind-tables \
              -Wall -Wextra -Wno-unused-parameter -MMD -MP \
              -Iinclude -I$(BUILD)/include -ISources/Kernel/hdr -I$(SHELL_DIR)/c

# The generated header is on this module's include path only.
SHELL_CFLAGS := $(MOD_CFLAGS) -I$(BUILD)/Sources/Desktop/ShellCLI

$(BUILD)/Sources/Desktop/ShellCLI/modhead.S: $(SHELL_DIR)/cmhg/ShellCLIHdr \
                                     $(SHELL_DIR)/VersionNum hdr/Services \
                                     hdr/SWIs tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(SHELL_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/ShellCLI/modhead.o: $(BUILD)/Sources/Desktop/ShellCLI/modhead.S
	$(CC) $(MOD_CFLAGS) -x assembler-with-cpp -c $< -o $@

# Module entries and their C prototypes, generated by tools/build/marshal.py.
$(BUILD)/Sources/Desktop/ShellCLI/veneers.S: $(SHELL_DIR)/s/boundaries \
                                     $(SHELL_DIR)/cmhg/ShellCLIHdr \
                                     tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(SHELL_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/ShellCLI/shell_veneers.h

$(BUILD)/Sources/Desktop/ShellCLI/shell_veneers.h: $(BUILD)/Sources/Desktop/ShellCLI/veneers.S

$(BUILD)/Sources/Desktop/ShellCLI/veneers.o: $(BUILD)/Sources/Desktop/ShellCLI/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SHELL_CFLAGS) -x assembler-with-cpp -c $< -o $@

# The two environment handlers, hand-written: see the head of the file.
$(BUILD)/Sources/Desktop/ShellCLI/enter.o: $(SHELL_DIR)/s/aarch32/enter.S
	@mkdir -p $(dir $@)
	$(CC) $(SHELL_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/ShellCLI/shellcli.o: $(SHELL_DIR)/c/shellcli.c Makefile \
                                      $(BUILD)/Sources/Desktop/ShellCLI/shell_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SHELL_CFLAGS) -ISources/Lib/CLib/include -c $< -o $@

$(BUILD)/Desktop/ShellCLI.elf: $(SHELL_OBJS) $(BUILD)/lib/libclib.a mk/ld/module.ld tools/build/modulecheck.py Makefile
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld -o $@.tmp $(SHELL_OBJS) $(BUILD)/lib/libclib.a
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SHELL_MOD): $(BUILD)/Desktop/ShellCLI.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ShellCLI - ported)"

-include $(BUILD)/Sources/Desktop/ShellCLI/shellcli.d $(BUILD)/Sources/Desktop/ShellCLI/enter.d

# WindowScroll. Built like ShellCLI; adds an EventV claimant whose handler
# runs at interrupt time and sets the word the task polls on.
WS_DIR  := Sources/Desktop/WindowScroll
WS_OBJS := $(BUILD)/Sources/Desktop/WindowScroll/modhead.o \
           $(BUILD)/Sources/Desktop/WindowScroll/windowscroll.o \
           $(BUILD)/Sources/Desktop/WindowScroll/wstask.o \
           $(BUILD)/Sources/Desktop/WindowScroll/wsutils.o \
           $(BUILD)/Sources/Desktop/WindowScroll/veneers.o
WS_MOD  := $(BUILD)/Desktop/WindowScroll.mod
WS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
             -I$(WS_DIR)/c -I$(BUILD)/Sources/Desktop/WindowScroll

$(BUILD)/Sources/Desktop/WindowScroll/modhead.S: $(WS_DIR)/cmhg/modhead tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(WS_DIR) -Ihdr -ISources/Kernel/hdr/Global \
	  -DCOMPONENT=WindowScroll

$(BUILD)/Sources/Desktop/WindowScroll/%.o: $(WS_DIR)/c/%.c Makefile \
                                  $(BUILD)/Sources/Desktop/WindowScroll/ws_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(WS_CFLAGS) -c $< -o $@

# Module entries, the two commands, the service handler and the EventV
# claimant, with their C prototypes. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/WindowScroll/veneers.S: $(WS_DIR)/s/boundaries \
                                         $(WS_DIR)/cmhg/modhead \
                                         tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(WS_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/WindowScroll/ws_veneers.h

$(BUILD)/Sources/Desktop/WindowScroll/ws_veneers.h: \
    $(BUILD)/Sources/Desktop/WindowScroll/veneers.S

$(BUILD)/Sources/Desktop/WindowScroll/veneers.o: \
    $(BUILD)/Sources/Desktop/WindowScroll/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(WS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/WindowScroll/modhead.o: $(BUILD)/Sources/Desktop/WindowScroll/modhead.S
	$(CC) $(WS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/WindowScroll.elf: $(WS_OBJS) $(BUILD)/lib/libdesktop.a $(BUILD)/lib/libclib.a \
                                   mk/ld/module.ld tools/build/modulecheck.py Makefile
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(WS_OBJS) $(BUILD)/lib/libdesktop.a $(BUILD)/lib/libclib.a
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(WS_MOD): $(BUILD)/Desktop/WindowScroll.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, WindowScroll - ported)"

-include $(WS_OBJS:.o=.d)

windowscroll: $(WS_MOD)
.PHONY: windowscroll

# FilerSWIs: three SWIs that build the Filer's messages. Owns a SWI chunk.
FS_DIR  := Sources/Desktop/FilerSWIs
FS_OBJS := $(BUILD)/Sources/Desktop/FilerSWIs/modhead.o \
           $(BUILD)/Sources/Desktop/FilerSWIs/filerswis.o \
           $(BUILD)/Sources/Desktop/FilerSWIs/fsmodule.o \
           $(BUILD)/Sources/Desktop/FilerSWIs/fssend_riscos.o \
           $(BUILD)/Sources/Desktop/FilerSWIs/veneers.o
FS_MOD  := $(BUILD)/Desktop/FilerSWIs.mod
FS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(FS_DIR)/c \
             -I$(BUILD)/Sources/Desktop/FilerSWIs

$(BUILD)/Sources/Desktop/FilerSWIs/modhead.S: $(FS_DIR)/cmhg/modhead $(FS_DIR)/VersionNum \
                                      tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(FS_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/FilerSWIs/%.o: $(FS_DIR)/c/%.c Makefile \
                                $(BUILD)/Sources/Desktop/FilerSWIs/fs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FS_CFLAGS) -c $< -o $@

# The veneers, and the prototypes of the four routines the SWI chunk
# dispatches to. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/FilerSWIs/veneers.S: $(FS_DIR)/s/boundaries \
                                      $(FS_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(FS_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/FilerSWIs/fs_veneers.h

$(BUILD)/Sources/Desktop/FilerSWIs/fs_veneers.h: $(BUILD)/Sources/Desktop/FilerSWIs/veneers.S

$(BUILD)/Sources/Desktop/FilerSWIs/veneers.o: $(BUILD)/Sources/Desktop/FilerSWIs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/FilerSWIs/modhead.o: $(BUILD)/Sources/Desktop/FilerSWIs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(FS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/FilerSWIs.elf: $(FS_OBJS) $(CLIB) mk/ld/module.ld \
                                tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(FS_OBJS) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(FS_MOD): $(BUILD)/Desktop/FilerSWIs.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, FilerSWIs - ported)"

filerswis: $(FS_MOD)
.PHONY: filerswis

# DragAnObject: DragASprite with the sprite rendered on the spot. Two SWIs,
# no workspace and no initialisation entry; a drag's state lives on the stack
# of the SWI that sets it up.
DAO_DIR  := Sources/Desktop/DragAnObj
DSK_DIR  := Sources/Desktop/Desktop
DSP_DIR  := Sources/Desktop/DragASprit
FLT_DIR  := Sources/Desktop/Filter
FRE_DIR  := Sources/Desktop/Free
TWN_DIR  := Sources/Desktop/TaskWindow
SWI_DIR  := Sources/Desktop/Switcher
PBD_DIR  := Sources/Desktop/Pinboard
DAO_OBJS := $(BUILD)/Sources/Desktop/DragAnObj/modhead.o \
            $(BUILD)/Sources/Desktop/DragAnObj/dragobj.o \
            $(BUILD)/Sources/Desktop/DragAnObj/daomodule.o \
            $(BUILD)/Sources/Desktop/DragAnObj/daorender_riscos.o \
            $(BUILD)/Sources/Desktop/DragAnObj/veneers.o
DAO_MOD  := $(BUILD)/Desktop/DragAnObject.mod
DAO_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(DAO_DIR)/c \
              -I$(BUILD)/Sources/Desktop/DragAnObj

$(BUILD)/Sources/Desktop/DragAnObj/modhead.S: $(DAO_DIR)/cmhg/modhead \
                                      $(DAO_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(DAO_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/DragAnObj/%.o: $(DAO_DIR)/c/%.c Makefile \
                                $(BUILD)/Sources/Desktop/DragAnObj/dao_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(DAO_CFLAGS) -c $< -o $@

# The SWI handler, the calls to the renderer, and their prototypes. See
# tools/build/marshal.py.
$(BUILD)/Sources/Desktop/DragAnObj/veneers.S: $(DAO_DIR)/s/boundaries \
                                      $(DAO_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(DAO_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/DragAnObj/dao_veneers.h

$(BUILD)/Sources/Desktop/DragAnObj/dao_veneers.h: $(BUILD)/Sources/Desktop/DragAnObj/veneers.S

$(BUILD)/Sources/Desktop/DragAnObj/veneers.o: $(BUILD)/Sources/Desktop/DragAnObj/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(DAO_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/DragAnObj/modhead.o: $(BUILD)/Sources/Desktop/DragAnObj/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(DAO_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/DragAnObject.elf: $(DAO_OBJS) $(CLIB) mk/ld/module.ld \
                                   tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(DAO_OBJS) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(DAO_MOD): $(BUILD)/Desktop/DragAnObject.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, DragAnObject - ported)"

-include $(DAO_OBJS:.o=.d)

draganobject: $(DAO_MOD)
.PHONY: draganobject

# Desktop: the module that starts the desktop. The old font-drawn welcome
# screen is not built; NewBanner's template window replaced it.
DSK_OBJS := $(BUILD)/Sources/Desktop/Desktop/modhead.o \
            $(BUILD)/Sources/Desktop/Desktop/desktop.o \
            $(BUILD)/Sources/Desktop/Desktop/dsktask.o \
            $(BUILD)/Sources/Desktop/Desktop/dskbanner.o \
            $(BUILD)/Sources/Desktop/Desktop/dskmodule.o \
            $(BUILD)/Sources/Desktop/Desktop/veneers.o
DSK_MOD  := $(BUILD)/Desktop/DesktopMod.mod
DSK_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(DSK_DIR)/c -I$(BUILD)/Sources/Desktop/Desktop

$(BUILD)/Sources/Desktop/Desktop/modhead.S: $(DSK_DIR)/cmhg/modhead \
                                    $(DSK_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(DSK_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/Desktop/%.o: $(DSK_DIR)/c/%.c Makefile \
                              $(BUILD)/Sources/Desktop/Desktop/dsk_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(DSK_CFLAGS) -c $< -o $@

# The module's one command and its prototype. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/Desktop/veneers.S: $(DSK_DIR)/s/boundaries \
                                    $(DSK_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(DSK_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/Desktop/dsk_veneers.h

$(BUILD)/Sources/Desktop/Desktop/dsk_veneers.h: $(BUILD)/Sources/Desktop/Desktop/veneers.S

$(BUILD)/Sources/Desktop/Desktop/veneers.o: $(BUILD)/Sources/Desktop/Desktop/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(DSK_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Desktop/modhead.o: $(BUILD)/Sources/Desktop/Desktop/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(DSK_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/DesktopMod.elf: $(DSK_OBJS) $(DESKTOP_LIB) $(CLIB) \
                                 mk/ld/module.ld tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(DSK_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(DSK_MOD): $(BUILD)/Desktop/DesktopMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Desktop - ported)"

-include $(DSK_OBJS:.o=.d)

desktopmodule: $(DSK_MOD)
.PHONY: desktopmodule

# DragASprite: a sprite dragged in place of an outline. OS entries come from
# s/boundaries; the three routines the Wimp calls during a drag stay in
# s/veneer.S, as they are called inside another task's poll with the boxes in
# registers.
DSP_OBJS := $(BUILD)/Sources/Desktop/DragASprit/modhead.o \
            $(BUILD)/Sources/Desktop/DragASprit/dragspr.o \
            $(BUILD)/Sources/Desktop/DragASprit/dsmodule.o \
            $(BUILD)/Sources/Desktop/DragASprit/dsstart.o \
            $(BUILD)/Sources/Desktop/DragASprit/dsdrag.o \
            $(BUILD)/Sources/Desktop/DragASprit/dsshadow.o \
            $(BUILD)/Sources/Desktop/DragASprit/veneers.o \
            $(BUILD)/Sources/Desktop/DragASprit/veneer.o
DSP_MOD  := $(BUILD)/Desktop/DragASpriteMod.mod
DSP_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(DSP_DIR)/c \
              -I$(BUILD)/Sources/Desktop/DragASprit

$(BUILD)/Sources/Desktop/DragASprit/modhead.S: $(DSP_DIR)/cmhg/modhead \
                                       $(DSP_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(DSP_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/DragASprit/%.o: $(DSP_DIR)/c/%.c Makefile \
                                 $(BUILD)/Sources/Desktop/DragASprit/ds_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(DSP_CFLAGS) -c $< -o $@

# Module entries and their C prototypes, generated by tools/build/marshal.py.
$(BUILD)/Sources/Desktop/DragASprit/veneers.S: $(DSP_DIR)/s/boundaries \
                                       $(DSP_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(DSP_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/DragASprit/ds_veneers.h

$(BUILD)/Sources/Desktop/DragASprit/ds_veneers.h: $(BUILD)/Sources/Desktop/DragASprit/veneers.S

$(BUILD)/Sources/Desktop/DragASprit/veneers.o: $(BUILD)/Sources/Desktop/DragASprit/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(DSP_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/DragASprit/veneer.o: $(DSP_DIR)/s/aarch32/veneer.S
	@mkdir -p $(dir $@)
	$(CC) $(DSP_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/DragASprit/modhead.o: $(BUILD)/Sources/Desktop/DragASprit/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(DSP_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/DragASpriteMod.elf: $(DSP_OBJS) $(CLIB) mk/ld/module.ld \
                                     tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(DSP_OBJS) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(DSP_MOD): $(BUILD)/Desktop/DragASpriteMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, DragASprite - ported)"

-include $(DSP_OBJS:.o=.d)

dragasprite: $(DSP_MOD)
.PHONY: dragasprite

# FilterManager: the six Wimp call-outs, each a list so several tasks can
# watch it. The routines the Wimp calls, and the one that calls a filter
# (r0-r10 set, the filter's word in r12), are hand-written in s/veneer.S.
FLT_OBJS := $(BUILD)/Sources/Desktop/Filter/modhead.o \
            $(BUILD)/Sources/Desktop/Filter/filtmgr.o \
            $(BUILD)/Sources/Desktop/Filter/fmwalk.o \
            $(BUILD)/Sources/Desktop/Filter/fmprint.o \
            $(BUILD)/Sources/Desktop/Filter/fmmodule.o \
            $(BUILD)/Sources/Desktop/Filter/veneers.o
FLT_MOD  := $(BUILD)/Desktop/FilterManager.mod
FLT_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(FLT_DIR)/c \
              -I$(BUILD)/Sources/Desktop/Filter

$(BUILD)/Sources/Desktop/Filter/modhead.S: $(FLT_DIR)/cmhg/modhead \
                                   $(FLT_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(FLT_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/Filter/%.o: $(FLT_DIR)/c/%.c Makefile \
                             $(BUILD)/Sources/Desktop/Filter/fm_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FLT_CFLAGS) -c $< -o $@

# Module entries, the twelve SWIs and the two service calls, with their C
# prototypes. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/Filter/veneers.S: $(FLT_DIR)/s/boundaries \
                                   $(FLT_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(FLT_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/Filter/fm_veneers.h

$(BUILD)/Sources/Desktop/Filter/fm_veneers.h: $(BUILD)/Sources/Desktop/Filter/veneers.S

$(BUILD)/Sources/Desktop/Filter/veneers.o: $(BUILD)/Sources/Desktop/Filter/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FLT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Filter/modhead.o: $(BUILD)/Sources/Desktop/Filter/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(FLT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/FilterManager.elf: $(FLT_OBJS) $(CLIB) mk/ld/module.ld \
                                    tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(FLT_OBJS) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(FLT_MOD): $(BUILD)/Desktop/FilterManager.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, FilterManager - ported)"

-include $(FLT_OBJS:.o=.d)

filtermanager: $(FLT_MOD)
.PHONY: filtermanager

# Free: free space on a disc, shown as three bars, and the drive's health. A
# Wimp task in a module.
FRE_OBJS := $(BUILD)/Sources/Desktop/Free/modhead.o \
            $(BUILD)/Sources/Desktop/Free/freecalc.o \
            $(BUILD)/Sources/Desktop/Free/freelist.o \
            $(BUILD)/Sources/Desktop/Free/freefs.o \
            $(BUILD)/Sources/Desktop/Free/freegfx.o \
            $(BUILD)/Sources/Desktop/Free/freeinfo.o \
            $(BUILD)/Sources/Desktop/Free/freetask.o \
            $(BUILD)/Sources/Desktop/Free/freemod.o \
            $(BUILD)/Sources/Desktop/Free/veneers.o
FRE_MOD  := $(BUILD)/Desktop/FreeMod.mod
FRE_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(FRE_DIR)/c -I$(BUILD)/Sources/Desktop/Free

$(BUILD)/Sources/Desktop/Free/modhead.S: $(FRE_DIR)/cmhg/modhead \
                                 $(FRE_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(FRE_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/Free/%.o: $(FRE_DIR)/c/%.c Makefile \
                           $(BUILD)/Sources/Desktop/Free/free_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FRE_CFLAGS) -c $< -o $@

# Every entry, with its C prototype. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/Free/veneers.S: $(FRE_DIR)/s/boundaries \
                                 $(FRE_DIR)/cmhg/modhead \
                                 $(FRE_DIR)/c/freeswis.h tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(FRE_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/Free/free_veneers.h

$(BUILD)/Sources/Desktop/Free/free_veneers.h: $(BUILD)/Sources/Desktop/Free/veneers.S

$(BUILD)/Sources/Desktop/Free/veneers.o: $(BUILD)/Sources/Desktop/Free/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FRE_CFLAGS) -I$(FRE_DIR)/c -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Free/modhead.o: $(BUILD)/Sources/Desktop/Free/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(FRE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/FreeMod.elf: $(FRE_OBJS) $(DESKTOP_LIB) $(CLIB) \
                              mk/ld/module.ld tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(FRE_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(FRE_MOD): $(BUILD)/Desktop/FreeMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Free - ported)"

-include $(FRE_OBJS:.o=.d)

freemodule: $(FRE_MOD)
.PHONY: freemodule
# RAMFSFiler: the RAM disc's icon bar icon and its menu. A Wimp task in a
# module with no windows. Its entries, including the task's stack, come from
# s/boundaries.
#
# Its menus are built from the message file with Sources/Lib/Desktop's
# `messagetrans_shape`, in place of the original's ROM template handed to
# MessageTrans_MakeMenus.
RFL_DIR := Sources/Desktop/RAMFSFiler
RFL_OBJS := $(BUILD)/Sources/Desktop/RAMFSFiler/modhead.o \
            $(BUILD)/Sources/Desktop/RAMFSFiler/rfsmod.o \
            $(BUILD)/Sources/Desktop/RAMFSFiler/rfstask.o \
            $(BUILD)/Sources/Desktop/RAMFSFiler/veneers.o
RFL_MOD  := $(BUILD)/Desktop/RAMFSFiler.mod
RFL_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(RFL_DIR)/c -I$(BUILD)/Sources/Desktop/RAMFSFiler

$(BUILD)/Sources/Desktop/RAMFSFiler/modhead.S: $(RFL_DIR)/cmhg/modhead \
                                       $(RFL_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(RFL_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/RAMFSFiler/%.o: $(RFL_DIR)/c/%.c Makefile \
                                 $(BUILD)/Sources/Desktop/RAMFSFiler/rfs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(RFL_CFLAGS) -c $< -o $@

# The four module entries and their C prototypes. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/RAMFSFiler/veneers.S: $(RFL_DIR)/s/boundaries \
                                       $(RFL_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(RFL_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/RAMFSFiler/rfs_veneers.h

$(BUILD)/Sources/Desktop/RAMFSFiler/rfs_veneers.h: $(BUILD)/Sources/Desktop/RAMFSFiler/veneers.S

$(BUILD)/Sources/Desktop/RAMFSFiler/veneers.o: $(BUILD)/Sources/Desktop/RAMFSFiler/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(RFL_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/RAMFSFiler/modhead.o: $(BUILD)/Sources/Desktop/RAMFSFiler/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(RFL_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/RAMFSFilerMod.elf: $(RFL_OBJS) $(DESKTOP_LIB) $(CLIB) \
                                    mk/ld/module.ld tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(RFL_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(RFL_MOD): $(BUILD)/Desktop/RAMFSFilerMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, RAMFSFiler - ported)"

-include $(RFL_OBJS:.o=.d)

ramfsfilermodule: $(RFL_MOD)
.PHONY: ramfsfilermodule

# ADFSFiler: a hard disc's icon bar icon and its menu. A Wimp task in a
# module over Sources/FileSys/ADFS, shaped like RAMFSFiler with several drives.
# What is not ported is listed in Sources/Desktop/ADFSFiler/cmhg/modhead.
AFL_DIR  := Sources/Desktop/ADFSFiler
AFL_OBJS := $(BUILD)/Sources/Desktop/ADFSFiler/modhead.o \
            $(BUILD)/Sources/Desktop/ADFSFiler/afsmenu.o \
            $(BUILD)/Sources/Desktop/ADFSFiler/afsmod.o \
            $(BUILD)/Sources/Desktop/ADFSFiler/afstask.o \
            $(BUILD)/Sources/Desktop/ADFSFiler/afsformat.o \
            $(BUILD)/Sources/Desktop/ADFSFiler/veneers.o
AFL_MOD  := $(BUILD)/Desktop/ADFSFiler.mod
# Sources/FileSys/FileCore/c is on the path for `fcdisc.h` only, so the disc
# record has one definition.
AFL_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(AFL_DIR)/c -ISources/FileSys/FileCore/c -I$(BUILD)/Sources/Desktop/ADFSFiler

$(BUILD)/Sources/Desktop/ADFSFiler/modhead.S: $(AFL_DIR)/cmhg/modhead \
                                      $(AFL_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(AFL_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/ADFSFiler/%.o: $(AFL_DIR)/c/%.c Makefile \
                                $(BUILD)/Sources/Desktop/ADFSFiler/afs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(AFL_CFLAGS) -c $< -o $@

# The five module entries, the UpCallV claimant and their C prototypes. See
# tools/build/marshal.py.
$(BUILD)/Sources/Desktop/ADFSFiler/veneers.S: $(AFL_DIR)/s/boundaries \
                                      $(AFL_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(AFL_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/ADFSFiler/afs_veneers.h

$(BUILD)/Sources/Desktop/ADFSFiler/afs_veneers.h: $(BUILD)/Sources/Desktop/ADFSFiler/veneers.S

$(BUILD)/Sources/Desktop/ADFSFiler/veneers.o: $(BUILD)/Sources/Desktop/ADFSFiler/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(AFL_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/ADFSFiler/modhead.o: $(BUILD)/Sources/Desktop/ADFSFiler/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(AFL_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/ADFSFilerMod.elf: $(AFL_OBJS) $(DESKTOP_LIB) $(CLIB) \
                                   mk/ld/module.ld tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(AFL_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(AFL_MOD): $(BUILD)/Desktop/ADFSFilerMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ADFSFiler)"

-include $(AFL_OBJS:.o=.d)

adfsfilermodule: $(AFL_MOD)
.PHONY: adfsfilermodule

# CDFSFiler: a CD's icon bar icon, its menu, and the audio player window. A
# Wimp task in a module over Sources/FileSys/CDFS/CDFS, shaped like ADFSFiler. What
# the reference build does not assemble is listed in
# Sources/Desktop/CDFSFiler/cmhg/modhead.
CFL_DIR  := Sources/Desktop/CDFSFiler
CFL_OBJS := $(BUILD)/Sources/Desktop/CDFSFiler/modhead.o \
            $(BUILD)/Sources/Desktop/CDFSFiler/cdfmenu.o \
            $(BUILD)/Sources/Desktop/CDFSFiler/cdfmod.o \
            $(BUILD)/Sources/Desktop/CDFSFiler/cdftask.o \
            $(BUILD)/Sources/Desktop/CDFSFiler/cdfplayer.o \
            $(BUILD)/Sources/Desktop/CDFSFiler/cdfshare.o \
            $(BUILD)/Sources/Desktop/CDFSFiler/veneers.o
CFL_MOD  := $(BUILD)/Desktop/CDFSFiler.mod
CFL_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(CFL_DIR)/c -I$(BUILD)/Sources/Desktop/CDFSFiler

$(BUILD)/Sources/Desktop/CDFSFiler/modhead.S: $(CFL_DIR)/cmhg/modhead \
                                      $(CFL_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(CFL_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/CDFSFiler/%.o: $(CFL_DIR)/c/%.c Makefile \
                                $(BUILD)/Sources/Desktop/CDFSFiler/cdf_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(CFL_CFLAGS) -c $< -o $@

$(BUILD)/Sources/Desktop/CDFSFiler/veneers.S: $(CFL_DIR)/s/boundaries \
                                      $(CFL_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(CFL_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/CDFSFiler/cdf_veneers.h

$(BUILD)/Sources/Desktop/CDFSFiler/cdf_veneers.h: $(BUILD)/Sources/Desktop/CDFSFiler/veneers.S

$(BUILD)/Sources/Desktop/CDFSFiler/veneers.o: $(BUILD)/Sources/Desktop/CDFSFiler/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(CFL_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/CDFSFiler/modhead.o: $(BUILD)/Sources/Desktop/CDFSFiler/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(CFL_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/CDFSFilerMod.elf: $(CFL_OBJS) $(DESKTOP_LIB) $(CLIB) \
                                   mk/ld/module.ld tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(CFL_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(CFL_MOD): $(BUILD)/Desktop/CDFSFilerMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, CDFSFiler)"

-include $(CFL_OBJS:.o=.d)

cdfsfilermodule: $(CFL_MOD)
.PHONY: cdfsfilermodule

# Host checks for CDFSFiler: all of c/cdfmenu.c (menu shading, disc sprite,
# playlist and shuffle, volume bar, shared-disc file, help tokens).
CFL_TEST_GEN := $(BUILD)/tests/cdfsfiler
CFL_TEST_FLAGS := $(HOST_CFLAGS) -I$(CFL_DIR)/c -Itests/lib -Iinclude

$(CFL_TEST_GEN)/%.o: Sources/Desktop/CDFSFiler/test/cdfsfiler/%.c $(CFL_DIR)/c/cdfmenu.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CFL_TEST_FLAGS) -c $< -o $@

$(CFL_TEST_GEN)/cdfmenu.o: $(CFL_DIR)/c/cdfmenu.c $(CFL_DIR)/c/cdfmenu.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CFL_TEST_FLAGS) -c $< -o $@

$(CFL_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CFL_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/cdfsfiler-checks: $(addprefix $(CFL_TEST_GEN)/,cases.o \
                                        cdfmenu.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

cdfsfilerchecks: $(BUILD)/tests/cdfsfiler-checks
	@$(BUILD)/tests/cdfsfiler-checks
.PHONY: cdfsfilerchecks

# NetFiler: Econet file servers on the icon bar, their menus, the server list
# window and the logon box. A Wimp task in a module over Sources/Networking/Econet/NetFS,
# shaped like CDFSFiler. What the reference build does not assemble is listed
# in Sources/Desktop/NetFiler/cmhg/modhead.
NFL_DIR  := Sources/Desktop/NetFiler
NFL_OBJS := $(BUILD)/Sources/Desktop/NetFiler/modhead.o \
            $(BUILD)/Sources/Desktop/NetFiler/nflcalc.o \
            $(BUILD)/Sources/Desktop/NetFiler/nflmod.o \
            $(BUILD)/Sources/Desktop/NetFiler/nfltask.o \
            $(BUILD)/Sources/Desktop/NetFiler/nflview.o \
            $(BUILD)/Sources/Desktop/NetFiler/veneers.o
NFL_MOD  := $(BUILD)/Desktop/NetFiler.mod
NFL_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(NFL_DIR)/c -I$(BUILD)/Sources/Desktop/NetFiler

$(BUILD)/Sources/Desktop/NetFiler/modhead.S: $(NFL_DIR)/cmhg/modhead \
                                     $(NFL_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(NFL_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/NetFiler/%.o: $(NFL_DIR)/c/%.c Makefile \
                               $(BUILD)/Sources/Desktop/NetFiler/nfl_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(NFL_CFLAGS) -c $< -o $@

$(BUILD)/Sources/Desktop/NetFiler/veneers.S: $(NFL_DIR)/s/boundaries \
                                     $(NFL_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(NFL_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/NetFiler/nfl_veneers.h

$(BUILD)/Sources/Desktop/NetFiler/nfl_veneers.h: $(BUILD)/Sources/Desktop/NetFiler/veneers.S

$(BUILD)/Sources/Desktop/NetFiler/veneers.o: $(BUILD)/Sources/Desktop/NetFiler/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(NFL_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/NetFiler/modhead.o: $(BUILD)/Sources/Desktop/NetFiler/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(NFL_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/NetFilerMod.elf: $(NFL_OBJS) $(DESKTOP_LIB) $(CLIB) \
                                  mk/ld/module.ld tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(NFL_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(NFL_MOD): $(BUILD)/Desktop/NetFilerMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, NetFiler)"

-include $(NFL_OBJS:.o=.d)

netfilermodule: $(NFL_MOD)
.PHONY: netfilermodule

# Host checks for NetFiler: all of c/nflcalc.c (disc paths, not-logged-on
# errors, sort orders, CMOS byte, view layout, menu widths, help tokens, logon
# keys, strings passed to the OS).
NFL_TEST_GEN := $(BUILD)/tests/netfiler
NFL_TEST_FLAGS := $(HOST_CFLAGS) -I$(NFL_DIR)/c -Itests/lib -Iinclude

$(NFL_TEST_GEN)/%.o: Sources/Desktop/NetFiler/test/netfiler/%.c $(NFL_DIR)/c/nflcalc.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NFL_TEST_FLAGS) -c $< -o $@

$(NFL_TEST_GEN)/nflcalc.o: $(NFL_DIR)/c/nflcalc.c $(NFL_DIR)/c/nflcalc.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NFL_TEST_FLAGS) -c $< -o $@

$(NFL_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NFL_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/netfiler-checks: $(addprefix $(NFL_TEST_GEN)/,cases.o \
                                       nflcalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

netfilerchecks: $(BUILD)/tests/netfiler-checks
	@$(BUILD)/tests/netfiler-checks
.PHONY: netfilerchecks

# Host checks for ADFSFiler: all of c/afsmenu.c (menu shading, help tokens,
# the format and backup state machine, and the format's sector, cylinder and
# head numbering), checked against FileCore's own format blocks.
AFL_TEST_GEN := $(BUILD)/tests/adfsfiler
AFL_TEST_FLAGS := $(HOST_CFLAGS) -I$(AFL_DIR)/c -ISources/FileSys/FileCore/c \
                  -Itests/lib -Iinclude

$(AFL_TEST_GEN)/%.o: Sources/Desktop/ADFSFiler/test/adfsfiler/%.c $(AFL_DIR)/c/afsmenu.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(AFL_TEST_FLAGS) -c $< -o $@

$(AFL_TEST_GEN)/afsmenu.o: $(AFL_DIR)/c/afsmenu.c $(AFL_DIR)/c/afsmenu.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(AFL_TEST_FLAGS) -c $< -o $@

$(AFL_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(AFL_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/adfsfiler-checks: $(addprefix $(AFL_TEST_GEN)/,checks.o \
                                   afsmenu.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

adfsfilerchecks: $(BUILD)/tests/adfsfiler-checks
	@$<
.PHONY: adfsfilerchecks

# ResourceFiler: the Apps icon on the icon bar, one menu entry, and the two
# directories they open through the Filer. Its entries, including the task's
# stack, come from s/boundaries. Its menu is built from the message file with
# `messagetrans_shape`, as RAMFSFiler's are.
RSF_DIR := Sources/Desktop/ResourceFiler
RSF_OBJS := $(BUILD)/Sources/Desktop/ResourceFiler/modhead.o \
            $(BUILD)/Sources/Desktop/ResourceFiler/rsfcalc.o \
            $(BUILD)/Sources/Desktop/ResourceFiler/rsfmod.o \
            $(BUILD)/Sources/Desktop/ResourceFiler/rsftask.o \
            $(BUILD)/Sources/Desktop/ResourceFiler/veneers.o
RSF_MOD  := $(BUILD)/Desktop/ResourceFiler.mod
RSF_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(RSF_DIR)/c -I$(BUILD)/Sources/Desktop/ResourceFiler

$(BUILD)/Sources/Desktop/ResourceFiler/modhead.S: $(RSF_DIR)/cmhg/modhead \
                                          $(RSF_DIR)/VersionNum \
                                          tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(RSF_DIR) -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/ResourceFiler/%.o: $(RSF_DIR)/c/%.c Makefile \
                                    $(BUILD)/Sources/Desktop/ResourceFiler/rsf_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(RSF_CFLAGS) -c $< -o $@

# The four module entries and their C prototypes. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/ResourceFiler/veneers.S: $(RSF_DIR)/s/boundaries \
                                          $(RSF_DIR)/cmhg/modhead \
                                          tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(RSF_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/ResourceFiler/rsf_veneers.h

$(BUILD)/Sources/Desktop/ResourceFiler/rsf_veneers.h: \
    $(BUILD)/Sources/Desktop/ResourceFiler/veneers.S

$(BUILD)/Sources/Desktop/ResourceFiler/veneers.o: \
    $(BUILD)/Sources/Desktop/ResourceFiler/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(RSF_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/ResourceFiler/modhead.o: \
    $(BUILD)/Sources/Desktop/ResourceFiler/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(RSF_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/ResourceFilerMod.elf: $(RSF_OBJS) $(DESKTOP_LIB) $(CLIB) \
                                       mk/ld/module.ld \
                                       tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(RSF_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(RSF_MOD): $(BUILD)/Desktop/ResourceFilerMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ResourceFiler - ported)"

-include $(RSF_OBJS:.o=.d)

resourcefilermodule: $(RSF_MOD)
.PHONY: resourcefilermodule

# Host checks for ResourceFiler: the help token, the icon's corners and the
# message that opens a directory.
RSF_TEST_GEN := $(BUILD)/tests/resfiler
RSF_TEST_FLAGS := $(HOST_CFLAGS) -I$(RSF_DIR)/c -Itests/lib -Iinclude

$(RSF_TEST_GEN)/%.o: Sources/Desktop/ResourceFiler/test/resfiler/%.c $(RSF_DIR)/c/rsfcalc.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RSF_TEST_FLAGS) -c $< -o $@

$(RSF_TEST_GEN)/%.o: $(RSF_DIR)/c/%.c $(RSF_DIR)/c/rsfcalc.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RSF_TEST_FLAGS) -c $< -o $@

$(RSF_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RSF_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/resfiler-checks: $(addprefix $(RSF_TEST_GEN)/,checks.o \
                                      rsfcalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

resfilerchecks: $(BUILD)/tests/resfiler-checks
	@$<
.PHONY: resfilerchecks


# TaskWindow: runs a command under the Wimp. A character written arrives on
# WrchV in SVC mode; the module saves registers and the SVC stack, drops to
# user mode on its own stack, polls the Wimp, and restores everything before
# OS_WriteC returns. That switch, the six vector claimants, the two kernel SWI
# table entries and the callback handler are in s/veneer.S; the rest is C.
TWN_OBJS := $(BUILD)/Sources/Desktop/TaskWindow/modhead.o \
            $(BUILD)/Sources/Desktop/TaskWindow/twbuf.o \
            $(BUILD)/Sources/Desktop/TaskWindow/twparse.o \
            $(BUILD)/Sources/Desktop/TaskWindow/twlist.o \
            $(BUILD)/Sources/Desktop/TaskWindow/twmsg.o \
            $(BUILD)/Sources/Desktop/TaskWindow/twpoll.o \
            $(BUILD)/Sources/Desktop/TaskWindow/twvec.o \
            $(BUILD)/Sources/Desktop/TaskWindow/twtask.o \
            $(BUILD)/Sources/Desktop/TaskWindow/twmod.o \
            $(BUILD)/Sources/Desktop/TaskWindow/veneers.o \
            $(BUILD)/Sources/Desktop/TaskWindow/veneer.o
TWN_MOD  := $(BUILD)/Desktop/TaskWindowMod.mod
TWN_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(TWN_DIR)/c -I$(BUILD)/Sources/Desktop/TaskWindow

$(BUILD)/Sources/Desktop/TaskWindow/modhead.S: $(TWN_DIR)/cmhg/modhead \
                                       $(TWN_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(TWN_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/TaskWindow/%.o: $(TWN_DIR)/c/%.c Makefile \
                                 $(BUILD)/Sources/Desktop/TaskWindow/tw_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(TWN_CFLAGS) -c $< -o $@

# The eight module entries and their C prototypes. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/TaskWindow/veneers.S: $(TWN_DIR)/s/boundaries \
                                       $(TWN_DIR)/cmhg/modhead \
                                       $(TWN_DIR)/c/twswis.h tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(TWN_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/TaskWindow/tw_veneers.h

$(BUILD)/Sources/Desktop/TaskWindow/tw_veneers.h: \
    $(BUILD)/Sources/Desktop/TaskWindow/veneers.S

$(BUILD)/Sources/Desktop/TaskWindow/veneers.o: $(BUILD)/Sources/Desktop/TaskWindow/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(TWN_CFLAGS) -I$(TWN_DIR)/c -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/TaskWindow/veneer.o: $(TWN_DIR)/s/aarch32/veneer.S \
                                      $(TWN_DIR)/c/twoffs.h
	@mkdir -p $(dir $@)
	$(CC) $(TWN_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/TaskWindow/modhead.o: $(BUILD)/Sources/Desktop/TaskWindow/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(TWN_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/TaskWindowMod.elf: $(TWN_OBJS) $(DESKTOP_LIB) $(CLIB) \
                                    mk/ld/module.ld tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(TWN_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(TWN_MOD): $(BUILD)/Desktop/TaskWindowMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, TaskWindow - ported)"

-include $(TWN_OBJS:.o=.d)

taskwinmodule: $(TWN_MOD)
.PHONY: taskwinmodule

# The Task Manager: a Wimp application in a module (icon bar icon, two menus,
# four dialogues, the task and memory window, shutdown, the desktop settings
# file), plus four SWIs and three commands.
SWI_OBJS := $(BUILD)/Sources/Desktop/Switcher/modhead.o \
            $(BUILD)/Sources/Desktop/Switcher/swcalc.o \
            $(BUILD)/Sources/Desktop/Switcher/swlist.o \
            $(BUILD)/Sources/Desktop/Switcher/swmenu.o \
            $(BUILD)/Sources/Desktop/Switcher/swpath.o \
            $(BUILD)/Sources/Desktop/Switcher/swmem.o \
            $(BUILD)/Sources/Desktop/Switcher/swwin.o \
            $(BUILD)/Sources/Desktop/Switcher/swsave.o \
            $(BUILD)/Sources/Desktop/Switcher/swtask.o \
            $(BUILD)/Sources/Desktop/Switcher/swmod.o \
            $(BUILD)/Sources/Desktop/Switcher/veneers.o \
            $(BUILD)/Sources/Desktop/Switcher/veneer.o
SWI_MOD  := $(BUILD)/Desktop/SwitcherMod.mod
SWI_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(SWI_DIR)/c -I$(BUILD)/Sources/Desktop/Switcher

$(BUILD)/Sources/Desktop/Switcher/modhead.S: $(SWI_DIR)/cmhg/modhead \
                                     $(SWI_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(SWI_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/Switcher/%.o: $(SWI_DIR)/c/%.c Makefile \
                               $(BUILD)/Sources/Desktop/Switcher/sw_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SWI_CFLAGS) -c $< -o $@

# The seven module entries and their C prototypes. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/Switcher/veneers.S: $(SWI_DIR)/s/boundaries \
                                     $(SWI_DIR)/cmhg/modhead \
                                     $(SWI_DIR)/c/swswis.h tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(SWI_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/Switcher/sw_veneers.h

$(BUILD)/Sources/Desktop/Switcher/sw_veneers.h: $(BUILD)/Sources/Desktop/Switcher/veneers.S

$(BUILD)/Sources/Desktop/Switcher/veneers.o: $(BUILD)/Sources/Desktop/Switcher/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SWI_CFLAGS) -I$(SWI_DIR)/c -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Switcher/veneer.o: $(SWI_DIR)/s/aarch32/veneer.S
	@mkdir -p $(dir $@)
	$(CC) $(SWI_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Switcher/modhead.o: $(BUILD)/Sources/Desktop/Switcher/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SWI_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/SwitcherMod.elf: $(SWI_OBJS) $(DESKTOP_LIB) $(CLIB) \
                                  mk/ld/module.ld tools/build/modulecheck.py
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SWI_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(SWI_MOD): $(BUILD)/Desktop/SwitcherMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, TaskManager - ported)"

-include $(SWI_OBJS:.o=.d)

switchermodule: $(SWI_MOD)
.PHONY: switchermodule

# The Pinboard: the backdrop, its icons, and windows iconised onto it.
PBD_OBJS := $(BUILD)/Sources/Desktop/Pinboard/modhead.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbgrid.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbpicture.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbname.o \
            $(BUILD)/Sources/Desktop/Pinboard/pblist.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbmenu.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbargs.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbsave.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbicon.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbwin.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbmsg.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbfile.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbtask.o \
            $(BUILD)/Sources/Desktop/Pinboard/pbmod.o \
            $(BUILD)/Sources/Desktop/Pinboard/veneers.o \
            $(BUILD)/Sources/Desktop/Pinboard/enter.o
PBD_MOD  := $(BUILD)/Desktop/PinboardMod.mod
PBD_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(PBD_DIR)/c -I$(BUILD)/Sources/Desktop/Pinboard

$(BUILD)/Sources/Desktop/Pinboard/modhead.S: $(PBD_DIR)/cmhg/modhead \
                                     $(PBD_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(PBD_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/Pinboard/%.o: $(PBD_DIR)/c/%.c Makefile \
                               $(BUILD)/Sources/Desktop/Pinboard/pb_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(PBD_CFLAGS) -c $< -o $@

# Module entries, the seven commands and the five service calls, with their C
# prototypes. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/Pinboard/veneers.S: $(PBD_DIR)/s/boundaries \
                                     $(PBD_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(PBD_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/Pinboard/pb_veneers.h

$(BUILD)/Sources/Desktop/Pinboard/pb_veneers.h: $(BUILD)/Sources/Desktop/Pinboard/veneers.S

$(BUILD)/Sources/Desktop/Pinboard/veneers.o: $(BUILD)/Sources/Desktop/Pinboard/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(PBD_CFLAGS) -x assembler-with-cpp -c $< -o $@

# The start entry and its OS_Exit, hand-written: see the head of the file.
$(BUILD)/Sources/Desktop/Pinboard/enter.o: $(PBD_DIR)/s/aarch32/enter.S
	@mkdir -p $(dir $@)
	$(CC) $(PBD_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Pinboard/modhead.o: $(BUILD)/Sources/Desktop/Pinboard/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(PBD_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/PinboardMod.elf: $(PBD_OBJS) $(DESKTOP_LIB) $(CLIB) \
                                  mk/ld/module.ld tools/build/modulecheck.py
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(PBD_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(PBD_MOD): $(BUILD)/Desktop/PinboardMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Pinboard - ported)"

-include $(PBD_OBJS:.o=.d)

pinboardmodule: $(PBD_MOD)
.PHONY: pinboardmodule

# The Filer: directory viewers and the operations on their contents.
FLR_DIR  := Sources/Desktop/Filer
FLR_OBJS := $(BUILD)/Sources/Desktop/Filer/modhead.o \
            $(BUILD)/Sources/Desktop/Filer/flrbox.o \
            $(BUILD)/Sources/Desktop/Filer/flrsort.o \
            $(BUILD)/Sources/Desktop/Filer/flrname.o \
            $(BUILD)/Sources/Desktop/Filer/flrtype.o \
            $(BUILD)/Sources/Desktop/Filer/flrargs.o \
            $(BUILD)/Sources/Desktop/Filer/flrmenu.o \
            $(BUILD)/Sources/Desktop/Filer/flrsel.o \
            $(BUILD)/Sources/Desktop/Filer/flrdir.o \
            $(BUILD)/Sources/Desktop/Filer/flrsave.o \
            $(BUILD)/Sources/Desktop/Filer/flrkey.o \
            $(BUILD)/Sources/Desktop/Filer/flrclick.o \
            $(BUILD)/Sources/Desktop/Filer/flrdrag.o \
            $(BUILD)/Sources/Desktop/Filer/flrmod.o \
            $(BUILD)/Sources/Desktop/Filer/flrview.o \
            $(BUILD)/Sources/Desktop/Filer/flrtask.o \
            $(BUILD)/Sources/Desktop/Filer/flrleave.o \
            $(BUILD)/Sources/Desktop/Filer/flrdraw.o \
            $(BUILD)/Sources/Desktop/Filer/flrclicks.o \
            $(BUILD)/Sources/Desktop/Filer/flrmsgs.o \
            $(BUILD)/Sources/Desktop/Filer/flrmenus.o \
            $(BUILD)/Sources/Desktop/Filer/flrdecode.o \
            $(BUILD)/Sources/Desktop/Filer/flraction.o \
            $(BUILD)/Sources/Desktop/Filer/flrdialog.o \
            $(BUILD)/Sources/Desktop/Filer/flrdrop.o \
            $(BUILD)/Sources/Desktop/Filer/flrapps.o \
            $(BUILD)/Sources/Desktop/Filer/flrsaved.o \
            $(BUILD)/Sources/Desktop/Filer/flrrename.o \
            $(BUILD)/Sources/Desktop/Filer/veneers.o
FLR_MOD  := $(BUILD)/Desktop/FilerMod.mod
FLR_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
              -I$(FLR_DIR)/c -I$(BUILD)/Sources/Desktop/Filer

$(BUILD)/Sources/Desktop/Filer/modhead.S: $(FLR_DIR)/cmhg/modhead \
                                  $(FLR_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(FLR_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/Filer/%.o: $(FLR_DIR)/c/%.c Makefile \
                            $(BUILD)/Sources/Desktop/Filer/flr_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FLR_CFLAGS) -c $< -o $@

# The fifteen module manager and UpCallV entries, with their C prototypes. See
# tools/build/marshal.py.
$(BUILD)/Sources/Desktop/Filer/veneers.S: $(FLR_DIR)/s/boundaries \
                                  $(FLR_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(FLR_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/Filer/flr_veneers.h

$(BUILD)/Sources/Desktop/Filer/flr_veneers.h: $(BUILD)/Sources/Desktop/Filer/veneers.S

$(BUILD)/Sources/Desktop/Filer/veneers.o: $(BUILD)/Sources/Desktop/Filer/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FLR_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Filer/modhead.o: $(BUILD)/Sources/Desktop/Filer/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(FLR_CFLAGS) -x assembler-with-cpp -c $< -o $@

-include $(FLR_OBJS:.o=.d)

$(BUILD)/Desktop/FilerMod.elf: $(FLR_OBJS) $(DESKTOP_LIB) $(CLIB) \
                               mk/ld/module.ld tools/build/modulecheck.py
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(FLR_OBJS) $(DESKTOP_LIB) $(CLIB)
	python3 tools/build/modulecheck.py $@.tmp
	mv $@.tmp $@

$(FLR_MOD): $(BUILD)/Desktop/FilerMod.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, the Filer)"

filermodule: $(FLR_MOD)
.PHONY: filermodule




-include $(FS_OBJS:.o=.d)

FA_DIR  := Sources/Desktop/FilerAct
FA_OBJS := $(BUILD)/Sources/Desktop/FilerAct/modhead.o \
           $(BUILD)/Sources/Desktop/FilerAct/fileract.o \
           $(BUILD)/Sources/Desktop/FilerAct/fatask.o \
           $(BUILD)/Sources/Desktop/FilerAct/faerror.o \
           $(BUILD)/Sources/Desktop/FilerAct/famsg.o \
           $(BUILD)/Sources/Desktop/FilerAct/fachain.o \
           $(BUILD)/Sources/Desktop/FilerAct/fanumber.o \
           $(BUILD)/Sources/Desktop/FilerAct/facopy.o \
           $(BUILD)/Sources/Desktop/FilerAct/faio_riscos.o \
           $(BUILD)/Sources/Desktop/FilerAct/fasearch.o \
           $(BUILD)/Sources/Desktop/FilerAct/fabuttons.o \
           $(BUILD)/Sources/Desktop/FilerAct/faops.o \
           $(BUILD)/Sources/Desktop/FilerAct/faenv.o \
           $(BUILD)/Sources/Desktop/FilerAct/faact.o \
           $(BUILD)/Sources/Desktop/FilerAct/fawin_desktop.o \
           $(BUILD)/Sources/Desktop/FilerAct/veneers.o \
           $(BUILD)/Sources/Desktop/FilerAct/resources.o
FA_MOD  := $(BUILD)/Desktop/FilerAct.mod
FA_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Lib/Desktop/include \
             -I$(FA_DIR)/c -I$(BUILD)/Sources/Desktop/FilerAct

$(BUILD)/Sources/Desktop/FilerAct/modhead.S: $(FA_DIR)/cmhg/modhead $(FA_DIR)/VersionNum \
                                     tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(FA_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/FilerAct/%.o: $(FA_DIR)/c/%.c Makefile \
                               $(BUILD)/Sources/Desktop/FilerAct/fa_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FA_CFLAGS) -c $< -o $@

# The four module entries and their C prototypes. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/FilerAct/veneers.S: $(FA_DIR)/s/boundaries \
                                     $(FA_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(FA_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/FilerAct/fa_veneers.h

$(BUILD)/Sources/Desktop/FilerAct/fa_veneers.h: $(BUILD)/Sources/Desktop/FilerAct/veneers.S

$(BUILD)/Sources/Desktop/FilerAct/veneers.o: $(BUILD)/Sources/Desktop/FilerAct/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FA_CFLAGS) -x assembler-with-cpp -c $< -o $@

# The module's two resource files, as a ResourceFS chain. The ROM's Messages
# module is not part of a ROM of our own parts, so the module carries them.
# Messages and CmdHelp are joined into one file: the header names
# Resources.FilerAct.Messages and looks its *Help tokens up there.
FA_RES_TEXT := $(FA_DIR)/Resources/UK/Messages+$(FA_DIR)/Resources/UK/CmdHelp
FA_RES_TPL  := $(FA_DIR)/Resources/UK/Templates,fec
FA_RES_BLOCK := $(BUILD)/Sources/Desktop/FilerAct/resources.bin

FA_RES_FILES := $(subst +, ,$(FA_RES_TEXT))

$(FA_RES_BLOCK): $(FA_RES_FILES) $(FA_RES_TPL) tools/build/mkresfs.py
	@mkdir -p $(dir $@)
	@python3 tools/build/mkresfs.py $@ --strip-directives \
	  --file "Resources.FilerAct.Messages=$(FA_RES_TEXT)" \
	  --file "Resources.FilerAct.Templates:fec=$(FA_RES_TPL)"

$(BUILD)/Sources/Desktop/FilerAct/resources.o: $(FA_DIR)/s/resources.S $(FA_RES_BLOCK)
	@mkdir -p $(dir $@)
	$(CC) $(FA_CFLAGS) -DRESOURCE_BLOCK='"$(FA_RES_BLOCK)"' \
	  -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/FilerAct/modhead.o: $(BUILD)/Sources/Desktop/FilerAct/modhead.S
	$(CC) $(FA_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/FilerAct.elf: $(FA_OBJS) $(CLIB_NOFLOAT) $(BUILD)/lib/libdesktop.a \
                               $(BUILD)/lib/libclib.a \
                               mk/ld/module.ld tools/build/modulecheck.py Makefile
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(FA_OBJS) $(CLIB_NOFLOAT) $(BUILD)/lib/libdesktop.a $(BUILD)/lib/libclib.a
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(FA_MOD): $(BUILD)/Desktop/FilerAct.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Filer_Action - ported)"

-include $(FA_OBJS:.o=.d)

fileraction: $(FA_MOD)
.PHONY: fileraction

# Wimp: the C/GAS port of the Wimp. `make wimp` links it and checks its module
# shape; `make wimpmachinetest` puts it in a ROM in place of the reference
# WindowManager, with `wimpreftest` as the control.
#
# Declined features, each documented at the relevant code: `initrectptrs`
# (every rectangle is its own claim); `forcetint_off` and tool-sprite tinting
# (the shipped `Tools,ff9` has no `table_` sprites); PlotIcon's unscaled plot
# and `slabinout`; the sender markers; and the portable's speed control (no
# Portable module in the ROM).
WIMP_DIR := Sources/Desktop/Wimp
WIMP_NAMES := wimp_module wimp_dispatch wimp_core wimp_config wimp_screen wimp_palette wimp_tint wimp_tintcalc wimp_syscell wimp_pointer \
               wimp_memory wimp_tasks wimp_context \
               wimp_windows wimp_redraw wimp_poll wimp_input wimp_click wimp_drag wimp_caret wimp_menus \
               wimp_templates wimp_messages wimp_sprites wimp_vector wimp_iconbar \
               wimp_theme wimp_buttonstyle wimp_painticon wimp_errors wimp_errorbox wimp_extensions wimp_watchdog \
               wimp_autoscroll wimp_command wimp_clipboard wimp_keys wimp_font \
               wimp_cbtask arch/aarch32/context
# `veneers.o` must sit immediately before `veneer.o`: two registration
# wrappers in the latter reach handlers in the former with `adr`, whose range
# is a few hundred bytes. The link fails if it is exceeded.
WIMP_OBJS := $(BUILD)/Sources/Desktop/Wimp/modhead.o \
              $(addprefix $(BUILD)/Sources/Desktop/Wimp/,$(addsuffix .o,$(WIMP_NAMES))) \
              $(BUILD)/Sources/Desktop/Wimp/veneers.o \
              $(BUILD)/Sources/Desktop/Wimp/veneer.o \
              $(BUILD)/Sources/Desktop/Wimp/extend.o \
              $(BUILD)/Sources/Desktop/Wimp/colourmap.o \
              $(BUILD)/Sources/Desktop/Wimp/resources.o
WIMP_MOD := $(BUILD)/Desktop/Wimp.mod
# WIMP_MODULE_TELEMETRY=1 makes the dispatcher report every SWI and error
# through RPCEmu's telemetry port. Off by default: on real hardware these are
# writes to nonexistent registers. `wimpmachinetest` turns it on.
WIMP_MODULE_TELEMETRY ?=
# Task slots and task handles are distinct enum types (`enum wimp_slot` in
# wimp.h); these three -Werror flags stop one being used as the other.
WIMP_CFLAGS := $(MOD_CFLAGS) -I$(WIMP_DIR)/c -I$(BUILD)/Sources/Desktop/Wimp \
                -Werror=implicit-enum-enum-cast -Werror=enum-compare \
                -Werror=implicit-int-enum-cast \
                $(if $(WIMP_MODULE_TELEMETRY),-DWIMP_MODULE_TELEMETRY,)

# Stamp file recording WIMP_MODULE_TELEMETRY, since make does not track the
# flag. Without it, switching the flag rebuilds only changed files and links
# objects that disagree about whether the reporting functions exist.
WIMP_STAMP := $(BUILD)/Desktop/Wimp.telemetry

.PHONY: wimp-telemetry-stamp
wimp-telemetry-stamp:
	@mkdir -p $(dir $(WIMP_STAMP))
	@printf '%s\n' '$(WIMP_MODULE_TELEMETRY)' | cmp -s - $(WIMP_STAMP) \
	  || printf '%s\n' '$(WIMP_MODULE_TELEMETRY)' > $(WIMP_STAMP)

$(WIMP_STAMP): wimp-telemetry-stamp ;

$(BUILD)/Sources/Desktop/Wimp/extend.o: $(WIMP_DIR)/s/aarch32/extend.S
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Wimp/modhead.S: $(WIMP_DIR)/cmhg/modhead \
                                   $(WIMP_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(WIMP_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/Wimp/%.o: $(WIMP_DIR)/c/%.c Makefile $(WIMP_STAMP) \
                            $(BUILD)/Sources/Desktop/Wimp/wimp_module_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -c $< -o $@

# The service handler, the Poll callback and the WrchV claimant, with their C
# prototypes. The module's other boundaries are listed in s/boundaries with
# why each is hand-written. See tools/build/marshal.py.
$(BUILD)/Sources/Desktop/Wimp/veneers.S: $(WIMP_DIR)/s/boundaries \
                                   $(WIMP_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(WIMP_DIR) --asm $@ \
	  --header $(BUILD)/Sources/Desktop/Wimp/wimp_module_veneers.h

$(BUILD)/Sources/Desktop/Wimp/wimp_module_veneers.h: $(BUILD)/Sources/Desktop/Wimp/veneers.S

$(BUILD)/Sources/Desktop/Wimp/veneers.o: $(BUILD)/Sources/Desktop/Wimp/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Wimp/veneer.o: $(WIMP_DIR)/s/aarch32/veneer.S
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -x assembler-with-cpp -c $< -o $@

# The colour-mapping pair, in its own file because it has no kernel
# dependency, so the state probe can link and run the shipped instructions.
$(BUILD)/Sources/Desktop/Wimp/colourmap.o: $(WIMP_DIR)/s/aarch32/colourmap.S
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/tests/wimp/colourmap.o: $(WIMP_DIR)/s/aarch32/colourmap.S
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -x assembler-with-cpp -c $< -o $@

# The Wimp's own resources. The icon bar, error box, command window and
# backdrop are templates loaded from here, and window furniture sizes are
# measured from Tools, so none of these is optional.
#
# Sprites is the ROM half of the shared sprite pool: error box icons,
# AutoScroll and other pointers, and window background tiles come from it.
# It is `Sources/Desktop/Wimp/Resources/UK/Sovereign/Sprites,ff9` unchanged. The
# reference ROM carries the Sovereign set (4bpp), not the Ursula set (8bpp),
# even though the ROM is built OPTIONS=Ursula; USERIF= selects resources,
# OPTIONS= selects assembly options.
WIMP_RES_TEXT := $(WIMP_DIR)/Resources/UK/Messages+$(WIMP_DIR)/Resources/UK/CmdHelp
WIMP_RES_TPL  := $(WIMP_DIR)/Resources/UK/Templates,fec
WIMP_RES_TOOLS := $(WIMP_DIR)/Resources/UK/Tools,ff9
WIMP_RES_SPR  := $(WIMP_DIR)/Resources/UK/Sprites,ff9
WIMP_RES_BLOCK := $(BUILD)/Sources/Desktop/Wimp/resources.bin
WIMP_RES_FILES := $(subst +, ,$(WIMP_RES_TEXT))

$(WIMP_RES_BLOCK): $(WIMP_RES_FILES) $(WIMP_RES_TPL) $(WIMP_RES_TOOLS) \
                    $(WIMP_RES_SPR) tools/build/mkresfs.py
	@mkdir -p $(dir $@)
	@python3 tools/build/mkresfs.py $@ --strip-directives \
	  --file "Resources.Wimp.Messages=$(WIMP_RES_TEXT)" \
	  --file "Resources.Wimp.Templates:fec=$(WIMP_RES_TPL)" \
	  --file "Resources.Wimp.Tools:ff9=$(WIMP_RES_TOOLS)" \
	  --file "Resources.Wimp.Sprites:ff9=$(WIMP_RES_SPR)"

$(BUILD)/Sources/Desktop/Wimp/resources.o: $(WIMP_DIR)/s/resources.S $(WIMP_RES_BLOCK)
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -DRESOURCE_BLOCK='"$(WIMP_RES_BLOCK)"' \
	  -x assembler-with-cpp -c $< -o $@

$(BUILD)/Sources/Desktop/Wimp/modhead.o: $(BUILD)/Sources/Desktop/Wimp/modhead.S
	$(CC) $(WIMP_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/Wimp.elf: $(WIMP_OBJS) $(BUILD)/lib/libclib.a mk/ld/module.ld \
                             tools/build/modulecheck.py Makefile
	$(LD) $(LDFLAGS) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(WIMP_OBJS) $(BUILD)/lib/libclib.a
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(WIMP_MOD): $(BUILD)/Desktop/Wimp.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Wimp - ported)"

-include $(WIMP_OBJS:.o=.d)

wimp: $(WIMP_MOD)
.PHONY: wimp

# Host checks: the veneers checked against their register images in the
# linked ARM image, and the module's C run against stand-ins under QEMU, as
# ARM32 and as AArch64. `wimpmachinetest` checks whether a desktop comes up.
WIMP64_QEMU := qemu-system-aarch64 -M virt -cpu cortex-a72 -m 1G -nographic \
               -monitor none -serial none -audio none -semihosting
wimpchecks: $(BUILD)/Desktop/Wimp.elf $(BUILD)/tests/wimp-messages.bin \
             $(BUILD)/tests/wimp-state.bin $(BUILD)/tests/wimp64-messages \
             $(BUILD)/tests/wimp64-state Sources/Desktop/Wimp/test/wimp/check.py
	@python3 Sources/Desktop/Wimp/test/wimp/check.py $(BUILD)/Desktop/Wimp.elf
	@output="$$(timeout 5 qemu-system-arm -M versatilepb -nographic -monitor none -serial none \
	  -audio none -semihosting -kernel $(BUILD)/tests/wimp-messages.bin 2>&1 || true)"; \
	  case "$$output" in *"wimp messages: PASS"*) ;; *) echo "$$output"; exit 1;; esac
	@output="$$(timeout 10 qemu-system-arm -M versatilepb -nographic -monitor none -serial none \
	  -audio none -semihosting -kernel $(BUILD)/tests/wimp-state.bin 2>&1 || true)"; \
	  case "$$output" in *"wimp state: PASS"*) ;; *) echo "$$output"; exit 1;; esac
	@output="$$(timeout 5 $(WIMP64_QEMU) -kernel $(BUILD)/tests/wimp64-messages 2>&1 || true)"; \
	  case "$$output" in *"wimp messages: PASS"*) ;; *) echo "AArch64: $$output"; exit 1;; esac
	@output="$$(timeout 10 $(WIMP64_QEMU) -kernel $(BUILD)/tests/wimp64-state 2>&1 || true)"; \
	  case "$$output" in *"wimp state: PASS"*) ;; *) echo "AArch64: $$output"; exit 1;; esac
	@echo "wimp: the messages and state probes pass as ARM32 and as AArch64"
.PHONY: wimpchecks

# The message list handling (all/none/selected), built with an RMA stand-in
# and run under QEMU ARM semihosting. --gc-sections drops the SWI-facing
# routines that need a live RISC OS address validator.
$(BUILD)/tests/wimp/messages.o: Sources/Desktop/Wimp/test/wimp/messages.c Sources/Desktop/Wimp/c/wimp.h \
                                Sources/Desktop/Wimp/test/wimp/standins.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_PROBE_CFLAGS) -c $< -o $@

$(BUILD)/tests/wimp/wimp_messages.o: Sources/Desktop/Wimp/c/wimp_messages.c Sources/Desktop/Wimp/c/wimp.h \
                                     $(BUILD)/Sources/Desktop/Wimp/wimp_module_veneers.h \
                                     Sources/Desktop/Wimp/test/wimp/standins.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_PROBE_CFLAGS) -c $< -o $@

$(BUILD)/tests/wimp/start.o: Sources/Desktop/Wimp/test/wimp/start.S Sources/Desktop/Wimp/test/wimp/probe.ld
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -c $< -o $@

$(BUILD)/tests/wimp/os.o: Sources/Desktop/Wimp/test/wimp/os.S
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -c $< -o $@

$(BUILD)/tests/wimp/oscalls.o: Sources/Desktop/Wimp/test/wimp/os.c Sources/Desktop/Wimp/test/wimp/os.h \
                                Sources/Desktop/Wimp/c/wimp.h Sources/Desktop/Wimp/test/wimp/standins.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_PROBE_CFLAGS) -c $< -o $@

$(BUILD)/tests/wimp-messages: $(BUILD)/tests/wimp/start.o \
                               $(BUILD)/tests/wimp/os.o \
                               $(BUILD)/tests/wimp/oscalls.o \
                               $(BUILD)/tests/wimp/aeabi.o \
                               $(BUILD)/tests/wimp/messages.o \
                               $(BUILD)/tests/wimp/wimp_messages.o Sources/Desktop/Wimp/test/wimp/probe.ld
	$(LD) --gc-sections -T Sources/Desktop/Wimp/test/wimp/probe.ld -o $@ $(filter %.o,$^)

$(BUILD)/tests/wimp-messages.bin: $(BUILD)/tests/wimp-messages
	$(OBJCOPY) -O binary $< $@

# The state probe runs the handlers through the real SWI dispatcher against
# the RMA and CMOS stand-ins in Sources/Desktop/Wimp/test/wimp/os.c. Every Wimp unit except the
# module entry points and the ARM veneer is linked; Sources/Desktop/Wimp/test/wimp/check.py covers
# the veneer.
WIMP_PROBE_NAMES := $(filter-out wimp_module,$(WIMP_NAMES))
WIMP_PROBE_OBJS := $(addprefix $(BUILD)/tests/wimp/,$(addsuffix .o,$(WIMP_PROBE_NAMES)))
# Every C file in the probes calls the OS through Sources/Desktop/Wimp/test/wimp/os.c's named
# stand-ins rather than a SWI; see Sources/Desktop/Wimp/test/wimp/standins.h.
WIMP_PROBE_CFLAGS := $(WIMP_CFLAGS) -include Sources/Desktop/Wimp/test/wimp/standins.h \
                     -ffunction-sections -fdata-sections

$(BUILD)/tests/wimp/wimp_%.o: $(WIMP_DIR)/c/wimp_%.c $(WIMP_DIR)/c/wimp.h \
                                $(BUILD)/Sources/Desktop/Wimp/wimp_module_veneers.h \
                                Sources/Desktop/Wimp/test/wimp/standins.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_PROBE_CFLAGS) -c $< -o $@

$(BUILD)/tests/wimp/arch/%.o: $(WIMP_DIR)/c/arch/%.c $(WIMP_DIR)/c/wimp.h \
                               $(WIMP_DIR)/c/arch/context.h Sources/Desktop/Wimp/test/wimp/standins.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_PROBE_CFLAGS) -c $< -o $@

$(BUILD)/tests/wimp/state.o: Sources/Desktop/Wimp/test/wimp/state.c Sources/Desktop/Wimp/test/wimp/os.h \
                              $(wildcard Sources/Desktop/Wimp/test/wimp/state_*.c) \
                              Sources/Desktop/Wimp/c/wimp.h Sources/Desktop/Wimp/test/wimp/standins.h \
                              $(BUILD)/Sources/Desktop/Wimp/wimp_module_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_PROBE_CFLAGS) -c $< -o $@

# The shipped template, Tools and Sprites files, embedded for the loader tests.
$(BUILD)/tests/wimp/templates.o: Sources/Desktop/Wimp/test/wimp/templates.S $(WIMP_RES_TPL) \
                                 $(WIMP_RES_TOOLS) $(WIMP_RES_SPR)
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -DTEMPLATE_FILE='"$(WIMP_RES_TPL)"' \
	  -DTOOLS_FILE='"$(WIMP_RES_TOOLS)"' \
	  -DSPRITES_FILE='"$(WIMP_RES_SPR)"' \
	  -x assembler-with-cpp -c $< -o $@

# The generated veneers are linked into the probe as they ship, since they
# only marshal registers (no mode change, callback or task resume).
# `Sources/Desktop/Wimp/test/wimp/swi.S` enters them.
$(BUILD)/tests/wimp/swi.o: Sources/Desktop/Wimp/test/wimp/swi.S Sources/Desktop/Wimp/s/aarch32/contextrestore.inc
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) -x assembler-with-cpp -c $< -o $@

# The call-outs to filters and application routines would enter code the
# probe has not got, so it links them under other names and records the calls
# in Sources/Desktop/Wimp/test/wimp/veneers.c.
$(BUILD)/tests/wimp/genveneers.o: $(BUILD)/Sources/Desktop/Wimp/veneers.S Makefile
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) \
	  -Dwimp_module_filter_poll=wimp_module_generated_filter_poll \
	  -Dwimp_module_filter_rectangle_call=wimp_module_generated_filter_rectangle_call \
	  -Dwimp_module_filter_block_copy=wimp_module_generated_filter_block_copy \
	  -Dwimp_module_autoscroll_call_veneer=wimp_module_generated_autoscroll_call_veneer \
	  -Dwimp_module_drag_call_veneer=wimp_module_generated_drag_call_veneer \
	  -x assembler-with-cpp -c $< -o $@

$(BUILD)/tests/wimp-state: $(BUILD)/tests/wimp/start.o \
                            $(BUILD)/tests/wimp/os.o \
                            $(BUILD)/tests/wimp/oscalls.o \
                            $(BUILD)/tests/wimp/state.o \
                            $(BUILD)/tests/wimp/veneers.o \
                            $(BUILD)/tests/wimp/swi.o \
                            $(BUILD)/tests/wimp/genveneers.o \
                            $(BUILD)/Sources/Desktop/Wimp/extend.o \
                            $(BUILD)/tests/wimp/colourmap.o \
                            $(BUILD)/tests/wimp/templates.o \
                            $(BUILD)/tests/wimp/aeabi.o \
                            $(WIMP_PROBE_OBJS) Sources/Desktop/Wimp/test/wimp/probe.ld
	$(LD) --gc-sections -T Sources/Desktop/Wimp/test/wimp/probe.ld -o $@ $(filter %.o,$^)

# The ARM32 division and memory helpers, as the module gets them from the
# C library.
$(BUILD)/tests/wimp/aeabi.o: Sources/Lib/CLib/platform/arm32/aeabi.c
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_CFLAGS) $(CLIB_CPPFLAGS) -ffunction-sections -c $< -o $@

$(BUILD)/tests/wimp/veneers.o: Sources/Desktop/Wimp/test/wimp/veneers.c Sources/Desktop/Wimp/c/wimp.h \
                               Sources/Desktop/Wimp/test/wimp/standins.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP_PROBE_CFLAGS) -c $< -o $@

$(BUILD)/tests/wimp-state.bin: $(BUILD)/tests/wimp-state
	$(OBJCOPY) -O binary $< $@

# The same two probes built for AArch64 and run under qemu-system-aarch64:
# the Wimp's portable C and its AArch64 layer with the AArch64 module flags,
# less the tiny code model, which cannot reach across the state probe.
# c/arch/aarch64/entries.c and s/aarch64/veneer.S are linked with their
# symbols weak, so the stand-ins Sources/Desktop/Wimp/test/wimp/veneers.c and
# Sources/Desktop/Wimp/test/wimp/aarch64/swi.S replace the entries a bare-metal probe cannot
# run, as s/aarch32/veneer.S is left out of the ARM32 probe.
WIMP64 := $(BUILD)/tests/wimp64
WIMP64_TARGET := aarch64-none-elf
WIMP64_CFLAGS := --target=$(WIMP64_TARGET) \
                 $(filter-out --target=% $(ALIGN_FLAGS),$(COMMON_FLAGS)) \
                 $(ARCH_a64virt) -mcmodel=small -fpie -fvisibility=hidden \
                 -fno-jump-tables -Os -g -std=gnu17 $(WARNINGS) $(DEPFLAGS) \
                 -Iinclude -ISources/Kernel/hdr $(LIB_INCLUDES) \
                 -I$(WIMP_DIR) -I$(WIMP_DIR)/c -I$(WIMP64) \
                 -Werror=implicit-enum-enum-cast -Werror=enum-compare \
                 -Werror=implicit-int-enum-cast
WIMP64_PROBE_CFLAGS := $(WIMP64_CFLAGS) -include Sources/Desktop/Wimp/test/wimp/standins.h \
                       -ffunction-sections -fdata-sections
WIMP64_ASFLAGS := --target=$(WIMP64_TARGET) -Iinclude -ISources/Kernel/hdr \
                  -I$(WIMP_DIR)/c -I$(WIMP64) $(DEPFLAGS) -x assembler-with-cpp
WIMP64_NAMES := $(filter-out arch/aarch32/context,$(WIMP_PROBE_NAMES)) \
                arch/aarch64/context arch/aarch64/intercept
WIMP64_OBJS := $(addprefix $(WIMP64)/,$(addsuffix .o,$(WIMP64_NAMES)))
# UnicodeData's library, which stands in for the module in the probe.
WIMP64_UNICODE := $(addprefix $(WIMP64)/unicode/,unicode.o unicase.o ungrapheme.o unidata_generated.o)
$(WIMP64)/unicode/%.o: Sources/Lib/Unicode/%.c
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_CFLAGS) -ffunction-sections -fdata-sections -c $< -o $@

$(WIMP64)/veneers.S: $(WIMP_DIR)/s/boundaries $(WIMP_DIR)/cmhg/modhead \
                     tools/build/marshal.py tools/build/marshal_a64.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py --target $(WIMP64_TARGET) $(WIMP_DIR) \
	  --asm $@ --header $(WIMP64)/wimp_module_veneers.h > /dev/null

$(WIMP64)/wimp_module_veneers.h: $(WIMP64)/veneers.S

$(WIMP64)/wimp_%.o: $(WIMP_DIR)/c/wimp_%.c $(WIMP_DIR)/c/wimp.h \
                    $(WIMP64)/wimp_module_veneers.h Sources/Desktop/Wimp/test/wimp/standins.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_PROBE_CFLAGS) -c $< -o $@

$(WIMP64)/arch/%.o: $(WIMP_DIR)/c/arch/%.c $(WIMP_DIR)/c/wimp.h \
                    $(WIMP_DIR)/c/arch/context.h $(WIMP64)/wimp_module_veneers.h \
                    Sources/Desktop/Wimp/test/wimp/standins.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_PROBE_CFLAGS) -c $< -o $@

$(WIMP64)/weak/entries.o: $(WIMP64)/arch/aarch64/entries.o
	@mkdir -p $(dir $@)
	$(OBJCOPY) --weaken $< $@

$(WIMP64)/s/%.o: $(WIMP_DIR)/s/aarch64/%.S
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_ASFLAGS) -c $< -o $@

$(WIMP64)/weak/veneer.o: $(WIMP64)/s/veneer.o
	@mkdir -p $(dir $@)
	$(OBJCOPY) --weaken $< $@

$(WIMP64)/probe/%.o: Sources/Desktop/Wimp/test/wimp/aarch64/%.S
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_ASFLAGS) -c $< -o $@

$(WIMP64)/probe/oscalls.o: Sources/Desktop/Wimp/test/wimp/os.c Sources/Desktop/Wimp/test/wimp/os.h \
                           $(WIMP_DIR)/c/wimp.h Sources/Desktop/Wimp/test/wimp/standins.h \
                           $(WIMP64)/wimp_module_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_PROBE_CFLAGS) -c $< -o $@

$(WIMP64)/probe/messages.o: Sources/Desktop/Wimp/test/wimp/messages.c $(WIMP_DIR)/c/wimp.h \
                            Sources/Desktop/Wimp/test/wimp/standins.h $(WIMP64)/wimp_module_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_PROBE_CFLAGS) -c $< -o $@

$(WIMP64)/probe/veneers.o: Sources/Desktop/Wimp/test/wimp/veneers.c $(WIMP_DIR)/c/wimp.h \
                           Sources/Desktop/Wimp/test/wimp/standins.h $(WIMP64)/wimp_module_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_PROBE_CFLAGS) -c $< -o $@

$(WIMP64)/probe/state.o: Sources/Desktop/Wimp/test/wimp/state.c Sources/Desktop/Wimp/test/wimp/os.h \
                         $(wildcard Sources/Desktop/Wimp/test/wimp/state_*.c) \
                         $(WIMP_DIR)/c/wimp.h Sources/Desktop/Wimp/test/wimp/standins.h \
                         $(WIMP64)/wimp_module_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_PROBE_CFLAGS) -c $< -o $@

$(WIMP64)/probe/templates.o: Sources/Desktop/Wimp/test/wimp/templates.S $(WIMP_RES_TPL) \
                             $(WIMP_RES_TOOLS) $(WIMP_RES_SPR)
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_ASFLAGS) -DTEMPLATE_FILE='"$(WIMP_RES_TPL)"' \
	  -DTOOLS_FILE='"$(WIMP_RES_TOOLS)"' \
	  -DSPRITES_FILE='"$(WIMP_RES_SPR)"' -c $< -o $@

$(WIMP64)/probe/genveneers.o: $(WIMP64)/veneers.S Makefile
	@mkdir -p $(dir $@)
	$(CC) $(WIMP64_ASFLAGS) \
	  -Dwimp_module_filter_poll=wimp_module_generated_filter_poll \
	  -Dwimp_module_filter_rectangle_call=wimp_module_generated_filter_rectangle_call \
	  -Dwimp_module_filter_block_copy=wimp_module_generated_filter_block_copy \
	  -Dwimp_module_autoscroll_call_veneer=wimp_module_generated_autoscroll_call_veneer \
	  -Dwimp_module_drag_call_veneer=wimp_module_generated_drag_call_veneer \
	  -c $< -o $@

$(BUILD)/tests/wimp64-messages: $(WIMP64)/probe/start.o $(WIMP64)/probe/os.o \
                                $(WIMP64)/probe/oscalls.o $(WIMP64)/probe/messages.o \
                                $(WIMP64)/wimp_messages.o Sources/Desktop/Wimp/test/wimp/aarch64/probe.ld
	$(LD) --gc-sections -T Sources/Desktop/Wimp/test/wimp/aarch64/probe.ld -o $@ $(filter %.o,$^)

$(BUILD)/tests/wimp64-state: $(WIMP64)/probe/start.o $(WIMP64)/probe/os.o \
                             $(WIMP64)/probe/oscalls.o $(WIMP64)/probe/state.o \
                             $(WIMP64)/probe/veneers.o $(WIMP64)/probe/swi.o \
                             $(WIMP64)/probe/genveneers.o $(WIMP64)/probe/templates.o \
                             $(WIMP64)/s/extend.o $(WIMP64)/s/colourmap.o \
                             $(WIMP64)/weak/veneer.o $(WIMP64)/weak/entries.o \
                             $(WIMP64_OBJS) $(WIMP64_UNICODE) Sources/Desktop/Wimp/test/wimp/aarch64/probe.ld
	$(LD) --gc-sections -T Sources/Desktop/Wimp/test/wimp/aarch64/probe.ld -o $@ $(filter %.o,$^)

-include $(WIMP64_OBJS:.o=.d) $(WIMP64)/probe/state.d $(WIMP64)/probe/oscalls.d

# Host checks for FilerSWIs.
FS_TEST_GEN := $(BUILD)/tests/filerswis
FS_TEST_FLAGS := $(HOST_CFLAGS) -I$(FS_DIR)/c -ISources/Desktop/FilerSWIs/test/filerswis -Itests/lib \
                 -Iinclude

$(FS_TEST_GEN)/%.o: Sources/Desktop/FilerSWIs/test/filerswis/%.c Sources/Desktop/FilerSWIs/test/filerswis/fssend_test.h \
                    $(FS_DIR)/c/filerswis.h $(FS_DIR)/c/fssend.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FS_TEST_FLAGS) -c $< -o $@

$(FS_TEST_GEN)/filerswis.o: $(FS_DIR)/c/filerswis.c $(FS_DIR)/c/filerswis.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FS_TEST_FLAGS) -c $< -o $@

$(FS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/filerswis-checks: $(addprefix $(FS_TEST_GEN)/,checks.o \
                                   filerswis.o fssend_test.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

filerswischecks: $(BUILD)/tests/filerswis-checks
	@$<
.PHONY: filerswischecks

# Host checks for DragAnObject's arithmetic and its two mask loops.
DAO_TEST_GEN := $(BUILD)/tests/dragobj
DAO_TEST_FLAGS := $(HOST_CFLAGS) -I$(DAO_DIR)/c -Itests/lib -Iinclude

$(DAO_TEST_GEN)/%.o: Sources/Desktop/DragAnObj/test/dragobj/%.c $(DAO_DIR)/c/dragobj.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DAO_TEST_FLAGS) -c $< -o $@

$(DAO_TEST_GEN)/dragobj.o: $(DAO_DIR)/c/dragobj.c $(DAO_DIR)/c/dragobj.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DAO_TEST_FLAGS) -c $< -o $@

$(DAO_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DAO_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/dragobj-checks: $(addprefix $(DAO_TEST_GEN)/,checks.o \
                                 dragobj.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

dragobjchecks: $(BUILD)/tests/dragobj-checks
	@$<
.PHONY: dragobjchecks

# Host checks for DragASprite's arithmetic: sprite position in the drag box,
# sprite space needed, the hatching pattern, the drag bounding box and the
# drop shadow compositing.
DSP_TEST_GEN := $(BUILD)/tests/dragspr
DSP_TEST_FLAGS := $(HOST_CFLAGS) -I$(DSP_DIR)/c -Itests/lib -Iinclude

$(DSP_TEST_GEN)/%.o: Sources/Desktop/DragASprit/test/dragspr/%.c $(DSP_DIR)/c/dragspr.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DSP_TEST_FLAGS) -c $< -o $@

$(DSP_TEST_GEN)/dragspr.o: $(DSP_DIR)/c/dragspr.c $(DSP_DIR)/c/dragspr.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DSP_TEST_FLAGS) -c $< -o $@

$(DSP_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DSP_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/dragspr-checks: $(addprefix $(DSP_TEST_GEN)/,checks.o \
                                 dragspr.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

dragsprchecks: $(BUILD)/tests/dragspr-checks
	@$<
.PHONY: dragsprchecks

# Host checks for FilterManager's lists, walks and listing. fm_call_filter is
# the seam: here it records the call and returns what a filter would have
# left, so claimed, masked and self-deregistering filters can be checked.
FLT_TEST_GEN := $(BUILD)/tests/filter
FLT_TEST_FLAGS := $(HOST_CFLAGS) -I$(FLT_DIR)/c -I$(BUILD)/Sources/Desktop/Filter -Itests/lib -Iinclude

$(FLT_TEST_GEN)/%.o: Sources/Desktop/Filter/test/filter/%.c $(FLT_DIR)/c/filtmgr.h \
                     $(BUILD)/Sources/Desktop/Filter/fm_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FLT_TEST_FLAGS) -c $< -o $@

$(FLT_TEST_GEN)/%.o: $(FLT_DIR)/c/%.c $(FLT_DIR)/c/filtmgr.h \
                     $(BUILD)/Sources/Desktop/Filter/fm_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FLT_TEST_FLAGS) -c $< -o $@

$(FLT_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FLT_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/filter-checks: $(addprefix $(FLT_TEST_GEN)/,checks.o \
                                filtmgr.o fmwalk.o fmprint.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

filterchecks: $(BUILD)/tests/filter-checks
	@$<
.PHONY: filterchecks

# Host checks for Free's arithmetic: size formatting, bar lengths, per cent,
# the NVMe health log's fields, the rows and the lists.
FRE_TEST_GEN := $(BUILD)/tests/free
FRE_TEST_FLAGS := $(HOST_CFLAGS) -I$(FRE_DIR)/c -Itests/lib -Iinclude

$(FRE_TEST_GEN)/%.o: Sources/Desktop/Free/test/free/%.c $(FRE_DIR)/c/free.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FRE_TEST_FLAGS) -c $< -o $@

$(FRE_TEST_GEN)/%.o: $(FRE_DIR)/c/%.c $(FRE_DIR)/c/free.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FRE_TEST_FLAGS) -c $< -o $@

$(FRE_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FRE_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/free-checks: $(addprefix $(FRE_TEST_GEN)/,checks.o \
                              freecalc.o freelist.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

freechecks: $(BUILD)/tests/free-checks
	@$<
.PHONY: freechecks

# MessageTrans: message files and token lookup.
#
# Token lookup, value extraction and parameter substitution are pure and are
# checked on the host; the SWIs, RMA and ResourceFS handling are the module
# around them.

# MessageTrans as a module: the ten SWIs over the file half above.
MSG_OBJS := $(BUILD)/modules/msgtrans/modhead.o \
            $(BUILD)/modules/msgtrans/msgfile.o \
            $(BUILD)/modules/msgtrans/msgmenu.o \
            $(BUILD)/modules/msgtrans/msgopen.o \
            $(BUILD)/modules/msgtrans/msgmodule.o \
            $(BUILD)/modules/msgtrans/veneers.o
MSG_MOD  := $(BUILD)/modules/MessageTrans.mod
MSG_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Internat/MsgTrans/c \
              -I$(BUILD)/modules/msgtrans

$(BUILD)/modules/msgtrans/modhead.S: Sources/Internat/MsgTrans/cmhg/modhead \
                                     Sources/Internat/MsgTrans/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Internat/MsgTrans -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/msgtrans/%.o: Sources/Internat/MsgTrans/c/%.c Makefile \
                               $(BUILD)/modules/msgtrans/msgtrans_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(MSG_CFLAGS) -c $< -o $@

# The veneers and their C prototypes, from one declaration. See
# tools/build/marshal.py.
$(BUILD)/modules/msgtrans/veneers.S: Sources/Internat/MsgTrans/s/boundaries \
                                     Sources/Internat/MsgTrans/cmhg/modhead \
                                     tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Internat/MsgTrans --asm $@ \
	  --header $(BUILD)/modules/msgtrans/msgtrans_veneers.h

$(BUILD)/modules/msgtrans/msgtrans_veneers.h: \
    $(BUILD)/modules/msgtrans/veneers.S

$(BUILD)/modules/msgtrans/veneers.o: $(BUILD)/modules/msgtrans/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(MSG_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/msgtrans/modhead.o: $(BUILD)/modules/msgtrans/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(MSG_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/MessageTrans.elf: $(MSG_OBJS) $(CLIB) mk/ld/module.ld \
                                   tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(MSG_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(MSG_MOD): $(BUILD)/modules/MessageTrans.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, MessageTrans - ported)"

-include $(MSG_OBJS:.o=.d)

msgtransmodule: $(MSG_MOD)
.PHONY: msgtransmodule

MSG_DIR := Sources/Internat/MsgTrans
MSG_TEST_GEN := $(BUILD)/tests/msgtrans
MSG_TEST_FLAGS := $(HOST_CFLAGS) -I$(MSG_DIR)/c -Itests/lib -Iinclude \
                  -ISources/Utilities/Squash/c

$(MSG_TEST_GEN)/%.o: Sources/Internat/MsgTrans/test/msgtrans/%.c $(MSG_DIR)/c/msgfile.h \
                                    $(MSG_DIR)/c/msgopen.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MSG_TEST_FLAGS) -c $< -o $@

$(MSG_TEST_GEN)/%.o: $(MSG_DIR)/c/%.c $(MSG_DIR)/c/msgfile.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MSG_TEST_FLAGS) -c $< -o $@

$(MSG_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MSG_TEST_FLAGS) -c $< -o $@

# Squash's core, linked into the test so squashed message files are checked
# against the real compressor.
$(MSG_TEST_GEN)/squashlzw.o: Sources/Utilities/Squash/c/squashlzw.c \
                             Sources/Utilities/Squash/c/squashlzw.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MSG_TEST_FLAGS) -ISources/Utilities/Squash/c -c $< -o $@

$(BUILD)/tests/msgtrans-checks: $(addprefix $(MSG_TEST_GEN)/,checks.o \
                                  msgfile.o msgmenu.o msgopen.o squashlzw.o \
                                  check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

msgtranschecks: $(BUILD)/tests/msgtrans-checks
	@$<
.PHONY: msgtranschecks

# ResourceFS: files already in memory, found by name.
#
# A module registers a block that is a chain of records, each holding a file's
# name and data. The host checks cover walking the chain and wildcard
# matching, including treating a directory as implied by names rather than
# stored.

# ResourceFS as a module.
RFS_OBJS := $(BUILD)/modules/resourcefs/modhead.o \
            $(BUILD)/modules/resourcefs/resfs.o \
            $(BUILD)/modules/resourcefs/resfsmodule.o \
            $(BUILD)/modules/resourcefs/veneers.o
RFS_MOD  := $(BUILD)/modules/ResourceFS.mod
RFS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/ResourceFS/c \
              -I$(BUILD)/modules/resourcefs

$(BUILD)/modules/resourcefs/modhead.S: Sources/FileSys/ResourceFS/cmhg/modhead \
                                       Sources/FileSys/ResourceFS/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/ResourceFS -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/resourcefs/%.o: Sources/FileSys/ResourceFS/c/%.c Makefile \
                                 $(BUILD)/modules/resourcefs/resfs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(RFS_CFLAGS) -c $< -o $@

# The veneers and their C prototypes, from one declaration. See
# tools/build/marshal.py.
$(BUILD)/modules/resourcefs/veneers.S: Sources/FileSys/ResourceFS/s/boundaries \
                                       Sources/FileSys/ResourceFS/cmhg/modhead \
                                       tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/ResourceFS --asm $@ \
	  --header $(BUILD)/modules/resourcefs/resfs_veneers.h

$(BUILD)/modules/resourcefs/resfs_veneers.h: $(BUILD)/modules/resourcefs/veneers.S

$(BUILD)/modules/resourcefs/veneers.o: $(BUILD)/modules/resourcefs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(RFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/resourcefs/fsblock.o: Sources/FileSys/ResourceFS/s/fsblock.S
	@mkdir -p $(dir $@)
	$(CC) $(RFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/resourcefs/modhead.o: $(BUILD)/modules/resourcefs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(RFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/ResourceFS.elf: $(RFS_OBJS) $(CLIB) mk/ld/module.ld \
                                 tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(RFS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(RFS_MOD): $(BUILD)/modules/ResourceFS.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ResourceFS - ported)"

-include $(RFS_OBJS:.o=.d)

resourcefsmodule: $(RFS_MOD)
.PHONY: resourcefsmodule

# The two registration errors and *ResourceFS's help, where the module's
# header names them.
MOD_DESKTOP_RES += Resources.ResourceFS.Messages=Sources/FileSys/ResourceFS/Resources/UK/Messages
$(MOD_KRES_BLOCK): Sources/FileSys/ResourceFS/Resources/UK/Messages

RFS_DIR := Sources/FileSys/ResourceFS
RFS_TEST_GEN := $(BUILD)/tests/resourcefs
RFS_TEST_FLAGS := $(HOST_CFLAGS) -I$(RFS_DIR)/c -Itests/lib -Iinclude

$(RFS_TEST_GEN)/%.o: Sources/FileSys/ResourceFS/test/resourcefs/%.c $(RFS_DIR)/c/resfs.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RFS_TEST_FLAGS) -c $< -o $@

$(RFS_TEST_GEN)/%.o: $(RFS_DIR)/c/%.c $(RFS_DIR)/c/resfs.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RFS_TEST_FLAGS) -c $< -o $@

$(RFS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RFS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/resourcefs-checks: $(addprefix $(RFS_TEST_GEN)/,checks.o \
                                    resfs.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

resourcefschecks: $(BUILD)/tests/resourcefs-checks
	@$<
.PHONY: resourcefschecks

# RamFS: a FileCore medium, not a filing system itself. It supplies two entry
# points that move data to and from dynamic area 5, and the disc-shape
# arithmetic for a given size. The arithmetic is checked on the host; the
# entry points on the machine by Sources/FileSys/RAMFS/test/ramfs.
RAMFS_OBJS := $(BUILD)/modules/ramfs/modhead.o \
              $(BUILD)/modules/ramfs/discrec.o \
              $(BUILD)/modules/ramfs/ramfsmodule.o \
              $(BUILD)/modules/ramfs/veneers.o
RAMFS_MOD  := $(BUILD)/modules/RamFS.mod
RAMFS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/RAMFS/c \
                -ISources/FileSys/FileCore/c -I$(BUILD)/modules/ramfs

$(BUILD)/modules/ramfs/modhead.S: Sources/FileSys/RAMFS/cmhg/modhead \
                                  Sources/FileSys/RAMFS/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/RAMFS -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/ramfs/%.o: Sources/FileSys/RAMFS/c/%.c Makefile \
                            $(BUILD)/modules/ramfs/ramfs_veneers.h \
                            Sources/FileSys/FileCore/c/fcdisc.h
	@mkdir -p $(dir $@)
	$(CC) $(RAMFS_CFLAGS) -c $< -o $@

# The veneers and the incarnation descriptor, with their C prototypes. See
# tools/build/marshal.py.
$(BUILD)/modules/ramfs/veneers.S: Sources/FileSys/RAMFS/s/boundaries \
                                  Sources/FileSys/RAMFS/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/RAMFS --asm $@ \
	  --header $(BUILD)/modules/ramfs/ramfs_veneers.h

$(BUILD)/modules/ramfs/ramfs_veneers.h: $(BUILD)/modules/ramfs/veneers.S

$(BUILD)/modules/ramfs/veneers.o: $(BUILD)/modules/ramfs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(RAMFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/ramfs/modhead.o: $(BUILD)/modules/ramfs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(RAMFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/RamFS.elf: $(RAMFS_OBJS) $(CLIB) mk/ld/module.ld \
                            tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(RAMFS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(RAMFS_MOD): $(BUILD)/modules/RamFS.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, RamFS - ported)"

-include $(RAMFS_OBJS:.o=.d)

ramfsmodule: $(RAMFS_MOD)
.PHONY: ramfsmodule

# ADFS: FileCore client for ATA and IOMD 765 floppy controllers.
#
# c/ata.c is the interface as arithmetic (register offsets, commands, sector
# addressing, identify block) and issues nothing, so `make adfschecks` runs it
# on the host; c/adfsmodule.c touches the bus and calls the OS.
ADFS_OBJS := $(BUILD)/modules/adfs/modhead.o \
             $(BUILD)/modules/adfs/ata.o \
             $(BUILD)/modules/adfs/flpdcb.o \
             $(BUILD)/modules/adfs/floppy.o \
             $(BUILD)/modules/adfs/floppyio.o \
             $(BUILD)/modules/adfs/floppyfiq.o \
             $(BUILD)/modules/adfs/adfsmodule.o \
             $(BUILD)/modules/adfs/veneers.o
ADFS_MOD  := $(BUILD)/modules/ADFS.mod
# ADFS_CFLAGS_EXTRA=-DADFS_TRACE writes every low-level call to the telemetry
# port. make does not see a flag change, so delete $(BUILD)/modules/adfs
# before building with it and again before building without it.
ADFS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/ADFS/c \
               -ISources/FileSys/FileCore/c -I$(BUILD)/modules/adfs $(ADFS_CFLAGS_EXTRA)

$(BUILD)/modules/adfs/modhead.S: Sources/FileSys/ADFS/cmhg/modhead \
                                 Sources/FileSys/ADFS/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/ADFS -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/adfs/%.o: Sources/FileSys/ADFS/c/%.c Makefile \
                           $(BUILD)/modules/adfs/adfs_veneers.h \
                           Sources/FileSys/ADFS/c/ata.h Sources/FileSys/ADFS/c/adfstrace.h \
                           Sources/FileSys/FileCore/c/fcdisc.h
	@mkdir -p $(dir $@)
	$(CC) $(ADFS_CFLAGS) -c $< -o $@

# The veneers and the incarnation descriptor, with their C prototypes. See
# tools/build/marshal.py.
$(BUILD)/modules/adfs/veneers.S: Sources/FileSys/ADFS/s/boundaries \
                                 Sources/FileSys/ADFS/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/ADFS --asm $@ \
	  --header $(BUILD)/modules/adfs/adfs_veneers.h

$(BUILD)/modules/adfs/adfs_veneers.h: $(BUILD)/modules/adfs/veneers.S

$(BUILD)/modules/adfs/veneers.o: $(BUILD)/modules/adfs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(ADFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/adfs/floppyfiq.o: Sources/FileSys/ADFS/s/floppyfiq.S
	@mkdir -p $(dir $@)
	$(CC) $(ADFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/adfs/modhead.o: $(BUILD)/modules/adfs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(ADFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/ADFS.elf: $(ADFS_OBJS) $(CLIB) mk/ld/module.ld \
                           tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(ADFS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(ADFS_MOD): $(BUILD)/modules/ADFS.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ADFS - ATA and floppy)"

-include $(ADFS_OBJS:.o=.d)

adfsmodule: $(ADFS_MOD)
.PHONY: adfsmodule

RAMFS_DIR := Sources/FileSys/RAMFS
RAMFS_TEST_GEN := $(BUILD)/tests/ramfs
RAMFS_TEST_FLAGS := $(HOST_CFLAGS) -I$(RAMFS_DIR)/c -ISources/FileSys/FileCore/c \
                    -Itests/lib -Iinclude -ISources/Kernel/hdr

$(RAMFS_TEST_GEN)/%.o: Sources/FileSys/RAMFS/test/ramfs/%.c $(RAMFS_DIR)/c/ramfs.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RAMFS_TEST_FLAGS) -c $< -o $@

$(RAMFS_TEST_GEN)/%.o: $(RAMFS_DIR)/c/%.c $(RAMFS_DIR)/c/ramfs.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RAMFS_TEST_FLAGS) -c $< -o $@

$(RAMFS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RAMFS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/ramfs-checks: $(addprefix $(RAMFS_TEST_GEN)/,checks.o \
                               discrec.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

ramfschecks: $(BUILD)/tests/ramfs-checks
	@$<
.PHONY: ramfschecks

# FileCore: the disc filing system under ADFS and RamFS.
#
# One module with an incarnation per client: FileCore_Create makes
# FileCore%RAM and FileCore%ADFS, each registered with FileSwitch under its
# client's name. Disc formats are C checked on the host against images the
# reference machine wrote; the module is checked on the machine against the
# reference FileCore. Sources/FileSys/FileCore/cmhg/modhead records coverage.
FC_OBJS := $(BUILD)/modules/filecore/modhead.o \
           $(BUILD)/modules/filecore/fcdisc.o \
           $(BUILD)/modules/filecore/fcprocess.o \
           $(BUILD)/modules/filecore/fcfiq.o \
           $(BUILD)/modules/filecore/fcnewmap.o \
           $(BUILD)/modules/filecore/fcoldmap.o \
           $(BUILD)/modules/filecore/fcdir.o \
           $(BUILD)/modules/filecore/fcbigdir.o \
           $(BUILD)/modules/filecore/fcobject.o \
           $(BUILD)/modules/filecore/fcmount.o \
           $(BUILD)/modules/filecore/fcsector.o \
           $(BUILD)/modules/filecore/fcdircache.o \
           $(BUILD)/modules/filecore/fcbuf.o \
           $(BUILD)/modules/filecore/fcbuffer.o \
           $(BUILD)/modules/filecore/fclayout.o \
           $(BUILD)/modules/filecore/fcalloc.o \
           $(BUILD)/modules/filecore/fccompact.o \
           $(BUILD)/modules/filecore/fcfile.o \
           $(BUILD)/modules/filecore/fcdrive.o \
           $(BUILD)/modules/filecore/fcpath.o \
           $(BUILD)/modules/filecore/fcfs.o \
           $(BUILD)/modules/filecore/fcswi.o \
           $(BUILD)/modules/filecore/fccmd.o \
           $(BUILD)/modules/filecore/fcformat.o \
           $(BUILD)/modules/filecore/fcmodule.o \
           $(BUILD)/modules/filecore/fiqos.o \
           $(BUILD)/modules/filecore/veneers.o
FC_MOD  := $(BUILD)/modules/FileCore.mod
FC_CFLAGS := $(MOD_CFLAGS) $(FC_CFLAGS_EXTRA) -ISources/Lib/CLib/include -ISources/FileSys/FileCore/c \
             -I$(BUILD)/modules/filecore

$(BUILD)/modules/filecore/modhead.S: Sources/FileSys/FileCore/cmhg/modhead \
                                     Sources/FileSys/FileCore/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/FileCore -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/filecore/%.o: Sources/FileSys/FileCore/c/%.c Makefile \
                               $(BUILD)/modules/filecore/fc_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FC_CFLAGS) -c $< -o $@

$(BUILD)/modules/filecore/veneers.S: Sources/FileSys/FileCore/s/boundaries \
                                     Sources/FileSys/FileCore/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/FileCore --asm $@ \
	  --header $(BUILD)/modules/filecore/fc_veneers.h

$(BUILD)/modules/filecore/fc_veneers.h: $(BUILD)/modules/filecore/veneers.S

$(BUILD)/modules/filecore/veneers.o: $(BUILD)/modules/filecore/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FC_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/filecore/fiqos.o: Sources/FileSys/FileCore/s/aarch32/fiqos.S
	@mkdir -p $(dir $@)
	$(CC) $(FC_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/filecore/modhead.o: $(BUILD)/modules/filecore/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(FC_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/FileCore.elf: $(FC_OBJS) $(CLIB) mk/ld/module.ld \
                               tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(FC_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(FC_MOD): $(BUILD)/modules/FileCore.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, FileCore - ported)"

-include $(FC_OBJS:.o=.d)

filecoremodule: $(FC_MOD)
.PHONY: filecoremodule

# Module oracle: the RamFS cases with the reference kernel and RamFS in both
# ROMs, differing only in FileCore. The reference run is shared with
# ramfsmodtest.
$(BUILD)/tests/filecore-refmod.rom: $(HAL_IOMD_BIN) $(BUILD)/tests/ramfs.mod $(FC_MOD)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  $(addprefix --module ,$(foreach m,$(filter-out 030-FileCore,$(TEST_CHAIN_ramfs)),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(FC_MOD) \
	  --module $(LEGACY)/romparts/032-RamFS.mod \
	  --module $(BUILD)/tests/ramfs.mod

# FileCore's filing system on the RAM disc, the same way: reference kernel and
# RamFS in both ROMs, only FileCore differs. Sources/FileSys/FileCore/test/fcfs covers directories,
# wildcards, renaming, restamping, handles, directory growth, and dismount and
# rename of the disc.
TEST_CHAIN_fcfs := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                   005-Messages 006-MessageTrans 007-UK
TEST_MODS_fcfs = $(FC_MOD) $(LEGACY)/romparts/032-RamFS.mod

$(BUILD)/tests/fcfs-refmod.rom: $(HAL_IOMD_BIN) $(BUILD)/tests/fcfs.mod
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  $(addprefix --module ,$(foreach m,$(TEST_CHAIN_fcfs),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(LEGACY)/romparts/030-FileCore.mod \
	  --module $(LEGACY)/romparts/032-RamFS.mod \
	  --module $(BUILD)/tests/fcfs.mod

fcfsmodtest: $(BUILD)/tests/fcfs-ref.rom $(BUILD)/tests/fcfs-refmod.rom \
             tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference FileCore:"
	@tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcfs-refmod.rom \
	  $(abspath $(BUILD))/tests/fcfs-refmod.txt
	@echo "this FileCore:"
	@tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcfs-ref.rom \
	  $(abspath $(BUILD))/tests/fcfs-ref.txt
	@echo
	@tools/build/reporttest.sh fcfsmod $(BUILD)/tests/fcfs-refmod.txt \
	  $(BUILD)/tests/fcfs-ref.txt $(BUILD)/tests/fcfsmod.diff \
	  Sources/FileSys/FileCore/test/fcfs/differs-reference
	@if grep -aq '^  FAIL  ' $(BUILD)/tests/fcfs-ref.txt; then \
	  echo "fcfsmodtest: our report carries FAILed checks:"; \
	  grep -a '^  FAIL  ' $(BUILD)/tests/fcfs-ref.txt; \
	  exit 1; \
	fi
.PHONY: fcfsmodtest

# The same cases with our FileSwitch in front of our FileCore, compared with
# the run above that differs only in FileSwitch. A filing system's own
# commands reach FileCore only through FileSwitch (FSControl_StarMinus and
# FSControl_ReadModuleBase, which supply the incarnation's private word), so
# only this pairing tests those reasons.
$(BUILD)/tests/fcfs-oursw.rom: $(HAL_IOMD_BIN) $(BUILD)/tests/fcfs.mod $(BUILD)/modules/FileSwitch.mod $(FC_MOD)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  --module $(FSW_MOD) \
	  $(addprefix --module ,$(foreach m,$(filter-out 002-FileSwitch,$(TEST_CHAIN_fcfs)),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(FC_MOD) \
	  --module $(LEGACY)/romparts/032-RamFS.mod \
	  --module $(BUILD)/tests/fcfs.mod

fcfsfswtest: $(BUILD)/tests/fcfs-ref.rom $(BUILD)/tests/fcfs-oursw.rom \
             tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference FileSwitch:"
	@tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcfs-ref.rom \
	  $(abspath $(BUILD))/tests/fcfs-ref.txt
	@echo "this FileSwitch:"
	@tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcfs-oursw.rom \
	  $(abspath $(BUILD))/tests/fcfs-oursw.txt
	@echo
	@tools/build/reporttest.sh fcfsfsw $(BUILD)/tests/fcfs-ref.txt \
	  $(BUILD)/tests/fcfs-oursw.txt $(BUILD)/tests/fcfsfsw.diff \
	  Sources/FileSys/FileCore/test/fcfs/differs-fileswitch
	@if grep -aq '^  FAIL  ' $(BUILD)/tests/fcfs-oursw.txt; then \
	  echo "fcfsfswtest: our report carries FAILed checks:"; \
	  grep -a '^  FAIL  ' $(BUILD)/tests/fcfs-oursw.txt; \
	  exit 1; \
	fi
.PHONY: fcfsfswtest

filecoremodtest: $(BUILD)/tests/ramfs-refmod.rom $(BUILD)/tests/filecore-refmod.rom \
                 tools/build/rom-test.sh tools/build/reporttest.sh
	@echo "the reference FileCore:"
	@tools/build/rom-test.sh $(abspath $(BUILD))/tests/ramfs-refmod.rom \
	  $(abspath $(BUILD))/tests/ramfs-refmod.txt
	@echo "this FileCore:"
	@tools/build/rom-test.sh $(abspath $(BUILD))/tests/filecore-refmod.rom \
	  $(abspath $(BUILD))/tests/filecore-refmod.txt
	@echo
	@tools/build/reporttest.sh filecoremod $(BUILD)/tests/ramfs-refmod.txt \
	  $(BUILD)/tests/filecore-refmod.txt $(BUILD)/tests/filecoremod.diff
	@if grep -aq '^  FAIL  ' $(BUILD)/tests/filecore-refmod.txt; then \
	  echo "filecoremodtest: our report carries FAILed checks:"; \
	  grep -a '^  FAIL  ' $(BUILD)/tests/filecore-refmod.txt; \
	  exit 1; \
	fi
.PHONY: filecoremodtest

# A test module that is a FileCore client, recording every call FileCore
# makes to its low-level entry: operation and flags, disc address, length and
# each scatter-list pair. Sources/FileSys/FileCore/test/fcprobe/cases.c creates an incarnation over an
# RMA block and asks for file buffers. Background transfers are visible only
# here, since their flag, process block and callback reach only the driver.
#
# One ROM, not two. The reference-FileCore variant,
# `$(BUILD)/tests/fcprobe-refmod.rom`, is not a target because the reference
# hangs deleting the incarnation after the last case. Build and run it by
# hand:
#
#   make build/tests/fcprobe-refmod.rom
#   tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcprobe-refmod.rom
#     $(abspath $(BUILD))/tests/fcprobe-refmod.txt 60
#
# (one command line), and read the report from the telemetry, which
# survives the hang.
TEST_CHAIN_fcprobe := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK
TEST_MODS_fcprobe = $(FC_MOD)
# FCPROBE_CFLAGS_EXTRA=-DFCPROBE_TRACE writes every low-level call to the
# telemetry port, which still works after a hang. FileCore's FC_CFLAGS_EXTRA
# is the equivalent on its side. make does not see a flag change, so delete
# $(BUILD)/tests/fcprobe/mod/cases.o and $(BUILD)/tests/fcprobe.mod before
# building with it, and again before building without it.
TEST_EXTRA_CFLAGS_fcprobe := -ISources/FileSys/FileCore/c -ISources/FileSys/RAMFS/c \
                             -I$(BUILD)/tests/fcprobe $(FCPROBE_CFLAGS_EXTRA)
# Sources/Lib/CLib supplies the division RamFS's disc shaping needs.
TEST_MOD_EXTRA_fcprobe := $(BUILD)/tests/fcprobe/mod/veneers.o \
                          $(BUILD)/tests/fcprobe/mod/discrec.o $(CLIB)

# The probe's own boundaries: the two entries FileCore calls and the
# incarnation descriptor. They use `static-base`: a test's writable data is a
# block claimed at initialisation with sb pointing at it, so entries reached
# from FileCore must restore sb before the C runs.
$(BUILD)/tests/fcprobe/mod/veneers.S: Sources/FileSys/FileCore/test/fcprobe/s/boundaries \
                                      Sources/FileSys/RAMFS/c/ramfs.h \
                                      tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/FileCore/test/fcprobe --asm $@ \
	  --header $(BUILD)/tests/fcprobe/fcprobe_veneers.h

$(BUILD)/tests/fcprobe/fcprobe_veneers.h: $(BUILD)/tests/fcprobe/mod/veneers.S

$(BUILD)/tests/fcprobe/mod/veneers.o: $(BUILD)/tests/fcprobe/mod/veneers.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(MOD_PIFLAGS) $(TEST_CFLAGS) \
	  -ISources/FileSys/RAMFS/c -ISources/FileSys/FileCore/c -c $< -o $@

# The probe's disc shape is RamFS's, since its medium is a block of RAM.
$(BUILD)/tests/fcprobe/mod/discrec.o: Sources/FileSys/RAMFS/c/discrec.c \
                                      Sources/FileSys/RAMFS/c/ramfs.h \
                                      Sources/FileSys/FileCore/c/fcdisc.h
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(MOD_PIFLAGS) $(TEST_CFLAGS) \
	  -ISources/FileSys/FileCore/c -ISources/FileSys/RAMFS/c -c $< -o $@

$(BUILD)/tests/fcprobe/mod/cases.o: $(BUILD)/tests/fcprobe/fcprobe_veneers.h \
                                    Sources/FileSys/RAMFS/c/ramfs.h

# The measuring ROM: the probe over the reference FileCore. See above for why
# it is not a target of its own.
$(BUILD)/tests/fcprobe-refmod.rom: $(HAL_IOMD_BIN) $(BUILD)/tests/fcprobe.mod \
                                   tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  $(addprefix --module ,$(foreach m,$(TEST_CHAIN_fcprobe),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(LEGACY)/romparts/030-FileCore.mod \
	  --module $(BUILD)/tests/fcprobe.mod

fcprobetest: $(BUILD)/tests/fcprobe-ref.rom tools/build/rom-test.sh
	@tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcprobe-ref.rom \
	  $(abspath $(BUILD))/tests/fcprobe-ref.txt
	@cat $(BUILD)/tests/fcprobe-ref.txt
	@if grep -aq '^ *FAIL ' $(BUILD)/tests/fcprobe-ref.txt; then \
	  echo "fcprobetest: the report carries FAILed checks:"; \
	  grep -a '^ *FAIL ' $(BUILD)/tests/fcprobe-ref.txt; \
	  exit 1; \
	fi
.PHONY: fcprobetest

# Sources/FileSys/ADFS/test/ideprobe: reads sectors from the IDE controller directly.
#
# Sources/Hardware/IOMD/ata.S only describes the controller (register blocks, PIO mode 0).
# This test module finds the device with OS_Hardware, calls ControllerInfo,
# resets the bus, identifies both units and reads the FileCore boot block
# from each by CHS and by LBA, checking both against each other and against
# the record written into the image beforehand.
#
# The images are laid out by Sources/FileSys/FileCore/test/filecore's layimages, the same writer
# `make filecoreimages` uses. One ROM: there is no reference module to
# compare against.
TEST_CHAIN_ideprobe := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                       005-Messages 006-MessageTrans 007-UK
TEST_EXTRA_CFLAGS_ideprobe := -ISources/FileSys/ADFS/c -ISources/FileSys/RAMFS/c \
                              -ISources/FileSys/FileCore/c
# Sources/Lib/CLib supplies the division RamFS's disc shaping needs.
TEST_MOD_EXTRA_ideprobe := $(BUILD)/tests/ideprobe/mod/ata.o \
                           $(BUILD)/tests/ideprobe/mod/discrec.o $(CLIB)

$(BUILD)/tests/ideprobe/mod/ata.o: Sources/FileSys/ADFS/c/ata.c Sources/FileSys/ADFS/c/ata.h
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(MOD_PIFLAGS) $(TEST_CFLAGS) \
	  -ISources/FileSys/ADFS/c -c $< -o $@

# The disc's shape, so the test can compute what was written into the boot
# block.
$(BUILD)/tests/ideprobe/mod/discrec.o: Sources/FileSys/RAMFS/c/discrec.c \
                                       Sources/FileSys/RAMFS/c/ramfs.h \
                                       Sources/FileSys/FileCore/c/fcdisc.h
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(MOD_PIFLAGS) $(TEST_CFLAGS) \
	  -ISources/FileSys/FileCore/c -ISources/FileSys/RAMFS/c -c $< -o $@

# The two discs this run reads, laid out on the build machine.
IDEPROBE_IMAGES := $(BUILD)/tests/ideprobe

ideprobetest: $(BUILD)/tests/ideprobe-ours.rom \
              $(BUILD)/tests/filecore-layimages tools/build/rom-test.sh
	@mkdir -p $(IDEPROBE_IMAGES)
	@rm -f $(IDEPROBE_IMAGES)/hd4.hdf $(IDEPROBE_IMAGES)/hd5.hdf
	@$(BUILD)/tests/filecore-layimages $(IDEPROBE_IMAGES) winnies
	@RPCEMU_HD4=$(abspath $(IDEPROBE_IMAGES))/hd4.hdf \
	 RPCEMU_HD5=$(abspath $(IDEPROBE_IMAGES))/hd5.hdf \
	 tools/build/rom-test.sh $(abspath $(BUILD))/tests/ideprobe-ours.rom \
	   $(abspath $(BUILD))/tests/ideprobe-ours.txt 120
	@cat $(BUILD)/tests/ideprobe-ours.txt
	@if grep -aq '^ *FAIL ' $(BUILD)/tests/ideprobe-ours.txt; then \
	  echo "ideprobetest: the report carries FAILed checks:"; \
	  grep -a '^ *FAIL ' $(BUILD)/tests/ideprobe-ours.txt; \
	  exit 1; \
	fi
	@python3 tools/check/idepattern.py $(IDEPROBE_IMAGES)/hd4.hdf
.PHONY: ideprobetest

# ADFS against the reference ADFS.
#
# Two ROMs differing only in ADFS, with the reference kernel and FileCore
# under both, so any difference in the reports is ADFS's.
#
# The discs are laid out and filled by Sources/FileSys/FileCore/test/filecore's layimages before each
# boot, because the test writes a file and a reused image would differ.
ADFSMOD_UNDER_TEST := $(ADFS_MOD)
TEST_CHAIN_adfsmod := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                      005-Messages 006-MessageTrans 007-UK 030-FileCore
# $(MOD_KRES_MOD) goes in both ROMs: without it this module's message file is
# absent and token errors fall back to the global messages. The reference
# ADFS carries its error blocks in its own image.
TEST_MODS_adfsmod = $(MOD_KRES_MOD) $(ADFSMOD_UNDER_TEST)
# Listings are captured from WrchV, since the text is what is under test.
TEST_MOD_EXTRA_adfsmod := $(BUILD)/tests/lib/mod/vectorclaim.o

$(BUILD)/tests/adfsmod-refmod.rom: $(HAL_IOMD_BIN) $(BUILD)/tests/adfsmod.mod \
                                   $(MOD_KRES_MOD) $(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-kernel \
	  $(addprefix --module ,$(foreach m,$(TEST_CHAIN_adfsmod),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(MOD_KRES_MOD) \
	  --module $(LEGACY)/romparts/031-ADFS.mod \
	  --module $(BUILD)/tests/adfsmod.mod

ADFS_TEST_IMAGES := $(BUILD)/tests/adfsmod

# One boot over a freshly laid-out pair of discs.
adfs_run = rm -f $(ADFS_TEST_IMAGES)/hd4.hdf $(ADFS_TEST_IMAGES)/hd5.hdf; \
           $(BUILD)/tests/filecore-layimages $(ADFS_TEST_IMAGES) winnies filled \
             > $(ADFS_TEST_IMAGES)/laid.txt \
             || { cat $(ADFS_TEST_IMAGES)/laid.txt; exit 1; }; \
           RPCEMU_HD4=$(abspath $(ADFS_TEST_IMAGES))/hd4.hdf \
           RPCEMU_HD5=$(abspath $(ADFS_TEST_IMAGES))/hd5.hdf \
           tools/build/rom-test.sh $(abspath $(BUILD))/tests/$(1) \
             $(abspath $(BUILD))/tests/$(2) 300

adfstest: $(BUILD)/tests/adfsmod-ref.rom $(BUILD)/tests/adfsmod-refmod.rom \
          $(BUILD)/tests/filecore-layimages \
          tools/build/rom-test.sh tools/build/reporttest.sh
	@mkdir -p $(ADFS_TEST_IMAGES)
	@echo "the reference module:"
	@$(call adfs_run,adfsmod-refmod.rom,adfsmod-refmod.txt)
	@echo "this module:"
	@$(call adfs_run,adfsmod-ref.rom,adfsmod-ref.txt)
	@echo
	@tools/build/reporttest.sh adfsmod \
	  $(BUILD)/tests/adfsmod-refmod.txt \
	  $(BUILD)/tests/adfsmod-ref.txt \
	  $(BUILD)/tests/adfsmod.diff \
	  $(wildcard Sources/FileSys/ADFS/test/adfsmod/differs-reference)
.PHONY: adfstest

# ADFS's background transfers, measured from the driver side.
#
# A read-ahead that is waited for and one left running return the same bytes,
# so only the driver can tell them apart; the test asks the driver's counts.
# One ROM, since the reference ADFS has no such count. Our FileCore is used
# because the background transfers belong to its file cache.
TEST_CHAIN_adfsbg := 002-FileSwitch 003-ResourceFS 004-TerritoryManager \
                     005-Messages 006-MessageTrans 007-UK
TEST_MODS_adfsbg = $(MOD_KRES_MOD) $(FC_MOD) $(ADFS_MOD)

ADFSBG_IMAGES := $(BUILD)/tests/adfsbg

adfsbgtest: $(BUILD)/tests/adfsbg-ref.rom $(BUILD)/tests/filecore-layimages \
            tools/build/rom-test.sh
	@mkdir -p $(ADFSBG_IMAGES)
	@rm -f $(ADFSBG_IMAGES)/hd4.hdf $(ADFSBG_IMAGES)/hd5.hdf
	@$(BUILD)/tests/filecore-layimages $(ADFSBG_IMAGES) winnies filled \
	  > $(ADFSBG_IMAGES)/laid.txt || { cat $(ADFSBG_IMAGES)/laid.txt; exit 1; }
	@RPCEMU_HD4=$(abspath $(ADFSBG_IMAGES))/hd4.hdf \
	 RPCEMU_HD5=$(abspath $(ADFSBG_IMAGES))/hd5.hdf \
	 tools/build/rom-test.sh $(abspath $(BUILD))/tests/adfsbg-ref.rom \
	   $(abspath $(BUILD))/tests/adfsbg-ref.txt 300
	@cat $(BUILD)/tests/adfsbg-ref.txt
	@if grep -aq '^ *FAIL ' $(BUILD)/tests/adfsbg-ref.txt; then \
	  echo "adfsbgtest: the report carries FAILed checks:"; \
	  grep -a '^ *FAIL ' $(BUILD)/tests/adfsbg-ref.txt; \
	  exit 1; \
	fi
.PHONY: adfsbgtest


# FileCore host checks: one binary per concern, each linking the module's C
# for it over the host medium. The medium is checked first, as every later
# check reads through it.
FC_DIR := Sources/FileSys/FileCore
FC_TEST_GEN := $(BUILD)/tests/filecore
FC_TEST_FLAGS := $(HOST_CFLAGS) -I$(FC_DIR)/c -ISources/FileSys/FileCore/test/filecore/host \
                 -Itests/lib -Iinclude -ISources/Kernel/hdr -ISources/FileSys/RAMFS/c \
                 -I$(BUILD)/modules/filecore -DFC_TEST_DIR='"$(FC_TEST_GEN)"'
# fcfiq.c is compiled into the host checks too and includes the generated
# veneer header, so the checks generate and find it as the module build does.
FC_TEST_HDRS := $(wildcard $(FC_DIR)/c/*.h) $(wildcard Sources/FileSys/FileCore/test/filecore/host/*.h) \
                $(BUILD)/modules/filecore/fc_veneers.h

$(FC_TEST_GEN)/%.o: Sources/FileSys/FileCore/test/filecore/%.c $(FC_TEST_HDRS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_TEST_FLAGS) -c $< -o $@

$(FC_TEST_GEN)/host/%.o: Sources/FileSys/FileCore/test/filecore/host/%.c $(FC_TEST_HDRS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_TEST_FLAGS) -c $< -o $@

$(FC_TEST_GEN)/%.o: $(FC_DIR)/c/%.c $(FC_TEST_HDRS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_TEST_FLAGS) -c $< -o $@

$(FC_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/filecore-mediumcheck: $(addprefix $(FC_TEST_GEN)/,mediumcheck.o \
                                       host/hostmedium.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/filecore-newmapcheck: $(addprefix $(FC_TEST_GEN)/,newmapcheck.o \
                                       fcnewmap.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/filecore-sectorcheck: $(addprefix $(FC_TEST_GEN)/,sectorcheck.o \
                                       fcsector.o host/hostmedium.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Reads the images the reference machine wrote with the port's C. Not part of
# the checks above: run `make filecoreimagecheck` after `make filecoreimages`.
FC_CORE_OBJS := $(addprefix $(FC_TEST_GEN)/,fcnewmap.o fcoldmap.o fcdir.o fcbigdir.o \
                  fcobject.o fcmount.o fclayout.o fcalloc.o fccompact.o fcfile.o \
                  fcdircache.o)

$(BUILD)/tests/filecore-imagecheck: $(addprefix $(FC_TEST_GEN)/,imagecheck.o \
                                      host/hostmedium.o check.o) $(FC_CORE_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

filecoreimagecheck: $(BUILD)/tests/filecore-imagecheck
	@test -f $(FC_IMAGES)/run1.txt \
	  || { echo "filecoreimagecheck: no images under $(FC_IMAGES); make filecoreimages first"; exit 1; }
	@$< $(FC_IMAGES)
.PHONY: filecoreimagecheck

$(BUILD)/tests/filecore-layoutcheck: $(addprefix $(FC_TEST_GEN)/,layoutcheck.o \
                                       discrec.o host/hostmedium.o check.o) \
                                     $(FC_CORE_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# RamFS's disc shaping, for the hard discs the layout check makes.
$(FC_TEST_GEN)/discrec.o: Sources/FileSys/RAMFS/c/discrec.c Sources/FileSys/RAMFS/c/ramfs.h \
                          Sources/FileSys/FileCore/c/fcdisc.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_TEST_FLAGS) -ISources/FileSys/RAMFS/c -c $< -o $@

# Writes the port's layouts into image files for the reference machine to
# fill; `make filecoreimageslaid` runs the three passes and checks the
# transcripts as for the reference's own layouts.
$(BUILD)/tests/filecore-layimages: $(addprefix $(FC_TEST_GEN)/,layimages.o \
                                     discrec.o host/hostmedium.o host/fillimage.o) \
                                   $(FC_CORE_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# A host directory written into a hard disc image, with the port's own
# layout, allocation and directory code. See Sources/FileSys/FileCore/test/filecore/putimage.c.
$(BUILD)/tests/filecore-putimage: $(addprefix $(FC_TEST_GEN)/,putimage.o \
                                    discrec.o host/hostmedium.o) \
                                  $(FC_CORE_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Compares the reference's listing of a disc the port filled with its listing
# of the disc it filled itself: entries and free space should match.
$(BUILD)/tests/filecore-fillcompare: $(addprefix $(FC_TEST_GEN)/,fillcompare.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

FC_FILLED := $(BUILD)/tests/fcfilled
fc_filled_run = RPCEMU_DISC0=$(abspath $(FC_FILLED))/$(1) \
                RPCEMU_DISC1=$(abspath $(FC_FILLED))/$(2) \
                RPCEMU_HD4=$(abspath $(FC_FILLED))/hd4.hdf \
                RPCEMU_HD5=$(abspath $(FC_FILLED))/hd5.hdf \
                RPCEMU_GUEST_WORD=$$((0x700 + $(3))) \
                tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcimages-ref.rom \
                  $(abspath $(FC_FILLED))/run$(3).txt 600

# Every disc laid out and filled by the port, listed by the reference, and
# the listings compared with the reference's own from `make filecoreimages`.
filecoreimagesfilled: $(BUILD)/tests/fcimages-ref.rom $(BUILD)/tests/filecore-layimages \
                      $(BUILD)/tests/filecore-imagecheck $(BUILD)/tests/filecore-fillcompare \
                      tools/build/rom-test.sh
	@mkdir -p $(FC_FILLED)
	@$(BUILD)/tests/filecore-layimages $(FC_FILLED) all filled
	@$(call fc_filled_run,L.adl,D.adf,1)
	@$(call fc_filled_run,E.adf,F.adf,2)
	@$(call fc_filled_run,Ep.adf,Fp.adf,3)
	@grep -ac 'reads back as written' $(FC_FILLED)/run1.txt $(FC_FILLED)/run2.txt \
	  $(FC_FILLED)/run3.txt
	@! grep -a 'DIFFERS\|error &' $(FC_FILLED)/run1.txt $(FC_FILLED)/run2.txt \
	  $(FC_FILLED)/run3.txt | grep -av '&000108bd'
	@$(BUILD)/tests/filecore-imagecheck $(FC_FILLED)
	@$(BUILD)/tests/filecore-fillcompare $(FC_IMAGES) $(FC_FILLED)
.PHONY: filecoreimagesfilled

FC_LAID := $(BUILD)/tests/fclaid
fc_laid_run = RPCEMU_DISC0=$(abspath $(FC_LAID))/$(1) \
              RPCEMU_DISC1=$(abspath $(FC_LAID))/$(2) \
              RPCEMU_HD4=$(abspath $(FC_LAID))/hd4.hdf \
              RPCEMU_HD5=$(abspath $(FC_LAID))/hd5.hdf \
              RPCEMU_GUEST_WORD=$$((0x300 + $(3))) \
              tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcimages-ref.rom \
                $(abspath $(FC_LAID))/run$(3).txt 600

filecoreimageslaid: $(BUILD)/tests/fcimages-ref.rom $(BUILD)/tests/filecore-layimages \
                    $(BUILD)/tests/filecore-imagecheck tools/build/rom-test.sh
	@mkdir -p $(FC_LAID)
	@$(BUILD)/tests/filecore-layimages $(FC_LAID)
	@$(call fc_laid_run,L.adl,D.adf,1)
	@$(call fc_laid_run,E.adf,F.adf,2)
	@$(call fc_laid_run,Ep.adf,Fp.adf,3)
	@grep -ac 'reads back as written' $(FC_LAID)/run1.txt $(FC_LAID)/run2.txt \
	  $(FC_LAID)/run3.txt
	@! grep -a 'DIFFERS\|error &' $(FC_LAID)/run1.txt $(FC_LAID)/run2.txt \
	  $(FC_LAID)/run3.txt | grep -av '&000108bd'
	@$(BUILD)/tests/filecore-imagecheck $(FC_LAID)
.PHONY: filecoreimageslaid

$(BUILD)/tests/filecore-alloccheck: $(addprefix $(FC_TEST_GEN)/,alloccheck.o \
                                      fcnewmap.o fcalloc.o fccompact.o fcobject.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The RAM disc's shape on the host: what Sources/FileSys/FileCore/test/fcfs asks on the machine,
# asked of the core alone.
$(FC_TEST_GEN)/ramdisccheck.o: Sources/FileSys/FileCore/test/filecore/ramdisccheck.c \
                               $(wildcard Sources/FileSys/FileCore/c/*.h) Sources/FileSys/RAMFS/c/ramfs.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_TEST_FLAGS) -ISources/FileSys/RAMFS/c -c $< -o $@

$(BUILD)/tests/filecore-ramdisccheck: $(addprefix $(FC_TEST_GEN)/,ramdisccheck.o \
                                        discrec.o host/hostmedium.o check.o) \
                                      $(FC_CORE_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(FC_TEST_GEN)/oldcompactcheck.o: Sources/FileSys/FileCore/test/filecore/oldcompactcheck.c \
                                  $(wildcard Sources/FileSys/FileCore/c/*.h)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_TEST_FLAGS) -c $< -o $@

$(FC_TEST_GEN)/dircachecheck.o: Sources/FileSys/FileCore/test/filecore/dircachecheck.c \
                                $(wildcard Sources/FileSys/FileCore/c/*.h) Sources/FileSys/RAMFS/c/ramfs.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_TEST_FLAGS) -ISources/FileSys/RAMFS/c -c $< -o $@

$(BUILD)/tests/filecore-dircachecheck: $(addprefix $(FC_TEST_GEN)/,dircachecheck.o \
                                         discrec.o host/hostmedium.o check.o) \
                                       $(FC_CORE_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The file cache, checked by counting disc reads in the host medium's log.
$(BUILD)/tests/filecore-bufcheck: $(addprefix $(FC_TEST_GEN)/,bufcheck.o \
                                    fcbuf.o host/hostmedium.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The background transfer process block, checked by acting as the driver:
# walks the list and writes completion where ADFS does.
$(BUILD)/tests/filecore-processcheck: $(addprefix $(FC_TEST_GEN)/,processcheck.o \
                                        fcprocess.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Transfer buffer source selection (which of the seven sources the next chunk
# comes from, and where a buffer run lies in the chunks). Claiming memory
# needs a machine and is not checked here.
$(BUILD)/tests/filecore-buffercheck: $(addprefix $(FC_TEST_GEN)/,buffercheck.o \
                                       fcbuffer.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/filecore-oldcompactcheck: $(addprefix $(FC_TEST_GEN)/,oldcompactcheck.o \
                                           discrec.o host/hostmedium.o check.o) \
                                         $(FC_CORE_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/filecore-fiqcheck: $(FC_TEST_GEN)/fiqcheck.o $(FC_TEST_GEN)/check.o
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(FC_TEST_GEN)/fiqcheck.o: Sources/FileSys/FileCore/c/fcfiq.c

# FileSwitch's entries (c/fcfs.c and the lookup under them) called as
# FileSwitch calls them, with the calls they make answered by the check. The
# two are compiled against Sources/FileSys/FileCore/test/filecore/fshost's stand-in OS headers,
# which -iquote puts ahead of include/.
FC_FS_FLAGS := $(HOST_CFLAGS) -iquote Sources/FileSys/FileCore/test/filecore/fshost -I$(FC_DIR)/c \
               -ISources/FileSys/FileCore/test/filecore/host -Itests/lib -ISources/Kernel/hdr -ISources/FileSys/RAMFS/c \
               -I$(BUILD)/modules/filecore
FC_FS_HDRS := $(FC_TEST_HDRS) $(wildcard Sources/FileSys/FileCore/test/filecore/fshost/riscos/*.h)

$(FC_TEST_GEN)/fs/%.o: $(FC_DIR)/c/%.c $(FC_FS_HDRS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_FS_FLAGS) -c $< -o $@

$(FC_TEST_GEN)/fs/fscheck.o: Sources/FileSys/FileCore/test/filecore/fscheck.c $(FC_FS_HDRS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FC_FS_FLAGS) -c $< -o $@

$(BUILD)/tests/filecore-fscheck: $(addprefix $(FC_TEST_GEN)/,fs/fscheck.o fs/fcfs.o fs/fcpath.o \
                                   fcbuf.o discrec.o host/hostmedium.o check.o) \
                                 $(FC_CORE_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

FC_CHECKS := fiqcheck mediumcheck newmapcheck sectorcheck alloccheck layoutcheck ramdisccheck \
              dircachecheck bufcheck buffercheck processcheck oldcompactcheck fscheck

filecorechecks: $(FC_CHECKS:%=$(BUILD)/tests/filecore-%)
	@for c in $^; do $$c || exit 1; done
.PHONY: filecorechecks

# ADFS host checks: the IDE interface as arithmetic.
#
# Command-block register placement, register values for a transfer of N
# sectors from sector A, and identify block decoding, including a sector count
# of 0 meaning 256, LBA/CHS round trips and the byte-swapped identify
# strings. The bus itself is tested by Sources/FileSys/ADFS/test/ideprobe.
ADFS_DIR := Sources/FileSys/ADFS
ADFS_TEST_GEN := $(BUILD)/tests/adfs
ADFS_TEST_FLAGS := $(HOST_CFLAGS) -I$(ADFS_DIR)/c -ISources/FileSys/FileCore/c -Itests/lib -Iinclude

$(ADFS_TEST_GEN)/%.o: Sources/FileSys/ADFS/test/adfs/%.c $(ADFS_DIR)/c/ata.h $(ADFS_DIR)/c/flpdcb.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ADFS_TEST_FLAGS) -c $< -o $@

$(ADFS_TEST_GEN)/%.o: $(ADFS_DIR)/c/%.c $(ADFS_DIR)/c/ata.h $(ADFS_DIR)/c/flpdcb.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ADFS_TEST_FLAGS) -c $< -o $@

$(ADFS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ADFS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/adfs-checks: $(addprefix $(ADFS_TEST_GEN)/,cases.o ata.o floppy.o \
                              flpdcb.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

adfschecks: $(BUILD)/tests/adfs-checks
	@$(BUILD)/tests/adfs-checks
.PHONY: adfschecks

# Disc images written by the reference machine: one emulator run per pair of
# floppy formats with both hard discs attached, then runs that back up the
# filled D disc, grow a file through a handle on fresh L and E discs, and
# fill and compact fresh L and D discs. The reference's *Compact fails with a
# disc error on the last (see Sources/FileSys/FileCore/test/fcimages/differs-reference-run6); the
# error check allows it. Images start as zero-filled files: the emulator
# takes a floppy's format from size and extension. Sources/FileSys/FileCore/test/fcimages/cases.c
# describes the runs. The transcripts are what the port's reader is checked
# against. Nothing here goes in git.
FC_IMAGES := $(BUILD)/tests/fcimages
FC_IMAGE_FILES := $(addprefix $(FC_IMAGES)/,L.adl D.adf E.adf F.adf Ep.adf Fp.adf \
                                            Dbak.adf Lg.adl Eg.adf Lc.adl Dc.adf \
                                            hd4.hdf hd5.hdf)
fc_image_run = RPCEMU_DISC0=$(abspath $(FC_IMAGES))/$(1) \
               RPCEMU_DISC1=$(abspath $(FC_IMAGES))/$(2) \
               RPCEMU_HD4=$(abspath $(FC_IMAGES))/hd4.hdf \
               RPCEMU_HD5=$(abspath $(FC_IMAGES))/hd5.hdf \
               RPCEMU_GUEST_WORD=$(3) \
               tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcimages-ref.rom \
                 $(abspath $(FC_IMAGES))/run$$(($(3) & 0xff)).txt 600

# The hard discs are laid out by the port before every run, since the
# reference cannot lay out a blank IDE drive; the reference then fills them.
# The same runs with this FileCore under the reference ADFS, into their own
# directory. Transcripts must match the reference's line for line with
# directory stamps masked, except for declared differences in
# Sources/FileSys/FileCore/test/fcimages/differs-reference-run<n>, and the images must read as the
# transcripts claim.
FC_OURS := $(BUILD)/tests/fcimagesours
FC_OURS_FILES := $(FC_OURS)/L.adl $(FC_OURS)/D.adf $(FC_OURS)/E.adf \
                 $(FC_OURS)/Ep.adf $(FC_OURS)/F.adf $(FC_OURS)/Fp.adf \
                 $(FC_OURS)/Dbak.adf $(FC_OURS)/Lg.adl $(FC_OURS)/Eg.adf \
                 $(FC_OURS)/Lc.adl $(FC_OURS)/Dc.adf \
                 $(FC_OURS)/hd4.hdf $(FC_OURS)/hd5.hdf

$(BUILD)/tests/fcimages-ourfc.rom: $(HAL_IOMD_BIN) $(BUILD)/tests/fcimages.mod \
                                   $(FC_MOD) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --reference-hal --reference-kernel \
	  $(addprefix --module ,$(foreach m,$(filter-out 030-FileCore 031-ADFS,$(TEST_CHAIN_fcimages)),\
	                          $(LEGACY)/romparts/$(m).mod)) \
	  --module $(FC_MOD) \
	  --module $(LEGACY)/romparts/031-ADFS.mod \
	  --module $(BUILD)/tests/fcimages.mod

fc_ours_run = RPCEMU_DISC0=$(abspath $(FC_OURS))/$(1) \
              RPCEMU_DISC1=$(abspath $(FC_OURS))/$(2) \
              RPCEMU_HD4=$(abspath $(FC_OURS))/hd4.hdf \
              RPCEMU_HD5=$(abspath $(FC_OURS))/hd5.hdf \
              RPCEMU_GUEST_WORD=$(3) \
              tools/build/rom-test.sh $(abspath $(BUILD))/tests/fcimages-ourfc.rom \
                $(abspath $(FC_OURS))/run$$(($(3) & 0xff)).txt 600

filecoreimagesours: $(BUILD)/tests/fcimages-ourfc.rom tools/build/rom-test.sh \
                    $(BUILD)/tests/filecore-layimages $(BUILD)/tests/filecore-imagecheck
	@test -f $(FC_IMAGES)/run1.txt \
	  || { echo "filecoreimagesours: no reference transcripts under $(FC_IMAGES); make filecoreimages first"; exit 1; }
	@mkdir -p $(FC_OURS)
	@rm -f $(FC_OURS_FILES)
	@truncate -s 640K $(FC_OURS)/L.adl
	@truncate -s 800K $(FC_OURS)/D.adf $(FC_OURS)/E.adf $(FC_OURS)/Ep.adf
	@truncate -s 1600K $(FC_OURS)/F.adf $(FC_OURS)/Fp.adf
	@truncate -s 800K $(FC_OURS)/Dbak.adf $(FC_OURS)/Eg.adf $(FC_OURS)/Dc.adf
	@truncate -s 640K $(FC_OURS)/Lg.adl $(FC_OURS)/Lc.adl
	@$(BUILD)/tests/filecore-layimages $(FC_OURS) winnies
	@$(call fc_ours_run,L.adl,D.adf,$$((0x200 + 1)))
	@$(call fc_ours_run,E.adf,F.adf,$$((0x200 + 2)))
	@$(call fc_ours_run,Ep.adf,Fp.adf,$$((0x200 + 3)))
	@$(call fc_ours_run,D.adf,Dbak.adf,$$((0x200 + 4)))
	@$(call fc_ours_run,Lg.adl,Eg.adf,$$((0x200 + 5)))
	@$(call fc_ours_run,Lc.adl,Dc.adf,$$((0x200 + 6)))
	@for n in 1 2 3 4 5 6; do \
	  sed 's/\( D &fffffd33 &\)[0-9a-f]\{8\}/\1--------/' $(FC_IMAGES)/run$$n.txt > $(FC_OURS)/run$$n.ref; \
	  sed 's/\( D &fffffd33 &\)[0-9a-f]\{8\}/\1--------/' $(FC_OURS)/run$$n.txt > $(FC_OURS)/run$$n.ours; \
	  differs=Sources/FileSys/FileCore/test/fcimages/differs-reference-run$$n; [ -f $$differs ] || differs=; \
	  tools/build/reporttest.sh fcimages$$n $(FC_OURS)/run$$n.ref $(FC_OURS)/run$$n.ours \
	    $(FC_OURS)/run$$n.diff $$differs || exit 1; \
	done
	@$(BUILD)/tests/filecore-imagecheck $(FC_OURS)
.PHONY: filecoreimagesours

filecoreimages: $(BUILD)/tests/fcimages-ref.rom tools/build/rom-test.sh \
                $(BUILD)/tests/filecore-layimages
	@mkdir -p $(FC_IMAGES)
	@rm -f $(FC_IMAGE_FILES)
	@truncate -s 640K $(FC_IMAGES)/L.adl
	@truncate -s 800K $(FC_IMAGES)/D.adf $(FC_IMAGES)/E.adf $(FC_IMAGES)/Ep.adf
	@truncate -s 1600K $(FC_IMAGES)/F.adf $(FC_IMAGES)/Fp.adf
	@truncate -s 800K $(FC_IMAGES)/Dbak.adf $(FC_IMAGES)/Eg.adf $(FC_IMAGES)/Dc.adf
	@truncate -s 640K $(FC_IMAGES)/Lg.adl $(FC_IMAGES)/Lc.adl
	@$(BUILD)/tests/filecore-layimages $(FC_IMAGES) winnies
	@$(call fc_image_run,L.adl,D.adf,$$((0x200 + 1)))
	@$(call fc_image_run,E.adf,F.adf,$$((0x200 + 2)))
	@$(call fc_image_run,Ep.adf,Fp.adf,$$((0x200 + 3)))
	@$(call fc_image_run,D.adf,Dbak.adf,$$((0x200 + 4)))
	@$(call fc_image_run,Lg.adl,Eg.adf,$$((0x200 + 5)))
	@$(call fc_image_run,Lc.adl,Dc.adf,$$((0x200 + 6)))
	@grep -ac 'reads back as written' $(FC_IMAGES)/run1.txt $(FC_IMAGES)/run2.txt \
	  $(FC_IMAGES)/run3.txt $(FC_IMAGES)/run4.txt $(FC_IMAGES)/run5.txt $(FC_IMAGES)/run6.txt
	@! grep -a 'DIFFERS\|error &' $(FC_IMAGES)/run1.txt $(FC_IMAGES)/run2.txt \
	  $(FC_IMAGES)/run3.txt $(FC_IMAGES)/run4.txt $(FC_IMAGES)/run5.txt $(FC_IMAGES)/run6.txt \
	  | grep -av '&000108bd\|compact: error &230108c7'
.PHONY: filecoreimages

# TerritoryManager: territory registry, SWI router and clock.
#
# Answers ten SWIs, routes twenty-six to the selected territory, plus four
# beyond a reserved block of seventeen; seven commands; four service calls;
# and a ticker event for the next clock change.
#
# Five of its eight C files are pure and checked on the host: routing,
# calendar arithmetic, the time zone byte, conversion requests, and daylight
# saving state (the order of a territory's two change instants gives the
# hemisphere).
#
TERR_OBJS := $(BUILD)/modules/territory/modhead.o \
             $(BUILD)/modules/territory/daylight.o \
             $(BUILD)/modules/territory/registry.o \
             $(BUILD)/modules/territory/terrcmd.o \
             $(BUILD)/modules/territory/terrentry.o \
             $(BUILD)/modules/territory/terrfmt.o \
             $(BUILD)/modules/territory/terrload.o \
             $(BUILD)/modules/territory/terrmodule.o \
             $(BUILD)/modules/territory/terrtime.o \
             $(BUILD)/modules/territory/terrzone.o \
             $(BUILD)/modules/territory/veneers.o
TERR_MOD  := $(BUILD)/modules/TerritoryManager.mod
TERR_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Internat/Territory/Manager/c \
               -I$(BUILD)/modules/territory

$(BUILD)/modules/territory/modhead.S: Sources/Internat/Territory/Manager/cmhg/modhead \
                                      Sources/Internat/Territory/Manager/VersionNum \
                                      hdr/Services tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Internat/Territory/Manager -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/territory/%.o: Sources/Internat/Territory/Manager/c/%.c Makefile \
                                $(BUILD)/modules/territory/terr_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(TERR_CFLAGS) -c $< -o $@

# The three module entries and their C prototypes. See tools/build/marshal.py.
$(BUILD)/modules/territory/veneers.S: Sources/Internat/Territory/Manager/s/boundaries \
                                      Sources/Internat/Territory/Manager/cmhg/modhead \
                                      tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Internat/Territory/Manager --asm $@ \
	  --header $(BUILD)/modules/territory/terr_veneers.h

$(BUILD)/modules/territory/terr_veneers.h: $(BUILD)/modules/territory/veneers.S

$(BUILD)/modules/territory/veneers.o: $(BUILD)/modules/territory/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(TERR_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/territory/modhead.o: $(BUILD)/modules/territory/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(TERR_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/TerritoryManager.elf: $(TERR_OBJS) $(CLIB) mk/ld/module.ld \
                                       tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(TERR_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(TERR_MOD): $(BUILD)/modules/TerritoryManager.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, TerritoryManager - ported)"

-include $(TERR_OBJS:.o=.d)

territorymodule: $(TERR_MOD)
.PHONY: territorymodule

# UK: the territory module TerritoryManager routes to.
#
# All twenty-six territory entries are implemented; UK is in OURS_MODULES.
# `ukmodtest` compares its registered entry points with the reference module.
#
# Eighteen character tables (eleven property bitmaps, seven translations) are
# generated from the reference territory's assembler; `ukcharscheck` finds
# each of them in the reference module image.
UKT_DIR   := Sources/Internat/Territory/UK
UKT_GEN   := $(BUILD)/modules/ukterritory
UKT_TABLES := $(UKT_DIR)/c/uktables_data.c
UKT_REF   := $(RO_SRC)/Internat/Territory/TerritoryModule
# `terrtime.o` is TerritoryManager's calendar arithmetic, shared here rather
# than duplicated as the reference does; `make territorychecks` checks it.
UKT_OBJS  := $(UKT_GEN)/modhead.o \
             $(UKT_GEN)/ukdate.o \
             $(UKT_GEN)/ukdst.o \
             $(UKT_GEN)/ukcollate.o \
             $(UKT_GEN)/ukparse.o \
             $(UKT_GEN)/uktables.o \
             $(UKT_GEN)/uktables_data.o \
             $(UKT_GEN)/terroverlay.o \
             $(UKT_GEN)/terrtime.o \
             $(UKT_GEN)/ukmodule.o \
             $(UKT_GEN)/veneers.o
UKT_MOD   := $(BUILD)/modules/UK.mod
UKT_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(UKT_DIR)/c \
              -ISources/Internat/Territory/Manager/c -I$(UKT_GEN)

# The tables are kept in the tree; this regenerates them to compare.
$(UKT_GEN)/uktables_data.gen.c: $(UKT_REF)/s/UK $(UKT_REF)/s/Tables tools/build/ukcharsgen.py
	@mkdir -p $(dir $@)
	@python3 tools/build/ukcharsgen.py $(UKT_REF) $@

ukcharscheck: $(UKT_GEN)/uktables_data.gen.c
	@cmp $< $(UKT_TABLES)
	@python3 tools/check/ukcharscheck.py $(UKT_TABLES) $(LEGACY)/romparts/007-UK.mod
.PHONY: ukcharscheck

$(UKT_GEN)/modhead.S: $(UKT_DIR)/cmhg/modhead $(UKT_DIR)/VersionNum \
                      hdr/Services tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(UKT_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(UKT_GEN)/veneers.S: $(UKT_DIR)/s/boundaries $(UKT_DIR)/cmhg/modhead \
                      tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(UKT_DIR) --asm $@ \
	  --header $(UKT_GEN)/uk_veneers.h

$(UKT_GEN)/uk_veneers.h: $(UKT_GEN)/veneers.S

$(UKT_GEN)/%.o: $(UKT_GEN)/%.S
	@mkdir -p $(dir $@)
	$(CC) $(UKT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(UKT_GEN)/uktables_data.o: $(UKT_TABLES) $(UKT_DIR)/c/uktables.h
	@mkdir -p $(dir $@)
	$(CC) $(UKT_CFLAGS) -c $< -o $@

$(UKT_GEN)/%.o: $(UKT_DIR)/c/%.c Makefile $(UKT_GEN)/uk_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(UKT_CFLAGS) -c $< -o $@

$(UKT_GEN)/terrtime.o: Sources/Internat/Territory/Manager/c/terrtime.c Makefile
	@mkdir -p $(dir $@)
	$(CC) $(UKT_CFLAGS) -c $< -o $@

$(BUILD)/modules/UK.elf: $(UKT_OBJS) $(CLIB) mk/ld/module.ld \
                         tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(UKT_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(UKT_MOD): $(BUILD)/modules/UK.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, UK - all twenty-six entries)"

-include $(UKT_OBJS:.o=.d)

ukterritorymodule: $(UKT_MOD)
.PHONY: ukterritorymodule

# Obey: runs a file of *commands, one line at a time.
#
# A line may start an application that never returns, so the loop must be
# re-enterable from an exit handler: one RMA block per open file, chained
# because *Obey can nest, and UpCallV and ErrorV claimed while any file is
# open.
OBEY_OBJS := $(BUILD)/modules/obey/modhead.o \
             $(BUILD)/modules/obey/obeyblock.o \
             $(BUILD)/modules/obey/obeycmd.o \
             $(BUILD)/modules/obey/obeyline.o \
             $(BUILD)/modules/obey/obeymodule.o \
             $(BUILD)/modules/obey/veneers.o
OBEY_MOD  := $(BUILD)/modules/Obey.mod
OBEY_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Utilities/Obey/c \
               -I$(BUILD)/modules/obey

$(BUILD)/modules/obey/modhead.S: Sources/Utilities/Obey/cmhg/modhead \
                                 Sources/Utilities/Obey/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Utilities/Obey -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/obey/%.o: Sources/Utilities/Obey/c/%.c Makefile \
                           $(BUILD)/modules/obey/obey_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(OBEY_CFLAGS) -c $< -o $@

# The veneers and their C prototypes, from one declaration. See
# tools/build/marshal.py.
$(BUILD)/modules/obey/veneers.S: Sources/Utilities/Obey/s/boundaries \
                                 Sources/Utilities/Obey/cmhg/modhead \
                                 tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Utilities/Obey --asm $@ \
	  --header $(BUILD)/modules/obey/obey_veneers.h

$(BUILD)/modules/obey/obey_veneers.h: $(BUILD)/modules/obey/veneers.S

$(BUILD)/modules/obey/veneers.o: $(BUILD)/modules/obey/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(OBEY_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/obey/modhead.o: $(BUILD)/modules/obey/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(OBEY_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/Obey.elf: $(OBEY_OBJS) $(CLIB) mk/ld/module.ld \
                           tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(OBEY_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(OBEY_MOD): $(BUILD)/modules/Obey.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Obey)"

-include $(OBEY_OBJS:.o=.d)

obeymodule: $(OBEY_MOD)
.PHONY: obeymodule

# Mouse: the quadrature mouse driver. A PointerV claimant only; no SWIs or
# commands. Movement is the 16-bit difference of two free-running counters,
# and the buttons are inverted bits in the middle of a word.
MOUSE_OBJS := $(BUILD)/modules/mouse/modhead.o \
              $(BUILD)/modules/mouse/mousedev.o \
              $(BUILD)/modules/mouse/mousemodule.o \
              $(BUILD)/modules/mouse/veneers.o
MOUSE_MOD  := $(BUILD)/modules/Mouse.mod
MOUSE_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/Mouse/c \
                -I$(BUILD)/modules/mouse

$(BUILD)/modules/mouse/modhead.S: Sources/HWSupport/Mouse/cmhg/modhead \
                                  Sources/HWSupport/Mouse/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/Mouse -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/mouse/%.o: Sources/HWSupport/Mouse/c/%.c Makefile \
                            $(BUILD)/modules/mouse/mouse_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(MOUSE_CFLAGS) -c $< -o $@

# The three module entries and their C prototypes. See tools/build/marshal.py.
$(BUILD)/modules/mouse/veneers.S: Sources/HWSupport/Mouse/s/boundaries \
                                  Sources/HWSupport/Mouse/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/Mouse --asm $@ \
	  --header $(BUILD)/modules/mouse/mouse_veneers.h

$(BUILD)/modules/mouse/mouse_veneers.h: $(BUILD)/modules/mouse/veneers.S

$(BUILD)/modules/mouse/veneers.o: $(BUILD)/modules/mouse/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(MOUSE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/mouse/modhead.o: $(BUILD)/modules/mouse/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(MOUSE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/Mouse.elf: $(MOUSE_OBJS) $(CLIB) mk/ld/module.ld \
                            tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(MOUSE_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(MOUSE_MOD): $(BUILD)/modules/Mouse.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Mouse)"

-include $(MOUSE_OBJS:.o=.d)

mousemodule: $(MOUSE_MOD)
.PHONY: mousemodule

# BlendTable: tables of blended colours. Two palettes and a translucency give
# a byte per colour pair (the nearest entry in the second palette to their
# mix), so a translucent plot is two lookups. Built tables are cached.
BT_OBJS := $(BUILD)/modules/blendtable/modhead.o \
           $(BUILD)/modules/blendtable/btblend.o \
           $(BUILD)/modules/blendtable/btcache.o \
           $(BUILD)/modules/blendtable/btmodule.o \
           $(BUILD)/modules/blendtable/veneers.o
BT_MOD  := $(BUILD)/modules/BlendTable.mod
BT_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Video/Render/BlendTable/c \
             -I$(BUILD)/modules/blendtable

$(BUILD)/modules/blendtable/modhead.S: Sources/Video/Render/BlendTable/cmhg/modhead \
                                       Sources/Video/Render/BlendTable/VersionNum \
                                       tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Video/Render/BlendTable -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/blendtable/%.o: Sources/Video/Render/BlendTable/c/%.c Makefile \
                                 $(BUILD)/modules/blendtable/bt_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(BT_CFLAGS) -c $< -o $@

# The veneers and their C prototypes, from one declaration. See
# tools/build/marshal.py.
$(BUILD)/modules/blendtable/veneers.S: Sources/Video/Render/BlendTable/s/boundaries \
                                       Sources/Video/Render/BlendTable/cmhg/modhead \
                                       tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/BlendTable --asm $@ \
	  --header $(BUILD)/modules/blendtable/bt_veneers.h

$(BUILD)/modules/blendtable/bt_veneers.h: \
    $(BUILD)/modules/blendtable/veneers.S

$(BUILD)/modules/blendtable/veneers.o: $(BUILD)/modules/blendtable/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(BT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/blendtable/modhead.o: $(BUILD)/modules/blendtable/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(BT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/BlendTable.elf: $(BT_OBJS) $(CLIB) mk/ld/module.ld \
                                 tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(BT_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(BT_MOD): $(BUILD)/modules/BlendTable.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, BlendTable)"

-include $(BT_OBJS:.o=.d)

blendtablemodule: $(BT_MOD)
.PHONY: blendtablemodule

# ScrSaver: intercepts the screen blanker. Two service calls; no SWIs,
# commands or resources. When the blanker is about to blank it starts a saver
# application in <ScrSaver$Dir> through the Task Manager, stopped through a
# poll word. Sources/Desktop/ScrSaver/cmhg/modhead lists the conditions.
SAV_OBJS := $(BUILD)/modules/scrsaver/modhead.o \
            $(BUILD)/modules/scrsaver/scrsaver_decide.o \
            $(BUILD)/modules/scrsaver/scrsaver_module.o \
            $(BUILD)/modules/scrsaver/veneers.o
SAV_MOD  := $(BUILD)/modules/ScrSaver.mod
SAV_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Desktop/ScrSaver/c \
              -I$(BUILD)/modules/scrsaver

$(BUILD)/modules/scrsaver/modhead.S: Sources/Desktop/ScrSaver/cmhg/modhead \
                                     Sources/Desktop/ScrSaver/VersionNum \
                                     tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Desktop/ScrSaver -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/scrsaver/%.o: Sources/Desktop/ScrSaver/c/%.c Makefile \
                               $(BUILD)/modules/scrsaver/scrsaver_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SAV_CFLAGS) -c $< -o $@

# The three module entries and their C prototypes. See tools/build/marshal.py.
$(BUILD)/modules/scrsaver/veneers.S: Sources/Desktop/ScrSaver/s/boundaries \
                                     Sources/Desktop/ScrSaver/cmhg/modhead \
                                     tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Desktop/ScrSaver --asm $@ \
	  --header $(BUILD)/modules/scrsaver/scrsaver_veneers.h

$(BUILD)/modules/scrsaver/scrsaver_veneers.h: \
    $(BUILD)/modules/scrsaver/veneers.S

$(BUILD)/modules/scrsaver/veneers.o: $(BUILD)/modules/scrsaver/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SAV_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/scrsaver/modhead.o: $(BUILD)/modules/scrsaver/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SAV_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/ScrSaver.elf: $(SAV_OBJS) $(CLIB) mk/ld/module.ld \
                               tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SAV_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SAV_MOD): $(BUILD)/modules/ScrSaver.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ScrSaver)"

-include $(SAV_OBJS:.o=.d)

scrsavermodule: $(SAV_MOD)
.PHONY: scrsavermodule

# Draw: the DrawV path vector.
#
# The SWI handler moves the reason from r11 to r8 and calls DrawV; the DrawV
# claimant, installed at initialisation and after every reset, does the work.
# Fifteen of the sixteen reasons translate their arguments into a
# Draw_ProcessPath call and go round the vector again (`changetoprocesspath`),
# which is the published behaviour.
#
# Partial: `processpath` and what is behind it (`DrProcess`, `DrOutput`,
# `DrQFill`, `DrArith`) are missing; callers get `UnimplementedDraw`. See
# `cmhg/modhead`.
DRAW_OBJS := $(BUILD)/modules/draw/modhead.o \
             $(BUILD)/modules/draw/draw_module.o \
             $(BUILD)/modules/draw/draw_translate.o \
             $(BUILD)/modules/draw/drawpipe.o \
             $(BUILD)/modules/draw/drawpath.o \
             $(BUILD)/modules/draw/drawflatten.o \
             $(BUILD)/modules/draw/drawedge.o \
             $(BUILD)/modules/draw/drawdash.o \
             $(BUILD)/modules/draw/drawthicken.o \
             $(BUILD)/modules/draw/drawtransform.o \
             $(BUILD)/modules/draw/drawvector.o \
             $(BUILD)/modules/draw/drawout.o \
             $(BUILD)/modules/draw/drawoutfill.o \
             $(BUILD)/modules/draw/drawedges.o \
             $(BUILD)/modules/draw/drawfill.o \
             $(BUILD)/modules/draw/drawraster.o \
             $(BUILD)/modules/draw/drawqfill.o \
             $(BUILD)/modules/draw/drawclip.o \
             $(BUILD)/modules/draw/veneers.o
DRAW_MOD  := $(BUILD)/modules/Draw.mod
DRAW_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Video/Render/Draw/c \
               -I$(BUILD)/modules/draw

$(BUILD)/modules/draw/modhead.S: Sources/Video/Render/Draw/cmhg/modhead \
                                 Sources/Video/Render/Draw/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Video/Render/Draw -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/draw/%.o: Sources/Video/Render/Draw/c/%.c Makefile \
                           $(BUILD)/modules/draw/drawmod_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(DRAW_CFLAGS) -c $< -o $@

# The six entries (the four in the header, the DrawV claimant and its sixteen
# arms) and their C prototypes. See tools/build/marshal.py.
$(BUILD)/modules/draw/veneers.S: Sources/Video/Render/Draw/s/boundaries \
                                 Sources/Video/Render/Draw/cmhg/modhead \
                                 tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/Draw --asm $@ \
	  --header $(BUILD)/modules/draw/drawmod_veneers.h

$(BUILD)/modules/draw/drawmod_veneers.h: $(BUILD)/modules/draw/veneers.S

$(BUILD)/modules/draw/veneers.o: $(BUILD)/modules/draw/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(DRAW_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/draw/modhead.o: $(BUILD)/modules/draw/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(DRAW_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/Draw.elf: $(DRAW_OBJS) $(CLIB) mk/ld/module.ld \
                           tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(DRAW_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(DRAW_MOD): $(BUILD)/modules/Draw.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Draw - ported)"

-include $(DRAW_OBJS:.o=.d)

draw: $(DRAW_MOD)
.PHONY: draw

# DrawFile is the Draw file-format module, not Draw's path vector module.
# Its bounded parser rejects malformed nested records before rendering code can
# inspect them.
DRAWFILE_OBJS := $(BUILD)/modules/drawfile/modhead.o \
                 $(BUILD)/modules/drawfile/textarea.o \
                 $(BUILD)/modules/drawfile/drawfile.o \
                 $(BUILD)/modules/drawfile/drawfile_module.o \
                 $(BUILD)/modules/drawfile/veneers.o
DRAWFILE_MOD := $(BUILD)/modules/DrawFile.mod
DRAWFILE_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Video/Render/DrawFile/c \
                   -I$(BUILD)/modules/drawfile

$(BUILD)/modules/drawfile/modhead.S: Sources/Video/Render/DrawFile/cmhg/modhead \
                                     Sources/Video/Render/DrawFile/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Video/Render/DrawFile -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/drawfile/%.o: Sources/Video/Render/DrawFile/c/%.c Makefile \
                               $(BUILD)/modules/drawfile/drawfile_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(DRAWFILE_CFLAGS) -c $< -o $@

$(BUILD)/modules/drawfile/veneers.S: Sources/Video/Render/DrawFile/s/boundaries \
                                     Sources/Video/Render/DrawFile/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/DrawFile --asm $@ \
	  --header $(BUILD)/modules/drawfile/drawfile_veneers.h

$(BUILD)/modules/drawfile/drawfile_veneers.h: $(BUILD)/modules/drawfile/veneers.S

$(BUILD)/modules/drawfile/veneers.o: $(BUILD)/modules/drawfile/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(DRAWFILE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/drawfile/modhead.o: $(BUILD)/modules/drawfile/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(DRAWFILE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/DrawFile.elf: $(DRAWFILE_OBJS) $(CLIB_NOFLOAT) $(CLIB) mk/ld/module.ld \
                               tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld -o $@.tmp $(DRAWFILE_OBJS) $(CLIB_NOFLOAT) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(DRAWFILE_MOD): $(BUILD)/modules/DrawFile.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, DrawFile - ported)"

-include $(DRAWFILE_OBJS:.o=.d)

drawfile: $(DRAWFILE_MOD)
.PHONY: drawfile

DRAWFILE_TEST_GEN := $(BUILD)/tests/drawfile
DRAWFILE_TEST_FLAGS := $(HOST_CFLAGS) -ISources/Video/Render/DrawFile/c -Itests/lib

$(DRAWFILE_TEST_GEN)/check.o $(DRAWFILE_TEST_GEN)/drawfile.o: Sources/Video/Render/DrawFile/c/drawfile.h

$(DRAWFILE_TEST_GEN)/%.o: Sources/Video/Render/DrawFile/test/drawfile/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DRAWFILE_TEST_FLAGS) -c $< -o $@

$(DRAWFILE_TEST_GEN)/drawfile.o: Sources/Video/Render/DrawFile/c/drawfile.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DRAWFILE_TEST_FLAGS) -c $< -o $@

$(DRAWFILE_TEST_GEN)/libcheck.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DRAWFILE_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/drawfile-checks: $(DRAWFILE_TEST_GEN)/check.o \
                               $(DRAWFILE_TEST_GEN)/drawfile.o \
                               $(DRAWFILE_TEST_GEN)/libcheck.o
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

drawfilechecks: $(BUILD)/tests/drawfile-checks
	@$(BUILD)/tests/drawfile-checks
	@$(BUILD)/tests/drawfile-render
.PHONY: drawfilechecks

$(BUILD)/tests/drawfile-render: Sources/Video/Render/DrawFile/test/drawfile/render.c Sources/Video/Render/DrawFile/c/drawfile.c \
 Sources/Video/Render/DrawFile/c/drawfile_module.c Sources/Video/Render/DrawFile/c/textarea.c Sources/Video/Render/DrawFile/c/area_support.h \
 Sources/Video/Render/DrawFile/c/drawfile.h $(wildcard Sources/Video/Render/DrawFile/test/drawfile/host/riscos/*.h) \
 Sources/Video/Render/DrawFile/test/drawfile/host/drawfile_veneers.h tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) -std=gnu17 -g -no-pie -fsanitize=address,undefined -fno-sanitize-recover=all \
	  -ISources/Video/Render/DrawFile/test/drawfile/host -ISources/Video/Render/DrawFile/c -Itests/lib -Iinclude \
	  Sources/Video/Render/DrawFile/test/drawfile/render.c Sources/Video/Render/DrawFile/c/drawfile.c tests/lib/check.c -o $@
drawfilechecks: $(BUILD)/tests/drawfile-render
hostchecks: drawfilechecks

# SpriteExtend: the OS_SpriteOp extensions.
#
# Like Draw, the SWI chunk (seven JPEG reasons) is the smaller part; the module
# claims SpriteV at initialisation and after every reset, and every
# OS_SpriteOp passes through it.
#
# Partial: the SpriteV claimant passes every call on, so the module changes
# nothing in a ROM. The JPEG decoder's three dynamic areas are over
# `include/riscos/growarea.h`. The seven SWIs return the module's "not
# supported by this version" error; `cmhg/modhead` lists what is missing.
SPRX_JPEG_SRC := $(notdir $(sort $(wildcard Sources/Video/Render/SprExtend/jpeg/j*.c)))
SPRX_JPEG_OBJS := $(addprefix $(BUILD)/modules/spriteextend/,\
                    $(SPRX_JPEG_SRC:.c=.o))

# Six of the twenty-six are compiled but not linked.
#
# `jcapimin`, `jchuff`, `jcmarker`, `jcmaster`, `jcparam` and `jctrans` are
# libjpeg's compression side; the module writes its own baseline stream
# instead (see `Sources/Video/Render/SprExtend/c/sprx_jwrite.h`).
#
# `jcomapi` is not one of them: its routines are shared with the decoder.
#
# All twenty-six are still compiled with warnings as errors, and
# `make jpegclosure` checks them for no writable data and accounted-for
# undefined symbols. Only the link list is shorter.
#
# `jaricom` stays linked: `jdarith.c` uses its tables.
SPRX_JPEG_NOLINK := jcapimin.o jchuff.o jcmarker.o jcmaster.o jcparam.o \
                    jctrans.o
SPRX_JPEG_LINKED := $(filter-out $(addprefix $(BUILD)/modules/spriteextend/,\
                      $(SPRX_JPEG_NOLINK)),$(SPRX_JPEG_OBJS))

SPRX_OBJS := $(BUILD)/modules/spriteextend/modhead.o \
             $(BUILD)/modules/spriteextend/sprx_module.o \
             $(BUILD)/modules/spriteextend/sprx_panic.o \
             $(BUILD)/modules/spriteextend/sprx_jpegpanic.o \
             $(BUILD)/modules/spriteextend/sprx_jpegmem.o \
             $(BUILD)/modules/spriteextend/sprx_jpeginfo.o \
             $(BUILD)/modules/spriteextend/sprx_jpegfile.o \
             $(BUILD)/modules/spriteextend/sprx_jpegswi.o \
             $(BUILD)/modules/spriteextend/sprx_jpegmerge.o \
             $(BUILD)/modules/spriteextend/sprx_diffuse.o \
             $(BUILD)/modules/spriteextend/sprx_decode.o \
             $(BUILD)/modules/spriteextend/sprx_compile.o \
             $(BUILD)/modules/spriteextend/sprx_pixel.o \
             $(BUILD)/modules/spriteextend/sprx_convert.o \
             $(BUILD)/modules/spriteextend/sprx_dither.o \
             $(BUILD)/modules/spriteextend/sprx_ttr.o \
             $(BUILD)/modules/spriteextend/sprx_blend.o \
             $(BUILD)/modules/spriteextend/sprx_translate.o \
             $(BUILD)/modules/spriteextend/sprx_plot.o \
             $(BUILD)/modules/spriteextend/sprx_fetch.o \
             $(BUILD)/modules/spriteextend/sprx_save.o \
             $(BUILD)/modules/spriteextend/sprx_xloop.o \
             $(BUILD)/modules/spriteextend/sprx_yloop.o \
             $(BUILD)/modules/spriteextend/sprx_blitter.o \
             $(BUILD)/modules/spriteextend/sprx_jpegplot.o \
             $(BUILD)/modules/spriteextend/sprx_sprtrans.o \
             $(BUILD)/modules/spriteextend/sprx_sprite.o \
             $(BUILD)/modules/spriteextend/sprx_sprrows.o \
             $(BUILD)/modules/spriteextend/sprx_sprcols.o \
             $(BUILD)/modules/spriteextend/sprx_palette.o \
             $(BUILD)/modules/spriteextend/sprx_ttrprep.o \
             $(BUILD)/modules/spriteextend/sprx_kernel.o \
             $(BUILD)/modules/spriteextend/sprx_clip.o \
             $(BUILD)/modules/spriteextend/sprx_where.o \
             $(BUILD)/modules/spriteextend/sprx_vars.o \
             $(BUILD)/modules/spriteextend/sprx_flags.o \
             $(BUILD)/modules/spriteextend/sprx_workspace.o \
             $(BUILD)/modules/spriteextend/sprx_format.o \
             $(BUILD)/modules/spriteextend/sprx_putscaled.o \
             $(BUILD)/modules/spriteextend/sprx_machine.o \
             $(BUILD)/modules/spriteextend/sprx_spriteop.o \
             $(BUILD)/modules/spriteextend/sprx_handoff.o \
             $(BUILD)/modules/spriteextend/sprx_rows.o \
             $(BUILD)/modules/spriteextend/sprx_blendget.o \
             $(BUILD)/modules/spriteextend/sprx_jwrite.o \
             $(BUILD)/modules/spriteextend/sprx_jpegdims.o \
             $(BUILD)/modules/spriteextend/sprx_jpegplotswi.o \
             $(BUILD)/modules/spriteextend/sprx_charsprite.o \
             $(BUILD)/modules/spriteextend/sprx_createalpha.o \
             $(BUILD)/modules/spriteextend/sprx_pointer.o \
             $(BUILD)/modules/spriteextend/sprx_setpointer.o \
             $(BUILD)/modules/spriteextend/sprx_sysarea.o \
             $(BUILD)/modules/spriteextend/sprx_tilefast.o \
             $(BUILD)/modules/spriteextend/sprx_jpegfetch.o \
             $(BUILD)/modules/spriteextend/sprx_jpegsprite.o \
             $(BUILD)/modules/spriteextend/sprx_jpegjoin.o \
             $(BUILD)/modules/spriteextend/sprx_alpha.o \
             $(BUILD)/modules/spriteextend/sprx_alphaconv.o \
             $(BUILD)/modules/spriteextend/sprx_alphasize.o \
             $(BUILD)/modules/spriteextend/sprx_changedbox.o \
             $(BUILD)/modules/spriteextend/sprx_checkarea.o \
             $(BUILD)/modules/spriteextend/sprx_source.o \
             $(BUILD)/modules/spriteextend/sprx_rowstate.o \
             $(BUILD)/modules/spriteextend/sprx_transform.o \
             $(BUILD)/modules/spriteextend/sprx_tile.o \
             $(BUILD)/modules/spriteextend/sprx_sprpal.o \
             $(BUILD)/modules/spriteextend/sprx_append.o \
             $(BUILD)/modules/spriteextend/sprx_edge.o \
             $(BUILD)/modules/spriteextend/sprx_fill.o \
             $(BUILD)/modules/spriteextend/sprx_puttransformed.o \
             $(BUILD)/modules/spriteextend/sprx_transcode.o \
             $(BUILD)/modules/spriteextend/sprx_table32k.o \
             $(BUILD)/modules/spriteextend/sprx_yuvtabs.o \
             $(SPRX_JPEG_LINKED) \
             $(BUILD)/modules/spriteextend/blockmove.o \
             $(BUILD)/modules/spriteextend/transform.o \
             $(BUILD)/modules/spriteextend/veneers.o
SPRX_MOD  := $(BUILD)/modules/SpriteExtend.mod
SPRX_C_ROWS ?= 1
SPRX_CFLAGS := $(MOD_CFLAGS) -DSPRX_C_ROWS=$(SPRX_C_ROWS) -ffunction-sections -fdata-sections \
               -ISources/Lib/CLib/include -ISources/Video/Render/SprExtend/c \
               -ISources/Video/Render/SprExtend/jpeg -DRISCOS -DAVOID_TABLES \
               -DNO_ZERO_ROW_TEST -DLAZYDECODE \
               -I$(BUILD)/modules/spriteextend

# The IJG sources are third-party and are built with these warnings off
# rather than edited:
#   -Wno-char-subscripts       tables indexed by a `char` holding a marker's
#                              low byte, never negative
#   -Wno-unused-but-set-variable  markers read and deliberately ignored
#   -Wno-unused-function       encoder parts the transcoding does not reach
#   -Wno-sign-compare          two comparisons under `#ifdef LAZYDECODE` in
#                              jdcoefct.c between `JDIMENSION` counters and
#                              `int` bounds from `cinfo->xmin`; safe because
#                              `xmin` is never negative
# The rest of the tree's warning set applies.
SPRX_JPEG_CFLAGS := $(SPRX_CFLAGS) -Wno-char-subscripts \
                    -Wno-unused-but-set-variable -Wno-unused-function \
                    -Wno-sign-compare

# The two YUV tables, generated rather than stored. `yuvtabscheck` checks the
# generator against the reference's own.
$(BUILD)/modules/spriteextend/sprx_yuvtabs.c: tools/build/yuvtabs.py
	@mkdir -p $(dir $@)
	@python3 tools/build/yuvtabs.py $@

$(BUILD)/modules/spriteextend/sprx_yuvtabs.o: \
    $(BUILD)/modules/spriteextend/sprx_yuvtabs.c \
    Sources/Video/Render/SprExtend/c/sprx_yuvtabs.h Makefile
	@mkdir -p $(dir $@)
	$(CC) $(SPRX_CFLAGS) -c $< -o $@

SPRX_REF := ../legacy/riscos-src/RiscOS/Sources/Video/Render/SprExtend

yuvtabscheck: tools/build/yuvtabs.py
	@python3 tools/build/yuvtabs.py --check $(SPRX_REF)/c/genyuvtabs
.PHONY: yuvtabscheck

$(BUILD)/modules/spriteextend/%.o: Sources/Video/Render/SprExtend/jpeg/%.c Makefile \
                                   $(BUILD)/modules/spriteextend/sprx_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SPRX_JPEG_CFLAGS) -c $< -o $@

$(BUILD)/modules/spriteextend/modhead.S: Sources/Video/Render/SprExtend/cmhg/modhead \
                                         Sources/Video/Render/SprExtend/VersionNum \
                                         tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Video/Render/SprExtend -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/spriteextend/%.o: Sources/Video/Render/SprExtend/c/%.c Makefile \
                                   $(BUILD)/modules/spriteextend/sprx_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SPRX_CFLAGS) -c $< -o $@

# The five entries (the four in the header and the SpriteV claimant) and their
# C prototypes.
$(BUILD)/modules/spriteextend/veneers.S: Sources/Video/Render/SprExtend/s/boundaries \
                                         Sources/Video/Render/SprExtend/cmhg/modhead \
                                         tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/SprExtend --asm $@ \
	  --header $(BUILD)/modules/spriteextend/sprx_veneers.h

$(BUILD)/modules/spriteextend/sprx_veneers.h: \
    $(BUILD)/modules/spriteextend/veneers.S

$(BUILD)/modules/spriteextend/veneers.o: \
    $(BUILD)/modules/spriteextend/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SPRX_CFLAGS) -x assembler-with-cpp -c $< -o $@

# The bit block move, the one row loop not generated at run time. See the
# file.
$(BUILD)/modules/spriteextend/blockmove.o: Sources/Video/Render/SprExtend/s/aarch32/blockmove.S
	@mkdir -p $(dir $@)
	$(CC) $(SPRX_CFLAGS) -x assembler-with-cpp -c $< -o $@

# Entry to a transformed plot's row, which puts the row state on the stack
# because the row has no register to reach it with. See the file.
$(BUILD)/modules/spriteextend/transform.o: Sources/Video/Render/SprExtend/s/aarch32/transform.S \
                                           Sources/Video/Render/SprExtend/c/sprx_rowsize.h
	@mkdir -p $(dir $@)
	$(CC) $(SPRX_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/spriteextend/modhead.o: \
    $(BUILD)/modules/spriteextend/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SPRX_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/SpriteExtend.elf: $(SPRX_OBJS) $(CLIB) mk/ld/module.ld \
                                   tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SPRX_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SPRX_MOD): $(BUILD)/modules/SpriteExtend.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, SpriteExtend - whole,\
 the bit block move included)"

-include $(SPRX_OBJS:.o=.d)

spriteextend: $(SPRX_MOD)
.PHONY: spriteextend

# Compiles the IJG headers with this module's flags against this tree's C
# library, so the four changes marked CHANGED FOR THIS PORT are checked. Also
# asserts that `small_pool` is the decompressor's last member, which the bump
# allocator relies on.
$(BUILD)/modules/spriteextend/headercheck.o: \
    Sources/Video/Render/SprExtend/jpeg/headercheck.c \
    $(wildcard Sources/Video/Render/SprExtend/jpeg/*.h) \
    Sources/Video/Render/SprExtend/c/commondefs.h Makefile
	@mkdir -p $(dir $@)
	@$(CC) $(SPRX_CFLAGS) -Wall -Wextra -Werror -DRISCOS \
	  -ISources/Video/Render/SprExtend/jpeg -c $< -o $@
	@echo "spriteextend: the IJG headers compile, and small_pool is last"

SPRX_TEST_GEN := $(BUILD)/tests/spriteextend
SPRX_TEST_FLAGS := $(HOST_CFLAGS) -ISources/Video/Render/SprExtend/c -Itests/lib \
                   -I$(SPRX_TEST_GEN)

# `s/aarch32/blockmove.S` cannot be compiled for the host, so its assembled .text is
# turned into words and run on the interpreter in
# `Sources/Video/Render/SprExtend/test/spriteextend/host/armrun.c`, entered as the generated loop enters it.
$(SPRX_TEST_GEN)/blockmove.bin: $(BUILD)/modules/spriteextend/blockmove.o
	@mkdir -p $(dir $@)
	$(OBJCOPY) -O binary --only-section=.text $< $@

$(SPRX_TEST_GEN)/blockmovewords.h: $(SPRX_TEST_GEN)/blockmove.bin \
                                   tools/build/asmwords.py
	@mkdir -p $(dir $@)
	@python3 tools/build/asmwords.py $< $@ sprx_block_move_words

$(SPRX_TEST_GEN)/blockmove.o $(SPRX_TEST_GEN)/xloop.o: \
    $(SPRX_TEST_GEN)/blockmovewords.h

# The same for the transformed row's entry, the module's other hand-written
# join.
$(SPRX_TEST_GEN)/transform.bin: $(BUILD)/modules/spriteextend/transform.o
	@mkdir -p $(dir $@)
	$(OBJCOPY) -O binary --only-section=.text $< $@

$(SPRX_TEST_GEN)/transformrowwords.h: $(SPRX_TEST_GEN)/transform.bin \
                                      tools/build/asmwords.py
	@mkdir -p $(dir $@)
	@python3 tools/build/asmwords.py $< $@ sprx_enter_transform_row_words

$(SPRX_TEST_GEN)/transformrow.o: $(SPRX_TEST_GEN)/transformrowwords.h

$(SPRX_TEST_GEN)/%.o: Sources/Video/Render/SprExtend/test/spriteextend/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_TEST_FLAGS) -ISources/Video/Render/SprExtend/test/spriteextend/host -c $< -o $@

$(SPRX_TEST_GEN)/%.o: Sources/Video/Render/SprExtend/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_TEST_FLAGS) -c $< -o $@

$(SPRX_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/sprxpanic-checks: $(addprefix $(SPRX_TEST_GEN)/,panic.o \
                                   sprx_panic.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Suites whose subject reaches a SWI. `Sources/Video/Render/SprExtend/test/spriteextend/host` shadows
# `riscos/swi.h` (inline assembly for one processor) and `riscos/growarea.h`
# (so areas can be watched); the growarea stand-in keeps an area's base fixed
# when it grows, as the real one does.
# The shadow directory is given with `-iquote` so it is searched first for
# quoted includes regardless of HOST_CFLAGS' `-Iinclude`.
# The three -D flags match the module build.
SPRX_SHADOW_FLAGS := -iquote Sources/Video/Render/SprExtend/test/spriteextend/host $(HOST_CFLAGS) -DRISCOS \
                 -I$(BUILD)/modules/spriteextend \
                 -DAVOID_TABLES -DNO_ZERO_ROW_TEST -DLAZYDECODE \
                 -ISources/Video/Render/SprExtend/c -ISources/Video/Render/SprExtend/jpeg \
                 -Itests/lib

$(SPRX_TEST_GEN)/shadow/%.o: Sources/Video/Render/SprExtend/test/spriteextend/%.c | $(BUILD)/modules/spriteextend/sprx_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_SHADOW_FLAGS) -c $< -o $@

$(SPRX_TEST_GEN)/shadow/%.o: Sources/Video/Render/SprExtend/test/spriteextend/host/%.c | $(BUILD)/modules/spriteextend/sprx_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_SHADOW_FLAGS) -c $< -o $@

$(SPRX_TEST_GEN)/shadow/%.o: Sources/Video/Render/SprExtend/c/%.c | $(BUILD)/modules/spriteextend/sprx_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_SHADOW_FLAGS) -c $< -o $@

$(SPRX_TEST_GEN)/shadow/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_SHADOW_FLAGS) -c $< -o $@

$(BUILD)/tests/sprxjpegmem-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                     jpegmem.o sprx_jpegmem.o sprx_panic.o \
                                     sprx_jpegpanic.o growarea.o hostsprx.o \
                                     check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/sprxjpeginfo-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                      jpeginfo.o sprx_jpeginfo.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The file window used by `JPEG_FileInfo`. The shadow directory stubs its
# three filing system calls; the OS_GBPB stub reports the count not
# transferred, as the real SWI does, and a short transfer is not an error.
$(BUILD)/tests/sprxjpegfile-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                      jpegfile.o sprx_jpegfile.o \
                                      sprx_jpeginfo.o files.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The JPEG decoder end to end: a JPEG built in the test, scanned, and read
# back a line at a time. Links most of the library, the memory manager over
# the growable-area stand-in, and the diffusion.
# `jaricom.o` is needed: its tables serve the arithmetic decoder too, and
# `jdarith.c` reaches them.
# The compression files are not linked; the transcoder writes its own baseline
# stream (see `sprx_jwrite.h`). `jcomapi` stays: the decoder uses it.
SPRX_DECODE_OBJS := jdapimin.o jdapistd.o jdarith.o jdatasrc.o jdatadst.o \
                    jdcoefct.o jdcolor.o jddctmgr.o jdhuff.o jdinput.o \
                    jdmainct.o jdmarker.o jdmaster.o jdpostct.o jdsample.o \
                    jdtrans.o jidctfst.o jutils.o jaricom.o jcomapi.o

# The decoder and the files that join it to a plot. A scaled plot has a JPEG
# arm, so every test that links `sprx_putscaled.o` links these too, with the
# C rows a plot is drawn by where its code cannot run and the blend's tables.
# They are the module's own code, not stand-ins.
SPRX_JOIN_JPEG_OBJS := $(addprefix $(SPRX_TEST_GEN)/shadow/,\
    sprx_rows.o sprx_blendget.o sprx_jpegjoin.o sprx_jpegsprite.o sprx_jpegplot.o sprx_jpeginfo.o \
    sprx_decode.o sprx_transcode.o sprx_jwrite.o sprx_jpegmerge.o sprx_diffuse.o \
    sprx_yuvtabs.o sprx_jpegmem.o sprx_panic.o sprx_jpegpanic.o \
    growarea.o hosttable32k.o $(SPRX_DECODE_OBJS))


# The pixel-less sprite a JPEG is plotted as: a valid header and the four
# words where the pixels would be.
$(BUILD)/tests/sprxjpegsprite-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                        jpegsprite.o sprx_jpegsprite.o \
                                        sprx_sprite.o hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# One row of a JPEG fetched by the blitter: the two words of workspace between
# the generated code and the decoder.
$(BUILD)/tests/sprxjpegfetch-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                       jpegfetch.o sprx_jpegfetch.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The four JPEG plotting SWIs: the four ways into one plot, the flag word
# translated rather than passed on, and a loaded file's block freed whether
# the plot worked or not.
$(BUILD)/tests/sprxjpegplotswi-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
    jpegplotswi.o sprx_jpegplotswi.o sprx_jpegdims.o \
    sprx_jpegswi.o hostrma.o files.o hostjpeg.o hosthandoff.o \
    sprx_putscaled.o sprx_spriteop.o sprx_handoff.o sprx_setpointer.o sprx_pointer.o \
    hostpointer.o sprx_sysarea.o sprx_createalpha.o sprx_alpha.o \
    sprx_alphaconv.o sprx_alphasize.o sprx_charsprite.o sprx_tilefast.o \
    hostgraphicsv.o sprx_sprrows.o sprx_sprcols.o sprx_checkarea.o \
    sprx_sprpal.o sprx_append.o sprx_puttransformed.o sprx_tile.o \
    sprx_fill.o sprx_edge.o sprx_source.o sprx_transform.o sprx_rowstate.o \
    sprx_sprtrans.o sprx_changedbox.o sprx_clip.o sprx_where.o sprx_vars.o \
    sprx_flags.o sprx_format.o sprx_workspace.o sprx_kernel.o \
    sprx_ttrprep.o sprx_palette.o sprx_sprite.o sprx_blitter.o \
    sprx_yloop.o sprx_xloop.o sprx_fetch.o sprx_save.o sprx_plot.o \
    sprx_translate.o sprx_blend.o sprx_ttr.o sprx_dither.o sprx_convert.o \
    sprx_pixel.o sprx_compile.o sprx_jpegfile.o \
    hostmodes.o hostsprx.o check.o) $(SPRX_JOIN_JPEG_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The JPEG-only part of a scaled plot: the variables it cannot read, the
# columns given to the decoder, and what the decoder agreed to.
$(BUILD)/tests/sprxjpegbegin-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                       jpegbegin.o hostjpeg.o \
                                       sprx_jpegjoin.o sprx_jpegsprite.o \
                                       sprx_jpegplot.o sprx_jpeginfo.o \
                                       sprx_vars.o sprx_format.o \
                                       sprx_sprite.o sprx_decode.o \
                                       sprx_transcode.o sprx_jwrite.o sprx_jpegmerge.o \
                                       sprx_diffuse.o sprx_yuvtabs.o \
                                       sprx_jpegmem.o sprx_panic.o \
                                       sprx_jpegpanic.o growarea.o \
                                       hostmodes.o hostsprx.o hosttable32k.o \
                                       check.o $(SPRX_DECODE_OBJS))
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The baseline JPEG this module writes, read back by libjpeg: every
# coefficient of every block must match.
$(BUILD)/tests/sprxjwrite-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                    jwrite.o sprx_decode.o \
                                    hostjpeg.o \
                                    sprx_transcode.o sprx_jwrite.o \
                                    sprx_jpegmerge.o \
                                    sprx_diffuse.o sprx_yuvtabs.o \
                                    sprx_jpegmem.o sprx_panic.o \
                                    sprx_jpegpanic.o growarea.o hostsprx.o \
                                    hosttable32k.o check.o $(SPRX_DECODE_OBJS))
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/sprxdecode-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                    decode.o hostjpeg.o \
                                    sprx_decode.o sprx_transcode.o sprx_jwrite.o \
                                    sprx_jpegmerge.o sprx_diffuse.o \
                                    sprx_yuvtabs.o sprx_jpegmem.o \
                                    sprx_panic.o sprx_jpegpanic.o \
                                    growarea.o hostsprx.o hosttable32k.o \
                                    check.o $(SPRX_DECODE_OBJS))
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The merged upsampler, checked against the two library stages it replaces,
# so `jdcolor.c` and the generated tables are linked beside it.
# The library's own files on the host, with the same warnings turned off as in
# the module build (see the list above the module's flags).
$(SPRX_TEST_GEN)/shadow/%.o: Sources/Video/Render/SprExtend/jpeg/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_SHADOW_FLAGS) -Wno-sign-compare -Wno-char-subscripts \
	  -Wno-unused-but-set-variable -Wno-unused-function \
	  -Wno-unused-parameter -Wno-shift-negative-value -Wno-empty-body \
	  -c $< -o $@

$(SPRX_TEST_GEN)/shadow/sprx_yuvtabs.o: \
    $(BUILD)/modules/spriteextend/sprx_yuvtabs.c \
    Sources/Video/Render/SprExtend/c/sprx_yuvtabs.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_SHADOW_FLAGS) -c $< -o $@

# The instruction encoders and the compiler around them. The binary also
# prints one line per encoding for `asmencodecheck`, so the two targets share
# it.
$(BUILD)/tests/sprxemit-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                 emit.o sprx_compile.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(SPRX_TEST_GEN)/%.o: Sources/Video/Render/SprExtend/test/spriteextend/host/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_TEST_FLAGS) -ISources/Video/Render/SprExtend/test/spriteextend/host -c $< -o $@

# The pixel formats: which exist, where each gun sits, and how to widen one.
# Runs instructions, so it links the ARM interpreter.
$(BUILD)/tests/sprxpixel-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                   pixel.o sprx_pixel.o sprx_compile.o \
                                   armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Pixel format conversion: compiled, run, and compared against a plain C
# conversion built from the same table.
$(BUILD)/tests/sprxconvert-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                     convert.o sprx_convert.o sprx_pixel.o \
                                     sprx_compile.o armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The translation tables: for pixels arithmetic cannot convert.
$(BUILD)/tests/sprxttr-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                 ttr.o sprx_ttr.o sprx_dither.o \
                                 sprx_convert.o sprx_pixel.o sprx_compile.o \
                                 armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The blend: compiled, run, and compared against a per-gun
# `(src*a + dst*(256-a))/256` worked out in plain C from the same format
# table.
$(BUILD)/tests/sprxblend-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                   blend.o sprx_blend.o sprx_ttr.o \
                                   sprx_dither.o sprx_convert.o \
                                   sprx_pixel.o sprx_compile.o armrun.o \
                                   check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The per-pixel path: which of conversion, dither, tables and blend apply to a
# pixel, and in what order.
$(BUILD)/tests/sprxtranslate-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                       translate.o sprx_translate.o \
                                       sprx_blend.o sprx_ttr.o sprx_dither.o \
                                       sprx_convert.o sprx_pixel.o \
                                       sprx_compile.o armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The plot description: the key word the code cache picks a compiled loop by,
# and the numbers the loop is built from.
$(BUILD)/tests/sprxplot-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                  plot.o sprx_plot.o sprx_translate.o \
                                  sprx_blend.o sprx_ttr.o sprx_dither.o \
                                  sprx_convert.o sprx_pixel.o sprx_compile.o \
                                  check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Reading a row of packed pixels: the rotate, the counter at the top of a
# register, and the word loaded exactly when the last one runs out.
$(BUILD)/tests/sprxfetch-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                   fetch.o sprx_fetch.o sprx_plot.o \
                                   sprx_translate.o sprx_blend.o sprx_ttr.o \
                                   sprx_dither.o sprx_convert.o \
                                   sprx_pixel.o sprx_compile.o armrun.o \
                                   check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Writing a row of pixels to the screen: the fetch's rotate and counter, the
# eight plot actions, and the pixels either side of the row.
$(BUILD)/tests/sprxsave-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                  save.o sprx_save.o sprx_fetch.o \
                                  sprx_plot.o \
                                  sprx_translate.o sprx_blend.o sprx_ttr.o \
                                  sprx_dither.o sprx_convert.o sprx_pixel.o \
                                  sprx_compile.o armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Entering a transformed plot's row: the ten registers, r12, and the row state
# on the stack. The veneer is assembled and run on the interpreter against a
# stub row that records what it was given.
$(BUILD)/tests/sprxtransformrow-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                          transformrow.o armrun.o \
                                          sprx_compile.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The bit block move: the one row loop not generated at run time. Assembled
# from `s/aarch32/blockmove.S`, run on the interpreter, and checked against a plain C
# bit move at every end offset and length.
$(BUILD)/tests/sprxblockmove-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                       blockmove.o armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# One row of a plot from sprite to screen: fetch, translation and save run
# together in the shape the plot's numbers pick.
$(BUILD)/tests/sprxxloop-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                   xloop.o sprx_xloop.o sprx_fetch.o \
                                   sprx_save.o sprx_plot.o sprx_translate.o \
                                   sprx_blend.o sprx_ttr.o sprx_dither.o \
                                   sprx_convert.o sprx_pixel.o sprx_compile.o \
                                   armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# A whole sprite plotted onto a whole screen through the compiled blitter;
# the only check that covers the second dimension.
$(BUILD)/tests/sprxyloop-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                   yloop.o sprx_yloop.o sprx_xloop.o \
                                   sprx_fetch.o sprx_save.o sprx_plot.o \
                                   sprx_translate.o sprx_blend.o sprx_ttr.o \
                                   sprx_dither.o sprx_convert.o \
                                   sprx_pixel.o sprx_compile.o armrun.o \
                                   check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The blitter entry: the last decisions a plot needs, and the cache that
# reuses a compiled loop for the same plot shape.
$(BUILD)/tests/sprxblitter-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                     blitter.o sprx_blitter.o sprx_yloop.o \
                                     sprx_xloop.o sprx_fetch.o sprx_save.o \
                                     sprx_sprtrans.o sprx_rowstate.o \
                                     sprx_plot.o sprx_translate.o \
                                     sprx_blend.o sprx_ttr.o sprx_dither.o \
                                     sprx_convert.o sprx_pixel.o \
                                     sprx_compile.o armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# What to ask the JPEG decoder for when the result will be plotted.
$(BUILD)/tests/sprxjpegplot-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                      jpegplot.o sprx_jpegplot.o sprx_plot.o \
                                      sprx_translate.o sprx_blend.o \
                                      sprx_ttr.o sprx_dither.o \
                                      sprx_convert.o sprx_pixel.o \
                                      sprx_compile.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The transformed plot: a sprite drawn through a matrix, with a fixed-point
# source position and edges clamped rather than tested.
$(BUILD)/tests/sprxsprtrans-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                      sprtrans.o sprx_sprtrans.o \
                                      sprx_xloop.o sprx_fetch.o sprx_save.o \
                                      sprx_plot.o sprx_translate.o \
                                      sprx_blend.o sprx_ttr.o sprx_dither.o \
                                      sprx_convert.o sprx_pixel.o \
                                      sprx_compile.o armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Finding a sprite, the first step of nearly every OS_SpriteOp reason.
$(BUILD)/tests/sprxsprite-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                    sprite.o sprx_sprite.o hostsprx.o \
                                    growarea.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Inserting and deleting rows in a sprite: moving everything above the
# insertion point and correcting four numbers, for both the image and the mask,
# which differ in shape.
$(BUILD)/tests/sprxsprrows-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                     sprrows.o sprx_sprrows.o sprx_sprite.o \
                                     hostsprx.o growarea.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Inserting and deleting columns: the row job done a bit at a time, checked
# against a bit-level model.
$(BUILD)/tests/sprxsprcols-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                     sprcols.o sprx_sprcols.o sprx_sprrows.o \
                                     sprx_sprite.o hostsprx.o growarea.o \
                                     check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Two old colour forms converted to the screen's, checked against the
# reference's arithmetic.
$(BUILD)/tests/sprxpalette-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                     palette.o sprx_palette.o sprx_pixel.o \
                                     check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Classifying and validating a plot's translation table; the only code that
# looks inside the caller's table.
$(BUILD)/tests/sprxttrprep-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                     ttrprep.o sprx_ttrprep.o sprx_palette.o \
                                     sprx_pixel.o hostsprx.o growarea.o \
                                     check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Plots handed back to the kernel. Checked both ways: too strict is a silent
# slowdown, too loose a wrong picture.
$(BUILD)/tests/sprxkernel-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                    kernel.o sprx_kernel.o sprx_sprite.o \
                                    hostsprx.o growarea.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# A sprite and destination turned into blitter parameters, then a blitter run
# on them: the whole path from plot request to picture.
$(BUILD)/tests/sprxclip-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                  clip.o sprx_clip.o sprx_where.o \
                                  sprx_yloop.o \
                                  sprx_xloop.o sprx_fetch.o sprx_save.o \
                                  sprx_plot.o sprx_translate.o sprx_blend.o \
                                  sprx_ttr.o sprx_dither.o sprx_convert.o \
                                  sprx_pixel.o sprx_compile.o armrun.o \
                                  check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The changed box a display driver reads: inclusive edges against counted
# sizes.
$(BUILD)/tests/sprxchangedbox-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                        changedbox.o sprx_changedbox.o \
                                        check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# What the module reads from the machine once: which instructions the
# compiler may emit, and where the ECF patterns are. Covers four ways of
# declining, none of which returns an error.
$(BUILD)/tests/sprxmachine-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                     machine.o sprx_machine.o hostmodes.o \
                                     sprx_changedbox.o \
                                     check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The two transformed reasons joined up: the order, the two flag words, and
# the walk a transformed plot produces.
$(BUILD)/tests/sprxputtransformed-checks: \
    $(addprefix $(SPRX_TEST_GEN)/shadow/,\
      puttransformed.o sprx_puttransformed.o sprx_fill.o sprx_edge.o \
      sprx_source.o sprx_transform.o sprx_rowstate.o sprx_clip.o \
      sprx_where.o sprx_vars.o sprx_flags.o sprx_format.o sprx_workspace.o \
      sprx_kernel.o sprx_ttrprep.o sprx_palette.o sprx_sprite.o \
      sprx_blitter.o sprx_yloop.o sprx_xloop.o sprx_fetch.o sprx_save.o \
      sprx_sprtrans.o sprx_plot.o sprx_translate.o sprx_blend.o sprx_ttr.o \
      sprx_dither.o sprx_convert.o sprx_pixel.o sprx_compile.o \
      sprx_sprrows.o sprx_spriteop.o sprx_handoff.o sprx_putscaled.o sprx_changedbox.o \
    sprx_tilefast.o \
    hostgraphicsv.o \
    sprx_setpointer.o sprx_pointer.o hostpointer.o \
    sprx_sysarea.o \
    sprx_createalpha.o sprx_alpha.o sprx_alphaconv.o sprx_alphasize.o \
      sprx_charsprite.o \
      sprx_checkarea.o sprx_sprpal.o sprx_append.o sprx_sprcols.o \
      sprx_tile.o \
      hostmodes.o hosthandoff.o hostsprx.o check.o) $(SPRX_JOIN_JPEG_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The four edges together, checked against the shape's definition: correct
# edges can still be paired wrongly.
$(BUILD)/tests/sprxfill-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                  fill.o sprx_fill.o sprx_edge.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^ -lm

# The edges of the quadrilateral a transformed sprite is drawn into: every row
# of every line, against the exact answer from the line's two ends.
$(BUILD)/tests/sprxedge-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                  edge.o sprx_edge.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Tiling a window by copying rectangles rather than plotting every tile. The
# host models the screen as a grid of cells, since correctness cannot be seen
# from the list of operations.
$(BUILD)/tests/sprxtilefast-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                      tilefast.o sprx_tilefast.o sprx_tile.o \
                                      hosttile.o hostmodes.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# SpriteReason_AppendSprite as a caller reaches it, mostly the case where one
# sprite has a mask and the other has not.
$(BUILD)/tests/sprxappendreason-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
    appendreason.o sprx_spriteop.o sprx_handoff.o sprx_setpointer.o sprx_pointer.o \
    sprx_tilefast.o \
    hostgraphicsv.o \
    sprx_sysarea.o sprx_createalpha.o sprx_alpha.o sprx_alphaconv.o \
    sprx_alphasize.o sprx_charsprite.o sprx_tile.o sprx_putscaled.o \
    sprx_puttransformed.o sprx_fill.o sprx_edge.o sprx_source.o \
    sprx_transform.o sprx_rowstate.o sprx_sprtrans.o sprx_clip.o \
    sprx_where.o sprx_vars.o sprx_flags.o sprx_format.o sprx_workspace.o \
    sprx_kernel.o sprx_ttrprep.o sprx_palette.o sprx_sprite.o \
    sprx_sprrows.o sprx_sprcols.o sprx_sprpal.o sprx_append.o \
    sprx_changedbox.o sprx_checkarea.o sprx_blitter.o sprx_yloop.o \
    sprx_xloop.o sprx_fetch.o sprx_save.o sprx_plot.o sprx_translate.o \
    sprx_blend.o sprx_ttr.o sprx_dither.o sprx_convert.o sprx_pixel.o \
    sprx_compile.o hostmodes.o hostpointer.o hosthandoff.o hostsprx.o \
    check.o) $(SPRX_JOIN_JPEG_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Setting a pointer shape: a temporary sprite, output switched to it, the
# caller's sprite plotted in, and the result handed to the hardware.
$(BUILD)/tests/sprxsetpointer-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                        setpointer.o sprx_setpointer.o \
                                        sprx_sysarea.o \
                                        sprx_pointer.o sprx_sprite.o \
                                        sprx_vars.o hostmodes.o \
                                        hostpointer.o hosthandoff.o \
                                        hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# A sprite made into the mouse pointer: which sprites are refused, the active
# point, and which sprite colour each mouse colour is.
$(BUILD)/tests/sprxpointer-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                     pointer.o sprx_pointer.o sprx_sprite.o \
                                     hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# SpriteReason_CreateRemoveAlpha as a caller reaches it: four destinations,
# each with several no-op cases.
$(BUILD)/tests/sprxcreatealpha-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                         createalpha.o sprx_createalpha.o \
                                         sprx_alpha.o sprx_alphaconv.o \
                                         sprx_alphasize.o sprx_sprite.o \
                                         sprx_sprrows.o hostmodes.o \
                                         hosthandoff.o hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Converting one kind of transparency to another pixel by pixel; one
# direction runs backwards because the destination is eight times the size.
$(BUILD)/tests/sprxalphaconv-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                       alphaconv.o sprx_alphaconv.o \
                                       sprx_alpha.o sprx_sprite.o \
                                       sprx_sprrows.o hostmodes.o \
                                       hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Making room for a mask in a sprite area and removing it, which moves
# everything after it; every byte is tagged with its origin.
$(BUILD)/tests/sprxalphasize-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                       alphasize.o sprx_alphasize.o \
                                       sprx_sprite.o hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# What a sprite's transparency actually holds, as opposed to what its mode
# word says: five answers from one walk.
$(BUILD)/tests/sprxalpha-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                   alpha.o sprx_alpha.o sprx_sprite.o \
                                   sprx_sprrows.o hostmodes.o hostsprx.o \
                                   check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The eight-by-eight sprite a character is painted from: a valid header, and
# the bits reversed.
$(BUILD)/tests/sprxcharsprite-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                        charsprite.o sprx_charsprite.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Two sprites merged in place in the sprite area with no scratch space; every
# pixel is tagged with its origin and checked.
$(BUILD)/tests/sprxappend-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                    append.o sprx_append.o sprx_sprite.o \
                                    sprx_sprrows.o \
                                    hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# A sprite's own palette: reading its size, removing it and adding one, each
# of which moves everything after it in the area.
$(BUILD)/tests/sprxsprpal-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                    sprpal.o sprx_sprpal.o sprx_sprite.o \
                                    hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# One system font character, drawn by building a sprite from the OS's
# character definition and plotting it.
$(BUILD)/tests/sprxcharreason-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
    charreason.o sprx_spriteop.o sprx_handoff.o sprx_charsprite.o sprx_tile.o \
    sprx_tilefast.o \
    hostgraphicsv.o \
    sprx_setpointer.o sprx_pointer.o hostpointer.o \
    sprx_sysarea.o \
    sprx_createalpha.o sprx_alpha.o sprx_alphaconv.o sprx_alphasize.o \
    sprx_putscaled.o sprx_puttransformed.o sprx_fill.o sprx_edge.o \
    sprx_source.o sprx_transform.o sprx_rowstate.o sprx_sprtrans.o \
    sprx_clip.o sprx_where.o sprx_vars.o sprx_flags.o sprx_format.o \
    sprx_workspace.o sprx_kernel.o sprx_ttrprep.o sprx_palette.o \
    sprx_sprite.o sprx_sprrows.o sprx_sprcols.o sprx_sprpal.o \
    sprx_append.o sprx_changedbox.o sprx_checkarea.o \
    sprx_blitter.o sprx_yloop.o sprx_xloop.o sprx_fetch.o sprx_save.o \
    sprx_plot.o sprx_translate.o sprx_blend.o sprx_ttr.o sprx_dither.o \
    sprx_convert.o sprx_pixel.o sprx_compile.o \
    hostmodes.o hosthandoff.o hostsprx.o check.o) $(SPRX_JOIN_JPEG_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The tiling loop as a caller reaches it: covering the window twice, missing a
# strip, or not ending.
$(BUILD)/tests/sprxtilereason-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
    tilereason.o sprx_spriteop.o sprx_handoff.o sprx_tile.o sprx_putscaled.o \
    sprx_tilefast.o \
    hostgraphicsv.o \
    sprx_setpointer.o sprx_pointer.o hostpointer.o \
    sprx_sysarea.o \
    sprx_createalpha.o sprx_alpha.o sprx_alphaconv.o sprx_alphasize.o \
    sprx_charsprite.o \
    sprx_puttransformed.o sprx_fill.o sprx_edge.o sprx_source.o \
    sprx_transform.o sprx_rowstate.o sprx_changedbox.o sprx_checkarea.o \
    sprx_sprpal.o sprx_append.o sprx_sprrows.o sprx_sprcols.o sprx_clip.o \
    sprx_where.o sprx_vars.o sprx_flags.o sprx_format.o sprx_workspace.o \
    sprx_kernel.o sprx_ttrprep.o sprx_palette.o sprx_sprite.o sprx_blitter.o \
    sprx_yloop.o sprx_xloop.o sprx_fetch.o sprx_save.o sprx_sprtrans.o \
    sprx_plot.o sprx_translate.o sprx_blend.o sprx_ttr.o sprx_dither.o \
    sprx_convert.o sprx_pixel.o sprx_compile.o hostmodes.o hosthandoff.o \
    hostsprx.o check.o) $(SPRX_JOIN_JPEG_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Tiling a sprite over the graphics window: where the first tile starts, how
# much of it shows, and whether the whole job can be done by copying.
$(BUILD)/tests/sprxtile-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                  tile.o sprx_tile.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The transformed plot's fixed-point arithmetic, checked against the same
# product computed with the host's 128-bit integer.
$(BUILD)/tests/sprxtransform-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                       transform.o sprx_transform.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The block a transformed plot's compiled code reads off the stack, and the
# three fields in it that are packed or scaled.
$(BUILD)/tests/sprxrowstate-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                      rowstate.o sprx_rowstate.o \
                                      sprx_workspace.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Which part of the sprite a transformed plot draws: the same rectangle means
# different things with and without a matrix.
$(BUILD)/tests/sprxsource-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                    source.o sprx_source.o hostsprx.o \
                                    check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Sprite area validation. Each case is a valid block broken in exactly one
# number.
$(BUILD)/tests/sprxcheckarea-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                       checkarea.o sprx_checkarea.o \
                                       sprx_sprite.o hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The two reasons that change a sprite's shape, driven through the vector: the
# sign of the count, a zero count, and the strict sprite lookup these use and
# the plotting reasons do not.
$(BUILD)/tests/sprxadjust-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                    adjust.o sprx_spriteop.o sprx_handoff.o sprx_sprpal.o \
                                    sprx_tilefast.o \
                                    hostgraphicsv.o \
                                    sprx_setpointer.o sprx_pointer.o hostpointer.o \
                                    sprx_sysarea.o \
                                    sprx_createalpha.o sprx_alpha.o sprx_alphaconv.o sprx_alphasize.o \
                                    sprx_charsprite.o \
                                    sprx_putscaled.o sprx_sprrows.o \
                                    sprx_sprcols.o sprx_sprite.o \
                                    sprx_clip.o sprx_where.o sprx_vars.o \
                                    sprx_flags.o sprx_format.o \
                                    sprx_workspace.o sprx_kernel.o \
                                    sprx_ttrprep.o sprx_palette.o \
                                    sprx_changedbox.o sprx_checkarea.o \
                                    sprx_append.o sprx_puttransformed.o \
                                    sprx_tile.o \
                                    sprx_fill.o sprx_edge.o sprx_source.o \
                                    sprx_transform.o sprx_rowstate.o \
                                    sprx_sprtrans.o hosthandoff.o \
                                    sprx_blitter.o \
                                    sprx_yloop.o sprx_xloop.o sprx_fetch.o \
                                    sprx_save.o sprx_plot.o sprx_translate.o \
                                    sprx_blend.o sprx_ttr.o sprx_dither.o \
                                    sprx_convert.o sprx_pixel.o \
                                    sprx_compile.o hostmodes.o hosthandoff.o \
                                    hostsprx.o check.o) $(SPRX_JOIN_JPEG_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The C rows against the compiled loop, over plots chosen at random: both
# run on the same memory, the loop on the interpreter, and the screens must
# match. The bit block move and the transformed row's entry are run as the
# assembled words.
$(SPRX_TEST_GEN)/shadow/rows.o: Sources/Video/Render/SprExtend/test/spriteextend/rows.c \
                                $(SPRX_TEST_GEN)/blockmovewords.h \
                                $(SPRX_TEST_GEN)/transformrowwords.h \
                                | $(BUILD)/modules/spriteextend/sprx_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPRX_SHADOW_FLAGS) -I$(SPRX_TEST_GEN) -c $< -o $@

$(BUILD)/tests/sprxrows-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                  rows.o armrun.o sprx_putscaled.o \
                                  sprx_puttransformed.o sprx_fill.o \
                                  sprx_edge.o sprx_source.o sprx_transform.o \
                                  sprx_rowstate.o sprx_sprtrans.o \
                                  sprx_changedbox.o sprx_clip.o sprx_where.o \
                                  sprx_vars.o sprx_flags.o sprx_format.o \
                                  sprx_workspace.o sprx_kernel.o \
                                  sprx_ttrprep.o sprx_palette.o sprx_sprite.o \
                                  sprx_blitter.o sprx_yloop.o sprx_xloop.o \
                                  sprx_fetch.o sprx_save.o sprx_plot.o \
                                  sprx_translate.o sprx_blend.o sprx_ttr.o \
                                  sprx_dither.o sprx_convert.o sprx_pixel.o \
                                  sprx_compile.o sprx_jpegdims.o \
                                  hostmodes.o hosthandoff.o \
                                  hostsprx.o hostjpeg.o check.o) \
                                $(SPRX_JOIN_JPEG_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -pthread -o $@ $^

# `Go_PutSpriteScaled` itself. Its parts are checked elsewhere; this checks
# the order of its checks (by making two things wrong and seeing which is
# reported) and the four ways a plot ends.
$(BUILD)/tests/sprxputscaled-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                       putscaled.o sprx_putscaled.o \
                                       sprx_spriteop.o sprx_handoff.o sprx_changedbox.o \
                                       sprx_tilefast.o \
                                       hostgraphicsv.o \
                                       sprx_setpointer.o sprx_pointer.o hostpointer.o \
                                       sprx_sysarea.o \
                                       sprx_createalpha.o sprx_alpha.o sprx_alphaconv.o sprx_alphasize.o \
                                       sprx_charsprite.o \
                                       sprx_sprrows.o sprx_sprcols.o \
                                       sprx_checkarea.o sprx_sprpal.o \
                                       sprx_append.o sprx_puttransformed.o \
                                       sprx_tile.o \
                                       sprx_fill.o sprx_edge.o sprx_source.o \
                                       sprx_transform.o sprx_rowstate.o \
                                       sprx_sprtrans.o \
                                       hosthandoff.o \
                                       sprx_clip.o sprx_where.o sprx_vars.o \
                                       sprx_flags.o sprx_format.o \
                                       sprx_workspace.o sprx_kernel.o \
                                       sprx_ttrprep.o sprx_palette.o \
                                       sprx_sprite.o sprx_blitter.o \
                                       sprx_yloop.o sprx_xloop.o \
                                       sprx_fetch.o sprx_save.o sprx_plot.o \
                                       sprx_translate.o sprx_blend.o \
                                       sprx_ttr.o sprx_dither.o \
                                       sprx_convert.o sprx_pixel.o \
                                       sprx_compile.o hostmodes.o \
                                       hostsprx.o hostjpeg.o check.o) $(SPRX_JOIN_JPEG_OBJS)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# A mode turned into a pixel format. Depth alone is not enough: two formats
# share a depth, and the wrong one only gives wrong colours.
$(BUILD)/tests/sprxformat-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                    format.o sprx_format.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The workspace block the compiled code reads, and the offsets naming it.
# Checked by counting, so a field added to the block and forgotten fails.
$(BUILD)/tests/sprxworkspace-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                       workspace.o sprx_workspace.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Decoding the caller's flag word: the two halves of R5, the eight-bit
# translucency, the reserved bits, and the chain that decides an alpha blend.
$(BUILD)/tests/sprxflags-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                   flags.o sprx_flags.o hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The screen, the sprite's mode, and the caller's coordinates converted to the
# module's working units.
$(BUILD)/tests/sprxvars-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                  vars.o sprx_vars.o hostmodes.o hostsprx.o \
                                  check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Where in memory a plot starts: the sprite's image and mask, and the
# screen.
$(BUILD)/tests/sprxwhere-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                   where.o sprx_where.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The register banks a plot saves between its four nested blocks: registers
# filled, switched, clobbered and checked.
$(BUILD)/tests/sprxbank-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                  bank.o sprx_compile.o armrun.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The ordered dither, two bits finer than the screen, compiled and run over
# every format at every brightness.
$(BUILD)/tests/sprxdither-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                    dither.o sprx_dither.o sprx_convert.o \
                                    sprx_pixel.o sprx_compile.o armrun.o \
                                    check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Checks the pixel format table against the reference assembly it was
# transcribed from (`Sources/PutScaled`), which cannot be linked.
pixelformatcheck: tools/check/pixelformats.py \
                  Sources/Video/Render/SprExtend/c/sprx_pixel.c
	@python3 tools/check/pixelformats.py \
	  Sources/Video/Render/SprExtend/c/sprx_pixel.c $(SPRX_REF)/Sources/PutScaled
.PHONY: pixelformatcheck

# Runs sequences of generated code. `asmencodecheck` checks each instruction's
# encoding; this checks what a sequence computes.
$(BUILD)/tests/sprxarmrun-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                    arm.o armrun.o sprx_compile.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Every encoding checked against what the tree's own assembler makes of the
# same instruction. The checker states the intent in ARM assembly, not as
# numbers.
asmencodecheck: $(BUILD)/tests/sprxemit-checks tools/check/asmencode.py
	@python3 tools/check/asmencode.py $(BUILD)/tests/sprxemit-checks \
	  $(CC) $(MOD_CFLAGS)
.PHONY: asmencodecheck

# The diffusion: pure arithmetic over pixel rows, so built with the plain test
# flags rather than the shadowed ones.
$(BUILD)/tests/sprxdiffuse-checks: $(addprefix $(SPRX_TEST_GEN)/,\
                                     diffuse.o sprx_diffuse.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/sprxjpegmerge-checks: $(addprefix $(SPRX_TEST_GEN)/shadow/,\
                                       jpegmerge.o sprx_jpegmerge.o \
                                       sprx_yuvtabs.o jdcolor.o jutils.o \
                                       sprx_panic.o sprx_jpegpanic.o \
                                       growarea.o hostsprx.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# `jpegclosure` (below) checks every symbol the IJG files want against what
# may supply one: the ROM link drops unreached code, so a missing symbol would
# otherwise go unnoticed until a decode reaches it.
# `jpegsizes`: the same file compiled with and without JPEG_INTERNALS, and the
# struct sizes the module's two halves share compared through the symbol
# tables.
$(BUILD)/modules/spriteextend/headercheckplain.o: \
    Sources/Video/Render/SprExtend/jpeg/headercheck.c \
    $(wildcard Sources/Video/Render/SprExtend/jpeg/*.h) \
    $(wildcard Sources/Video/Render/SprExtend/c/*.h) Makefile
	@mkdir -p $(dir $@)
	@$(CC) $(SPRX_CFLAGS) -Wall -Wextra -Werror -DSPRX_NO_INTERNALS \
	  -c $< -o $@

jpegsizes: $(BUILD)/modules/spriteextend/headercheck.o \
           $(BUILD)/modules/spriteextend/headercheckplain.o
	@a=$$(llvm-nm --print-size $(BUILD)/modules/spriteextend/headercheck.o \
	       | awk '/sprx_size_probe/ {print $$1" "$$2}' | sort); \
	 b=$$(llvm-nm --print-size $(BUILD)/modules/spriteextend/headercheckplain.o \
	       | awk '/sprx_size_probe/ {print $$1" "$$2}' | sort); \
	 if [ "$$a" != "$$b" ]; then \
	   echo "jpegsizes: JPEG_INTERNALS changes a struct the module shares"; \
	   echo "  with:    $$a"; echo "  without: $$b"; exit 1; \
	 fi; \
	 echo "jpegsizes: JPEG_INTERNALS moves nothing the module's two halves share"
.PHONY: jpegsizes

# The library's objects plus the two module files the library calls into: the
# memory manager (`jpeg_memory_mgr`) and the merged upsampler
# (`jinit_merged_upsampler`, named by `jdmaster.c`). Both must be present for
# the closure check to link the library at all.
SPRX_JPEG_CLOSURE := $(SPRX_JPEG_OBJS) \
                     $(BUILD)/modules/spriteextend/sprx_jpegmem.o \
                     $(BUILD)/modules/spriteextend/sprx_jpegmerge.o \
                     $(BUILD)/modules/spriteextend/sprx_yuvtabs.o

jpegclosure: $(SPRX_JPEG_CLOSURE) tools/check/jpegclosure.py
	@python3 tools/check/jpegclosure.py $(LD) $(SPRX_JPEG_CLOSURE)
.PHONY: jpegclosure

sprxmergedcheck:
	@python3 tools/check/sprxmerged.py
.PHONY: sprxmergedcheck

sprxchecks: sprxmergedcheck $(BUILD)/modules/spriteextend/headercheck.o \
            $(BUILD)/tests/sprxpanic-checks \
            $(BUILD)/tests/sprxjpegmem-checks \
            $(BUILD)/tests/sprxjpeginfo-checks \
            $(BUILD)/tests/sprxjpegfile-checks \
            $(BUILD)/tests/sprxjpegmerge-checks \
            $(BUILD)/tests/sprxdiffuse-checks \
            $(BUILD)/tests/sprxdecode-checks \
            $(BUILD)/tests/sprxjwrite-checks \
            $(BUILD)/tests/sprxjpegbegin-checks \
            $(BUILD)/tests/sprxjpegfetch-checks \
            $(BUILD)/tests/sprxjpegsprite-checks \
            $(BUILD)/tests/sprxjpegplotswi-checks \
            $(BUILD)/tests/sprxemit-checks \
            $(BUILD)/tests/sprxarmrun-checks \
            $(BUILD)/tests/sprxpixel-checks \
            $(BUILD)/tests/sprxconvert-checks \
            $(BUILD)/tests/sprxdither-checks \
            $(BUILD)/tests/sprxbank-checks \
            $(BUILD)/tests/sprxttr-checks \
            $(BUILD)/tests/sprxblend-checks \
            $(BUILD)/tests/sprxtranslate-checks \
            $(BUILD)/tests/sprxplot-checks \
            $(BUILD)/tests/sprxfetch-checks \
            $(BUILD)/tests/sprxsave-checks \
            $(BUILD)/tests/sprxblockmove-checks \
            $(BUILD)/tests/sprxtransformrow-checks \
            $(BUILD)/tests/sprxxloop-checks \
            $(BUILD)/tests/sprxyloop-checks \
            $(BUILD)/tests/sprxblitter-checks \
            $(BUILD)/tests/sprxjpegplot-checks \
            $(BUILD)/tests/sprxsprtrans-checks \
            $(BUILD)/tests/sprxsprite-checks \
            $(BUILD)/tests/sprxsprrows-checks \
            $(BUILD)/tests/sprxsprcols-checks \
            $(BUILD)/tests/sprxpalette-checks \
            $(BUILD)/tests/sprxttrprep-checks \
            $(BUILD)/tests/sprxkernel-checks \
            $(BUILD)/tests/sprxclip-checks \
            $(BUILD)/tests/sprxwhere-checks \
            $(BUILD)/tests/sprxvars-checks \
            $(BUILD)/tests/sprxflags-checks \
            $(BUILD)/tests/sprxworkspace-checks \
            $(BUILD)/tests/sprxformat-checks \
            $(BUILD)/tests/sprxputscaled-checks \
            $(BUILD)/tests/sprxrows-checks \
            $(BUILD)/tests/sprxadjust-checks \
            $(BUILD)/tests/sprxcheckarea-checks \
            $(BUILD)/tests/sprxsource-checks \
            $(BUILD)/tests/sprxrowstate-checks \
            $(BUILD)/tests/sprxtransform-checks \
            $(BUILD)/tests/sprxtile-checks \
            $(BUILD)/tests/sprxsprpal-checks \
            $(BUILD)/tests/sprxtilereason-checks \
            $(BUILD)/tests/sprxcharsprite-checks \
            $(BUILD)/tests/sprxcharreason-checks \
            $(BUILD)/tests/sprxalpha-checks \
            $(BUILD)/tests/sprxalphasize-checks \
            $(BUILD)/tests/sprxalphaconv-checks \
            $(BUILD)/tests/sprxcreatealpha-checks \
            $(BUILD)/tests/sprxpointer-checks \
            $(BUILD)/tests/sprxsetpointer-checks \
            $(BUILD)/tests/sprxappendreason-checks \
            $(BUILD)/tests/sprxtilefast-checks \
            $(BUILD)/tests/sprxappend-checks \
            $(BUILD)/tests/sprxedge-checks \
            $(BUILD)/tests/sprxfill-checks \
            $(BUILD)/tests/sprxputtransformed-checks \
            $(BUILD)/tests/sprxmachine-checks \
            $(BUILD)/tests/sprxchangedbox-checks
	@$(BUILD)/tests/sprxpanic-checks
	@$(BUILD)/tests/sprxjpegmem-checks
	@$(BUILD)/tests/sprxjpeginfo-checks
	@$(BUILD)/tests/sprxjpegfile-checks
	@$(BUILD)/tests/sprxjpegmerge-checks
	@$(BUILD)/tests/sprxdiffuse-checks
	@$(BUILD)/tests/sprxdecode-checks
	@$(BUILD)/tests/sprxjwrite-checks
	@$(BUILD)/tests/sprxjpegbegin-checks
	@$(BUILD)/tests/sprxjpegplotswi-checks
	@$(BUILD)/tests/sprxjpegfetch-checks
	@$(BUILD)/tests/sprxjpegsprite-checks
	@$(BUILD)/tests/sprxemit-checks > /dev/null
	@$(BUILD)/tests/sprxarmrun-checks
	@$(BUILD)/tests/sprxpixel-checks
	@$(BUILD)/tests/sprxconvert-checks
	@$(BUILD)/tests/sprxdither-checks
	@$(BUILD)/tests/sprxbank-checks
	@$(BUILD)/tests/sprxttr-checks
	@$(BUILD)/tests/sprxblend-checks
	@$(BUILD)/tests/sprxtranslate-checks
	@$(BUILD)/tests/sprxplot-checks
	@$(BUILD)/tests/sprxfetch-checks
	@$(BUILD)/tests/sprxsave-checks
	@$(BUILD)/tests/sprxblockmove-checks
	@$(BUILD)/tests/sprxtransformrow-checks
	@$(BUILD)/tests/sprxxloop-checks
	@$(BUILD)/tests/sprxyloop-checks
	@$(BUILD)/tests/sprxblitter-checks
	@$(BUILD)/tests/sprxjpegplot-checks
	@$(BUILD)/tests/sprxsprtrans-checks
	@$(BUILD)/tests/sprxsprite-checks
	@$(BUILD)/tests/sprxsprrows-checks
	@$(BUILD)/tests/sprxsprcols-checks
	@$(BUILD)/tests/sprxpalette-checks
	@$(BUILD)/tests/sprxttrprep-checks
	@$(BUILD)/tests/sprxkernel-checks
	@$(BUILD)/tests/sprxwhere-checks
	@$(BUILD)/tests/sprxvars-checks
	@$(BUILD)/tests/sprxflags-checks
	@$(BUILD)/tests/sprxworkspace-checks
	@$(BUILD)/tests/sprxformat-checks
	@$(BUILD)/tests/sprxmachine-checks
	@$(BUILD)/tests/sprxchangedbox-checks
	@$(BUILD)/tests/sprxputscaled-checks
	@$(BUILD)/tests/sprxrows-checks
	@$(BUILD)/tests/sprxadjust-checks
	@$(BUILD)/tests/sprxcheckarea-checks
	@$(BUILD)/tests/sprxsource-checks
	@$(BUILD)/tests/sprxrowstate-checks
	@$(BUILD)/tests/sprxtransform-checks
	@$(BUILD)/tests/sprxtile-checks
	@$(BUILD)/tests/sprxsprpal-checks
	@$(BUILD)/tests/sprxtilereason-checks
	@$(BUILD)/tests/sprxcharsprite-checks
	@$(BUILD)/tests/sprxcharreason-checks
	@$(BUILD)/tests/sprxalpha-checks
	@$(BUILD)/tests/sprxalphasize-checks
	@$(BUILD)/tests/sprxalphaconv-checks
	@$(BUILD)/tests/sprxcreatealpha-checks
	@$(BUILD)/tests/sprxpointer-checks
	@$(BUILD)/tests/sprxsetpointer-checks
	@$(BUILD)/tests/sprxappendreason-checks
	@$(BUILD)/tests/sprxtilefast-checks
	@$(BUILD)/tests/sprxappend-checks
	@$(BUILD)/tests/sprxedge-checks
	@$(BUILD)/tests/sprxfill-checks
	@$(BUILD)/tests/sprxputtransformed-checks
	@$(BUILD)/tests/sprxclip-checks
	@$(MAKE) --no-print-directory sprxerrorcheck
	@$(MAKE) --no-print-directory asmencodecheck
	@$(MAKE) --no-print-directory pixelformatcheck
	@$(MAKE) --no-print-directory yuvtabscheck
	@$(MAKE) --no-print-directory jpegclosure
	@$(MAKE) --no-print-directory jpegsizes
.PHONY: sprxchecks

$(BUILD)/tests/modjob-checks: tests/modjob/checks.c include/riscos/modjob.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Iinclude $< -o $@

modjobchecks: $(BUILD)/tests/modjob-checks
	@$(BUILD)/tests/modjob-checks
.PHONY: modjobchecks

# PNGDecode's decoder on several cores against its own serial one: pictures
# of every colour type and filter pattern decoded with pthread runners,
# reverse pieces and fault replay, pixels equal.
# (Sources/Video/Render/PNGDecode/test/pngpar).
PNGPAR_DIR := Sources/Video/Render/PNGDecode
$(BUILD)/tests/pngpar-checks: $(PNGPAR_DIR)/test/pngpar/check.c \
    $(PNGPAR_DIR)/c/png.c $(PNGPAR_DIR)/c/inflate.c $(wildcard $(PNGPAR_DIR)/h/*.h)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(PNGPAR_DIR)/h -I$(PNGPAR_DIR)/c -pthread \
	  -o $@ $(PNGPAR_DIR)/test/pngpar/check.c $(PNGPAR_DIR)/c/png.c \
	  $(PNGPAR_DIR)/c/inflate.c

pngdecodechecks: $(BUILD)/tests/pngpar-checks
	@python3 $(PNGPAR_DIR)/test/pngpar/gen.py $(BUILD)/tests/pngpar-pictures
	@$(BUILD)/tests/pngpar-checks $(BUILD)/tests/pngpar-pictures
.PHONY: pngdecodechecks

# FontManager's C file readers, rasteriser and module boundary.
FM_OBJS := $(addprefix $(BUILD)/modules/fontmanager/,modhead.o fmfile.o \
  fmbitmap.o fmencoding.o fmalias.o fmraster.o fmhint.o fmcoverage.o drawedges.o drawraster.o drawfill.o drawflatten.o supersample.o fmstate.o fmutil.o fmpath.o \
  fmcache.o fmpaint.o fmblend.o fmlist.o fmmetrics.o fmmake.o fmmodule.o veneers.o)
FM_MOD := $(BUILD)/modules/FontManager.mod
FM_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Video/Render/Fonts/FontManager500/c \
             -I$(BUILD)/modules/fontmanager

$(BUILD)/modules/fontmanager/modhead.S: Sources/Video/Render/Fonts/FontManager500/cmhg/modhead Sources/Video/Render/Fonts/FontManager500/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Video/Render/Fonts/FontManager500 -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/fontmanager/%.o: Sources/Video/Render/Fonts/FontManager500/c/%.c Makefile \
                                  $(BUILD)/modules/fontmanager/fm_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FM_CFLAGS) -ISources/Video/Render/Draw/c -ISources/Video/Render/Super/c -c $< -o $@

# The SWI chunk and module header entries, generated with their C prototypes
# from one declaration. See tools/build/marshal.py.
$(BUILD)/modules/fontmanager/veneers.S: Sources/Video/Render/Fonts/FontManager500/s/boundaries \
                                        Sources/Video/Render/Fonts/FontManager500/cmhg/modhead \
                                        tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/Fonts/FontManager500 --asm $@ \
	  --header $(BUILD)/modules/fontmanager/fm_veneers.h

$(BUILD)/modules/fontmanager/fm_veneers.h: $(BUILD)/modules/fontmanager/veneers.S

$(BUILD)/modules/fontmanager/veneers.o: $(BUILD)/modules/fontmanager/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/fontmanager/modhead.o: $(BUILD)/modules/fontmanager/modhead.S
	$(CC) $(FM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(addprefix $(BUILD)/modules/fontmanager/,drawedges.o drawraster.o drawfill.o drawflatten.o): $(BUILD)/modules/fontmanager/%.o: Sources/Video/Render/Draw/c/%.c
	@mkdir -p $(dir $@)
	$(CC) $(FM_CFLAGS) -ISources/Video/Render/Draw/c -ISources/Video/Render/Super/c -c $< -o $@

$(BUILD)/modules/fontmanager/supersample.o: Sources/Video/Render/Super/c/supersample.c
	@mkdir -p $(dir $@)
	$(CC) $(FM_CFLAGS) -ISources/Video/Render/Draw/c -ISources/Video/Render/Super/c -c $< -o $@

$(BUILD)/modules/FontManager.elf: $(FM_OBJS) $(CLIB) mk/ld/module.ld tools/build/modulecheck.py
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld -o $@.tmp $(FM_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(FM_MOD): $(BUILD)/modules/FontManager.elf
	$(OBJCOPY) -O binary $< $@

$(BUILD)/tests/fontmanager-files: Sources/Video/Render/Fonts/FontManager500/c/fmfile.c Sources/Video/Render/Fonts/FontManager500/c/fmbitmap.c Sources/Video/Render/Fonts/FontManager500/c/fmencoding.c Sources/Video/Render/Fonts/FontManager500/c/fmalias.c Sources/Video/Render/Fonts/FontManager500/c/fmraster.c Sources/Video/Render/Fonts/FontManager500/c/fmhint.c Sources/Video/Render/Fonts/FontManager500/c/fmcoverage.c Sources/Video/Render/Draw/c/drawedges.c Sources/Video/Render/Draw/c/drawraster.c Sources/Video/Render/Draw/c/drawfill.c Sources/Video/Render/Draw/c/drawflatten.c Sources/Video/Render/Super/c/supersample.c Sources/Video/Render/Fonts/FontManager/test/fontmanager/filecheck.c Sources/Video/Render/Fonts/FontManager500/c/fmfile.h Sources/Video/Render/Fonts/FontManager500/c/fmbitmap.h Sources/Video/Render/Fonts/FontManager500/c/fmencoding.h Sources/Video/Render/Fonts/FontManager500/c/fmalias.h Sources/Video/Render/Fonts/FontManager500/c/fmraster.h
	@mkdir -p $(dir $@)
	$(HOSTCC) -std=c17 -Wall -Wextra -Werror -g -fsanitize=address,undefined -ISources/Video/Render/Fonts/FontManager500/c -ISources/Video/Render/Draw/c -ISources/Video/Render/Super/c $(filter %.c,$^) -o $@

$(BUILD)/tests/fontmanager-hints: Sources/Video/Render/Fonts/FontManager500/c/fmhint.c Sources/Video/Render/Fonts/FontManager/test/fontmanager/hintcheck.c Sources/Video/Render/Fonts/FontManager500/c/fmhint.h Sources/Video/Render/Fonts/FontManager500/c/fmraster.h Sources/Video/Render/Fonts/FontManager500/c/fmfile.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/Render/Fonts/FontManager500/c -ISources/Video/Render/Draw/c -ISources/Video/Render/Super/c $(filter %.c,$^) -o $@

# Font_ListFonts and Font_DecodeMenu, fmlist.c against a catalogue made by
# hand.
$(BUILD)/tests/fontmanager-list: Sources/Video/Render/Fonts/FontManager/test/fontmanager/listcheck.c Sources/Video/Render/Fonts/FontManager500/c/fmlist.c Sources/Video/Render/Fonts/FontManager500/c/fmutil.c Sources/Video/Render/Fonts/FontManager500/c/fmlist.h Sources/Video/Render/Fonts/FontManager500/c/fmstate.h tests/lib/hostlow.c tests/lib/hostlow.h $(TEST_LIB)/check.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/Render/Fonts/FontManager500/c -Itests/lib Sources/Video/Render/Fonts/FontManager/test/fontmanager/listcheck.c tests/lib/hostlow.c $(TEST_LIB)/check.c -o $@

fontmanagerchecks: $(BUILD)/tests/fontmanager-hints $(BUILD)/tests/fontmanager-files $(BUILD)/tests/fontmanager-list fontmanagerportchecks
	@$(BUILD)/tests/fontmanager-hints
	@$(BUILD)/tests/fontmanager-list
	python3 Sources/Video/Render/Fonts/FontManager/test/fontmanager/run-files.py $(BUILD)/tests/fontmanager-files Sources/Video/Render/Fonts/ROMFonts/Fonts

# The AArch64 ROM's FontManager (Sources/Video/Render/Fonts/FontManager): the port's
# own host checks of its BASIC floating point, its arithmetic and its cache
# allocator and compactor (Sources/Video/Render/Fonts/FontManager/test/fontmanager/port, stub_os.c standing in for
# the SWIs), with the sanitisers. The cache keeps 32-bit addresses, so the
# checks map their arena below 4GB.
FMP_DIR := Sources/Video/Render/Fonts/FontManager
FMP_CFLAGS := -std=gnu17 -g -O1 -fsanitize=address,undefined \
  -fno-sanitize-recover=all -Wall -Wno-unused-parameter \
  -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast -Wno-unused-variable \
  -Wno-unused-but-set-variable -Wno-unused-function \
  -ISources/Video/Render/Fonts/FontManager/test/fontmanager/port -I$(FMP_DIR)/h -I$(FMP_DIR)/compat -Iinclude
FMP_SRC_fp := $(FMP_DIR)/c/fmfp.c $(FMP_DIR)/c/fmerrors.c
FMP_SRC_arith := $(FMP_DIR)/c/fmfp.c $(FMP_DIR)/c/fmarith.c $(FMP_DIR)/c/fmerrors.c
FMP_SRC_cache := $(FMP_DIR)/c/fmcache.c $(FMP_DIR)/c/fmerrors.c
FMP_TESTS := fp arith cache
$(FMP_TESTS:%=$(BUILD)/tests/fontmanager-port-%): $(BUILD)/tests/fontmanager-port-%: \
    Sources/Video/Render/Fonts/FontManager/test/fontmanager/port/t_%.c Sources/Video/Render/Fonts/FontManager/test/fontmanager/port/stub_os.c \
    $(wildcard $(FMP_DIR)/h/*.h $(FMP_DIR)/compat/*.h) Makefile
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FMP_CFLAGS) -o $@ Sources/Video/Render/Fonts/FontManager/test/fontmanager/port/t_$*.c \
	  Sources/Video/Render/Fonts/FontManager/test/fontmanager/port/stub_os.c $(FMP_SRC_$*) -lm
fontmanagerportchecks: $(FMP_TESTS:%=$(BUILD)/tests/fontmanager-port-%)
	@for t in $(FMP_TESTS); do ASAN_OPTIONS=detect_leaks=0 \
	  $(BUILD)/tests/fontmanager-port-$$t || exit 1; done
.PHONY: fontmanagerportchecks

# Sources/Lib/Unicode: UTF-8, properties, grapheme clusters, case and normalisation
# over tables generated from the Unicode Character Database. Host checks with
# the sanitisers: the samples, then every line of the UCD's own
# GraphemeBreakTest.txt and NormalizationTest.txt (taken from the archive into
# $(BUILD)/tests/unicode), and the tables regenerated and compared byte for
# byte with the committed ones.
UNI_SRC := Sources/Lib/Unicode/unicode.c Sources/Lib/Unicode/ungrapheme.c Sources/Lib/Unicode/unicase.c \
           Sources/Lib/Unicode/unidata_generated.c
UNI_UCD := tools/unicode/ucd-18.0.0.tar.xz

$(BUILD)/tests/unicode/checks: Sources/Internat/UnicodeData/test/unicode/checks.c $(UNI_SRC) \
    Sources/Lib/Unicode/unicode.h Sources/Lib/Unicode/unidata_generated.h \
    tests/lib/check.c tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) -std=gnu17 -Wall -Wextra -Werror -g -fsanitize=address,undefined \
	  -fno-sanitize-recover=all -Itests/lib -ISources/Lib/Unicode $(filter %.c,$^) -o $@

unicodechecks: $(BUILD)/tests/unicode/checks Sources/Internat/UnicodeData/test/unicode/run-files.py \
    tools/build/unigen.py $(UNI_UCD)
	$(BUILD)/tests/unicode/checks
	python3 Sources/Internat/UnicodeData/test/unicode/run-files.py $(BUILD)/tests/unicode/checks $(UNI_UCD) \
	  $(BUILD)/tests/unicode
	@rm -rf $(BUILD)/tests/unicode/regen
	python3 tools/build/unigen.py --quiet --out $(BUILD)/tests/unicode/regen
	cmp Sources/Lib/Unicode/unidata_generated.c $(BUILD)/tests/unicode/regen/unidata_generated.c
	cmp Sources/Lib/Unicode/unidata_generated.h $(BUILD)/tests/unicode/regen/unidata_generated.h
.PHONY: unicodechecks

fontmanagermodule: $(FM_MOD)
.PHONY: fontmanagermodule fontmanagerchecks
-include $(FM_OBJS:.o=.d)

# The floating point emulator. With no FPA hardware, an FPA instruction is an
# undefined instruction; this module emulates it. The Wimp checks for the
# module by name before saving a task's floating point registers.
#
# It is entered through OS_ClaimProcessorVector, not a SWI, so its main entry
# is `fpe_handler_entry` in `s/aarch32/vector.S`, not in the module header.
# The AArch64 ROM's module is mk/a64/filing.mk's.
FPE_OBJS := $(addprefix $(BUILD)/modules/fpemulator/,modhead.o fpnum.o \
            fpround.o fpnan.o fparith.o fptrans.o fpconv.o fppacked.o \
            fpexec.o \
            fpcontext.o fpswi.o fpmodule.o fparm.o veneers.o vector.o)
FPE_MOD  := $(BUILD)/modules/FPEmulator.mod
FPE_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Programmer/FPEmulator/c \
              -I$(BUILD)/modules/fpemulator

$(BUILD)/modules/fpemulator/modhead.S: Sources/Programmer/FPEmulator/cmhg/modhead \
                                       Sources/Programmer/FPEmulator/VersionNum \
                                       tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Programmer/FPEmulator -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/fpemulator/%.o: Sources/Programmer/FPEmulator/c/%.c Makefile \
                                 $(BUILD)/modules/fpemulator/fpe_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FPE_CFLAGS) -c $< -o $@

$(BUILD)/modules/fpemulator/%.o: Sources/Programmer/FPEmulator/c/arch/aarch32/%.c \
                                 Makefile $(BUILD)/modules/fpemulator/fpe_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FPE_CFLAGS) -c $< -o $@

# The four module entries, generated with their C prototypes from one
# declaration. See tools/build/marshal.py. The rest of s/ is the processor
# vector and four routines C cannot express; they have no declaration.
$(BUILD)/modules/fpemulator/veneers.S: Sources/Programmer/FPEmulator/s/boundaries \
                                       Sources/Programmer/FPEmulator/cmhg/modhead \
                                       tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Programmer/FPEmulator --asm $@ \
	  --header $(BUILD)/modules/fpemulator/fpe_veneers.h

$(BUILD)/modules/fpemulator/fpe_veneers.h: \
	$(BUILD)/modules/fpemulator/veneers.S

$(BUILD)/modules/fpemulator/veneers.o: $(BUILD)/modules/fpemulator/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FPE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/fpemulator/vector.o: Sources/Programmer/FPEmulator/s/aarch32/vector.S
	@mkdir -p $(dir $@)
	$(CC) $(FPE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/fpemulator/modhead.o: $(BUILD)/modules/fpemulator/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(FPE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/FPEmulator.elf: $(FPE_OBJS) $(CLIB) mk/ld/module.ld \
                                 tools/build/modulecheck.py
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(FPE_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(FPE_MOD): $(BUILD)/modules/FPEmulator.elf
	$(OBJCOPY) -O binary $< $@

fpemulatormodule: $(FPE_MOD)
.PHONY: fpemulatormodule
-include $(FPE_OBJS:.o=.d)

# ColourTrans: all thirty-six SWIs of the chunk, the three *commands and the
# five services. The arithmetic is in Sources/Video/Render/Colours/c, with host
# checks under Sources/Video/Render/Colours/test/colourtrans; c/ctmodule.c joins it to the mode variables,
# the palette, the RMA and the two vectors.
#
# Entries are generated from s/boundaries except the hand-over of a whole
# register set to ColourV, which is hand-written; s/swi.S says why.
CTM_OBJS := $(BUILD)/modules/colourtrans/modhead.o \
            $(BUILD)/modules/colourtrans/ct32k.o \
            $(BUILD)/modules/colourtrans/ctbuild.o \
            $(BUILD)/modules/colourtrans/ctcache.o \
            $(BUILD)/modules/colourtrans/ctcie.o \
            $(BUILD)/modules/colourtrans/ctdevice.o \
            $(BUILD)/modules/colourtrans/ctfont.o \
            $(BUILD)/modules/colourtrans/ctfont8.o \
            $(BUILD)/modules/colourtrans/ctfilecal.o \
            $(BUILD)/modules/colourtrans/ctgcol.o \
            $(BUILD)/modules/colourtrans/ctdeep.o \
            $(BUILD)/modules/colourtrans/ctdither.o \
            $(BUILD)/modules/colourtrans/ctdither16.o \
            $(BUILD)/modules/colourtrans/ctfast.o \
            $(BUILD)/modules/colourtrans/ctmatch.o \
            $(BUILD)/modules/colourtrans/ctmodel.o \
            $(BUILD)/modules/colourtrans/ctmodule.o \
            $(BUILD)/modules/colourtrans/ctpalette.o \
            $(BUILD)/modules/colourtrans/ctpattern.o \
            $(BUILD)/modules/colourtrans/ctselect.o \
            $(BUILD)/modules/colourtrans/ctwrite.o \
            $(BUILD)/modules/colourtrans/cttable.o \
            $(BUILD)/modules/colourtrans/veneers.o \
            $(BUILD)/modules/colourtrans/swi.o
CTM_MOD  := $(BUILD)/modules/ColourTrans.mod
CTM_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Video/Render/Colours/c \
              -I$(BUILD)/modules/colourtrans

$(BUILD)/modules/colourtrans/modhead.S: Sources/Video/Render/Colours/cmhg/modhead \
                                        Sources/Video/Render/Colours/VersionNum \
                                        tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Video/Render/Colours -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/colourtrans/%.o: Sources/Video/Render/Colours/c/%.c Makefile \
                                  $(BUILD)/modules/colourtrans/ct_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(CTM_CFLAGS) -c $< -o $@

# The module entries, generated with their C prototypes from one declaration.
# See tools/build/marshal.py.
$(BUILD)/modules/colourtrans/veneers.S: Sources/Video/Render/Colours/s/boundaries \
                                        Sources/Video/Render/Colours/cmhg/modhead \
                                        tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/Colours --asm $@ \
	  --header $(BUILD)/modules/colourtrans/ct_veneers.h

$(BUILD)/modules/colourtrans/ct_veneers.h: \
		$(BUILD)/modules/colourtrans/veneers.S

$(BUILD)/modules/colourtrans/veneers.o: \
		$(BUILD)/modules/colourtrans/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(CTM_CFLAGS) -x assembler-with-cpp -c $< -o $@

# The one undeclared entry: the hand-over of a whole register set to ColourV,
# whose registers mean whatever the carried SWI says.
$(BUILD)/modules/colourtrans/swi.o: Sources/Video/Render/Colours/s/aarch32/swi.S
	@mkdir -p $(dir $@)
	$(CC) $(CTM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/colourtrans/modhead.o: \
		$(BUILD)/modules/colourtrans/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(CTM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/ColourTrans.elf: $(CTM_OBJS) $(CLIB) mk/ld/module.ld \
                                  tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(CTM_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(CTM_MOD): $(BUILD)/modules/ColourTrans.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ColourTrans)"

-include $(CTM_OBJS:.o=.d)

colourtransmodule: $(CTM_MOD)
.PHONY: colourtransmodule

# MimeMap: MIME type to file type and back, from a text file that may be
# absent. c/mimetable.c reads the file and searches it, issues no SWI and is
# checked on the build machine; c/mimemodule.c finds the file, claims the two
# blocks and turns a type column into a file type.
MM_OBJS := $(BUILD)/modules/mimemap/modhead.o \
           $(BUILD)/modules/mimemap/mimetable.o \
           $(BUILD)/modules/mimemap/mimemodule.o \
           $(BUILD)/modules/mimemap/veneers.o
MM_MOD  := $(BUILD)/modules/MimeMap.mod
MM_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Networking/MimeMap/c \
             -I$(BUILD)/modules/mimemap

$(BUILD)/modules/mimemap/modhead.S: Sources/Networking/MimeMap/cmhg/modhead \
                                    Sources/Networking/MimeMap/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Networking/MimeMap -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/mimemap/%.o: Sources/Networking/MimeMap/c/%.c Makefile \
                              $(BUILD)/modules/mimemap/mime_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(MM_CFLAGS) -c $< -o $@

# The veneers, generated with their C prototypes from one declaration. See
# tools/build/marshal.py.
$(BUILD)/modules/mimemap/veneers.S: Sources/Networking/MimeMap/s/boundaries \
                                    Sources/Networking/MimeMap/cmhg/modhead \
                                    tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Networking/MimeMap --asm $@ \
	  --header $(BUILD)/modules/mimemap/mime_veneers.h

$(BUILD)/modules/mimemap/mime_veneers.h: $(BUILD)/modules/mimemap/veneers.S

$(BUILD)/modules/mimemap/veneers.o: $(BUILD)/modules/mimemap/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(MM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/mimemap/modhead.o: $(BUILD)/modules/mimemap/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(MM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/MimeMap.elf: $(MM_OBJS) $(CLIB) mk/ld/module.ld \
                              tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(MM_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(MM_MOD): $(BUILD)/modules/MimeMap.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, MimeMap)"

-include $(MM_OBJS:.o=.d)

mimemapmodule: $(MM_MOD)
.PHONY: mimemapmodule

# MbufManager: the packet memory pool.
#
# c/mbufpool.c is the pool and its twenty-two operations and issues no SWI, so
# `make mbufmanagerchecks` runs it on the build machine at -m32, since the
# published layout depends on pointer size.
#
# s/aarch32/usrentry.S is the entry table for user-mode clients; it and s/boundaries
# say why it is hand-written and why the other table has no veneers.
MBUF_OBJS := $(BUILD)/modules/mbufmanager/modhead.o \
             $(BUILD)/modules/mbufmanager/mbufpool.o \
             $(BUILD)/modules/mbufmanager/mbufmodule.o \
             $(BUILD)/modules/mbufmanager/usrentry.o \
             $(BUILD)/modules/mbufmanager/veneers.o
MBUF_MOD  := $(BUILD)/modules/MbufManager.mod
MBUF_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Networking/MManager/c \
               -I$(BUILD)/modules/mbufmanager

$(BUILD)/modules/mbufmanager/modhead.S: Sources/Networking/MManager/cmhg/modhead \
                                        Sources/Networking/MManager/VersionNum \
                                        tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Networking/MManager -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/mbufmanager/%.o: Sources/Networking/MManager/c/%.c Makefile \
                                  $(BUILD)/modules/mbufmanager/mbufmanager_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(MBUF_CFLAGS) -c $< -o $@

$(BUILD)/modules/mbufmanager/veneers.S: Sources/Networking/MManager/s/boundaries \
                                        Sources/Networking/MManager/cmhg/modhead \
                                        tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Networking/MManager --asm $@ \
	  --header $(BUILD)/modules/mbufmanager/mbufmanager_veneers.h

$(BUILD)/modules/mbufmanager/mbufmanager_veneers.h: \
    $(BUILD)/modules/mbufmanager/veneers.S

$(BUILD)/modules/mbufmanager/veneers.o: $(BUILD)/modules/mbufmanager/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(MBUF_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/mbufmanager/usrentry.o: Sources/Networking/MManager/s/aarch32/usrentry.S
	@mkdir -p $(dir $@)
	$(CC) $(MBUF_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/mbufmanager/modhead.o: $(BUILD)/modules/mbufmanager/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(MBUF_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/MbufManager.elf: $(MBUF_OBJS) $(CLIB) mk/ld/module.ld \
                                  tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(MBUF_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(MBUF_MOD): $(BUILD)/modules/MbufManager.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, MbufManager)"

-include $(MBUF_OBJS:.o=.d)

mbufmanagermodule: $(MBUF_MOD)
.PHONY: mbufmanagermodule

# DHCP client.
#
# c/dhcppkt.c is the protocol (options, votes, lease dates) and c/dhcpif.c is
# the RFC 2131 state machine. Neither issues a SWI, so `make dhcpchecks` runs
# both against the scripted server in Sources/Networking/Inet/DHCP/test/dhcp. c/dhcpmodule.c is the
# machine half: SWIs, commands, the socket, and the two ioctls answered by
# Sources/Networking/Inet/Internet/c/bootp.c.
DHCP_OBJS := $(BUILD)/modules/dhcp/modhead.o \
             $(BUILD)/modules/dhcp/dhcppkt.o \
             $(BUILD)/modules/dhcp/dhcpif.o \
             $(BUILD)/modules/dhcp/dhcpmodule.o \
             $(BUILD)/modules/dhcp/veneers.o
DHCP_MOD  := $(BUILD)/modules/DHCP.mod
DHCP_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Networking/Inet/DHCP/c \
               -I$(BUILD)/modules/dhcp

$(BUILD)/modules/dhcp/modhead.S: Sources/Networking/Inet/DHCP/cmhg/modhead \
                                 Sources/Networking/Inet/DHCP/VersionNum \
                                 tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Networking/Inet/DHCP -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/dhcp/%.o: Sources/Networking/Inet/DHCP/c/%.c Makefile \
                           $(BUILD)/modules/dhcp/dhcp_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(DHCP_CFLAGS) -c $< -o $@

$(BUILD)/modules/dhcp/veneers.S: Sources/Networking/Inet/DHCP/s/boundaries \
                                 Sources/Networking/Inet/DHCP/cmhg/modhead \
                                 tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Networking/Inet/DHCP --asm $@ \
	  --header $(BUILD)/modules/dhcp/dhcp_veneers.h

$(BUILD)/modules/dhcp/dhcp_veneers.h: $(BUILD)/modules/dhcp/veneers.S

$(BUILD)/modules/dhcp/veneers.o: $(BUILD)/modules/dhcp/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(DHCP_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/dhcp/modhead.o: $(BUILD)/modules/dhcp/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(DHCP_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/DHCP.elf: $(DHCP_OBJS) $(CLIB) mk/ld/module.ld \
                           tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(DHCP_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(DHCP_MOD): $(BUILD)/modules/DHCP.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, DHCP)"

-include $(DHCP_OBJS:.o=.d)

dhcpmodule: $(DHCP_MOD)
.PHONY: dhcpmodule

# Net: the Econet programmer's interface over UDP.
#
# c/netcalc.c holds all the module's decisions (address map, control blocks
# and frame matching, retry schedules, port allocator, station-number parser,
# errors) and issues no SWI, so `make netchecks` runs it on the build machine.
# The other files are the module, the SWI chunk and the sockets.
#
# -ISources/Networking/Econet/Net is for `VersionNum`, read for the module version and
# Econet_Version.
NET_OBJS := $(BUILD)/modules/net/modhead.o \
            $(BUILD)/modules/net/netcalc.o \
            $(BUILD)/modules/net/netmod.o \
            $(BUILD)/modules/net/netswi.o \
            $(BUILD)/modules/net/netio.o \
            $(BUILD)/modules/net/veneers.o
NET_MOD  := $(BUILD)/modules/Net.mod
NET_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Networking/Econet/Net/c -ISources/Networking/Econet/Net \
              -I$(BUILD)/modules/net

$(BUILD)/modules/net/modhead.S: Sources/Networking/Econet/Net/cmhg/modhead \
                                Sources/Networking/Econet/Net/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Networking/Econet/Net -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/net/%.o: Sources/Networking/Econet/Net/c/%.c Makefile \
                          $(BUILD)/modules/net/net_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(NET_CFLAGS) -c $< -o $@

$(BUILD)/modules/net/veneers.S: Sources/Networking/Econet/Net/s/boundaries \
                                Sources/Networking/Econet/Net/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Networking/Econet/Net --asm $@ \
	  --header $(BUILD)/modules/net/net_veneers.h

$(BUILD)/modules/net/net_veneers.h: $(BUILD)/modules/net/veneers.S

$(BUILD)/modules/net/veneers.o: $(BUILD)/modules/net/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(NET_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/net/modhead.o: $(BUILD)/modules/net/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(NET_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/Net.elf: $(NET_OBJS) $(CLIB_NOFLOAT) $(CLIB) mk/ld/module.ld \
                          tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(NET_OBJS) $(CLIB_NOFLOAT) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(NET_MOD): $(BUILD)/modules/Net.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Net)"

-include $(NET_OBJS:.o=.d)

netmodule: $(NET_MOD)
.PHONY: netmodule

# NetFS: the Econet filing system over the Econet SWIs.
#
# c/netfscalc.c (date and access formats, unique identifier, server name
# rules, which directory handle a path uses) issues no SWI, so
# `make netfschecks` runs it on the build machine. The other four files are
# the server transaction and contexts, FileSwitch entries, commands and
# module.
#
# The two broadcast-reply waits use riscos/callbacks.h.
NETFS_OBJS := $(BUILD)/modules/netfs/modhead.o \
              $(BUILD)/modules/netfs/netfscalc.o \
              $(BUILD)/modules/netfs/netfsop.o \
              $(BUILD)/modules/netfs/netfsfile.o \
              $(BUILD)/modules/netfs/netfscmd.o \
              $(BUILD)/modules/netfs/netfsmod.o \
              $(BUILD)/modules/netfs/veneers.o
NETFS_MOD  := $(BUILD)/modules/NetFS.mod
NETFS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Networking/Econet/NetFS/c \
                -I$(BUILD)/modules/netfs

$(BUILD)/modules/netfs/modhead.S: Sources/Networking/Econet/NetFS/cmhg/modhead \
                                  Sources/Networking/Econet/NetFS/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Networking/Econet/NetFS -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/netfs/%.o: Sources/Networking/Econet/NetFS/c/%.c Makefile \
                            $(BUILD)/modules/netfs/netfs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(NETFS_CFLAGS) -c $< -o $@

$(BUILD)/modules/netfs/veneers.S: Sources/Networking/Econet/NetFS/s/boundaries \
                                  Sources/Networking/Econet/NetFS/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Networking/Econet/NetFS --asm $@ \
	  --header $(BUILD)/modules/netfs/netfs_veneers.h

$(BUILD)/modules/netfs/netfs_veneers.h: $(BUILD)/modules/netfs/veneers.S

$(BUILD)/modules/netfs/veneers.o: $(BUILD)/modules/netfs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(NETFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/netfs/modhead.o: $(BUILD)/modules/netfs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(NETFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/NetFS.elf: $(NETFS_OBJS) $(CLIB) mk/ld/module.ld \
                            tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(NETFS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(NETFS_MOD): $(BUILD)/modules/NetFS.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, NetFS)"

-include $(NETFS_OBJS:.o=.d)

netfsmodule: $(NETFS_MOD)
.PHONY: netfsmodule

# The reference NetFS and the port, each run once in RPCEmu with the same
# script against the AUN test peer's Econet file server, and their output and
# wire traffic compared. Runs are serial and each closes its emulator. Expected
# differences are in Sources/Networking/Econet/NetFS/test/netfs/machine/differs.
netfsmachinetest: $(NETFS_MOD) netmodule internetmodule
	python3 Sources/Networking/Econet/NetFS/test/netfs/machine/run.py reference
	python3 Sources/Networking/Econet/NetFS/test/netfs/machine/run.py ours
	python3 Sources/Networking/Econet/NetFS/test/netfs/machine/compare.py
.PHONY: netfsmachinetest

# NetPrint: the Econet printer server filing system. c/npcalc.c issues no
# SWIs and is checked on the build machine by `make netprintchecks`.
NETPRINT_DIR  := Sources/Networking/Econet/NetPrint
NETPRINT_OBJS := $(BUILD)/modules/netprint/modhead.o \
                 $(BUILD)/modules/netprint/npcalc.o \
                 $(BUILD)/modules/netprint/npnet.o \
                 $(BUILD)/modules/netprint/npfile.o \
                 $(BUILD)/modules/netprint/npmod.o \
                 $(BUILD)/modules/netprint/veneers.o
NETPRINT_MOD  := $(BUILD)/modules/NetPrint.mod
NETPRINT_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(NETPRINT_DIR)/c \
                   -I$(BUILD)/modules/netprint

$(BUILD)/modules/netprint/modhead.S: $(NETPRINT_DIR)/cmhg/modhead \
                                     $(NETPRINT_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(NETPRINT_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/netprint/%.o: $(NETPRINT_DIR)/c/%.c Makefile \
                               $(BUILD)/modules/netprint/netprint_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(NETPRINT_CFLAGS) -c $< -o $@

$(BUILD)/modules/netprint/veneers.S: $(NETPRINT_DIR)/s/boundaries \
                                     $(NETPRINT_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(NETPRINT_DIR) --asm $@ \
	  --header $(BUILD)/modules/netprint/netprint_veneers.h

$(BUILD)/modules/netprint/netprint_veneers.h: $(BUILD)/modules/netprint/veneers.S

$(BUILD)/modules/netprint/veneers.o: $(BUILD)/modules/netprint/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(NETPRINT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/netprint/modhead.o: $(BUILD)/modules/netprint/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(NETPRINT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/NetPrint.elf: $(NETPRINT_OBJS) $(CLIB) mk/ld/module.ld \
                               tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(NETPRINT_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(NETPRINT_MOD): $(BUILD)/modules/NetPrint.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, NetPrint)"

-include $(NETPRINT_OBJS:.o=.d)

netprintmodule: $(NETPRINT_MOD)
.PHONY: netprintmodule

# The reference NetPrint and the port, each run against the AUN test peer's
# printer server, and their output and wire traffic compared. Expected
# differences are in Sources/Networking/Econet/NetPrint/test/netprint/machine/differs.
netprintmachinetest: $(NETPRINT_MOD) netmodule internetmodule
	python3 Sources/Networking/Econet/NetPrint/test/netprint/machine/run.py reference
	python3 Sources/Networking/Econet/NetPrint/test/netprint/machine/run.py ours
	python3 Sources/Networking/Econet/NetPrint/test/netprint/machine/compare.py
.PHONY: netprintmachinetest

# DOSFS: an image filing system for DOS discs held in &FC8 files.
#
# c/dosvol.c and the files under it issue no SWI; they reach the image, clock,
# memory and file-type lookup through an operations table, so
# `make dosfschecks` runs them on the build machine. c/dosfsmodule.c supplies
# that table, the seven FileSwitch entries, the services, the two SWIs and
# *DOSMap.
#
# Not in the ROM yet: cmhg/modhead lists what is and is not answered.
DOSFS_DIR := Sources/FileSys/DOSFS
DOSFS_OBJS := $(BUILD)/modules/dosfs/modhead.o \
              $(BUILD)/modules/dosfs/dostime.o \
              $(BUILD)/modules/dosfs/dosname.o \
              $(BUILD)/modules/dosfs/dosfat.o \
              $(BUILD)/modules/dosfs/dosdir.o \
              $(BUILD)/modules/dosfs/dosformat.o \
              $(BUILD)/modules/dosfs/dosident.o \
              $(BUILD)/modules/dosfs/dosvol.o \
              $(BUILD)/modules/dosfs/dosfsmodule.o \
              $(BUILD)/modules/dosfs/veneers.o
DOSFS_MOD  := $(BUILD)/modules/DOSFS.mod
DOSFS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(DOSFS_DIR)/c \
                -I$(DOSFS_DIR) -I$(BUILD)/modules/dosfs

$(BUILD)/modules/dosfs/modhead.S: $(DOSFS_DIR)/cmhg/modhead \
                                  $(DOSFS_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(DOSFS_DIR) -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/dosfs/%.o: $(DOSFS_DIR)/c/%.c Makefile \
                            $(BUILD)/modules/dosfs/dosfs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(DOSFS_CFLAGS) -c $< -o $@

# The veneers, generated with their C prototypes from one declaration. See
# tools/build/marshal.py.
$(BUILD)/modules/dosfs/veneers.S: $(DOSFS_DIR)/s/boundaries \
                                  $(DOSFS_DIR)/cmhg/modhead \
                                  tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(DOSFS_DIR) --asm $@ \
	  --header $(BUILD)/modules/dosfs/dosfs_veneers.h

$(BUILD)/modules/dosfs/dosfs_veneers.h: $(BUILD)/modules/dosfs/veneers.S

$(BUILD)/modules/dosfs/veneers.o: $(BUILD)/modules/dosfs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(DOSFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/dosfs/modhead.o: $(BUILD)/modules/dosfs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(DOSFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/DOSFS.elf: $(DOSFS_OBJS) $(CLIB) mk/ld/module.ld \
                            tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(DOSFS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(DOSFS_MOD): $(BUILD)/modules/DOSFS.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, DOSFS)"

-include $(DOSFS_OBJS:.o=.d)

dosfsmodule: $(DOSFS_MOD)
.PHONY: dosfsmodule

# ScreenModes: monitor type 7, modes defined by a file, supplied through
# three service calls.
#
# c/modelist.c (the description and its arithmetic) and c/mdf.c (the reader)
# are checked on the build machine; c/screenmodes.c queries the display driver
# and the filing system. cmhg/modhead lists what is not yet ported.
SM_DIR  := Sources/Video/UserI/ScreenModes
SM_OBJS := $(BUILD)/modules/screenmodes/modhead.o \
           $(BUILD)/modules/screenmodes/modelist.o \
           $(BUILD)/modules/screenmodes/mdf.o \
           $(BUILD)/modules/screenmodes/edid.o \
           $(BUILD)/modules/screenmodes/edidtables.o \
           $(BUILD)/modules/screenmodes/screenmodes.o \
           $(BUILD)/modules/screenmodes/veneers.o
SM_MOD  := $(BUILD)/modules/ScreenModes.mod
SM_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(SM_DIR)/c -I$(SM_DIR) \
             -I$(BUILD)/modules/screenmodes

$(BUILD)/modules/screenmodes/modhead.S: $(SM_DIR)/cmhg/modhead \
                                        $(SM_DIR)/VersionNum \
                                        tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(SM_DIR) -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/screenmodes/%.o: $(SM_DIR)/c/%.c Makefile \
                                  $(BUILD)/modules/screenmodes/scrmodes_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SM_CFLAGS) -c $< -o $@

$(BUILD)/modules/screenmodes/veneers.S: $(SM_DIR)/s/boundaries \
                                        $(SM_DIR)/cmhg/modhead \
                                        tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(SM_DIR) --asm $@ \
	  --header $(BUILD)/modules/screenmodes/scrmodes_veneers.h

$(BUILD)/modules/screenmodes/scrmodes_veneers.h: \
	$(BUILD)/modules/screenmodes/veneers.S

$(BUILD)/modules/screenmodes/veneers.o: $(BUILD)/modules/screenmodes/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/screenmodes/modhead.o: $(BUILD)/modules/screenmodes/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/ScreenModes.elf: $(SM_OBJS) $(CLIB_NOFLOAT) $(CLIB) mk/ld/module.ld \
                                  tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SM_OBJS) $(CLIB_NOFLOAT) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SM_MOD): $(BUILD)/modules/ScreenModes.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ScreenModes)"

-include $(SM_OBJS:.o=.d)

screenmodesmodule: $(SM_MOD)
.PHONY: screenmodesmodule


# The Toolbox and its object classes are in Sources/Toolbox, each with
# its own rules; see the wildcard include near the end.
# IIC: a facade over OS_IICOp. IIC_Control is one transfer on bus 0;
# *IICDevices scans every device on every bus. c/iicscan.c (addresses offered,
# unclaimed device token, listing columns) is checked on the build machine;
# c/iicmodule.c issues the calls. cmhg/modhead covers the reference's second,
# non-HAL implementation.
IIC_OBJS := $(BUILD)/modules/iic/modhead.o \
            $(BUILD)/modules/iic/iicscan.o \
            $(BUILD)/modules/iic/iicmodule.o \
            $(BUILD)/modules/iic/veneers.o
IIC_MOD  := $(BUILD)/modules/IIC.mod
IIC_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/IIC/c \
              -I$(BUILD)/modules/iic

$(BUILD)/modules/iic/modhead.S: Sources/HWSupport/IIC/cmhg/modhead \
                                Sources/HWSupport/IIC/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/IIC -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/iic/%.o: Sources/HWSupport/IIC/c/%.c Makefile \
                          $(BUILD)/modules/iic/iic_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(IIC_CFLAGS) -c $< -o $@

# The veneers, generated with their C prototypes from one declaration. See
# tools/build/marshal.py.
$(BUILD)/modules/iic/veneers.S: Sources/HWSupport/IIC/s/boundaries \
                                Sources/HWSupport/IIC/cmhg/modhead \
                                tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/IIC --asm $@ \
	  --header $(BUILD)/modules/iic/iic_veneers.h

$(BUILD)/modules/iic/iic_veneers.h: $(BUILD)/modules/iic/veneers.S

$(BUILD)/modules/iic/veneers.o: $(BUILD)/modules/iic/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(IIC_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/iic/modhead.o: $(BUILD)/modules/iic/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(IIC_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/IIC.elf: $(IIC_OBJS) $(CLIB) mk/ld/module.ld \
                          tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(IIC_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(IIC_MOD): $(BUILD)/modules/IIC.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, IIC)"

-include $(IIC_OBJS:.o=.d)

iicmodule: $(IIC_MOD)
.PHONY: iicmodule

# BootNet: one CMOS bit, read at initialisation, decides whether the four
# dormant network modules in the ROM are started; its *Configure and *Status
# keyword sets and shows it. c/bootcfg.c (version and switch parsing) is
# checked on the build machine; c/bootnet.c reads the CMOS and starts the
# modules.
BNET_OBJS := $(BUILD)/modules/bootnet/modhead.o \
             $(BUILD)/modules/bootnet/bootcfg.o \
             $(BUILD)/modules/bootnet/bootnet.o \
             $(BUILD)/modules/bootnet/veneers.o
BNET_MOD  := $(BUILD)/modules/BootNet.mod
BNET_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Networking/Econet/BootNet/c \
               -I$(BUILD)/modules/bootnet

$(BUILD)/modules/bootnet/modhead.S: Sources/Networking/Econet/BootNet/cmhg/modhead \
                                    Sources/Networking/Econet/BootNet/VersionNum \
                                    tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Networking/Econet/BootNet -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/bootnet/%.o: Sources/Networking/Econet/BootNet/c/%.c Makefile \
                              $(BUILD)/modules/bootnet/bootnet_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(BNET_CFLAGS) -c $< -o $@

$(BUILD)/modules/bootnet/veneers.S: Sources/Networking/Econet/BootNet/s/boundaries \
                                    Sources/Networking/Econet/BootNet/cmhg/modhead \
                                    tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Networking/Econet/BootNet --asm $@ \
	  --header $(BUILD)/modules/bootnet/bootnet_veneers.h

$(BUILD)/modules/bootnet/bootnet_veneers.h: \
    $(BUILD)/modules/bootnet/veneers.S

$(BUILD)/modules/bootnet/veneers.o: $(BUILD)/modules/bootnet/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(BNET_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/bootnet/modhead.o: $(BUILD)/modules/bootnet/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(BNET_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/BootNet.elf: $(BNET_OBJS) $(CLIB) mk/ld/module.ld \
                              tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(BNET_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(BNET_MOD): $(BUILD)/modules/BootNet.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, BootNet)"

-include $(BNET_OBJS:.o=.d)

bootnetmodule: $(BNET_MOD)
.PHONY: bootnetmodule

# BootCommands: the thirteen *commands a boot sequence uses.
#
# No SWIs, services or vectors: a command table, a workspace holding the
# message file and one command line, and a start entry, because *Repeat must
# leave supervisor mode before running anything. The reference runs a second
# binary from ResourceFS for that; this enters the module instead, as the
# reference's own `#if 0` source does. c/bootcalc.c holds the OS-free decisions
# and is checked on the build machine; c/bootcmds.c is the module and
# c/bootrepeat.c runs once it has been entered.
BCMD_OBJS := $(BUILD)/modules/bootcmds/modhead.o \
             $(BUILD)/modules/bootcmds/bootcalc.o \
             $(BUILD)/modules/bootcmds/bootcmds.o \
             $(BUILD)/modules/bootcmds/bootrepeat.o \
             $(BUILD)/modules/bootcmds/veneers.o
BCMD_MOD  := $(BUILD)/modules/BootCommands.mod
BCMD_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Utilities/BootCmds/c \
               -I$(BUILD)/modules/bootcmds

$(BUILD)/modules/bootcmds/modhead.S: Sources/Utilities/BootCmds/cmhg/modhead \
                                     Sources/Utilities/BootCmds/VersionNum \
                                     tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Utilities/BootCmds -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/bootcmds/%.o: Sources/Utilities/BootCmds/c/%.c Makefile \
                               $(BUILD)/modules/bootcmds/bootcmds_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(BCMD_CFLAGS) -c $< -o $@

$(BUILD)/modules/bootcmds/veneers.S: Sources/Utilities/BootCmds/s/boundaries \
                                     Sources/Utilities/BootCmds/cmhg/modhead \
                                     tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Utilities/BootCmds --asm $@ \
	  --header $(BUILD)/modules/bootcmds/bootcmds_veneers.h

$(BUILD)/modules/bootcmds/bootcmds_veneers.h: \
    $(BUILD)/modules/bootcmds/veneers.S

$(BUILD)/modules/bootcmds/veneers.o: $(BUILD)/modules/bootcmds/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(BCMD_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/bootcmds/modhead.o: $(BUILD)/modules/bootcmds/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(BCMD_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/BootCommands.elf: $(BCMD_OBJS) $(CLIB_NOFLOAT) $(CLIB) mk/ld/module.ld \
                                   tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(BCMD_OBJS) $(CLIB_NOFLOAT) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(BCMD_MOD): $(BUILD)/modules/BootCommands.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, BootCommands)"

-include $(BCMD_OBJS:.o=.d)

bootcmdsmodule: $(BCMD_MOD)
.PHONY: bootcmdsmodule

# SpriteUtils: fifteen commands on the system sprite area. Thirteen are one
# OS_SpriteOp with the tail as the name; two print. c/spargs.c walks the names
# on a line (the second name, and *SDelete's list) and is checked on the build
# machine; c/spriteutils.c issues the calls.
SPU_OBJS := $(BUILD)/modules/spriteutils/modhead.o \
            $(BUILD)/modules/spriteutils/spargs.o \
            $(BUILD)/modules/spriteutils/spriteutils.o \
            $(BUILD)/modules/spriteutils/veneers.o
SPU_MOD  := $(BUILD)/modules/SpriteUtils.mod
SPU_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Video/Render/SpriteUtils/c \
              -I$(BUILD)/modules/spriteutils

$(BUILD)/modules/spriteutils/modhead.S: Sources/Video/Render/SpriteUtils/cmhg/modhead \
                                        Sources/Video/Render/SpriteUtils/VersionNum \
                                        tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Video/Render/SpriteUtils -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/spriteutils/%.o: Sources/Video/Render/SpriteUtils/c/%.c Makefile \
                                  $(BUILD)/modules/spriteutils/sprutil_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SPU_CFLAGS) -c $< -o $@

$(BUILD)/modules/spriteutils/veneers.S: Sources/Video/Render/SpriteUtils/s/boundaries \
                                        Sources/Video/Render/SpriteUtils/cmhg/modhead \
                                        tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/SpriteUtils --asm $@ \
	  --header $(BUILD)/modules/spriteutils/sprutil_veneers.h

$(BUILD)/modules/spriteutils/sprutil_veneers.h: \
    $(BUILD)/modules/spriteutils/veneers.S

$(BUILD)/modules/spriteutils/veneers.o: \
		$(BUILD)/modules/spriteutils/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SPU_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/spriteutils/modhead.o: \
		$(BUILD)/modules/spriteutils/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SPU_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/SpriteUtils.elf: $(SPU_OBJS) $(CLIB) mk/ld/module.ld \
                                  tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SPU_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SPU_MOD): $(BUILD)/modules/SpriteUtils.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, SpriteUtils)"

-include $(SPU_OBJS:.o=.d)

spriteutilsmodule: $(SPU_MOD)
.PHONY: spriteutilsmodule

# SerialMouse: two serial mouse protocols, claiming PointerV for two device
# types and TickerV to read the serial stream.
#
# The protocol state machines are in c/serdecode.c and are checked only on the
# build machine (the emulator has no serial mouse). c/sermouse.c claims, opens
# and reports.
SERM_OBJS := $(BUILD)/modules/sermouse/modhead.o \
             $(BUILD)/modules/sermouse/serdecode.o \
             $(BUILD)/modules/sermouse/sermouse.o \
             $(BUILD)/modules/sermouse/veneers.o
SERM_MOD  := $(BUILD)/modules/SerialMouse.mod
SERM_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/SerMouse/c \
               -I$(BUILD)/modules/sermouse

$(BUILD)/modules/sermouse/modhead.S: Sources/HWSupport/SerMouse/cmhg/modhead \
                                     Sources/HWSupport/SerMouse/VersionNum \
                                     tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/SerMouse -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/sermouse/%.o: Sources/HWSupport/SerMouse/c/%.c Makefile \
                               $(BUILD)/modules/sermouse/sermouse_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SERM_CFLAGS) -c $< -o $@

$(BUILD)/modules/sermouse/veneers.S: Sources/HWSupport/SerMouse/s/boundaries \
                                     Sources/HWSupport/SerMouse/cmhg/modhead \
                                     tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/SerMouse --asm $@ \
	  --header $(BUILD)/modules/sermouse/sermouse_veneers.h

$(BUILD)/modules/sermouse/sermouse_veneers.h: \
    $(BUILD)/modules/sermouse/veneers.S

$(BUILD)/modules/sermouse/veneers.o: $(BUILD)/modules/sermouse/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SERM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/sermouse/modhead.o: $(BUILD)/modules/sermouse/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SERM_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/SerialMouse.elf: $(SERM_OBJS) $(CLIB) mk/ld/module.ld \
                                  tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SERM_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SERM_MOD): $(BUILD)/modules/SerialMouse.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, SerialMouse)"

-include $(SERM_OBJS:.o=.d)

sermousemodule: $(SERM_MOD)
.PHONY: sermousemodule

# PS2Driver: PS/2 keyboards and mice on IOMD's ports and a Stork's.
#
# c/ps2proto.c is the OS-free protocol (port states, the two scan code tables,
# mouse reports), driven a byte at a time by `make ps2driverchecks`.
# c/ps2driver.c is the hardware, KeyV, PointerV and the kernel. Entries are
# generated from s/boundaries; the two interrupts and the two Korean key
# timers arrive in modes C cannot run in, so they are `svc`.
PS2_OBJS := $(BUILD)/modules/ps2driver/modhead.o \
            $(BUILD)/modules/ps2driver/ps2proto.o \
            $(BUILD)/modules/ps2driver/ps2driver.o \
            $(BUILD)/modules/ps2driver/veneers.o
PS2_MOD  := $(BUILD)/modules/PS2Driver.mod
PS2_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/PS2Driver/c \
              -I$(BUILD)/modules/ps2driver

$(BUILD)/modules/ps2driver/modhead.S: Sources/HWSupport/PS2Driver/cmhg/modhead \
                                      Sources/HWSupport/PS2Driver/VersionNum \
                                      tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/PS2Driver -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/ps2driver/%.o: Sources/HWSupport/PS2Driver/c/%.c Makefile \
                                $(BUILD)/modules/ps2driver/ps2_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(PS2_CFLAGS) -c $< -o $@

$(BUILD)/modules/ps2driver/veneers.S: Sources/HWSupport/PS2Driver/s/boundaries \
                                      Sources/HWSupport/PS2Driver/cmhg/modhead \
                                      tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/PS2Driver --asm $@ \
	  --header $(BUILD)/modules/ps2driver/ps2_veneers.h

$(BUILD)/modules/ps2driver/ps2_veneers.h: $(BUILD)/modules/ps2driver/veneers.S

$(BUILD)/modules/ps2driver/veneers.o: $(BUILD)/modules/ps2driver/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(PS2_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/ps2driver/modhead.o: $(BUILD)/modules/ps2driver/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(PS2_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/PS2Driver.elf: $(PS2_OBJS) $(CLIB) mk/ld/module.ld \
                                tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(PS2_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(PS2_MOD): $(BUILD)/modules/PS2Driver.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, PS2Driver)"

-include $(PS2_OBJS:.o=.d)

ps2drivermodule: $(PS2_MOD)
.PHONY: ps2drivermodule

# PipeFS: a filing system whose files are cyclic buffers.
#
# c/pipecalc.c is the OS-free half (the ring and its growth, the wildcard
# matcher, the enumeration order), run on the build machine by
# `make pipefschecks`. c/pipefs.c is the files, their heap and the three ways
# a caller can be made to wait.
PIPE_OBJS := $(BUILD)/modules/pipefs/modhead.o \
             $(BUILD)/modules/pipefs/pipecalc.o \
             $(BUILD)/modules/pipefs/pipefs.o \
             $(BUILD)/modules/pipefs/veneers.o
PIPE_MOD  := $(BUILD)/modules/PipeFS.mod
PIPE_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/PipeFS/c \
               -I$(BUILD)/modules/pipefs

$(BUILD)/modules/pipefs/modhead.S: Sources/FileSys/PipeFS/cmhg/modhead \
                                   Sources/FileSys/PipeFS/VersionNum \
                                   tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/PipeFS -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/pipefs/%.o: Sources/FileSys/PipeFS/c/%.c Makefile \
                             $(BUILD)/modules/pipefs/pipefs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(PIPE_CFLAGS) -c $< -o $@

$(BUILD)/modules/pipefs/veneers.S: Sources/FileSys/PipeFS/s/boundaries \
                                   Sources/FileSys/PipeFS/cmhg/modhead \
                                   tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/PipeFS --asm $@ \
	  --header $(BUILD)/modules/pipefs/pipefs_veneers.h

$(BUILD)/modules/pipefs/pipefs_veneers.h: $(BUILD)/modules/pipefs/veneers.S

$(BUILD)/modules/pipefs/veneers.o: $(BUILD)/modules/pipefs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(PIPE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/pipefs/modhead.o: $(BUILD)/modules/pipefs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(PIPE_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/PipeFS.elf: $(PIPE_OBJS) $(CLIB) mk/ld/module.ld \
                             tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(PIPE_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(PIPE_MOD): $(BUILD)/modules/PipeFS.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, PipeFS)"

-include $(PIPE_OBJS:.o=.d)

pipefsmodule: $(PIPE_MOD)
.PHONY: pipefsmodule

# SystemDevices: seven device filing systems.
#
# c/sysdevcalc.c is the OS-free half (the random: generator, the two name
# tables, and the four formats vdu: can write an unprintable character in),
# run on the build machine by `make systemdevschecks`. c/systemdevs.c is the
# seven devices, behind one set of entry points and seven private words.
SYSDEV_OBJS := $(BUILD)/modules/systemdevs/modhead.o \
               $(BUILD)/modules/systemdevs/sysdevcalc.o \
               $(BUILD)/modules/systemdevs/systemdevs.o \
               $(BUILD)/modules/systemdevs/veneers.o
SYSDEV_MOD  := $(BUILD)/modules/SystemDevices.mod
SYSDEV_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Utilities/SystemDevs/c \
                 -I$(BUILD)/modules/systemdevs

$(BUILD)/modules/systemdevs/modhead.S: Sources/Utilities/SystemDevs/cmhg/modhead \
                                       Sources/Utilities/SystemDevs/VersionNum \
                                       tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Utilities/SystemDevs -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/systemdevs/%.o: Sources/Utilities/SystemDevs/c/%.c Makefile \
                                 $(BUILD)/modules/systemdevs/sysdevs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SYSDEV_CFLAGS) -c $< -o $@

$(BUILD)/modules/systemdevs/veneers.S: Sources/Utilities/SystemDevs/s/boundaries \
                                       Sources/Utilities/SystemDevs/cmhg/modhead \
                                       tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Utilities/SystemDevs --asm $@ \
	  --header $(BUILD)/modules/systemdevs/sysdevs_veneers.h

$(BUILD)/modules/systemdevs/sysdevs_veneers.h: \
    $(BUILD)/modules/systemdevs/veneers.S

$(BUILD)/modules/systemdevs/veneers.o: $(BUILD)/modules/systemdevs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SYSDEV_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/systemdevs/modhead.o: $(BUILD)/modules/systemdevs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SYSDEV_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/SystemDevices.elf: $(SYSDEV_OBJS) $(CLIB) mk/ld/module.ld \
                                    tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SYSDEV_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SYSDEV_MOD): $(BUILD)/modules/SystemDevices.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, SystemDevices)"

-include $(SYSDEV_OBJS:.o=.d)

systemdevsmodule: $(SYSDEV_MOD)
.PHONY: systemdevsmodule

# SerialDeviceSupport: the old serial interface, forwarded to a DeviceFS
# driver.
#
# c/serialcalc.c holds the three translation tables between the 6850 and
# OS_SerialOp and the decisions from them; it issues no SWI, so
# `make serialsptchecks` runs it on the build machine. c/serialspt.c claims
# the vectors, finds the driver through Service_SerialDevice and calls it.
SERSPT_OBJS := $(BUILD)/modules/serialspt/modhead.o \
               $(BUILD)/modules/serialspt/serialcalc.o \
               $(BUILD)/modules/serialspt/serialspt.o \
               $(BUILD)/modules/serialspt/veneers.o
SERSPT_MOD  := $(BUILD)/modules/SerialDeviceSupport.mod
SERSPT_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/SerialSpt/c \
                 -I$(BUILD)/modules/serialspt

$(BUILD)/modules/serialspt/modhead.S: Sources/HWSupport/SerialSpt/cmhg/modhead \
                                      Sources/HWSupport/SerialSpt/VersionNum \
                                      tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/SerialSpt -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/serialspt/%.o: Sources/HWSupport/SerialSpt/c/%.c Makefile \
                                $(BUILD)/modules/serialspt/serialspt_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SERSPT_CFLAGS) -c $< -o $@

$(BUILD)/modules/serialspt/veneers.S: Sources/HWSupport/SerialSpt/s/boundaries \
                                      Sources/HWSupport/SerialSpt/cmhg/modhead \
                                      tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/SerialSpt --asm $@ \
	  --header $(BUILD)/modules/serialspt/serialspt_veneers.h

$(BUILD)/modules/serialspt/serialspt_veneers.h: \
    $(BUILD)/modules/serialspt/veneers.S

$(BUILD)/modules/serialspt/veneers.o: $(BUILD)/modules/serialspt/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SERSPT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/serialspt/%.o: Sources/HWSupport/SerialSpt/s/%.S
	@mkdir -p $(dir $@)
	$(CC) $(SERSPT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/serialspt/modhead.o: $(BUILD)/modules/serialspt/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SERSPT_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/SerialDeviceSupport.elf: $(SERSPT_OBJS) $(CLIB) \
                                          mk/ld/module.ld \
                                          tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SERSPT_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SERSPT_MOD): $(BUILD)/modules/SerialDeviceSupport.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, SerialDeviceSupport)"

-include $(SERSPT_OBJS:.o=.d)

serialsptmodule: $(SERSPT_MOD)
.PHONY: serialsptmodule

# FSLock: a password in CMOS and a write-protected filing system.
#
# c/fslockcalc.c (the password fold, two checksums, the drive-name cloak, the
# path and reason decisions) issues no SWI; `make fslockchecks` runs it on the
# build machine. c/fslock.c claims the vectors, replaces three of FileSwitch's
# low-level entries in its control block, and writes the CMOS bytes.
#
# s/crypt.S holds the four words the build patches. The
# scramble step is required: the module checksums its own image against a
# word tools/build/scramble.py writes, and an unscrambled image stops the
# machine when a protected filing system is first used. module.ld places that
# word last so the checksum covers everything before it.
FSLK_OBJS := $(BUILD)/modules/fslock/modhead.o \
             $(BUILD)/modules/fslock/fslockcalc.o \
             $(BUILD)/modules/fslock/fslock.o \
             $(BUILD)/modules/fslock/veneers.o \
             $(BUILD)/modules/fslock/crypt.o
FSLK_MOD  := $(BUILD)/modules/FSLock.mod
FSLK_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/FSLock/c \
               -I$(BUILD)/modules/fslock

$(BUILD)/modules/fslock/modhead.S: Sources/FileSys/FSLock/cmhg/modhead \
                                   Sources/FileSys/FSLock/VersionNum \
                                   tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/FSLock -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/fslock/%.o: Sources/FileSys/FSLock/c/%.c Makefile \
                             $(BUILD)/modules/fslock/fslock_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FSLK_CFLAGS) -c $< -o $@

$(BUILD)/modules/fslock/veneers.S: Sources/FileSys/FSLock/s/boundaries \
                                   Sources/FileSys/FSLock/cmhg/modhead \
                                   tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/FSLock --asm $@ \
	  --header $(BUILD)/modules/fslock/fslock_veneers.h

$(BUILD)/modules/fslock/fslock_veneers.h: $(BUILD)/modules/fslock/veneers.S

$(BUILD)/modules/fslock/veneers.o: $(BUILD)/modules/fslock/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FSLK_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/fslock/%.o: Sources/FileSys/FSLock/s/%.S
	@mkdir -p $(dir $@)
	$(CC) $(FSLK_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/fslock/modhead.o: $(BUILD)/modules/fslock/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(FSLK_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/FSLock.elf: $(FSLK_OBJS) $(CLIB) \
                             Sources/FileSys/FSLock/module.ld \
                             tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs \
	  -T Sources/FileSys/FSLock/module.ld \
	  -o $@.tmp $(FSLK_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

# scramble.py reads symbols from the ELF and patches the binary, so it needs
# both.
$(FSLK_MOD): $(BUILD)/modules/FSLock.elf tools/build/scramble.py
	$(OBJCOPY) -O binary $< $@
	@python3 tools/build/scramble.py $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, FSLock)"

-include $(FSLK_OBJS:.o=.d)

fslockmodule: $(FSLK_MOD)
.PHONY: fslockmodule

# CDFSDriver: the CD_ SWI chunk and its list of drivers.
#
# c/cdcalc.c holds the module's decisions (three addressing modes, SWI to
# driver reason numbering, routine selection per information word, the
# driver list, SCSI sense mapping) and issues no SWI, so
# `make cdfsdriverchecks` runs it on the build machine. c/cdfsdriver.c is the
# dispatcher, the thirteen SCSI commands it issues for drivers, and the
# errors.
CDD_OBJS := $(BUILD)/modules/cdfsdriver/modhead.o \
            $(BUILD)/modules/cdfsdriver/cdcalc.o \
            $(BUILD)/modules/cdfsdriver/cdfsdriver.o \
            $(BUILD)/modules/cdfsdriver/veneers.o
CDD_MOD  := $(BUILD)/modules/CDFSDriver.mod
CDD_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/CDFS/CDFSDriver/c \
              -I$(BUILD)/modules/cdfsdriver

$(BUILD)/modules/cdfsdriver/modhead.S: Sources/FileSys/CDFS/CDFSDriver/cmhg/modhead \
                                       Sources/FileSys/CDFS/CDFSDriver/VersionNum \
                                       tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/CDFS/CDFSDriver -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/cdfsdriver/%.o: Sources/FileSys/CDFS/CDFSDriver/c/%.c Makefile \
                                 $(BUILD)/modules/cdfsdriver/cd_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(CDD_CFLAGS) -c $< -o $@

$(BUILD)/modules/cdfsdriver/veneers.S: Sources/FileSys/CDFS/CDFSDriver/s/boundaries \
                                       Sources/FileSys/CDFS/CDFSDriver/cmhg/modhead \
                                       tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/CDFS/CDFSDriver --asm $@ \
	  --header $(BUILD)/modules/cdfsdriver/cd_veneers.h

$(BUILD)/modules/cdfsdriver/cd_veneers.h: $(BUILD)/modules/cdfsdriver/veneers.S

$(BUILD)/modules/cdfsdriver/veneers.o: $(BUILD)/modules/cdfsdriver/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(CDD_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/cdfsdriver/modhead.o: $(BUILD)/modules/cdfsdriver/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(CDD_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/CDFSDriver.elf: $(CDD_OBJS) $(CLIB) mk/ld/module.ld \
                                 tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(CDD_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(CDD_MOD): $(BUILD)/modules/CDFSDriver.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, CDFSDriver)"

-include $(CDD_OBJS:.o=.d)

cdfsdrivermodule: $(CDD_MOD)
.PHONY: cdfsdrivermodule

# CDFSSoftATAPI: the CD-ROM driver for an IDE packet device.
#
# c/atapicalc.c builds the twelve-byte command blocks and interprets the
# answers (identify block, mode page search, speed arithmetic, sense keys); it
# issues no SWI, so `make cdatapichecks` runs it on the build machine.
# c/cdatapi.c is the thirty-seven reasons, the call beneath them, and the
# drives.
#
# s/tag.S is the word &EE50EE50 whose address is passed to CD_Register,
# followed by a branch to the generated veneer.
ATAPI_OBJS := $(BUILD)/modules/cdatapi/modhead.o \
              $(BUILD)/modules/cdatapi/atapicalc.o \
              $(BUILD)/modules/cdatapi/cdatapi.o \
              $(BUILD)/modules/cdatapi/tag.o \
              $(BUILD)/modules/cdatapi/veneers.o
ATAPI_MOD  := $(BUILD)/modules/CDFSSoftATAPI.mod
ATAPI_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/CDFS/CDFSATAPI/c \
                -I$(BUILD)/modules/cdatapi

$(BUILD)/modules/cdatapi/modhead.S: Sources/FileSys/CDFS/CDFSATAPI/cmhg/modhead \
                                    Sources/FileSys/CDFS/CDFSATAPI/VersionNum \
                                    tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/CDFS/CDFSATAPI -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/cdatapi/%.o: Sources/FileSys/CDFS/CDFSATAPI/c/%.c Makefile \
                              $(BUILD)/modules/cdatapi/atapi_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(ATAPI_CFLAGS) -c $< -o $@

$(BUILD)/modules/cdatapi/veneers.S: Sources/FileSys/CDFS/CDFSATAPI/s/boundaries \
                                    Sources/FileSys/CDFS/CDFSATAPI/cmhg/modhead \
                                    tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/CDFS/CDFSATAPI --asm $@ \
	  --header $(BUILD)/modules/cdatapi/atapi_veneers.h

$(BUILD)/modules/cdatapi/atapi_veneers.h: $(BUILD)/modules/cdatapi/veneers.S

$(BUILD)/modules/cdatapi/veneers.o: $(BUILD)/modules/cdatapi/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(ATAPI_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/cdatapi/%.o: Sources/FileSys/CDFS/CDFSATAPI/s/%.S
	@mkdir -p $(dir $@)
	$(CC) $(ATAPI_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/cdatapi/modhead.o: $(BUILD)/modules/cdatapi/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(ATAPI_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/CDFSSoftATAPI.elf: $(ATAPI_OBJS) $(CLIB) mk/ld/module.ld \
                                    tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(ATAPI_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(ATAPI_MOD): $(BUILD)/modules/CDFSSoftATAPI.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, CDFSSoftATAPI)"

-include $(ATAPI_OBJS:.o=.d)

cdatapimodule: $(ATAPI_MOD)
.PHONY: cdatapimodule

# CDFS: the filing system for compact discs.
#
# c/isocalc.c translates ISO 9660 into RISC OS directories (volume descriptor
# layout, the root, directory records, name/date/type/length, the decoded
# directory pool) and issues no SWI, so `make cdfschecks` runs it on the build
# machine. c/cdfs.c is the seven FileSwitch entries, nine SWIs, nineteen
# commands and the CD_ calls beneath them.
CDFS_OBJS := $(BUILD)/modules/cdfs/modhead.o \
             $(BUILD)/modules/cdfs/isocalc.o \
             $(BUILD)/modules/cdfs/cdfs.o \
             $(BUILD)/modules/cdfs/veneers.o
CDFS_MOD  := $(BUILD)/modules/CDFS.mod
CDFS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/CDFS/CDFS/c \
               -I$(BUILD)/modules/cdfs

$(BUILD)/modules/cdfs/modhead.S: Sources/FileSys/CDFS/CDFS/cmhg/modhead \
                                 Sources/FileSys/CDFS/CDFS/VersionNum \
                                 tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/CDFS/CDFS -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/cdfs/%.o: Sources/FileSys/CDFS/CDFS/c/%.c Makefile \
                           $(BUILD)/modules/cdfs/cdfs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(CDFS_CFLAGS) -c $< -o $@

$(BUILD)/modules/cdfs/veneers.S: Sources/FileSys/CDFS/CDFS/s/boundaries \
                                 Sources/FileSys/CDFS/CDFS/cmhg/modhead \
                                 tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/CDFS/CDFS --asm $@ \
	  --header $(BUILD)/modules/cdfs/cdfs_veneers.h

$(BUILD)/modules/cdfs/cdfs_veneers.h: $(BUILD)/modules/cdfs/veneers.S

$(BUILD)/modules/cdfs/veneers.o: $(BUILD)/modules/cdfs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(CDFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/cdfs/modhead.o: $(BUILD)/modules/cdfs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(CDFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/CDFS.elf: $(CDFS_OBJS) $(CLIB) mk/ld/module.ld \
                           tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(CDFS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(CDFS_MOD): $(BUILD)/modules/CDFS.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, CDFS)"

-include $(CDFS_OBJS:.o=.d)

cdfsmodule: $(CDFS_MOD)
.PHONY: cdfsmodule

# SuperSample: a one-bit bitmap reduced four to one into four bits per pixel.
# Two SWIs; no workspace, initialisation, service handler or messages. The
# arithmetic is in c/supersample.c and checked on the build machine;
# c/supermodule.c is the SWI dispatch and the two errors.
SUPS_OBJS := $(BUILD)/modules/supersample/modhead.o \
             $(BUILD)/modules/supersample/supersample.o \
             $(BUILD)/modules/supersample/supermodule.o \
             $(BUILD)/modules/supersample/veneers.o
SUPS_MOD  := $(BUILD)/modules/SuperSample.mod
SUPS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Video/Render/Super/c \
               -I$(BUILD)/modules/supersample

$(BUILD)/modules/supersample/modhead.S: Sources/Video/Render/Super/cmhg/modhead \
                                        Sources/Video/Render/Super/VersionNum \
                                        tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Video/Render/Super -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/supersample/%.o: Sources/Video/Render/Super/c/%.c Makefile \
                                  $(BUILD)/modules/supersample/super_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SUPS_CFLAGS) -c $< -o $@

$(BUILD)/modules/supersample/veneers.S: Sources/Video/Render/Super/s/boundaries \
                                        Sources/Video/Render/Super/cmhg/modhead \
                                        tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/Super --asm $@ \
	  --header $(BUILD)/modules/supersample/super_veneers.h

$(BUILD)/modules/supersample/super_veneers.h: \
    $(BUILD)/modules/supersample/veneers.S

$(BUILD)/modules/supersample/veneers.o: \
		$(BUILD)/modules/supersample/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SUPS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/supersample/modhead.o: \
		$(BUILD)/modules/supersample/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SUPS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/SuperSample.elf: $(SUPS_OBJS) $(CLIB) mk/ld/module.ld \
                                  tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SUPS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SUPS_MOD): $(BUILD)/modules/SuperSample.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, SuperSample)"

-include $(SUPS_OBJS:.o=.d)

supersamplemodule: $(SUPS_MOD)
.PHONY: supersamplemodule

# Hourglass: the busy pointer. Seven SWIs and two commands over one pointer
# shape, advanced every fifteen centiseconds by a TickerV claimant.
#
# The image (glass, six sand frames, ten digits) is c/hourshape.h, extracted
# from the reference assembly by tools/build/hourshape.py and checked by
# `make hourshapecheck`. c/hourpix.c uses it, issues no SWI and is checked on
# the build machine. c/hourglass.c counts the depth and programs the pointer.
HOUR_DIR  := Sources/Desktop/Hourglass
HOUR_OBJS := $(BUILD)/modules/hourglass/modhead.o \
             $(BUILD)/modules/hourglass/hourpix.o \
             $(BUILD)/modules/hourglass/hourglass.o \
             $(BUILD)/modules/hourglass/veneers.o
HOUR_MOD  := $(BUILD)/modules/Hourglass.mod
HOUR_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Desktop/Hourglass/c \
               -I$(BUILD)/modules/hourglass

$(BUILD)/modules/hourglass/modhead.S: Sources/Desktop/Hourglass/cmhg/modhead \
                                      Sources/Desktop/Hourglass/VersionNum \
                                      tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Desktop/Hourglass -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/hourglass/%.o: Sources/Desktop/Hourglass/c/%.c Makefile \
                                $(BUILD)/modules/hourglass/hourglass_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(HOUR_CFLAGS) -c $< -o $@

$(BUILD)/modules/hourglass/veneers.S: Sources/Desktop/Hourglass/s/boundaries \
                                      Sources/Desktop/Hourglass/cmhg/modhead \
                                      tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Desktop/Hourglass --asm $@ \
	  --header $(BUILD)/modules/hourglass/hourglass_veneers.h

$(BUILD)/modules/hourglass/hourglass_veneers.h: \
    $(BUILD)/modules/hourglass/veneers.S

$(BUILD)/modules/hourglass/veneers.o: $(BUILD)/modules/hourglass/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(HOUR_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/hourglass/%.o: Sources/Desktop/Hourglass/s/%.S
	@mkdir -p $(dir $@)
	$(CC) $(HOUR_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/hourglass/modhead.o: $(BUILD)/modules/hourglass/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(HOUR_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/Hourglass.elf: $(HOUR_OBJS) $(CLIB) mk/ld/module.ld \
                                tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(HOUR_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(HOUR_MOD): $(BUILD)/modules/Hourglass.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Hourglass)"

-include $(HOUR_OBJS:.o=.d)

hourglassmodule: $(HOUR_MOD)
.PHONY: hourglassmodule

# Re-extracts the hourglass image from the reference source and compares it
# with the committed one. Needs the legacy sources, like `make hdrcheck`.
HOUR_SHAPE_SRC := $(RO_SRC)/Video/Render/Hourglass/s/Module

hourshapecheck:
	@mkdir -p $(BUILD)/hourshape
	@python3 tools/build/hourshape.py $(HOUR_SHAPE_SRC) \
	  $(BUILD)/hourshape/hourshape.h > /dev/null
	@cmp -s $(HOUR_DIR)/c/hourshape.h $(BUILD)/hourshape/hourshape.h \
	  || { echo "hourshapecheck: $(HOUR_DIR)/c/hourshape.h differs from"; \
	       echo "  what tools/build/hourshape.py makes of $(HOUR_SHAPE_SRC)"; \
	       exit 1; }
	@echo "hourshapecheck: the hourglass's picture is the reference's"
.PHONY: hourshapecheck

# Host checks for the sand, the figures and the two lamps.
HOUR_TEST_GEN := $(BUILD)/tests/hourglass
HOUR_TEST_FLAGS := $(HOST_CFLAGS) -I$(HOUR_DIR)/c -Itests/lib -Iinclude

$(HOUR_TEST_GEN)/%.o: Sources/Desktop/Hourglass/test/hourglass/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOUR_TEST_FLAGS) -c $< -o $@

$(HOUR_TEST_GEN)/%.o: $(HOUR_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOUR_TEST_FLAGS) -c $< -o $@

$(HOUR_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOUR_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/hourglass-checks: $(addprefix $(HOUR_TEST_GEN)/,cases.o \
                                       hourpix.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

hourglasschecks: $(BUILD)/tests/hourglass-checks
	@$(BUILD)/tests/hourglass-checks
.PHONY: hourglasschecks

# InverseTable: the nearest of the 256 colours to any colour.
#
# c/itablecalc.c builds a 32 x 32 x 32 cube of colour numbers and issues no
# SWI. c/itable.c reads the palette through ColourTrans, keeps the cube in a
# dynamic area and decides when it is stale. s/aarch32/callbacks.S drops to user mode
# so the machine stays responsive while a build runs.
ITAB_DIR  := Sources/Video/Render/ITable
ITAB_OBJS := $(BUILD)/modules/itable/modhead.o \
             $(BUILD)/modules/itable/itablecalc.o \
             $(BUILD)/modules/itable/itable.o \
             $(BUILD)/modules/itable/veneers.o \
             $(BUILD)/modules/itable/callbacks.o
ITAB_MOD  := $(BUILD)/modules/InverseTable.mod
ITAB_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Video/Render/ITable/c \
               -I$(BUILD)/modules/itable

$(BUILD)/modules/itable/modhead.S: Sources/Video/Render/ITable/cmhg/modhead \
                                   Sources/Video/Render/ITable/VersionNum \
                                   tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Video/Render/ITable -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/itable/%.o: Sources/Video/Render/ITable/c/%.c Makefile \
                             $(BUILD)/modules/itable/itable_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(ITAB_CFLAGS) -c $< -o $@

$(BUILD)/modules/itable/veneers.S: Sources/Video/Render/ITable/s/boundaries \
                                   Sources/Video/Render/ITable/cmhg/modhead \
                                   tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Video/Render/ITable --asm $@ \
	  --header $(BUILD)/modules/itable/itable_veneers.h

$(BUILD)/modules/itable/itable_veneers.h: $(BUILD)/modules/itable/veneers.S

$(BUILD)/modules/itable/veneers.o: $(BUILD)/modules/itable/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(ITAB_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/itable/%.o: Sources/Video/Render/ITable/s/aarch32/%.S
	@mkdir -p $(dir $@)
	$(CC) $(ITAB_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/itable/modhead.o: $(BUILD)/modules/itable/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(ITAB_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/InverseTable.elf: $(ITAB_OBJS) $(CLIB) mk/ld/module.ld \
                                   tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(ITAB_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(ITAB_MOD): $(BUILD)/modules/InverseTable.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, InverseTable)"

-include $(ITAB_OBJS:.o=.d)

itablemodule: $(ITAB_MOD)
.PHONY: itablemodule

# Host checks: the cube against a brute-force search of the same palette, and
# the standard 256-colour palette against the original's table.
ITAB_TEST_GEN := $(BUILD)/tests/itable
ITAB_TEST_FLAGS := $(HOST_CFLAGS) -I$(ITAB_DIR)/c -Itests/lib -Iinclude

$(ITAB_TEST_GEN)/%.o: Sources/Video/Render/ITable/test/itable/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ITAB_TEST_FLAGS) -c $< -o $@

$(ITAB_TEST_GEN)/%.o: $(ITAB_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ITAB_TEST_FLAGS) -c $< -o $@

$(ITAB_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ITAB_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/itable-checks: $(addprefix $(ITAB_TEST_GEN)/,cases.o \
                                    itablecalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

itablechecks: $(BUILD)/tests/itable-checks
	@$(BUILD)/tests/itable-checks
.PHONY: itablechecks

# ScreenBlanker: blanks the screen when the machine is idle.
#
# A timer counted down five times a second, reset by the keyboard buffer, a
# key press, the mouse and, if configured, a character written; blanking is a
# palette call. c/blankcalc.c is the OS-free arithmetic (the three clocks, two
# floors, and the delay as *BlankTime writes it). The four entries arriving in
# another processor mode are `svc-entry` in s/boundaries.
BLNK_DIR  := Sources/Desktop/ScrBlank
BLNK_OBJS := $(BUILD)/modules/scrblank/modhead.o \
             $(BUILD)/modules/scrblank/blankcalc.o \
             $(BUILD)/modules/scrblank/scrblank.o \
             $(BUILD)/modules/scrblank/veneers.o
BLNK_MOD  := $(BUILD)/modules/ScreenBlanker.mod
BLNK_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Desktop/ScrBlank/c \
               -I$(BUILD)/modules/scrblank

$(BUILD)/modules/scrblank/modhead.S: Sources/Desktop/ScrBlank/cmhg/modhead \
                                     Sources/Desktop/ScrBlank/VersionNum \
                                     tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Desktop/ScrBlank -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/scrblank/%.o: Sources/Desktop/ScrBlank/c/%.c Makefile \
                               $(BUILD)/modules/scrblank/blank_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(BLNK_CFLAGS) -c $< -o $@

$(BUILD)/modules/scrblank/veneers.S: Sources/Desktop/ScrBlank/s/boundaries \
                                     Sources/Desktop/ScrBlank/cmhg/modhead \
                                     tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Desktop/ScrBlank --asm $@ \
	  --header $(BUILD)/modules/scrblank/blank_veneers.h

$(BUILD)/modules/scrblank/blank_veneers.h: \
    $(BUILD)/modules/scrblank/veneers.S

$(BUILD)/modules/scrblank/veneers.o: $(BUILD)/modules/scrblank/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(BLNK_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/scrblank/%.o: Sources/Desktop/ScrBlank/s/%.S
	@mkdir -p $(dir $@)
	$(CC) $(BLNK_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/scrblank/modhead.o: $(BUILD)/modules/scrblank/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(BLNK_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/ScreenBlanker.elf: $(BLNK_OBJS) $(CLIB) mk/ld/module.ld \
                                    tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(BLNK_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(BLNK_MOD): $(BUILD)/modules/ScreenBlanker.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ScreenBlanker)"

-include $(BLNK_OBJS:.o=.d)

screenblankermodule: $(BLNK_MOD)
.PHONY: screenblankermodule

# Host checks for the clocks, the floors and the written delay.
BLNK_TEST_GEN := $(BUILD)/tests/scrblank
BLNK_TEST_FLAGS := $(HOST_CFLAGS) -I$(BLNK_DIR)/c -Itests/lib -Iinclude

$(BLNK_TEST_GEN)/%.o: Sources/Desktop/ScrBlank/test/scrblank/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BLNK_TEST_FLAGS) -c $< -o $@

$(BLNK_TEST_GEN)/%.o: $(BLNK_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BLNK_TEST_FLAGS) -c $< -o $@

$(BLNK_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BLNK_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/scrblank-checks: $(addprefix $(BLNK_TEST_GEN)/,cases.o \
                                      blankcalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

scrblankchecks: $(BUILD)/tests/scrblank-checks
	@$(BUILD)/tests/scrblank-checks
.PHONY: scrblankchecks

# Host checks: the reduction against the grid weights and against the
# arithmetic of the reference's generated tables.
SUPS_DIR := Sources/Video/Render/Super
SUPS_TEST_GEN := $(BUILD)/tests/supersample
SUPS_TEST_FLAGS := $(HOST_CFLAGS) -I$(SUPS_DIR)/c -Itests/lib -Iinclude

$(SUPS_TEST_GEN)/%.o: Sources/Video/Render/Super/test/supersample/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SUPS_TEST_FLAGS) -c $< -o $@

$(SUPS_TEST_GEN)/%.o: $(SUPS_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SUPS_TEST_FLAGS) -c $< -o $@

$(SUPS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SUPS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/supersample-checks: $(addprefix $(SUPS_TEST_GEN)/,cases.o \
                                         supersample.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

supersamplechecks: $(BUILD)/tests/supersample-checks
	@$(BUILD)/tests/supersample-checks
.PHONY: supersamplechecks

# Host checks for the two serial mouse protocols, byte by byte; the emulated
# machine has no serial mouse.
SERM_DIR := Sources/HWSupport/SerMouse
SERM_TEST_GEN := $(BUILD)/tests/sermouse
SERM_TEST_FLAGS := $(HOST_CFLAGS) -I$(SERM_DIR)/c -Itests/lib -Iinclude

$(SERM_TEST_GEN)/%.o: Sources/HWSupport/SerMouse/test/sermouse/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SERM_TEST_FLAGS) -c $< -o $@

$(SERM_TEST_GEN)/%.o: $(SERM_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SERM_TEST_FLAGS) -c $< -o $@

$(SERM_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SERM_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/sermouse-checks: $(addprefix $(SERM_TEST_GEN)/,cases.o \
                                      serdecode.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

sermousechecks: $(BUILD)/tests/sermouse-checks
	@$(BUILD)/tests/sermouse-checks
.PHONY: sermousechecks

# Host checks for the PS/2 protocol a byte at a time: every port state, what
# each sends next, both scan code tables and mouse reports. Covers what the
# machine oracle cannot reach: resend exhaustion, the Stork's pseudo-port and
# the Korean keys.
PS2_DIR := Sources/HWSupport/PS2Driver
PS2_TEST_GEN := $(BUILD)/tests/ps2driver
PS2_TEST_FLAGS := $(HOST_CFLAGS) -I$(PS2_DIR)/c -Itests/lib -Iinclude

$(PS2_TEST_GEN)/%.o: Sources/HWSupport/PS2Driver/test/ps2driver/%.c $(PS2_DIR)/c/ps2proto.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PS2_TEST_FLAGS) -c $< -o $@

$(PS2_TEST_GEN)/%.o: $(PS2_DIR)/c/%.c $(PS2_DIR)/c/ps2proto.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PS2_TEST_FLAGS) -c $< -o $@

$(PS2_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PS2_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/ps2driver-checks: $(addprefix $(PS2_TEST_GEN)/,cases.o \
                                       ps2proto.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

ps2driverchecks: $(BUILD)/tests/ps2driver-checks
	@$(BUILD)/tests/ps2driver-checks
.PHONY: ps2driverchecks

# Host checks for SerialDeviceSupport's three 6850/OS_SerialOp translation
# tables and the decisions from them, including the sixty of sixty-four format
# words no program uses. Pinpoints a wrong entry the machine oracle would only
# show as a difference.
SERSPT_DIR := Sources/HWSupport/SerialSpt
SERSPT_TEST_GEN := $(BUILD)/tests/serialspt
SERSPT_TEST_FLAGS := $(HOST_CFLAGS) -I$(SERSPT_DIR)/c -Itests/lib -Iinclude

$(SERSPT_TEST_GEN)/%.o: Sources/HWSupport/SerialSpt/test/serialspt/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SERSPT_TEST_FLAGS) -c $< -o $@

$(SERSPT_TEST_GEN)/%.o: $(SERSPT_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SERSPT_TEST_FLAGS) -c $< -o $@

$(SERSPT_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SERSPT_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/serialspt-checks: $(addprefix $(SERSPT_TEST_GEN)/,cases.o \
                                       serialcalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

serialsptchecks: $(BUILD)/tests/serialspt-checks
	@$(BUILD)/tests/serialspt-checks
.PHONY: serialsptchecks

# Host checks for CDFSDriver: addressing modes, SWI to reason numbering,
# routine selection per information word, the driver list (up to sixty-four
# drivers) and SCSI sense mapping. Pinpoints which number is wrong where the
# machine oracle only shows a difference.
CDD_DIR := Sources/FileSys/CDFS/CDFSDriver
CDD_TEST_GEN := $(BUILD)/tests/cdfsdriver
CDD_TEST_FLAGS := $(HOST_CFLAGS) -I$(CDD_DIR)/c -Itests/lib -Iinclude

$(CDD_TEST_GEN)/%.o: Sources/FileSys/CDFS/CDFSDriver/test/cdfsdriver/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CDD_TEST_FLAGS) -c $< -o $@

$(CDD_TEST_GEN)/%.o: $(CDD_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CDD_TEST_FLAGS) -c $< -o $@

$(CDD_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CDD_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/cdfsdriver-checks: $(addprefix $(CDD_TEST_GEN)/,cases.o \
                                        cdcalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

cdfsdriverchecks: $(BUILD)/tests/cdfsdriver-checks
	@$(BUILD)/tests/cdfsdriver-checks
.PHONY: cdfsdriverchecks

# Host checks for every twelve-byte command block CDFSSoftATAPI sends and how
# it interprets the answers; a machine oracle reports only that the drive
# refused it.
ATAPI_DIR := Sources/FileSys/CDFS/CDFSATAPI
ATAPI_TEST_GEN := $(BUILD)/tests/cdatapi
ATAPI_TEST_FLAGS := $(HOST_CFLAGS) -I$(ATAPI_DIR)/c -Itests/lib -Iinclude

$(ATAPI_TEST_GEN)/%.o: Sources/FileSys/CDFS/CDFSATAPI/test/cdatapi/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ATAPI_TEST_FLAGS) -c $< -o $@

$(ATAPI_TEST_GEN)/%.o: $(ATAPI_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ATAPI_TEST_FLAGS) -c $< -o $@

$(ATAPI_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ATAPI_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/cdatapi-checks: $(addprefix $(ATAPI_TEST_GEN)/,cases.o \
                                     atapicalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The headers each object was built from, so a changed header rebuilds it.
-include $(wildcard $(ATAPI_TEST_GEN)/*.d)

cdatapichecks: $(BUILD)/tests/cdatapi-checks
	@$(BUILD)/tests/cdatapi-checks
.PHONY: cdatapichecks

# Host checks for CDFS's disc-to-directory translation: sectors written by the
# test, and the names, dates, types and lengths that come out.
CDFS_DIR := Sources/FileSys/CDFS/CDFS
CDFS_TEST_GEN := $(BUILD)/tests/cdfs
CDFS_TEST_FLAGS := $(HOST_CFLAGS) -I$(CDFS_DIR)/c -Itests/lib -Iinclude

$(CDFS_TEST_GEN)/%.o: Sources/FileSys/CDFS/CDFS/test/cdfs/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CDFS_TEST_FLAGS) -c $< -o $@

$(CDFS_TEST_GEN)/%.o: $(CDFS_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CDFS_TEST_FLAGS) -c $< -o $@

$(CDFS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CDFS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/cdfs-checks: $(addprefix $(CDFS_TEST_GEN)/,cases.o \
                                   isocalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

cdfschecks: $(BUILD)/tests/cdfs-checks
	@$(BUILD)/tests/cdfs-checks
.PHONY: cdfschecks

# Host checks for FSLock's arithmetic: the password fold, the two checksums,
# the drive-name cloak, and the path, filing system number and low-level
# reason decisions. Expected values are stated, not computed from the code
# under test.
FSLK_DIR := Sources/FileSys/FSLock
FSLK_TEST_GEN := $(BUILD)/tests/fslock
FSLK_TEST_FLAGS := $(HOST_CFLAGS) -I$(FSLK_DIR)/c -Itests/lib -Iinclude

$(FSLK_TEST_GEN)/%.o: Sources/FileSys/FSLock/test/fslock/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FSLK_TEST_FLAGS) -c $< -o $@

$(FSLK_TEST_GEN)/%.o: $(FSLK_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FSLK_TEST_FLAGS) -c $< -o $@

$(FSLK_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FSLK_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/fslock-checks: $(addprefix $(FSLK_TEST_GEN)/,cases.o \
                                    fslockcalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Also checks the same arithmetic against the reference ROM's module in
# `legacy/romparts` (its default password, image checksum and cloaked fold),
# which also confirms that module is intact before a machine oracle boots it:
# a bad checksum stops the machine silently.
fslockchecks: $(BUILD)/tests/fslock-checks
	@$(BUILD)/tests/fslock-checks
	@python3 Sources/FileSys/FSLock/test/fslock/reference.py $(LEGACY)/romparts/035-FSLock.mod
.PHONY: fslockchecks

# Host checks for SystemDevices: the generator, the two name tables and the
# four listing formats. Three of the formats are reachable only by
# reconfiguring a machine.
SYSDEV_DIR := Sources/Utilities/SystemDevs
SYSDEV_TEST_GEN := $(BUILD)/tests/systemdevs
SYSDEV_TEST_FLAGS := $(HOST_CFLAGS) -I$(SYSDEV_DIR)/c -Itests/lib -Iinclude

$(SYSDEV_TEST_GEN)/%.o: Sources/Utilities/SystemDevs/test/systemdevs/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SYSDEV_TEST_FLAGS) -c $< -o $@

$(SYSDEV_TEST_GEN)/%.o: $(SYSDEV_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SYSDEV_TEST_FLAGS) -c $< -o $@

$(SYSDEV_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SYSDEV_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/systemdevs-checks: $(addprefix $(SYSDEV_TEST_GEN)/,cases.o \
                                        sysdevcalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

systemdevschecks: $(BUILD)/tests/systemdevs-checks
	@$(BUILD)/tests/systemdevs-checks
.PHONY: systemdevschecks

# Host checks for PipeFS's ring, matcher and ordering, chiefly ring growth,
# which moves a wrapped buffer's tail to the top of a larger block.
PIPE_DIR := Sources/FileSys/PipeFS
PIPE_TEST_GEN := $(BUILD)/tests/pipefs
PIPE_TEST_FLAGS := $(HOST_CFLAGS) -I$(PIPE_DIR)/c -Itests/lib -Iinclude

$(PIPE_TEST_GEN)/%.o: Sources/FileSys/PipeFS/test/pipefs/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PIPE_TEST_FLAGS) -c $< -o $@

$(PIPE_TEST_GEN)/%.o: $(PIPE_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PIPE_TEST_FLAGS) -c $< -o $@

$(PIPE_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PIPE_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/pipefs-checks: $(addprefix $(PIPE_TEST_GEN)/,cases.o \
                                    pipecalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

pipefschecks: $(BUILD)/tests/pipefs-checks
	@$(BUILD)/tests/pipefs-checks
.PHONY: pipefschecks

# Host checks: where the second name in a tail starts, and how a list of names
# is walked.
SPU_DIR := Sources/Video/Render/SpriteUtils
SPU_TEST_GEN := $(BUILD)/tests/spriteutils
SPU_TEST_FLAGS := $(HOST_CFLAGS) -I$(SPU_DIR)/c -Itests/lib -Iinclude

$(SPU_TEST_GEN)/%.o: Sources/Video/Render/SpriteUtils/test/spriteutils/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPU_TEST_FLAGS) -c $< -o $@

$(SPU_TEST_GEN)/%.o: $(SPU_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPU_TEST_FLAGS) -c $< -o $@

$(SPU_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SPU_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/spriteutils-checks: $(addprefix $(SPU_TEST_GEN)/,cases.o \
                                         spargs.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

spriteutilschecks: $(BUILD)/tests/spriteutils-checks
	@$(BUILD)/tests/spriteutils-checks
.PHONY: spriteutilschecks

# Host checks: the kernel version read from a help string, and the keyword's
# switch, including a short string the reference would read past the end of.
BNET_DIR := Sources/Networking/Econet/BootNet
BNET_TEST_GEN := $(BUILD)/tests/bootnet
BNET_TEST_FLAGS := $(HOST_CFLAGS) -I$(BNET_DIR)/c -Itests/lib -Iinclude

$(BNET_TEST_GEN)/%.o: Sources/Networking/Econet/BootNet/test/bootnet/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BNET_TEST_FLAGS) -c $< -o $@

$(BNET_TEST_GEN)/%.o: $(BNET_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BNET_TEST_FLAGS) -c $< -o $@

$(BNET_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BNET_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/bootnet-checks: $(addprefix $(BNET_TEST_GEN)/,cases.o \
                                     bootcfg.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

bootnetchecks: $(BUILD)/tests/bootnet-checks
	@$(BUILD)/tests/bootnet-checks
.PHONY: bootnetchecks

# ROMFonts host checks. c/fontvars.c (the two variables) has ordinary host
# checks. The font files are compared with the reference module's chain in
# `legacy/romparts/055-ROMFonts.mod`, entry by entry (name, type, attributes,
# contents), which needs no emulator and confirms the reference module is
# intact before a two-ROM oracle runs.
# These are declared here, not beside the module further down, because a
# prerequisite list is expanded as the makefile is read: a name defined later
# expands to nothing here and the check would silently run against stale
# output. See OURS_MODULES.
ROMFONTS_DIR := Sources/Video/Render/Fonts/ROMFonts
ROMFONTS_BLOCK := $(BUILD)/modules/romfonts/resources.bin
ROMFONTS_TEST_GEN := $(BUILD)/tests/romfonts
ROMFONTS_TEST_FLAGS := $(HOST_CFLAGS) -I$(ROMFONTS_DIR)/c -Itests/lib -Iinclude

$(ROMFONTS_TEST_GEN)/%.o: Sources/Video/Render/Fonts/ROMFonts/test/romfonts/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ROMFONTS_TEST_FLAGS) -c $< -o $@

$(ROMFONTS_TEST_GEN)/%.o: $(ROMFONTS_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ROMFONTS_TEST_FLAGS) -c $< -o $@

$(ROMFONTS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(ROMFONTS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/romfonts-checks: $(addprefix $(ROMFONTS_TEST_GEN)/,cases.o \
                                      fontvars.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

romfontschecks: $(BUILD)/tests/romfonts-checks $(ROMFONTS_BLOCK) \
                Sources/Video/Render/Fonts/ROMFonts/test/romfonts/reference.py
	@$(BUILD)/tests/romfonts-checks
	@python3 Sources/Video/Render/Fonts/ROMFonts/test/romfonts/reference.py $(ROMFONTS_BLOCK) \
	  $(LEGACY)/romparts/055-ROMFonts.mod
.PHONY: romfontschecks

# The AArch64 ROM's converted fonts (romfonts/fontlist64) made again from
# their TrueType sources by tools/build/ttf2riscos.py and compared byte for
# byte, then read back with the Font Manager's file reader.
unifontschecks: $(BUILD)/tests/fontmanager-files Sources/Video/Render/Fonts/ROMFonts/test/unifonts/regen.py \
                tools/build/ttf2riscos.py
	@python3 Sources/Video/Render/Fonts/ROMFonts/test/unifonts/regen.py $(BUILD)/tests/fontmanager-files \
	  $(BUILD)/tests/unifonts
.PHONY: unifontschecks

# Resource text under both alphabets: every Messages, CmdHelp and Templates
# text is ASCII, UTF-8, or Latin-1 that Latin-1 fallback decoding shows the
# same under the UTF-8 alphabet as under Latin1.
latin1reschecks: Sources/Internat/UnicodeData/test/unicode/latin1res.py
	@python3 Sources/Internat/UnicodeData/test/unicode/latin1res.py
.PHONY: latin1reschecks
hostchecks: unifontschecks latin1reschecks

# Host checks for what the thirteen commands decide without an OS: size
# suffixes, a configuration file's shape and checksum, the ResourceFS record
# *AddApp builds, the line *IfThere passes on, and *Repeat's three decisions
# about a directory entry.
BCMD_DIR := Sources/Utilities/BootCmds
BCMD_TEST_GEN := $(BUILD)/tests/bootcmds
BCMD_TEST_FLAGS := $(HOST_CFLAGS) -I$(BCMD_DIR)/c -Itests/lib -Iinclude

$(BCMD_TEST_GEN)/%.o: Sources/Utilities/BootCmds/test/bootcmds/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BCMD_TEST_FLAGS) -c $< -o $@

$(BCMD_TEST_GEN)/%.o: $(BCMD_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BCMD_TEST_FLAGS) -c $< -o $@

$(BCMD_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BCMD_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/bootcmds-checks: $(addprefix $(BCMD_TEST_GEN)/,cases.o \
                                      bootcalc.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/bootcmds-repeat-checks: Sources/Utilities/BootCmds/test/bootcmds/repeat.c \
    $(BCMD_DIR)/c/bootrepeat.c $(BCMD_DIR)/c/bootcalc.c \
    Sources/Utilities/BootCmds/test/bootcmds/host/riscos/swi.h Sources/Utilities/BootCmds/test/bootcmds/host/riscos/deskswi.h \
    Sources/Utilities/BootCmds/test/bootcmds/host/riscos/bootfxops.h \
    $(BUILD)/modules/bootcmds/bootcmds_veneers.h $(TEST_LIB)/check.c
	@mkdir -p $(dir $@)
	$(HOSTCC) -ISources/Utilities/BootCmds/test/bootcmds/host $(HOST_CFLAGS) -m32 -funsigned-char \
	  -Itests/lib -Iinclude -I$(BUILD)/modules/bootcmds \
	  Sources/Utilities/BootCmds/test/bootcmds/repeat.c $(BCMD_DIR)/c/bootcalc.c $(TEST_LIB)/check.c -o $@

bootcmdschecks: $(BUILD)/tests/bootcmds-checks $(BUILD)/tests/bootcmds-repeat-checks
	@$(BUILD)/tests/bootcmds-checks
	@$(BUILD)/tests/bootcmds-repeat-checks
.PHONY: bootcmdschecks

# Host checks for what *IICDevices decides before printing: addresses never
# offered, each address's token, and number columns, including the four-digit
# case the reference gets wrong.
IIC_DIR := Sources/HWSupport/IIC
IIC_TEST_GEN := $(BUILD)/tests/iicscan
IIC_TEST_FLAGS := $(HOST_CFLAGS) -I$(IIC_DIR)/c -Itests/lib -Iinclude

$(IIC_TEST_GEN)/%.o: Sources/HWSupport/IIC/test/iicscan/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(IIC_TEST_FLAGS) -c $< -o $@

$(IIC_TEST_GEN)/%.o: $(IIC_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(IIC_TEST_FLAGS) -c $< -o $@

$(IIC_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(IIC_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/iicscan-checks: $(addprefix $(IIC_TEST_GEN)/,cases.o \
                                   iicscan.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

iicscanchecks: $(BUILD)/tests/iicscan-checks
	@$(BUILD)/tests/iicscan-checks
.PHONY: iicscanchecks

# BufferManager: byte rings anyone can insert into or remove from, reached
# through INSV, REMV and CNPV. c/bufring.c holds the arithmetic (the two
# indices, the one slot never handed back, free and used space and their wrap
# rules), issues no SWI and is checked on the build machine. c/bufman.c claims
# the vectors, issues the events and upcalls, and wakes the device at the end
# of a ring.
BUFS_OBJS := $(BUILD)/modules/buffers/modhead.o \
             $(BUILD)/modules/buffers/bufring.o \
             $(BUILD)/modules/buffers/bufman.o \
             $(BUILD)/modules/buffers/veneers.o
BUFS_MOD  := $(BUILD)/modules/BufferManager.mod
BUFS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/Buffers/c \
               -I$(BUILD)/modules/buffers

$(BUILD)/modules/buffers/modhead.S: Sources/HWSupport/Buffers/cmhg/modhead \
                                    Sources/HWSupport/Buffers/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/Buffers -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/buffers/%.o: Sources/HWSupport/Buffers/c/%.c Makefile \
                              $(BUILD)/modules/buffers/bufman_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(BUFS_CFLAGS) -c $< -o $@

# The module entries, the ten SWIs, the three vector claimants (each enters
# supervisor mode because it issues a SWI), and the routine whose address
# Buffer_InternalInfo publishes.
$(BUILD)/modules/buffers/veneers.S: Sources/HWSupport/Buffers/s/boundaries \
                                    Sources/HWSupport/Buffers/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/Buffers --asm $@ \
	  --header $(BUILD)/modules/buffers/bufman_veneers.h

$(BUILD)/modules/buffers/bufman_veneers.h: $(BUILD)/modules/buffers/veneers.S

$(BUILD)/modules/buffers/veneers.o: $(BUILD)/modules/buffers/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(BUFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/buffers/modhead.o: $(BUILD)/modules/buffers/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(BUFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/BufferManager.elf: $(BUFS_OBJS) $(CLIB) mk/ld/module.ld \
                                    tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(BUFS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(BUFS_MOD): $(BUILD)/modules/BufferManager.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, BufferManager - ported)"

-include $(BUFS_OBJS:.o=.d)

# DeviceFS: the filing system that presents device drivers as files. All eight
# SWIs and the character-stream filing system are implemented.
DEVFS_OBJS := $(BUILD)/modules/devicefs/modhead.o \
              $(BUILD)/modules/devicefs/devreg.o \
              $(BUILD)/modules/devicefs/special.o \
              $(BUILD)/modules/devicefs/devdir.o \
              $(BUILD)/modules/devicefs/stream.o \
              $(BUILD)/modules/devicefs/devicefs.o \
              $(BUILD)/modules/devicefs/veneers.o
DEVFS_MOD  := $(BUILD)/modules/DeviceFS.mod
DEVFS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/DeviceFS/c \
                -I$(BUILD)/modules/devicefs

$(BUILD)/modules/devicefs/modhead.S: Sources/FileSys/DeviceFS/cmhg/modhead \
                                     Sources/FileSys/DeviceFS/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/DeviceFS -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/devicefs/%.o: Sources/FileSys/DeviceFS/c/%.c Makefile \
                               $(BUILD)/modules/devicefs/devfs_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(DEVFS_CFLAGS) -c $< -o $@

$(BUILD)/modules/devicefs/veneers.S: Sources/FileSys/DeviceFS/s/boundaries \
                                     Sources/FileSys/DeviceFS/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/DeviceFS --asm $@ \
	  --header $(BUILD)/modules/devicefs/devfs_veneers.h

$(BUILD)/modules/devicefs/devfs_veneers.h: $(BUILD)/modules/devicefs/veneers.S

$(BUILD)/modules/devicefs/veneers.o: $(BUILD)/modules/devicefs/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(DEVFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/devicefs/modhead.o: $(BUILD)/modules/devicefs/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(DEVFS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/DeviceFS.elf: $(DEVFS_OBJS) $(CLIB) mk/ld/module.ld \
                               tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(DEVFS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(DEVFS_MOD): $(BUILD)/modules/DeviceFS.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, DeviceFS)"

.PHONY: devicefsmodule
devicefsmodule: $(DEVFS_MOD)

-include $(DEVFS_OBJS:.o=.d)

buffersmodule: $(BUFS_MOD)
.PHONY: buffersmodule

# RTC: the battery-backed clock, and the soft clock kept in step with it.
#
# Four SWIs over a HAL device, and an hourly ticker that slews the centisecond
# timer by up to twenty parts in a thousand so the soft clock converges
# without jumping. c/rtcadjust.c (packed decimal, the year for chips that
# cannot hold one, the slew) is checked on the build machine; c/rtcmodule.c
# finds the clock, reads the CMOS and sets the timer.
RTC_OBJS := $(BUILD)/modules/rtc/modhead.o \
            $(BUILD)/modules/rtc/rtcadjust.o \
            $(BUILD)/modules/rtc/rtcmodule.o \
            $(BUILD)/modules/rtc/veneers.o
RTC_MOD  := $(BUILD)/modules/RTC.mod
RTC_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/RTC/c \
              -I$(BUILD)/modules/rtc

$(BUILD)/modules/rtc/modhead.S: Sources/HWSupport/RTC/cmhg/modhead \
                                Sources/HWSupport/RTC/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/RTC -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/rtc/%.o: Sources/HWSupport/RTC/c/%.c Makefile \
                          $(BUILD)/modules/rtc/rtc_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(RTC_CFLAGS) -c $< -o $@

# The module entries, four SWIs, the ticker and its callback, generated with
# their prototypes from one declaration. See tools/build/marshal.py.
$(BUILD)/modules/rtc/veneers.S: Sources/HWSupport/RTC/s/boundaries \
                                Sources/HWSupport/RTC/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/RTC --asm $@ \
	  --header $(BUILD)/modules/rtc/rtc_veneers.h

$(BUILD)/modules/rtc/rtc_veneers.h: $(BUILD)/modules/rtc/veneers.S

$(BUILD)/modules/rtc/veneers.o: $(BUILD)/modules/rtc/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(RTC_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/rtc/modhead.o: $(BUILD)/modules/rtc/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(RTC_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/RTC.elf: $(RTC_OBJS) $(CLIB) mk/ld/module.ld \
                          tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(RTC_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(RTC_MOD): $(BUILD)/modules/RTC.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, RTC)"

-include $(RTC_OBJS:.o=.d)

rtcmodule: $(RTC_MOD)
.PHONY: rtcmodule

# ROMFonts: the outline fonts and encodings in ROM, and the two variables that
# locate them.
#
# The forty-two files are the reference component's own;
# `Sources/Video/Render/Fonts/ROMFonts/fontlist` gives which a ROM registers and in what
# order (neither alphabetical nor directory order). c/fontvars.c decides
# `Font$Path` and `Font$Prefix` and is checked on the build machine;
# c/romfonts.c calls the OS.
# The wildcard covers four levels, the whole tree: `Messages1` at the top, an
# encoding below `Encodings`, a weight's files below a family, and an
# oblique's below that.
ROMFONTS_FILES := $(sort $(wildcard $(ROMFONTS_DIR)/Fonts/* $(ROMFONTS_DIR)/Fonts/*/* \
  $(ROMFONTS_DIR)/Fonts/*/*/* $(ROMFONTS_DIR)/Fonts/*/*/*/*))
ROMFONTS_OBJS := $(BUILD)/modules/romfonts/modhead.o \
                 $(BUILD)/modules/romfonts/fontvars.o \
                 $(BUILD)/modules/romfonts/romfonts.o \
                 $(BUILD)/modules/romfonts/resources.o \
                 $(BUILD)/modules/romfonts/veneers.o
ROMFONTS_MOD  := $(BUILD)/modules/ROMFonts.mod
ROMFONTS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(ROMFONTS_DIR)/c \
                   -I$(BUILD)/modules/romfonts

$(ROMFONTS_BLOCK): $(ROMFONTS_FILES) $(ROMFONTS_DIR)/fontlist \
                   tools/build/mkresfs.py
	@mkdir -p $(dir $@)
	@python3 tools/build/mkresfs.py $@ \
	  --tree Fonts=$(ROMFONTS_DIR)/Fonts \
	  --tree-list $(ROMFONTS_DIR)/fontlist

$(BUILD)/modules/romfonts/modhead.S: $(ROMFONTS_DIR)/cmhg/modhead \
                                     $(ROMFONTS_DIR)/VersionNum \
                                     tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(ROMFONTS_DIR) -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/romfonts/%.o: $(ROMFONTS_DIR)/c/%.c Makefile \
                               $(BUILD)/modules/romfonts/romfonts_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(ROMFONTS_CFLAGS) -c $< -o $@

$(BUILD)/modules/romfonts/veneers.S: $(ROMFONTS_DIR)/s/boundaries \
                                     $(ROMFONTS_DIR)/cmhg/modhead \
                                     tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(ROMFONTS_DIR) --asm $@ \
	  --header $(BUILD)/modules/romfonts/romfonts_veneers.h

$(BUILD)/modules/romfonts/romfonts_veneers.h: \
    $(BUILD)/modules/romfonts/veneers.S

$(BUILD)/modules/romfonts/veneers.o: $(BUILD)/modules/romfonts/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(ROMFONTS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/romfonts/modhead.o: $(BUILD)/modules/romfonts/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(ROMFONTS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/romfonts/resources.o: $(ROMFONTS_DIR)/s/resources.S \
                                       $(ROMFONTS_BLOCK)
	@mkdir -p $(dir $@)
	$(CC) $(ROMFONTS_CFLAGS) -x assembler-with-cpp \
	  -DRESOURCE_BLOCK='"$(ROMFONTS_BLOCK)"' -c $< -o $@

$(BUILD)/modules/ROMFonts.elf: $(ROMFONTS_OBJS) $(CLIB) mk/ld/module.ld \
                               tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(ROMFONTS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(ROMFONTS_MOD): $(BUILD)/modules/ROMFonts.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, ROMFonts)"

-include $(ROMFONTS_OBJS:.o=.d)

romfontsmodule: $(ROMFONTS_MOD)
.PHONY: romfontsmodule

# Squash: LZW compression and decompression in Unix compress format (readable
# by `gzip -d`, and able to read the squashed sprites in the reference ROM).
# Both directions are restartable when the caller runs out of input or room;
# all state lives in the caller's workspace. That is c/squashlzw.c, checked by
# Sources/Utilities/Squash/test/squash on the build machine. c/squashmodule.c is the SWI half; the
# original's two hand-written whole-buffer ARM routines are not ported, but
# their answers are kept.
SQ_OBJS := $(BUILD)/modules/squash/modhead.o \
           $(BUILD)/modules/squash/squashlzw.o \
           $(BUILD)/modules/squash/squashmodule.o \
           $(BUILD)/modules/squash/veneers.o
SQ_MOD  := $(BUILD)/modules/Squash.mod
SQ_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Utilities/Squash/c \
             -I$(BUILD)/modules/squash

$(BUILD)/modules/squash/modhead.S: Sources/Utilities/Squash/cmhg/modhead \
                                   Sources/Utilities/Squash/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Utilities/Squash -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/squash/%.o: Sources/Utilities/Squash/c/%.c Makefile \
                             $(BUILD)/modules/squash/squash_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SQ_CFLAGS) -c $< -o $@

# The veneers, generated with their C prototypes from one declaration so the
# two cannot drift. See tools/build/marshal.py.
$(BUILD)/modules/squash/veneers.S: Sources/Utilities/Squash/s/boundaries \
                                   Sources/Utilities/Squash/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Utilities/Squash --asm $@ \
	  --header $(BUILD)/modules/squash/squash_veneers.h

$(BUILD)/modules/squash/squash_veneers.h: $(BUILD)/modules/squash/veneers.S

$(BUILD)/modules/squash/veneers.o: $(BUILD)/modules/squash/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SQ_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/squash/modhead.o: $(BUILD)/modules/squash/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SQ_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/Squash.elf: $(SQ_OBJS) $(CLIB) mk/ld/module.ld \
                             tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SQ_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SQ_MOD): $(BUILD)/modules/Squash.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Squash)"

-include $(SQ_OBJS:.o=.d)

squashmodule: $(SQ_MOD)
.PHONY: squashmodule

# FileSwitch. A colon is an ordinary filename character, so a filing system
# prefix is not simply the text before the first colon; the checks assert the
# original's grammar and every place it gives up, including the comma that
# keeps a filetype out of a special field.
FSW_OBJS := $(BUILD)/modules/fileswitch/modhead.o \
            $(BUILD)/modules/fileswitch/fsargs.o \
            $(BUILD)/modules/fileswitch/fsbuf.o \
            $(BUILD)/modules/fileswitch/fsbyte.o \
            $(BUILD)/modules/fileswitch/fscanon.o \
            $(BUILD)/modules/fileswitch/fschain.o \
            $(BUILD)/modules/fileswitch/fsfile.o \
            $(BUILD)/modules/fileswitch/fsgbpb.o \
            $(BUILD)/modules/fileswitch/fsimage.o \
            $(BUILD)/modules/fileswitch/fstype.o \
            $(BUILD)/modules/fileswitch/fsdir.o \
            $(BUILD)/modules/fileswitch/fsdirs.o \
            $(BUILD)/modules/fileswitch/fsname.o \
            $(BUILD)/modules/fileswitch/fspath.o \
            $(BUILD)/modules/fileswitch/fspolice.o \
            $(BUILD)/modules/fileswitch/fsstream.o \
            $(BUILD)/modules/fileswitch/fswmodule.o \
            $(BUILD)/modules/fileswitch/veneers.o \
            $(BUILD)/modules/fileswitch/run.o
FSW_MOD  := $(BUILD)/modules/FileSwitch.mod
FSW_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/FileSys/FileSwitch/c \
              -I$(BUILD)/modules/fileswitch

$(BUILD)/modules/fileswitch/modhead.S: Sources/FileSys/FileSwitch/cmhg/modhead \
                                       Sources/FileSys/FileSwitch/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/FileSys/FileSwitch -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/fileswitch/%.o: Sources/FileSys/FileSwitch/c/%.c Makefile \
                                 $(BUILD)/modules/fileswitch/fsw_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(FSW_CFLAGS) -c $< -o $@

# The OS entry veneers and the routines that call into a filing system,
# generated with their C prototypes from one declaration. See
# tools/build/marshal.py.
$(BUILD)/modules/fileswitch/veneers.S: Sources/FileSys/FileSwitch/s/boundaries \
                                       Sources/FileSys/FileSwitch/cmhg/modhead \
                                       tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/FileSys/FileSwitch --asm $@ \
	  --header $(BUILD)/modules/fileswitch/fsw_veneers.h

$(BUILD)/modules/fileswitch/fsw_veneers.h: $(BUILD)/modules/fileswitch/veneers.S

$(BUILD)/modules/fileswitch/veneers.o: $(BUILD)/modules/fileswitch/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(FSW_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/fileswitch/run.o: Sources/FileSys/FileSwitch/s/aarch32/run.S
	@mkdir -p $(dir $@)
	$(CC) $(FSW_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/fileswitch/modhead.o: $(BUILD)/modules/fileswitch/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(FSW_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/FileSwitch.elf: $(FSW_OBJS) $(CLIB) mk/ld/module.ld \
                                 tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(FSW_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(FSW_MOD): $(BUILD)/modules/FileSwitch.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, FileSwitch - ported, parity not claimed)"

-include $(FSW_OBJS:.o=.d)

fileswitchmodule: $(FSW_MOD)
.PHONY: fileswitchmodule

FSW_DIR := Sources/FileSys/FileSwitch
FSW_TEST_GEN := $(BUILD)/tests/fileswitch
# The ARM32 layout: a control block links the next by a word.
FSW_TEST_FLAGS := $(HOST_CFLAGS) -m32 -funsigned-char -I$(FSW_DIR)/c -Itests/lib -Iinclude

$(FSW_TEST_GEN)/%.o: Sources/FileSys/FileSwitch/test/fileswitch/%.c $(FSW_DIR)/c/fsname.h \
                                            $(FSW_DIR)/c/fschain.h \
                                            $(FSW_DIR)/c/fsstream.h \
                                      $(FSW_DIR)/c/fspolice.h \
                                      $(FSW_DIR)/c/fsdirs.h \
                                      $(FSW_DIR)/c/fspath.h \
                                      $(FSW_DIR)/c/fscanon.h \
                                      $(FSW_DIR)/c/fsargs.h \
                                      $(FSW_DIR)/c/fsgbpb.h \
                                      $(FSW_DIR)/c/fsbyte.h \
                                      $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fspolice.h \
                                      $(FSW_DIR)/c/fsdirs.h \
                                      $(FSW_DIR)/c/fspath.h \
                                      $(FSW_DIR)/c/fscanon.h \
                                      $(FSW_DIR)/c/fsargs.h \
                                      $(FSW_DIR)/c/fsgbpb.h \
                                      $(FSW_DIR)/c/fsbyte.h \
                                      $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fsdirs.h \
                                      $(FSW_DIR)/c/fspath.h \
                                      $(FSW_DIR)/c/fscanon.h \
                                      $(FSW_DIR)/c/fsargs.h \
                                      $(FSW_DIR)/c/fsgbpb.h \
                                      $(FSW_DIR)/c/fsbyte.h \
                                      $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fspath.h \
                                      $(FSW_DIR)/c/fscanon.h \
                                      $(FSW_DIR)/c/fsargs.h \
                                      $(FSW_DIR)/c/fsgbpb.h \
                                      $(FSW_DIR)/c/fsbyte.h \
                                      $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fscanon.h \
                                      $(FSW_DIR)/c/fsargs.h \
                                      $(FSW_DIR)/c/fsgbpb.h \
                                      $(FSW_DIR)/c/fsbyte.h \
                                      $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fsargs.h \
                                      $(FSW_DIR)/c/fsgbpb.h \
                                      $(FSW_DIR)/c/fsbyte.h \
                                      $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fsgbpb.h \
                                      $(FSW_DIR)/c/fsbyte.h \
                                      $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fsbyte.h \
                                      $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h \
                                            $(FSW_DIR)/c/fsbuf.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FSW_TEST_FLAGS) -c $< -o $@

$(FSW_TEST_GEN)/%.o: $(FSW_DIR)/c/%.c $(FSW_DIR)/c/fsname.h \
                                      $(FSW_DIR)/c/fschain.h \
                                      $(FSW_DIR)/c/fsstream.h \
                                      $(FSW_DIR)/c/fspolice.h \
                                      $(FSW_DIR)/c/fsdirs.h \
                                      $(FSW_DIR)/c/fspath.h \
                                      $(FSW_DIR)/c/fscanon.h \
                                      $(FSW_DIR)/c/fsargs.h \
                                      $(FSW_DIR)/c/fsgbpb.h \
                                      $(FSW_DIR)/c/fsbyte.h \
                                      $(FSW_DIR)/c/fsfile.h \
                                      $(FSW_DIR)/c/fsdir.h \
                                      $(FSW_DIR)/c/fsbuf.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FSW_TEST_FLAGS) -c $< -o $@

$(FSW_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FSW_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/fileswitch-checks: $(addprefix $(FSW_TEST_GEN)/,checks.o \
                                    fsname.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fschain-checks: $(addprefix $(FSW_TEST_GEN)/,chaincheck.o \
                                 fschain.o fsname.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fsimage-checks: $(addprefix $(FSW_TEST_GEN)/,imagecheck.o \
                                 fsimage.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fstype-checks: $(addprefix $(FSW_TEST_GEN)/,typecheck.o \
                                fstype.o fsimage.o fsname.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fsstream-checks: $(addprefix $(FSW_TEST_GEN)/,streamcheck.o \
                                  fsstream.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fspolice-checks: $(addprefix $(FSW_TEST_GEN)/,policecheck.o \
                                  fspolice.o fsname.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fsdirs-checks: $(addprefix $(FSW_TEST_GEN)/,dircheck.o \
                                fsdirs.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fspath-checks: $(addprefix $(FSW_TEST_GEN)/,pathcheck.o \
                                fspath.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fscanon-checks: $(addprefix $(FSW_TEST_GEN)/,canoncheck.o \
                                 fscanon.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fsargs-checks: $(addprefix $(FSW_TEST_GEN)/,argscheck.o \
                                fsargs.o fsstream.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fsgbpb-checks: $(addprefix $(FSW_TEST_GEN)/,gbpbcheck.o \
                                fsgbpb.o fsstream.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fsbyte-checks: $(addprefix $(FSW_TEST_GEN)/,bytecheck.o \
                                fsbyte.o fsstream.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fsfile-checks: $(addprefix $(FSW_TEST_GEN)/,filecheck.o \
                                fsfile.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fsdir-checks: $(addprefix $(FSW_TEST_GEN)/,dirreadcheck.o \
                               fsdir.o fsfile.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

$(BUILD)/tests/fsbuf-checks: $(addprefix $(FSW_TEST_GEN)/,bufcheck.o \
                               fsbuf.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -m32 -o $@ $^

fswcover:
	@python3 tools/check/fswcover.py
.PHONY: fswcover

fileswitchchecks: fswcover $(BUILD)/tests/fileswitch-checks $(BUILD)/tests/fschain-checks \
                  $(BUILD)/tests/fsstream-checks $(BUILD)/tests/fspolice-checks \
                  $(BUILD)/tests/fsdirs-checks $(BUILD)/tests/fspath-checks \
                  $(BUILD)/tests/fscanon-checks $(BUILD)/tests/fsargs-checks \
                  $(BUILD)/tests/fsgbpb-checks $(BUILD)/tests/fsbyte-checks \
                  $(BUILD)/tests/fsfile-checks $(BUILD)/tests/fsdir-checks \
                  $(BUILD)/tests/fsbuf-checks $(BUILD)/tests/fsimage-checks \
                  $(BUILD)/tests/fstype-checks
	@$(BUILD)/tests/fileswitch-checks
	@$(BUILD)/tests/fschain-checks
	@$(BUILD)/tests/fsstream-checks
	@$(BUILD)/tests/fspolice-checks
	@$(BUILD)/tests/fsdirs-checks
	@$(BUILD)/tests/fspath-checks
	@$(BUILD)/tests/fscanon-checks
	@$(BUILD)/tests/fsargs-checks
	@$(BUILD)/tests/fsgbpb-checks
	@$(BUILD)/tests/fsbyte-checks
	@$(BUILD)/tests/fsfile-checks
	@$(BUILD)/tests/fsdir-checks
	@$(BUILD)/tests/fsbuf-checks
	@$(BUILD)/tests/fsimage-checks
	@$(BUILD)/tests/fstype-checks
.PHONY: fileswitchchecks

# UK territory host checks: the character tables and date formatting. The
# test reads the territory's shipped message file, so expected strings are the
# real tokens.
UKT_TEST_GEN := $(BUILD)/tests/ukterritory
UKT_TEST_FLAGS := $(HOST_CFLAGS) -I$(UKT_DIR)/c -ISources/Internat/Territory/Manager/c \
                  -I$(UKT_GEN) -Itests/lib -Iinclude

$(UKT_TEST_GEN)/%.o: Sources/Internat/Territory/UK/test/ukterritory/%.c $(UKT_DIR)/c/ukdate.h \
                                           $(UKT_DIR)/c/uktables.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(UKT_TEST_FLAGS) -c $< -o $@

$(UKT_TEST_GEN)/%.o: $(UKT_DIR)/c/%.c $(UKT_DIR)/c/uktables.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(UKT_TEST_FLAGS) -c $< -o $@

$(UKT_TEST_GEN)/uktables_data.o: $(UKT_TABLES) $(UKT_DIR)/c/uktables.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(UKT_TEST_FLAGS) -c $< -o $@

$(UKT_TEST_GEN)/terrtime.o: Sources/Internat/Territory/Manager/c/terrtime.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(UKT_TEST_FLAGS) -c $< -o $@

$(UKT_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(UKT_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/ukterritory-checks: $(addprefix $(UKT_TEST_GEN)/,checks.o \
                                     ukdate.o ukdst.o ukcollate.o \
                                     ukparse.o \
                                     uktables.o \
                                     uktables_data.o \
                                     terrtime.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

ukterritorychecks: $(BUILD)/tests/ukterritory-checks
	@$< $(UKT_DIR)/Resources/UK/Territory $(UKT_DIR)/Resources/UK/Messages
.PHONY: ukterritorychecks

TERR_DIR := Sources/Internat/Territory/Manager
TERR_TEST_GEN := $(BUILD)/tests/territory
TERR_TEST_FLAGS := $(HOST_CFLAGS) -I$(TERR_DIR)/c -Itests/lib -Iinclude

TERR_PURE := daylight registry terrfmt terrtime terrzone

$(TERR_TEST_GEN)/%.o: Sources/Internat/Territory/Manager/test/territory/%.c $(TERR_DIR)/c/daylight.h \
                                          $(TERR_DIR)/c/registry.h \
                                          $(TERR_DIR)/c/terrtime.h \
                                          $(TERR_DIR)/c/terrzone.h \
                                          $(TERR_DIR)/c/terrfmt.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(TERR_TEST_FLAGS) -c $< -o $@

$(TERR_TEST_GEN)/%.o: $(TERR_DIR)/c/%.c $(TERR_DIR)/c/daylight.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(TERR_TEST_FLAGS) -c $< -o $@

$(TERR_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(TERR_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/territory-checks: $(addprefix $(TERR_TEST_GEN)/,checks.o \
                                   $(TERR_PURE:%=%.o) check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

territorychecks: $(BUILD)/tests/territory-checks
	@$<
.PHONY: territorychecks

# Obey: parsing a *Obey tail. Options stop at the first space (`-c -v` sets
# one option, `-c-v` two); the filename stops at any control character; the
# directory is the filename up to its last dot, or `@` if it has none.
OBEY_DIR := Sources/Utilities/Obey
OBEY_TEST_GEN := $(BUILD)/tests/obey
OBEY_TEST_FLAGS := $(HOST_CFLAGS) -I$(OBEY_DIR)/c -Itests/lib -Iinclude

$(OBEY_TEST_GEN)/%.o: Sources/Utilities/Obey/test/obey/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(OBEY_TEST_FLAGS) -c $< -o $@

$(OBEY_TEST_GEN)/%.o: $(OBEY_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(OBEY_TEST_FLAGS) -c $< -o $@

$(OBEY_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(OBEY_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/obeycmd-checks: $(addprefix $(OBEY_TEST_GEN)/,cmdcheck.o \
                                 obeycmd.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The line reader works a byte at a time and serves both an open stream and an
# in-memory copy; only the byte source differs, and the end-of-file byte is
# the same either way.
$(BUILD)/tests/obeyline-checks: $(addprefix $(OBEY_TEST_GEN)/,linecheck.o \
                                  obeyline.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# A line of an obey file may start an application, which does not return, so
# per-file state is an RMA block an exit handler can return to. Blocks are
# chained because *Obey may itself appear in an obey file.
$(BUILD)/tests/obeyblock-checks: $(addprefix $(OBEY_TEST_GEN)/,blockcheck.o \
                                   obeyblock.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

obeychecks: $(BUILD)/tests/obeycmd-checks $(BUILD)/tests/obeyline-checks \
            $(BUILD)/tests/obeyblock-checks
	@$(BUILD)/tests/obeycmd-checks
	@$(BUILD)/tests/obeyline-checks
	@$(BUILD)/tests/obeyblock-checks
.PHONY: obeychecks

# Mouse: a wrapping counter and an inverted latch.
MOUSE_DIR := Sources/HWSupport/Mouse
MOUSE_TEST_GEN := $(BUILD)/tests/mouse
MOUSE_TEST_FLAGS := $(HOST_CFLAGS) -I$(MOUSE_DIR)/c -Itests/lib -Iinclude

$(MOUSE_TEST_GEN)/%.o: Sources/HWSupport/Mouse/test/mouse/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MOUSE_TEST_FLAGS) -c $< -o $@

$(MOUSE_TEST_GEN)/%.o: $(MOUSE_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MOUSE_TEST_FLAGS) -c $< -o $@

$(MOUSE_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MOUSE_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/mousedev-checks: $(addprefix $(MOUSE_TEST_GEN)/,devcheck.o \
                                  mousedev.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Named for the module because `mousechecks` is the kernel's (pointer position
# and OS_Mouse queue).
mousemodchecks: $(BUILD)/tests/mousedev-checks
	@$(BUILD)/tests/mousedev-checks
.PHONY: mousemodchecks

# ScrSaver: two comparisons and a command line, everything in the module
# other than SWIs.
SAV_DIR := Sources/Desktop/ScrSaver
SAV_TEST_GEN := $(BUILD)/tests/scrsaver
SAV_TEST_FLAGS := $(HOST_CFLAGS) -I$(SAV_DIR)/c -Itests/lib -Iinclude

$(SAV_TEST_GEN)/%.o: Sources/Desktop/ScrSaver/test/scrsaver/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SAV_TEST_FLAGS) -c $< -o $@

$(SAV_TEST_GEN)/%.o: $(SAV_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SAV_TEST_FLAGS) -c $< -o $@

$(SAV_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SAV_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/scrsaver-checks: $(addprefix $(SAV_TEST_GEN)/,decidecheck.o \
                                 scrsaver_decide.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

scrsaverchecks: $(BUILD)/tests/scrsaver-checks
	@$(BUILD)/tests/scrsaver-checks
.PHONY: scrsaverchecks

# MimeMap: the mapping file and its four lookups.
MM_DIR := Sources/Networking/MimeMap
MM_TEST_GEN := $(BUILD)/tests/mimemap
MM_TEST_FLAGS := $(HOST_CFLAGS) -I$(MM_DIR)/c -Itests/lib -Iinclude

$(MM_TEST_GEN)/%.o: Sources/Networking/MimeMap/test/mimemap/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MM_TEST_FLAGS) -c $< -o $@

$(MM_TEST_GEN)/%.o: $(MM_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MM_TEST_FLAGS) -c $< -o $@

$(MM_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MM_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/mimetable-checks: $(addprefix $(MM_TEST_GEN)/,tablecheck.o \
                                   mimetable.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

mimemapchecks: $(BUILD)/tests/mimetable-checks $(BUILD)/tests/mimemap-reload-checks
	@$(BUILD)/tests/mimetable-checks
	@$(BUILD)/tests/mimemap-reload-checks
.PHONY: mimemapchecks

$(BUILD)/tests/mimemap-reload-checks: Sources/Networking/MimeMap/test/mimemap/reload.c \
        $(MM_DIR)/c/mimemodule.c $(MM_DIR)/c/mimetable.c $(MM_DIR)/c/mimetable.h \
        $(BUILD)/modules/mimemap/mime_veneers.h $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MM_TEST_FLAGS) -I$(BUILD)/modules/mimemap \
	  -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast \
	  -ffunction-sections -fdata-sections -Wl,--gc-sections \
	  Sources/Networking/MimeMap/test/mimemap/reload.c $(MM_DIR)/c/mimetable.c $(TEST_LIB)/check.c -o $@

# The mbuf pool at the target ABI. -m32 is required: the checks assert the
# offset of every mbctl field, a layout compiled into every DCI 4 client, and
# 64-bit pointers move them all.
MBUF_DIR := Sources/Networking/MManager
MBUF_TEST_GEN := $(BUILD)/tests/mbufmanager
MBUF_TEST_FLAGS := -m32 -funsigned-char $(HOST_CFLAGS) -I$(MBUF_DIR)/c \
                   -Itests/lib -Iinclude

$(MBUF_TEST_GEN)/%.o: Sources/Networking/MManager/test/mbufmanager/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MBUF_TEST_FLAGS) -c $< -o $@

$(MBUF_TEST_GEN)/%.o: $(MBUF_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MBUF_TEST_FLAGS) -c $< -o $@

$(MBUF_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(MBUF_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/mbufmanager-checks: $(addprefix $(MBUF_TEST_GEN)/,checks.o \
                                     mbufpool.o check.o)
	$(HOSTCC) -m32 $(HOST_SANITISE) -o $@ $^

# Also checks the seven error numbers and two block sizes against the
# reference module's image.
mbufmanagerchecks: $(BUILD)/tests/mbufmanager-checks \
                   Sources/Networking/MManager/test/mbufmanager/reference.py
	@$(BUILD)/tests/mbufmanager-checks
	@python3 Sources/Networking/MManager/test/mbufmanager/reference.py
.PHONY: mbufmanagerchecks

# DHCP's protocol and conversations, on the build machine.
#
# -m32 and -funsigned-char as for the mbuf manager: Sources/Networking/Inet/DHCP/test/dhcp/cases.c asserts
# the layout of a 548-byte packet.
#
# Sources/Networking/Inet/DHCP/test/dhcp/stand.c implements everything Sources/Networking/Inet/DHCP/c/dhcphost.h
# declares, so whole conversations (offer, request, acknowledge, refuse,
# decline, expire) run and are checked.
DHCP_DIR := Sources/Networking/Inet/DHCP
DHCP_TEST_GEN := $(BUILD)/tests/dhcp
DHCP_TEST_FLAGS := -m32 -funsigned-char $(HOST_CFLAGS) -I$(DHCP_DIR)/c \
                   -ISources/Networking/Inet/DHCP/test/dhcp -Itests/lib -Iinclude

$(DHCP_TEST_GEN)/%.o: Sources/Networking/Inet/DHCP/test/dhcp/%.c Sources/Networking/Inet/DHCP/test/dhcp/stand.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DHCP_TEST_FLAGS) -c $< -o $@

$(DHCP_TEST_GEN)/%.o: $(DHCP_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DHCP_TEST_FLAGS) -c $< -o $@

$(DHCP_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DHCP_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/dhcp-checks: $(addprefix $(DHCP_TEST_GEN)/,cases.o stand.o \
                              dhcppkt.o dhcpif.o check.o)
	$(HOSTCC) -m32 $(HOST_SANITISE) -o $@ $^

# Also checks the SWI chunk, command table and help string against the
# reference module's image.
dhcpchecks: $(BUILD)/tests/dhcp-checks Sources/Networking/Inet/DHCP/test/dhcp/reference.py
	@$(BUILD)/tests/dhcp-checks
	@python3 Sources/Networking/Inet/DHCP/test/dhcp/reference.py
.PHONY: dhcpchecks

# Net: the AUN module's decisions at the target ABI.
#
# -m32 and -funsigned-char: internet addresses are held in words, 64-bit
# pointers would move every structure the module walks, and the
# station-number parser compares unsigned characters against a space.
NET_DIR := Sources/Networking/Econet/Net
NET_TEST_GEN := $(BUILD)/tests/net
NET_TEST_FLAGS := -m32 -funsigned-char $(HOST_CFLAGS) -I$(NET_DIR)/c \
                  -Itests/lib -Iinclude

$(NET_TEST_GEN)/%.o: Sources/Networking/Econet/Net/test/net/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NET_TEST_FLAGS) -c $< -o $@

$(NET_TEST_GEN)/%.o: $(NET_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NET_TEST_FLAGS) -c $< -o $@

$(NET_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NET_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/net-checks: $(addprefix $(NET_TEST_GEN)/,checks.o netcalc.o \
                             check.o)
	$(HOSTCC) -m32 $(HOST_SANITISE) -o $@ $^

netchecks: $(BUILD)/tests/net-checks
	@$(BUILD)/tests/net-checks
.PHONY: netchecks

# NetFS's arithmetic, -m32 -funsigned-char as for Net: the name rules compare
# characters against a space, and record unpacking lays out words.
NETFS_DIR := Sources/Networking/Econet/NetFS
NETFS_TEST_GEN := $(BUILD)/tests/netfs
NETFS_TEST_FLAGS := -m32 -funsigned-char $(HOST_CFLAGS) -I$(NETFS_DIR)/c \
                    -Itests/lib -Iinclude

$(NETFS_TEST_GEN)/%.o: Sources/Networking/Econet/NetFS/test/netfs/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NETFS_TEST_FLAGS) -c $< -o $@

$(NETFS_TEST_GEN)/%.o: $(NETFS_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NETFS_TEST_FLAGS) -c $< -o $@

$(NETFS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NETFS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/netfs-checks: $(addprefix $(NETFS_TEST_GEN)/,checks.o \
                               netfscalc.o check.o)
	$(HOSTCC) -m32 $(HOST_SANITISE) -o $@ $^

# The same arithmetic checked against the reference's machine code: each
# routine is lifted from 045-NetFS.mod and run under Sources/Networking/Econet/NetFS/test/netfs/armlet (a
# stdlib-only ARM interpreter) against the port's, loaded through ctypes. So
# it is built at the host's native width, without a sanitiser.
$(NETFS_TEST_GEN)/libnetfscalc.so: $(NETFS_DIR)/c/netfscalc.c \
                                   $(NETFS_DIR)/c/netfscalc.h
	@mkdir -p $(dir $@)
	$(HOSTCC) -shared -fPIC -O1 -g -funsigned-char -I$(NETFS_DIR)/c -o $@ $<

# The file server in RPCEmu's AUN test peer, driven as NetFS drives it, so
# fixture faults are found here rather than in an emulator run.
$(NETFS_TEST_GEN)/fixture: Sources/Networking/Econet/NetFS/test/netfs/fixture.c $(TEST_LIB)/check.c \
                           $(LEGACY)/rpcemu/src/network-econet-fs.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Wno-unused-function -Itests/lib -o $@ \
	  Sources/Networking/Econet/NetFS/test/netfs/fixture.c $(TEST_LIB)/check.c

# NetPrint's host checks.
NETPRINT_TEST_GEN := $(BUILD)/tests/netprint
NETPRINT_TEST_FLAGS := -m32 -funsigned-char $(HOST_CFLAGS) -I$(NETPRINT_DIR)/c \
                       -Itests/lib -Iinclude

$(NETPRINT_TEST_GEN)/%.o: Sources/Networking/Econet/NetPrint/test/netprint/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NETPRINT_TEST_FLAGS) -c $< -o $@

$(NETPRINT_TEST_GEN)/%.o: $(NETPRINT_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NETPRINT_TEST_FLAGS) -c $< -o $@

$(NETPRINT_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NETPRINT_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/netprint-checks: $(addprefix $(NETPRINT_TEST_GEN)/,checks.o \
                                  npcalc.o check.o)
	$(HOSTCC) -m32 $(HOST_SANITISE) -o $@ $^

# The machine test's printer server fixture, on the build machine.
$(NETPRINT_TEST_GEN)/fixture: Sources/Networking/Econet/NetPrint/test/netprint/fixture.c $(TEST_LIB)/check.c \
                              $(LEGACY)/rpcemu/src/network-econet-fs.h \
                              $(LEGACY)/rpcemu/src/network-econet-ps.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Wno-unused-function -Itests/lib -o $@ \
	  Sources/Networking/Econet/NetPrint/test/netprint/fixture.c $(TEST_LIB)/check.c

netprintchecks: $(BUILD)/tests/netprint-checks $(NETPRINT_TEST_GEN)/fixture
	@$(BUILD)/tests/netprint-checks
	@$(NETPRINT_TEST_GEN)/fixture
.PHONY: netprintchecks

netfschecks: $(BUILD)/tests/netfs-checks $(NETFS_TEST_GEN)/libnetfscalc.so \
             $(NETFS_TEST_GEN)/fixture
	@$(BUILD)/tests/netfs-checks
	@$(NETFS_TEST_GEN)/fixture
	@python3 Sources/Networking/Econet/NetFS/test/netfs/reference.py $(LEGACY)/romparts/045-NetFS.mod \
	  $(NETFS_TEST_GEN)/libnetfscalc.so
.PHONY: netfschecks

# ScreenModes: the monitor definition file, and what follows from it.
SM_TEST_GEN := $(BUILD)/tests/screenmodes
SM_TEST_FLAGS := $(HOST_CFLAGS) -I$(SM_DIR)/c -Itests/lib -Iinclude

$(SM_TEST_GEN)/%.o: Sources/Video/UserI/ScreenModes/test/screenmodes/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SM_TEST_FLAGS) -c $< -o $@

$(SM_TEST_GEN)/%.o: $(SM_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SM_TEST_FLAGS) -c $< -o $@

$(SM_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SM_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/screenmodes-checks: $(addprefix $(SM_TEST_GEN)/,cases.o \
                                     modelist.o mdf.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/screenmodes-edid-checks: $(addprefix $(SM_TEST_GEN)/,edidcheck.o edid.o edidtables.o modelist.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(SM_TEST_GEN)/bcm2712_gate.o: Sources/Video/HWVideo/BCMVideo/c/bcm2712_gate.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SM_TEST_FLAGS) -c $< -o $@

$(SM_TEST_GEN)/edidreal.o: Sources/Video/UserI/ScreenModes/test/screenmodes/edidreal.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SM_TEST_FLAGS) -ISources/Video/HWVideo/BCMVideo/c -c $< -o $@

$(BUILD)/tests/screenmodes-edidreal-checks: $(addprefix $(SM_TEST_GEN)/,edidreal.o edid.o edidtables.o modelist.o bcm2712_gate.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(SM_TEST_GEN)/edidnative.o: Sources/Video/UserI/ScreenModes/test/screenmodes/edidnative.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SM_TEST_FLAGS) -ISources/Video/HWVideo/BCMVideo/c -c $< -o $@

$(BUILD)/tests/screenmodes-edidnative-checks: $(addprefix $(SM_TEST_GEN)/,edidnative.o edid.o edidtables.o modelist.o bcm2712_gate.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The standard monitor files: generated by tools/monitors/gen_mdfs.py, read
# by the module's parser, and held to the Pi 5 driver's limits.
$(SM_TEST_GEN)/stdmdf.o: Sources/Video/UserI/ScreenModes/test/screenmodes/stdmdf.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SM_TEST_FLAGS) -ISources/Video/HWVideo/BCMVideo/c -c $< -o $@

$(BUILD)/tests/screenmodes-stdmdf-checks: $(addprefix $(SM_TEST_GEN)/,stdmdf.o mdf.o modelist.o bcm2712_gate.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

screenmodeschecks: $(BUILD)/tests/screenmodes-edid-checks $(BUILD)/tests/screenmodes-checks $(BUILD)/tests/screenmodes-edidreal-checks $(BUILD)/tests/screenmodes-edidnative-checks $(BUILD)/tests/screenmodes-stdmdf-checks
	@$(BUILD)/tests/screenmodes-edid-checks
	@$(BUILD)/tests/screenmodes-edidreal-checks
	@$(BUILD)/tests/screenmodes-edidnative-checks
	@$(BUILD)/tests/screenmodes-checks
	@python3 tools/monitors/gen_mdfs.py --check
	@mkdir -p $(BUILD)/tests/screenmodes && python3 tools/monitors/test_apply.py >$(BUILD)/tests/screenmodes/apply.log || { cat $(BUILD)/tests/screenmodes/apply.log; false; }
	@tail -n 1 $(BUILD)/tests/screenmodes/apply.log
	@python3 tools/monitors/gen_mdfs.py --beyond $(BUILD)/tests/screenmodes/beyond >/dev/null
	@$(BUILD)/tests/screenmodes-stdmdf-checks resources/monitors $(BUILD)/tests/screenmodes/beyond | tail -n 8
.PHONY: screenmodeschecks

# BCMVideo: taking the display over from a mode of its own, and the EDID the
# HAL keeps through a warm reset.
BCMV_TEST_GEN := $(BUILD)/tests/bcmvideo
$(BCMV_TEST_GEN)/takeover.o: Sources/Video/HWVideo/BCMVideo/test/bcmvideo/takeover.c include/hal/edidcache.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/HWVideo/BCMVideo/c -Itests/lib -Iinclude -c $< -o $@

$(BCMV_TEST_GEN)/bcm2712_gate.o: Sources/Video/HWVideo/BCMVideo/c/bcm2712_gate.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/HWVideo/BCMVideo/c -Itests/lib -Iinclude -c $< -o $@

$(BCMV_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Itests/lib -Iinclude -c $< -o $@

$(BUILD)/tests/bcmvideo-checks: $(addprefix $(BCMV_TEST_GEN)/,takeover.o bcm2712_gate.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BCMV_TEST_GEN)/hvsplane-test.o: Sources/Video/HWVideo/BCMVideo/test/bcmvideo/hvsplane.c Sources/Video/HWVideo/BCMVideo/c/hvsplane.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/HWVideo/BCMVideo/c -Itests/lib -Iinclude -c $< -o $@

$(BCMV_TEST_GEN)/hvsplane.o: Sources/Video/HWVideo/BCMVideo/c/hvsplane.c Sources/Video/HWVideo/BCMVideo/c/hvsplane.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/HWVideo/BCMVideo/c -Itests/lib -Iinclude -c $< -o $@

$(BUILD)/tests/bcmvideo-plane-checks: $(addprefix $(BCMV_TEST_GEN)/,hvsplane-test.o hvsplane.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BCMV_TEST_GEN)/hsmclock-test.o: Sources/Video/HWVideo/BCMVideo/test/bcmvideo/hsmclock.c Sources/Video/HWVideo/BCMVideo/c/hsmclock.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/HWVideo/BCMVideo/c -Itests/lib -Iinclude -c $< -o $@

$(BUILD)/tests/bcmvideo-hsm-checks: $(addprefix $(BCMV_TEST_GEN)/,hsmclock-test.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BCMV_TEST_GEN)/planecap-test.o: Sources/Video/HWVideo/BCMVideo/test/bcmvideo/planecap.c Sources/Video/HWVideo/BCMVideo/c/planecap.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/HWVideo/BCMVideo/c -Itests/lib -Iinclude -c $< -o $@

$(BCMV_TEST_GEN)/planecap.o: Sources/Video/HWVideo/BCMVideo/c/planecap.c Sources/Video/HWVideo/BCMVideo/c/planecap.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/HWVideo/BCMVideo/c -Itests/lib -Iinclude -c $< -o $@

$(BUILD)/tests/bcmvideo-planecap-checks: $(addprefix $(BCMV_TEST_GEN)/,planecap-test.o planecap.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The HDMI mirror (BCMVideo 1.30): its planning maths, and the restored
# 10 October CPU-copy route's bounded copy and phase scheduler.
$(BCMV_TEST_GEN)/mirrorplan-test.o: Sources/Video/HWVideo/BCMVideo/test/bcmvideo/mirrorplan.c Sources/Video/HWVideo/BCMVideo/c/mirrorplan.h Sources/Video/HWVideo/BCMVideo/c/hvsplane.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/HWVideo/BCMVideo/c -Itests/lib -Iinclude -c $< -o $@

$(BCMV_TEST_GEN)/mirrorplan.o: Sources/Video/HWVideo/BCMVideo/c/mirrorplan.c Sources/Video/HWVideo/BCMVideo/c/mirrorplan.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/Video/HWVideo/BCMVideo/c -Itests/lib -Iinclude -c $< -o $@

$(BUILD)/tests/bcmvideo-mirror-checks: $(addprefix $(BCMV_TEST_GEN)/,mirrorplan-test.o mirrorplan.o hvsplane.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/bcmvideo-mirrorcopy-checks: Sources/Video/HWVideo/BCMVideo/test/mirrorcopy.c Sources/Video/HWVideo/BCMVideo/c/mirrorcopy.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $<

$(BUILD)/tests/bcmvideo-mirrorsched-checks: Sources/Video/HWVideo/BCMVideo/test/mirrorsched.c Sources/Video/HWVideo/BCMVideo/c/mirrorsched.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $<

bcmvideochecks: $(BUILD)/tests/bcmvideo-checks $(BUILD)/tests/bcmvideo-plane-checks $(BUILD)/tests/bcmvideo-hsm-checks $(BUILD)/tests/bcmvideo-planecap-checks $(BUILD)/tests/bcmvideo-mirror-checks $(BUILD)/tests/bcmvideo-mirrorcopy-checks $(BUILD)/tests/bcmvideo-mirrorsched-checks
	@$(BUILD)/tests/bcmvideo-planecap-checks
	@$(BUILD)/tests/bcmvideo-mirror-checks
	@$(BUILD)/tests/bcmvideo-mirrorcopy-checks && echo "bcmvideo-mirrorcopy: passed"
	@$(BUILD)/tests/bcmvideo-mirrorsched-checks && echo "bcmvideo-mirrorsched: passed"
	@python3 -I Sources/Video/HWVideo/BCMVideo/test/noscreenfade.py Sources/Video/HWVideo/BCMVideo
	@$(BUILD)/tests/bcmvideo-hsm-checks
	@$(BUILD)/tests/bcmvideo-checks
	@$(BUILD)/tests/bcmvideo-plane-checks
.PHONY: bcmvideochecks

# Screen Setup: the file its setting is saved in.
SCRNSET_TEST_GEN := $(BUILD)/tests/scrnsetup
$(SCRNSET_TEST_GEN)/cfgpath.o: Sources/SystemRes/Configure2/PlugIns/ScrnSetup/test/cfgpath.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -ISources/SystemRes/Configure2/PlugIns/ScrnSetup/h -Itests/lib -Iinclude -c $< -o $@

$(SCRNSET_TEST_GEN)/cfgpathlib.o: Sources/SystemRes/Configure2/PlugIns/ScrnSetup/c/cfgpath.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -DCFGPATH_HOST -ISources/SystemRes/Configure2/PlugIns/ScrnSetup/h -c $< -o $@

$(SCRNSET_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Itests/lib -Iinclude -c $< -o $@

$(BUILD)/tests/scrnsetup-checks: $(addprefix $(SCRNSET_TEST_GEN)/,cfgpath.o cfgpathlib.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

scrnsetupchecks: $(BUILD)/tests/scrnsetup-checks
	@$(BUILD)/tests/scrnsetup-checks
.PHONY: scrnsetupchecks

# DOSFS on the build machine. The filing system files issue no SWI (image,
# memory, clock and file-type lookup come through an operations table), so
# these checks build a 720K floppy in a buffer and run the filing system on
# it with no emulator.
DOSFS_TEST_GEN := $(BUILD)/tests/dosfs
DOSFS_TEST_FLAGS := $(HOST_CFLAGS) -I$(DOSFS_DIR)/c -Itests/lib -Iinclude

$(DOSFS_TEST_GEN)/%.o: Sources/FileSys/DOSFS/test/dosfs/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DOSFS_TEST_FLAGS) -c $< -o $@

$(DOSFS_TEST_GEN)/%.o: $(DOSFS_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DOSFS_TEST_FLAGS) -c $< -o $@

$(DOSFS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DOSFS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/dosfs-checks: $(addprefix $(DOSFS_TEST_GEN)/,cases.o \
                               dosvol.o dosdir.o dosfat.o dosname.o \
                               dostime.o dosformat.o dosident.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

dosfschecks: $(BUILD)/tests/dosfs-checks
	@$(BUILD)/tests/dosfs-checks
.PHONY: dosfschecks

-include $(DOSFS_TEST_GEN)/*.d

# The Toolbox host checks cover the parts that issue no SWI: c/resfile.c
# (resource file walking and offset relocation), c/object.c (object IDs from
# address and counter), c/task.c (task and interest lists, resource search
# order), c/filter.c (post-filter decisions) and c/memarith.c (dynamic area
# sizing and address validation).

# Squash: the compressed stream, and the two halves of a restart.
SQ_DIR := Sources/Utilities/Squash
SQ_TEST_GEN := $(BUILD)/tests/squash
SQ_TEST_FLAGS := $(HOST_CFLAGS) -I$(SQ_DIR)/c -Itests/lib -Iinclude

$(SQ_TEST_GEN)/%.o: Sources/Utilities/Squash/test/squash/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SQ_TEST_FLAGS) -c $< -o $@

$(SQ_TEST_GEN)/%.o: $(SQ_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SQ_TEST_FLAGS) -c $< -o $@

$(SQ_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SQ_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/squashlzw-checks: $(addprefix $(SQ_TEST_GEN)/,lzwcheck.o \
                                   squashlzw.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

squashchecks: $(BUILD)/tests/squashlzw-checks
	@$(BUILD)/tests/squashlzw-checks
.PHONY: squashchecks

# BufferManager: the two indices and the one slot that is never handed back.
BUFS_DIR := Sources/HWSupport/Buffers
BUFS_TEST_GEN := $(BUILD)/tests/buffers
BUFS_TEST_FLAGS := $(HOST_CFLAGS) -I$(BUFS_DIR)/c -Itests/lib -Iinclude

$(BUFS_TEST_GEN)/%.o: Sources/HWSupport/Buffers/test/buffers/%.c $(BUFS_DIR)/c/bufring.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BUFS_TEST_FLAGS) -c $< -o $@

$(BUFS_TEST_GEN)/%.o: $(BUFS_DIR)/c/%.c $(BUFS_DIR)/c/bufring.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BUFS_TEST_FLAGS) -c $< -o $@

$(BUFS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BUFS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/buffers-checks: $(addprefix $(BUFS_TEST_GEN)/,cases.o \
                                 bufring.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

bufferschecks: $(BUILD)/tests/buffers-checks
	@$(BUILD)/tests/buffers-checks
.PHONY: bufferschecks

# DeviceFS host checks: registration lists, special fields, directory records
# and stream state. No OS calls.
DEVFS_DIR := Sources/FileSys/DeviceFS
DEVFS_TEST_GEN := $(BUILD)/tests/devicefs
DEVFS_TEST_FLAGS := $(HOST_CFLAGS) -I$(DEVFS_DIR)/c -Itests/lib -Iinclude

$(DEVFS_TEST_GEN)/%.o: Sources/FileSys/DeviceFS/test/devicefs/%.c $(DEVFS_DIR)/c/devreg.h \
                       $(DEVFS_DIR)/c/special.h $(DEVFS_DIR)/c/devdir.h $(DEVFS_DIR)/c/stream.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DEVFS_TEST_FLAGS) -c $< -o $@

$(DEVFS_TEST_GEN)/%.o: $(DEVFS_DIR)/c/%.c $(DEVFS_DIR)/c/devreg.h \
                       $(DEVFS_DIR)/c/special.h $(DEVFS_DIR)/c/devdir.h $(DEVFS_DIR)/c/stream.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DEVFS_TEST_FLAGS) -c $< -o $@

$(DEVFS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DEVFS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/devicefs-checks: $(addprefix $(DEVFS_TEST_GEN)/,cases.o \
                                 devreg.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/devicefs-special: $(addprefix $(DEVFS_TEST_GEN)/,specialcases.o \
                                 special.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/devicefs-dir: $(addprefix $(DEVFS_TEST_GEN)/,dircases.o \
                                 devdir.o devreg.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/devicefs-stream: $(addprefix $(DEVFS_TEST_GEN)/,streamcases.o stream.o devreg.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

devicefschecks: $(BUILD)/tests/devicefs-checks $(BUILD)/tests/devicefs-special \
                $(BUILD)/tests/devicefs-dir $(BUILD)/tests/devicefs-stream
	@$(BUILD)/tests/devicefs-checks
	@$(BUILD)/tests/devicefs-special
	@$(BUILD)/tests/devicefs-dir
	@$(BUILD)/tests/devicefs-stream
.PHONY: devicefschecks

# RTC: the packed decimal, the year a chip cannot hold, and the slew.
RTC_DIR := Sources/HWSupport/RTC
RTC_TEST_GEN := $(BUILD)/tests/rtc
RTC_TEST_FLAGS := $(HOST_CFLAGS) -I$(RTC_DIR)/c -Itests/lib -Iinclude

$(RTC_TEST_GEN)/%.o: Sources/HWSupport/RTC/test/rtc/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RTC_TEST_FLAGS) -c $< -o $@

$(RTC_TEST_GEN)/%.o: $(RTC_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RTC_TEST_FLAGS) -c $< -o $@

$(RTC_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RTC_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/rtcadjust-checks: $(addprefix $(RTC_TEST_GEN)/,adjustcheck.o \
                                   rtcadjust.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

rtcchecks: $(BUILD)/tests/rtcadjust-checks
	@$(BUILD)/tests/rtcadjust-checks
.PHONY: rtcchecks

# Draw: the fifteen reasons in `s/Draw` other than `processpath`. Each turns
# its arguments into a Draw_ProcessPath argument set, a pure function of nine
# words, so it is checked on the host with no machine or emulator.
DRAW_DIR := Sources/Video/Render/Draw
DRAW_TEST_GEN := $(BUILD)/tests/draw
DRAW_TEST_FLAGS := $(HOST_CFLAGS) -I$(DRAW_DIR)/c -Itests/lib -Iinclude

$(DRAW_TEST_GEN)/%.o: Sources/Video/Render/Draw/test/draw/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DRAW_TEST_FLAGS) -c $< -o $@

$(DRAW_TEST_GEN)/%.o: $(DRAW_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DRAW_TEST_FLAGS) -c $< -o $@

$(DRAW_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DRAW_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/drawtranslate-checks: $(addprefix $(DRAW_TEST_GEN)/,\
                                       translate.o draw_translate.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawarith-checks: $(addprefix $(DRAW_TEST_GEN)/,arith.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawpath-checks: $(addprefix $(DRAW_TEST_GEN)/,path.o \
                                  drawpath.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawflatten-checks: $(addprefix $(DRAW_TEST_GEN)/,flatten.o \
                                     drawflatten.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawtransform-checks: $(addprefix $(DRAW_TEST_GEN)/,\
                                       transform.o drawtransform.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawvector-checks: $(addprefix $(DRAW_TEST_GEN)/,vector.o \
                                    drawvector.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawdash-checks: $(addprefix $(DRAW_TEST_GEN)/,dash.o \
                                  drawdash.o drawvector.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawedge-checks: $(addprefix $(DRAW_TEST_GEN)/,edge.o \
                                  drawedge.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawthicken-checks: $(addprefix $(DRAW_TEST_GEN)/,thicken.o \
                                     drawthicken.o drawvector.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawout-checks: $(addprefix $(DRAW_TEST_GEN)/,out.o \
                                 drawout.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawedges-checks: $(addprefix $(DRAW_TEST_GEN)/,edges.o \
                                   drawedges.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawfill-checks: $(addprefix $(DRAW_TEST_GEN)/,fill.o \
                                  drawfill.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawraster-checks: $(addprefix $(DRAW_TEST_GEN)/,raster.o \
                                    drawraster.o drawedges.o drawfill.o \
                                    check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawclip-checks: $(addprefix $(DRAW_TEST_GEN)/,clip.o \
                                  drawclip.o drawraster.o drawedges.o \
                                  drawfill.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawqfill-checks: $(addprefix $(DRAW_TEST_GEN)/,qfill.o \
                                   drawqfill.o drawraster.o drawedges.o \
                                   drawfill.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawoutfill-checks: $(addprefix $(DRAW_TEST_GEN)/,outfill.o \
                                     drawoutfill.o drawpipe.o drawpath.o \
                                     drawflatten.o drawedge.o drawdash.o \
                                     drawthicken.o drawtransform.o \
                                     drawvector.o drawout.o drawraster.o \
                                     drawqfill.o drawedges.o drawfill.o \
                                     check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/drawpipe-checks: $(addprefix $(DRAW_TEST_GEN)/,pipe.o \
                                  drawoutfill.o drawraster.o drawqfill.o \
                                  drawedges.o drawfill.o \
                                  drawpipe.o drawpath.o drawflatten.o \
                                  drawedge.o drawdash.o drawthicken.o \
                                  drawtransform.o drawvector.o drawout.o \
                                  check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

drawchecks: $(BUILD)/tests/drawtranslate-checks \
            $(BUILD)/tests/drawarith-checks $(BUILD)/tests/drawpath-checks \
            $(BUILD)/tests/drawflatten-checks \
            $(BUILD)/tests/drawtransform-checks \
            $(BUILD)/tests/drawvector-checks $(BUILD)/tests/drawdash-checks \
            $(BUILD)/tests/drawedge-checks \
            $(BUILD)/tests/drawthicken-checks \
            $(BUILD)/tests/drawout-checks \
            $(BUILD)/tests/drawpipe-checks \
            $(BUILD)/tests/drawedges-checks \
            $(BUILD)/tests/drawfill-checks \
            $(BUILD)/tests/drawraster-checks \
            $(BUILD)/tests/drawclip-checks \
            $(BUILD)/tests/drawqfill-checks \
            $(BUILD)/tests/drawoutfill-checks
	@$(BUILD)/tests/drawtranslate-checks
	@$(BUILD)/tests/drawarith-checks
	@$(BUILD)/tests/drawpath-checks
	@$(BUILD)/tests/drawflatten-checks
	@$(BUILD)/tests/drawtransform-checks
	@$(BUILD)/tests/drawvector-checks
	@$(BUILD)/tests/drawdash-checks
	@$(BUILD)/tests/drawedge-checks
	@$(BUILD)/tests/drawthicken-checks
	@$(BUILD)/tests/drawout-checks
	@$(BUILD)/tests/drawpipe-checks
	@$(BUILD)/tests/drawedges-checks
	@$(BUILD)/tests/drawfill-checks
	@$(BUILD)/tests/drawraster-checks
	@$(BUILD)/tests/drawclip-checks
	@$(BUILD)/tests/drawqfill-checks
	@$(BUILD)/tests/drawoutfill-checks
.PHONY: drawchecks

# BlendTable: the blend and the cache it is kept in.
BT_DIR := Sources/Video/Render/BlendTable
BT_TEST_GEN := $(BUILD)/tests/blendtable
BT_TEST_FLAGS := $(HOST_CFLAGS) -I$(BT_DIR)/c -Itests/lib -Iinclude

$(BT_TEST_GEN)/%.o: Sources/Video/Render/BlendTable/test/blendtable/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BT_TEST_FLAGS) -c $< -o $@

$(BT_TEST_GEN)/%.o: $(BT_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BT_TEST_FLAGS) -c $< -o $@

$(BT_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BT_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/btblend-checks: $(addprefix $(BT_TEST_GEN)/,blendcheck.o \
                                 btblend.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/btcache-checks: $(addprefix $(BT_TEST_GEN)/,cachecheck.o \
                                 btcache.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

blendtablechecks: $(BUILD)/tests/btblend-checks $(BUILD)/tests/btcache-checks
	@$(BUILD)/tests/btblend-checks
	@$(BUILD)/tests/btcache-checks
.PHONY: blendtablechecks

# ColourTrans: the colour models; six SWIs in three pairs, arithmetic on three
# or four numbers with no palette, mode or cache.
CT_DIR := Sources/Video/Render/Colours
CT_TEST_GEN := $(BUILD)/tests/colourtrans
CT_TEST_FLAGS := $(HOST_CFLAGS) -I$(CT_DIR)/c -Itests/lib -Iinclude

$(CT_TEST_GEN)/%.o: Sources/Video/Render/Colours/test/colourtrans/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CT_TEST_FLAGS) -c $< -o $@

$(CT_TEST_GEN)/%.o: $(CT_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CT_TEST_FLAGS) -c $< -o $@

$(CT_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(CT_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/ctmodel-checks: $(addprefix $(CT_TEST_GEN)/,modelcheck.o \
                                 ctmodel.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/ctpalette-checks: $(addprefix $(CT_TEST_GEN)/,palettecheck.o \
                                   ctpalette.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/ctmatch-checks: $(addprefix $(CT_TEST_GEN)/,matchcheck.o \
                                 ctmatch.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/ctdither-checks: $(addprefix $(CT_TEST_GEN)/,dithercheck.o \
                                  ctdither.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Colour dithering chooses which four colours to place together, so it needs
# the colour matching. It also needs ctpattern: the word one cell row reduces
# to lives there, as its depths follow `getpattern` rather than `SetGCOL`.
$(BUILD)/tests/ctcolourdither-checks: \
		$(addprefix $(CT_TEST_GEN)/,colourdithercheck.o ctdither.o \
		  ctmatch.o ctpattern.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/ctdeep-checks: $(addprefix $(CT_TEST_GEN)/,deepcheck.o \
                                ctdeep.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/cttable-checks: $(addprefix $(CT_TEST_GEN)/,tablecheck.o \
                                 cttable.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The palette builder uses the palette reader; checking the hard-bit table
# against the bit extraction needs both.
$(BUILD)/tests/ctbuild-checks: $(addprefix $(CT_TEST_GEN)/,buildcheck.o \
                                 ctbuild.o ctpalette.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The shortcut is checked against the search it replaces, so the search and
# the palette it walks are linked too.
$(BUILD)/tests/ctfast-checks: $(addprefix $(CT_TEST_GEN)/,fastcheck.o \
                                ctfast.o ctmatch.o ctbuild.o ctpalette.o \
                                check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The ECF patterns: eight words the VDU drivers tile over a filled area, so an
# unavailable colour becomes a pattern of available ones.
$(BUILD)/tests/ctpattern-checks: $(addprefix $(CT_TEST_GEN)/,patterncheck.o \
                                   ctpattern.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# When a cached answer may be used and when it must be discarded. Links the
# entry format and hash in ctdeep.
$(BUILD)/tests/ctcache-checks: $(addprefix $(CT_TEST_GEN)/,ctcachecheck.o \
                                 ctcache.o ctdeep.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The deep-mode arm of the same font SWI, linked with the general arm so the
# difference can be asserted.
$(BUILD)/tests/ctfont8-checks: $(addprefix $(CT_TEST_GEN)/,font8check.o \
                                 ctfont8.o ctfont.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Calibration and loadings saved to a file as lines of *commands the module
# implements. Uses the table formats in ctcie.
$(BUILD)/tests/ctfilecal-checks: $(addprefix $(CT_TEST_GEN)/,filecalcheck.o \
                                   ctfilecal.o ctcie.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Dithering at sixteen bits needs no palette: the pixel is a function of the
# colour, so the achieved colour is one AND.
$(BUILD)/tests/ctdither16-checks: \
		$(addprefix $(CT_TEST_GEN)/,dither16check.o ctdither16.o \
		  check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# GCOLs, colour numbers and tints: setting a colour rather than translating
# one. Uses the two packings in cttable.
$(BUILD)/tests/ctgcol-checks: $(addprefix $(CT_TEST_GEN)/,gcolcheck.o \
                                ctgcol.o cttable.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The anti-aliasing ramp between a background and a foreground colour.
$(BUILD)/tests/ctfont-checks: $(addprefix $(CT_TEST_GEN)/,fontcheck.o \
                                ctfont.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Putting a colour through a calibration table. Uses ctcie's table format
# constants rather than restating them.
$(BUILD)/tests/ctdevice-checks: $(addprefix $(CT_TEST_GEN)/,devicecheck.o \
                                  ctdevice.o ctcie.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Writing a palette. Its compaction test is the exact inverse of the read
# side's tint expansion, which the checks assert.
$(BUILD)/tests/ctwrite-checks: $(addprefix $(CT_TEST_GEN)/,writecheck.o \
                                 ctwrite.o ctpalette.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The fifteen-bit tables: the twelve-byte answer, and what is behind it.
$(BUILD)/tests/ct32k-checks: $(addprefix $(CT_TEST_GEN)/,t32kcheck.o \
                               ct32k.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The CIE conversions and the arithmetic that reads a calibration table.
$(BUILD)/tests/ctcie-checks: $(addprefix $(CT_TEST_GEN)/,ciecheck.o \
                               ctcie.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BUILD)/tests/ctselect-checks: $(addprefix $(CT_TEST_GEN)/,selectcheck.o \
                                  ctselect.o ctfast.o ctmatch.o ctbuild.o \
                                  ctpalette.o ctdeep.o cttable.o ctdevice.o \
                                  ctcie.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

colourtranschecks: $(BUILD)/tests/ctmodel-checks \
                   $(BUILD)/tests/ctpalette-checks \
                   $(BUILD)/tests/ctmatch-checks \
                   $(BUILD)/tests/ctdither-checks \
                   $(BUILD)/tests/ctdeep-checks \
                   $(BUILD)/tests/cttable-checks \
                   $(BUILD)/tests/ctbuild-checks \
                   $(BUILD)/tests/ctfast-checks \
                   $(BUILD)/tests/ctselect-checks \
                   $(BUILD)/tests/ctcolourdither-checks \
                   $(BUILD)/tests/ctcie-checks \
                   $(BUILD)/tests/ct32k-checks \
                   $(BUILD)/tests/ctwrite-checks \
                   $(BUILD)/tests/ctdevice-checks \
                   $(BUILD)/tests/ctfont-checks \
                   $(BUILD)/tests/ctgcol-checks \
                   $(BUILD)/tests/ctdither16-checks \
                   $(BUILD)/tests/ctfilecal-checks \
                   $(BUILD)/tests/ctfont8-checks \
                   $(BUILD)/tests/ctcache-checks \
                   $(BUILD)/tests/ctpattern-checks
	@$(BUILD)/tests/ctmodel-checks
	@$(BUILD)/tests/ctpalette-checks
	@$(BUILD)/tests/ctmatch-checks
	@$(BUILD)/tests/ctdither-checks
	@$(BUILD)/tests/ctdeep-checks
	@$(BUILD)/tests/cttable-checks
	@$(BUILD)/tests/ctbuild-checks
	@$(BUILD)/tests/ctfast-checks
	@$(BUILD)/tests/ctselect-checks
	@$(BUILD)/tests/ctcolourdither-checks
	@$(BUILD)/tests/ctcie-checks
	@$(BUILD)/tests/ct32k-checks
	@$(BUILD)/tests/ctwrite-checks
	@$(BUILD)/tests/ctdevice-checks
	@$(BUILD)/tests/ctfont-checks
	@$(BUILD)/tests/ctgcol-checks
	@$(BUILD)/tests/ctdither16-checks
	@$(BUILD)/tests/ctfilecal-checks
	@$(BUILD)/tests/ctfont8-checks
	@$(BUILD)/tests/ctcache-checks
	@$(BUILD)/tests/ctpattern-checks
.PHONY: colourtranschecks

# The generator's output against the reference ROM's Messages module.
colourtablescheck: $(LEGACY)/romparts/005-Messages.mod \
                   tools/build/colourtables.py tools/build/resfsextract.py
	@python3 tools/build/colourtables.py --check $<
.PHONY: colourtablescheck
hostchecks: colourtablescheck

-include $(wildcard $(CT_TEST_GEN)/*.d)

-include $(wildcard $(BT_TEST_GEN)/*.d)

-include $(wildcard $(MOUSE_TEST_GEN)/*.d)

-include $(wildcard $(OBEY_TEST_GEN)/*.d)

# Task Manager host checks: memory bar scale and its inverse for dragging,
# the window's row chain, menu selections, help tokens, and hand-built text.
# The scale and its inverse must agree, or a dragged bar jumps away from the
# pointer.
SWI_TEST_GEN := $(BUILD)/tests/switcher
SWI_TEST_FLAGS := $(HOST_CFLAGS) -I$(SWI_DIR)/c -Itests/lib -Iinclude

$(SWI_TEST_GEN)/%.o: Sources/Desktop/Switcher/test/switcher/%.c $(SWI_DIR)/c/switcher.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SWI_TEST_FLAGS) -c $< -o $@

$(SWI_TEST_GEN)/%.o: $(SWI_DIR)/c/%.c $(SWI_DIR)/c/switcher.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SWI_TEST_FLAGS) -c $< -o $@

$(SWI_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SWI_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/switcher-checks: $(addprefix $(SWI_TEST_GEN)/,checks.o \
                                  swcalc.o swlist.o swmenu.o swpath.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

switcherchecks: $(BUILD)/tests/switcher-checks
	@$<
.PHONY: switcherchecks

# The Pinboard's arithmetic, checked on the host: the icon grid, the icon
# lists, icon names and what a menu selection means.
PBD_TEST_GEN := $(BUILD)/tests/pinboard
PBD_TEST_FLAGS := $(HOST_CFLAGS) -I$(PBD_DIR)/c -Itests/lib -Iinclude

$(PBD_TEST_GEN)/%.o: Sources/Desktop/Pinboard/test/pinboard/%.c $(PBD_DIR)/c/pinboard.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PBD_TEST_FLAGS) -c $< -o $@

$(PBD_TEST_GEN)/%.o: $(PBD_DIR)/c/%.c $(PBD_DIR)/c/pinboard.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PBD_TEST_FLAGS) -c $< -o $@

$(PBD_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PBD_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/pinboard-checks: $(addprefix $(PBD_TEST_GEN)/,checks.o \
                                  pbgrid.o pbpicture.o pbname.o pblist.o pbmenu.o pbargs.o \
                                  pbsave.o \
                                  check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

pinboardchecks: $(BUILD)/tests/pinboard-checks
	@$<
.PHONY: pinboardchecks

# The Filer's JPEG thumbnail gate (what may be handed to SpriteExtend while
# output is redirected into a sprite) and thumbnail sizing: pure functions,
# checked on the host.
THJ_DIR := Sources/Desktop/Filer
THJ_GEN := $(BUILD)/tests/thumbjpeg
$(THJ_GEN)/checks.o: $(THJ_DIR)/test/thumbjpeg/checks.c $(THJ_DIR)/c/thumbjpeg.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(THJ_DIR)/c -Itests/lib -c $< -o $@
$(THJ_GEN)/thumbjpeg.o: $(THJ_DIR)/c/thumbjpeg.c $(THJ_DIR)/c/thumbjpeg.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(THJ_DIR)/c -c $< -o $@
$(THJ_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Itests/lib -c $< -o $@
$(BUILD)/tests/thumbjpeg-checks: $(addprefix $(THJ_GEN)/,checks.o thumbjpeg.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^
thumbjpegchecks: $(BUILD)/tests/thumbjpeg-checks
	@$<
.PHONY: thumbjpegchecks

# The Filer's DC-only JPEG thumbnail decoder against libjpeg's own 1/8
# scaling (Pillow), and damaged copies of the same files under the
# sanitisers (Sources/Desktop/Filer/test/thumbdc).
TDC_GEN := $(BUILD)/tests/thumbdc
$(TDC_GEN)/thumbdc.o: $(THJ_DIR)/c/thumbdc.c $(THJ_DIR)/c/thumbdc.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(THJ_DIR)/c -c $< -o $@
$(TDC_GEN)/cli.o: $(THJ_DIR)/test/thumbdc/cli.c $(THJ_DIR)/c/thumbdc.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(THJ_DIR)/c -c $< -o $@
$(TDC_GEN)/thumbdc-cli: $(TDC_GEN)/thumbdc.o $(TDC_GEN)/cli.o
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^
thumbdcchecks: $(TDC_GEN)/thumbdc-cli
	@python3 -I $(THJ_DIR)/test/thumbdc/run.py $< $(TDC_GEN)/corpus
.PHONY: thumbdcchecks

# The C Filer (Sources/Desktop/Filer/c) checked on the host: its string and
# path handling, UTF-8 names, sort orders and viewer layout, compiled from c/
# as the module has them and run against stand-ins for the SWIs and globals
# they reach (test/filer/checks.c). Built for 32 bits, as the Filer passes
# addresses to its SWIs as words.
FLR_TEST_GEN := $(BUILD)/tests/filer
FLR_TEST_SRC := strutils sortdir utf8 pathmunge layout collate
FLR_UNICODE_SRC := unicode unicase ungrapheme unidata_generated
$(BUILD)/tests/filer-checks: Sources/Desktop/Filer/test/filer/checks.c \
    $(FLR_TEST_SRC:%=Sources/Desktop/Filer/c/%.c) \
    $(FLR_UNICODE_SRC:%=Sources/Lib/Unicode/%.c) $(TEST_LIB)/check.c \
    $(wildcard Sources/Desktop/Filer/c/*.h)
	@mkdir -p $(dir $@)
	$(HOSTCC) -m32 -funsigned-char -std=gnu17 -g -Wall -Wextra \
	  -Wno-unused-parameter -Wno-unused-but-set-variable \
	  -ISources/Desktop/Filer/c -Iinclude -Itests/lib \
	  -o $@ $(filter %.c,$^)
filerchecks: $(BUILD)/tests/filer-checks
	@$<
.PHONY: filerchecks

# TaskWindow's decisions, checked on the host: the two rings between task and
# window, the control sequences -ctrl swallows, function-key expansion, the
# command line, the task chain and Wimp message decoding. The co-routine
# switch is not checked here.
TWN_TEST_GEN := $(BUILD)/tests/taskwin
TWN_TEST_FLAGS := $(HOST_CFLAGS) -I$(TWN_DIR)/c -Itests/lib -Iinclude \
                  -ISources/Kernel/hdr

$(TWN_TEST_GEN)/%.o: Sources/Desktop/TaskWindow/test/taskwin/%.c $(TWN_DIR)/c/taskwin.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(TWN_TEST_FLAGS) -c $< -o $@

$(TWN_TEST_GEN)/%.o: $(TWN_DIR)/c/%.c $(TWN_DIR)/c/taskwin.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(TWN_TEST_FLAGS) -c $< -o $@

$(TWN_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(TWN_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/taskwin-checks: $(addprefix $(TWN_TEST_GEN)/,checks.o \
                                 twbuf.o twparse.o twlist.o twmsg.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

taskwinchecks: $(BUILD)/tests/taskwin-checks
	@$<
.PHONY: taskwinchecks

# Desktop's arithmetic, checked on the host: its command line, the CMOS bits
# selecting which ROM applications to start, and the year in its banner.
DSK_TEST_GEN := $(BUILD)/tests/desktopmod
DSK_TEST_FLAGS := $(HOST_CFLAGS) -I$(DSK_DIR)/c -Itests/lib -Iinclude

$(DSK_TEST_GEN)/%.o: Sources/Desktop/Desktop/test/desktopmod/%.c $(DSK_DIR)/c/desktop.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DSK_TEST_FLAGS) -c $< -o $@

$(DSK_TEST_GEN)/desktop.o: $(DSK_DIR)/c/desktop.c $(DSK_DIR)/c/desktop.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DSK_TEST_FLAGS) -c $< -o $@

$(DSK_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(DSK_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/desktopmod-checks: $(addprefix $(DSK_TEST_GEN)/,checks.o \
                                    desktop.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

desktopmodchecks: $(BUILD)/tests/desktopmod-checks
	@$<
.PHONY: desktopmodchecks

FA_TEST_GEN := $(BUILD)/tests/fileract
FA_REF_DIR := $(LEGACY)/riscos-src/RiscOS/Sources/Desktop/FilerAct
# The original sources include their headers by the original build's layout,
# recreated under FA_TEST_INC.
FA_TEST_INC := $(FA_TEST_GEN)/inc
FA_TEST_FLAGS := $(HOST_CFLAGS) -I$(FA_DIR)/c -I$(FA_TEST_INC) \
                 -ISources/Desktop/FilerAct/test/fileract -Itests/lib -Iinclude
# Sources/Lib/CLib/include holds this tree's stdio.h, not the host's, so only the two
# files that need clib get it.
FA_TEST_CLIB_FLAGS := $(FA_TEST_FLAGS) -ISources/Lib/CLib/include

$(FA_TEST_INC)/Chains.h: $(FA_REF_DIR)/h/Chains
	@mkdir -p $(dir $@)
	@ln -sf $(abspath $<) $@

$(FA_TEST_GEN)/checks.o: Sources/Desktop/FilerAct/test/fileract/checks.c Sources/Desktop/FilerAct/test/fileract/debug.h \
                         $(FA_DIR)/c/fachain.h $(FA_DIR)/c/fanumber.h \
                         $(FA_DIR)/c/facopy.h $(FA_DIR)/c/fasearch.h \
                         $(FA_DIR)/c/faenv.h Sources/Desktop/FilerAct/test/fileract/fawin_test.h \
                         $(FA_TEST_INC)/Chains.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_CLIB_FLAGS) -c $< -o $@

$(FA_TEST_GEN)/reference-chains.o: $(FA_REF_DIR)/c/Chains Sources/Desktop/FilerAct/test/fileract/debug.h \
                                   $(FA_TEST_INC)/Chains.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_FLAGS) -w -x c -c $< -o $@

$(FA_TEST_GEN)/faact.o: $(FA_DIR)/c/faact.c $(FA_DIR)/c/faact.h $(FA_DIR)/c/faenv.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_CLIB_FLAGS) -c $< -o $@

$(FA_TEST_GEN)/faenv.o: $(FA_DIR)/c/faenv.c $(FA_DIR)/c/faenv.h $(FA_DIR)/c/fawin.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_CLIB_FLAGS) -c $< -o $@

$(FA_TEST_GEN)/fanumber.o: $(FA_DIR)/c/fanumber.c $(FA_DIR)/c/fanumber.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_CLIB_FLAGS) -c $< -o $@

$(FA_TEST_GEN)/%.o: $(FA_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_FLAGS) -c $< -o $@

# clib's locale is compiled in rather than substituted: the number formatting
# asks the locale for its separators, and the host libc's answer does not
# apply to the module.
$(FA_TEST_GEN)/clib-locale.o: Sources/Lib/CLib/locale/api.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_CLIB_FLAGS) -c $< -o $@

$(FA_TEST_GEN)/fawin_test.o: Sources/Desktop/FilerAct/test/fileract/fawin_test.c Sources/Desktop/FilerAct/test/fileract/fawin_test.h \
                             $(FA_DIR)/c/fawin.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_FLAGS) -c $< -o $@

$(FA_TEST_GEN)/faio_test.o: Sources/Desktop/FilerAct/test/fileract/faio_test.c Sources/Desktop/FilerAct/test/fileract/faio_test.h \
                            $(FA_DIR)/c/faio.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/fileract-checks: $(addprefix $(FA_TEST_GEN)/,checks.o \
                                  reference-chains.o fachain.o fanumber.o \
                                  facopy.o fasearch.o fabuttons.o faops.o faenv.o faact.o \
                                  famsg.o faio_test.o fawin_test.o \
                                  clib-locale.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(FA_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(FA_TEST_FLAGS) -c $< -o $@

fileractcoverage:
	@python3 $(FA_DIR)/coverage/check.py $(FA_REF_DIR)

fileractchecks: $(BUILD)/tests/fileract-checks fileractcoverage
	@$<

.PHONY: fileractchecks fileractcoverage

shellcli: $(SHELL_MOD)
.PHONY: shellcli

# Checks that need no execution: the image holds no relocation it cannot
# carry to another address, and the build still refuses what it should.
# Sources/Desktop/ShellCLI/test/shellcli/buildcheck.py is stdlib Python; a check here must not need a
# package that is not vendored, or it fails in a clean checkout.
# NetStatus: the Econet hourglass, a claimant on EconetV that never claims.
# c/nsdecide.c holds the decisions Sources/Networking/Econet/NetStatus/test/netstatus checks. See
# Sources/Networking/Econet/NetStatus/cmhg/modhead.
NS_OBJS := $(BUILD)/modules/netstatus/modhead.o \
           $(BUILD)/modules/netstatus/nsdecide.o \
           $(BUILD)/modules/netstatus/nsmodule.o \
           $(BUILD)/modules/netstatus/veneers.o
NS_MOD  := $(BUILD)/modules/NetStatus.mod
NS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/Networking/Econet/NetStatus/c \
             -I$(BUILD)/modules/netstatus

$(BUILD)/modules/netstatus/modhead.S: Sources/Networking/Econet/NetStatus/cmhg/modhead \
                                      Sources/Networking/Econet/NetStatus/VersionNum \
                                      tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Networking/Econet/NetStatus -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/netstatus/%.o: Sources/Networking/Econet/NetStatus/c/%.c Makefile \
                                $(BUILD)/modules/netstatus/ns_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(NS_CFLAGS) -c $< -o $@

$(BUILD)/modules/netstatus/veneers.S: Sources/Networking/Econet/NetStatus/s/boundaries \
                                      Sources/Networking/Econet/NetStatus/cmhg/modhead \
                                      tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Networking/Econet/NetStatus --asm $@ \
	  --header $(BUILD)/modules/netstatus/ns_veneers.h

$(BUILD)/modules/netstatus/ns_veneers.h: \
    $(BUILD)/modules/netstatus/veneers.S

$(BUILD)/modules/netstatus/veneers.o: $(BUILD)/modules/netstatus/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(NS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/netstatus/modhead.o: $(BUILD)/modules/netstatus/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(NS_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/NetStatus.elf: $(NS_OBJS) $(CLIB) mk/ld/module.ld \
                                tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(NS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(NS_MOD): $(BUILD)/modules/NetStatus.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, NetStatus)"

-include $(NS_OBJS:.o=.d)

netstatusmodule: $(NS_MOD)
.PHONY: netstatusmodule

# NetStatus's decisions against stand-ins: depth counting, the two
# percentages the original gets wrong, the LED for each low-level operation,
# and the version parse that decides whether the hourglass can be coloured.
NS_DIR := Sources/Networking/Econet/NetStatus
NS_TEST_GEN := $(BUILD)/tests/netstatus
NS_TEST_FLAGS := $(HOST_CFLAGS) -I$(NS_DIR)/c -Itests/lib -Iinclude

$(NS_TEST_GEN)/%.o: Sources/Networking/Econet/NetStatus/test/netstatus/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NS_TEST_FLAGS) -c $< -o $@

$(NS_TEST_GEN)/%.o: $(NS_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NS_TEST_FLAGS) -c $< -o $@

$(NS_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(NS_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/netstatus-checks: $(addprefix $(NS_TEST_GEN)/,cases.o                                    nsdecide.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

netstatuschecks: $(BUILD)/tests/netstatus-checks
	@$(BUILD)/tests/netstatus-checks
.PHONY: netstatuschecks

# The vendored NetBSD rump kernel under the Socket chunk.
#
# tools/build/vendor-rump.py populates Sources/Networking/Inet/Internet/netbsd and
# writes netbsd-local/sources.mk, which lists what to compile; no NetBSD file
# is named here.
#
# -fropi -frwpi make the code reach its writable globals through r9 and
# everything else PC-relatively, so the image carries no relocation and the
# writable data becomes a block claimed at initialisation. $(RUMP_STAMP)
# reports the cost each build.
#
# -Wno-everything: this is third-party code with its own warning discipline.
RUMP_DIR   := Sources/Networking/Inet/Internet/netbsd
RUMP_LOCAL := Sources/Networking/Inet/Internet/netbsd-local

# Where the `machine`, `arm` and `evbarm` header directories are made.
# `machine` is evbarm's, as this is a 32-bit ARM kernel.
#
# All three must be set before sources.mk is included: its include path is
# written in terms of them and is simply expanded.
RUMP_STAGE := $(BUILD)/modules/internet/include

include $(RUMP_LOCAL)/sources.mk

# RUMP_DEFINES and RUMP_INCLUDES come from sources.mk, written by the tool
# that chose the files, so the compiler reads the same headers that were
# vendored.

# -fcommon, unlike the rest of the tree: librump/rumpnet's net_stub.c has
# tentative definitions of ifnet_list, ifnet_pslist and ifnet_mtx that must
# merge with the real ones in sys/net/if.c; -fno-common makes them duplicate
# strong definitions.
# -mno-movt: the module relocator cannot take MOVW/MOVT pairs, which ARMv7
# code would otherwise use for addresses.
RUMP_CFLAGS := --target=arm-none-eabi $(ARCH_MACHINE) -mno-movt -ffreestanding \
               -fno-strict-aliasing -fropi -frwpi -fno-jump-tables \
               -fno-stack-protector -fcommon -mno-unaligned-access \
               -fno-unwind-tables -fno-asynchronous-unwind-tables \
               -Os -g -std=gnu99 -Wno-everything -MMD -MP \
               $(RUMP_DEFINES) $(RUMP_INCLUDES)

RUMP_SRCS := $(sort $(foreach c,$(RUMP_COMPONENTS),$(RUMP_SRCS_$(c))))
RUMP_OBJS := $(patsubst %.c,$(BUILD)/modules/internet/rump/%.o,$(RUMP_SRCS)) \
             $(BUILD)/modules/internet/rump/local/vers.o

# The machine half: ours, compiled with the vendored code's flags because it
# is called from that code and reads its headers, but without
# -Wno-everything.
INET_MACHINE_SRCS := atomics hypercall scheduler stack stack_ioctl stack_route stack_sysctl aunwire routewire sockabi
INET_MACHINE_OBJS := $(addprefix $(BUILD)/modules/internet/machine/,\
                                 $(addsuffix .o,$(INET_MACHINE_SRCS))) \
                     $(BUILD)/modules/internet/machine/context_switch.o \
                     $(BUILD)/modules/internet/machine/irqs.o
INET_MACHINE_CFLAGS = $(RUMP_CFLAGS) -ISources/Networking/Inet/Internet/c/arch/aarch32 \
                      -Wall -Wextra -Werror

$(BUILD)/modules/internet/machine/irqs.o: Sources/Networking/Inet/Internet/s/aarch32/irqs.S
	@mkdir -p $(dir $@)
	$(CC) --target=arm-none-eabi $(ARCH_MACHINE) -c $< -o $@

$(BUILD)/modules/internet/machine/context_switch.o: Sources/Networking/Inet/Internet/s/aarch32/context.S
	@mkdir -p $(dir $@)
	$(CC) --target=arm-none-eabi $(ARCH_MACHINE) -c $< -o $@

$(BUILD)/modules/internet/machine/%.o: Sources/Networking/Inet/Internet/c/%.c \
                                       $(RUMP_STAGE)/.made Makefile \
                                       $(RUMP_LOCAL)/sources.mk
	@mkdir -p $(dir $@)
	$(CC) $(INET_MACHINE_CFLAGS) -c $< -o $@

# The other side of the hypercall layer, compiled with the module's own flags
# because it reads include/riscos, which the vendored -nostdinc include path
# cannot supply. It is still renamed with the rest so that its callers' names
# match.
INET_SIDE_SRCS := hypmachine dcimachine
INET_SIDE_OBJS := $(addprefix $(BUILD)/modules/internet/machine/side/,\
                              $(addsuffix .o,$(INET_SIDE_SRCS)))

$(BUILD)/modules/internet/machine/side/dcimachine.o: INET_CFLAGS := $(filter-out -fpie -ffixed-r9,$(MOD_CFLAGS)) -fropi -frwpi -mno-movt -ISources/Lib/CLib/include -ISources/Networking/Inet/Internet/c -ISources/Networking/Inet/Internet/c/arch/aarch32 -I$(BUILD)/modules/internet

$(BUILD)/modules/internet/machine/side/dcimachine.o: \
    $(BUILD)/modules/internet/inet_veneers.h

$(BUILD)/modules/internet/machine/side/%.o: Sources/Networking/Inet/Internet/c/%.c Makefile
	@mkdir -p $(dir $@)
	$(CC) $(INET_CFLAGS) -ffixed-r9 -c $< -o $@

RUMP_STAMP := $(BUILD)/modules/internet/rump/relocations.stamp

$(RUMP_STAGE)/.made: Makefile
	@mkdir -p $(RUMP_STAGE)
	@ln -sfn $(abspath $(RUMP_DIR))/sys/arch/arm/include $(RUMP_STAGE)/arm
	@ln -sfn $(abspath $(RUMP_DIR))/sys/arch/evbarm/include $(RUMP_STAGE)/machine
	@ln -sfn $(abspath $(RUMP_DIR))/sys/arch/evbarm/include $(RUMP_STAGE)/evbarm
	@touch $@

$(BUILD)/modules/internet/rump/%.o: $(RUMP_DIR)/%.c $(RUMP_STAGE)/.made \
                                    Makefile $(RUMP_LOCAL)/sources.mk
	@mkdir -p $(dir $@)
	@$(CC) $(RUMP_CFLAGS) -c $< -o $@

$(BUILD)/modules/internet/rump/local/%.o: $(RUMP_LOCAL)/%.c \
                                          $(RUMP_STAGE)/.made Makefile
	@mkdir -p $(dir $@)
	@$(CC) $(RUMP_CFLAGS) -c $< -o $@

# tools/check/rumpimage.py reports what the objects cost a ROM module and
# refuses a relocation against code. Anything else is data, which is copied
# into a block claimed at initialisation.
$(RUMP_STAMP): $(RUMP_OBJS) $(INET_MACHINE_OBJS) $(INET_SIDE_OBJS) \
               $(CLIB) tools/check/rumpimage.py Makefile
	@python3 tools/check/rumpimage.py $(RUMP_OBJS) $(INET_MACHINE_OBJS) \
	  $(INET_SIDE_OBJS) --clib $(CLIB)
	@touch $@

# The vendored stack and Sources/Lib/CLib both define printf, snprintf and the like,
# so the objects are renamed into rumpns_* as rump's own build does.
# tools/build/rumprename.py documents the rule and writes the list of names
# it applied.
INET_RENAME_DIR  := $(BUILD)/modules/internet/renamed
INET_RENAME_LIST := $(BUILD)/modules/internet/rumpns.syms
INET_RENAME_STAMP := $(INET_RENAME_DIR)/renamed.stamp

$(INET_RENAME_STAMP): $(RUMP_OBJS) $(INET_MACHINE_OBJS) \
                      $(INET_SIDE_OBJS) $(CLIB) \
                      tools/build/rumprename.py tools/build/rumprwrefs.py tools/build/rumprelocs.py Makefile
	@mkdir -p $(INET_RENAME_DIR)
	@python3 tools/build/rumprename.py --out $(INET_RENAME_DIR) \
	  --clib $(CLIB) --list $(INET_RENAME_LIST) \
	  $(RUMP_OBJS) $(INET_MACHINE_OBJS) $(INET_SIDE_OBJS)
	@touch $@

# The link, in two passes: the relocation table is part of the image, so the
# first link is measured by tools/build/rumprelocs.py, which writes the table,
# and the second includes it. The two must agree, which shows the table's
# offsets do not depend on the image size.
#
# Undefined symbols are allowed because the Ethernet adapter is not written
# yet; tools/check/rumplink.py passes only the names listed as owed.
INET_RENAMED_OBJS := $(patsubst $(BUILD)/modules/internet/%,\
                       $(INET_RENAME_DIR)/%,$(RUMP_OBJS) $(INET_MACHINE_OBJS) \
                       $(INET_SIDE_OBJS))
INET_RELOCS  := $(BUILD)/modules/internet/relocs.S
# The workspace is compiled like the module's other C and linked into the
# image rather than the facade, because it reads module-rw.ld's marks and
# mk/ld/module.ld has none.
INET_WORKSPACE_OBJ := $(BUILD)/modules/internet/workspace.o
INET_IMAGE   := $(BUILD)/modules/internet/image.elf
INET_RW_LD   := Sources/Networking/Inet/Internet/module-rw.ld
INET_RW_LDFLAGS := -z undefs --emit-relocs -T $(INET_RW_LD)

$(INET_IMAGE): $(INET_RENAME_STAMP) $(INET_WORKSPACE_OBJ) $(CLIB) \
               $(INET_RW_LD) tools/build/rumprelocs.py \
               tools/check/rumplink.py Makefile
	@$(LD) $(INET_RW_LDFLAGS) -o $@.measured $(INET_RENAMED_OBJS) \
	  $(INET_WORKSPACE_OBJ) $(CLIB)
	@python3 tools/build/rumprelocs.py $@.measured --asm $(INET_RELOCS) \
	  --quiet
	@$(CC) --target=arm-none-eabi -x assembler -c $(INET_RELOCS) \
	  -o $(INET_RELOCS:.S=.o)
	@$(LD) $(INET_RW_LDFLAGS) -o $@.tmp $(INET_RENAMED_OBJS) \
	  $(INET_WORKSPACE_OBJ) $(INET_RELOCS:.S=.o) $(CLIB)
	@python3 tools/build/rumprelocs.py $@.tmp --asm $@.again.S
	@cmp -s $(INET_RELOCS) $@.again.S || { echo "internetimage: the table \
	  the first link produced is not the table the second one wants, so \
	  the template's layout depends on the image size after all"; exit 1; }
	@python3 tools/check/rumplink.py $@.tmp
	@rm -f $@.measured $@.again.S
	@mv $@.tmp $@

internetimage: $(INET_IMAGE)
.PHONY: internetimage

internetrump: $(RUMP_STAMP) $(INET_RENAME_STAMP) $(INET_IMAGE)
.PHONY: internetrump

-include $(RUMP_OBJS:.o=.d) $(INET_MACHINE_OBJS:.o=.d) \
         $(INET_SIDE_OBJS:.o=.d)

# Checks the vendored tree against what the tool says it vendored.
rumpcheck:
	@python3 tools/build/vendor-rump.py --check
.PHONY: rumpcheck

# Internet: the Socket SWI chunk backed by the NetBSD rump kernel.
# Host and ARM tests cover the ABI, scheduler and teardown; internetmachinetest
# exercises DCI/NAT, DNS and HTTP. RPCEmu must provide host entropy.
INET_OBJS := $(BUILD)/modules/internet/modhead.o \
             $(BUILD)/modules/internet/sockabi.o \
             $(BUILD)/modules/internet/internet.o \
             $(BUILD)/modules/internet/bootp.o \
             $(BUILD)/modules/internet/veneers.o \
             $(BUILD)/modules/internet/stackcall.o
INET_MOD  := $(BUILD)/modules/Internet.mod
INET_CFLAGS := $(MOD_CFLAGS) -ffixed-r9 -ISources/Lib/CLib/include -ISources/Networking/Inet/Internet/c \
               -I$(BUILD)/modules/internet

$(BUILD)/modules/internet/modhead.S: Sources/Networking/Inet/Internet/cmhg/modhead \
                                     Sources/Networking/Inet/Internet/VersionNum \
                                     tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Networking/Inet/Internet -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/modules/internet/%.o: Sources/Networking/Inet/Internet/c/%.c Makefile \
                               $(BUILD)/modules/internet/inet_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(INET_CFLAGS) -c $< -o $@

$(BUILD)/modules/internet/veneers.S: Sources/Networking/Inet/Internet/s/boundaries \
                                     Sources/Networking/Inet/Internet/cmhg/modhead \
                                     tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/Networking/Inet/Internet --asm $@ \
	  --header $(BUILD)/modules/internet/inet_veneers.h

$(BUILD)/modules/internet/inet_veneers.h: \
    $(BUILD)/modules/internet/veneers.S

$(BUILD)/modules/internet/veneers.o: $(BUILD)/modules/internet/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(INET_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/internet/modhead.o: $(BUILD)/modules/internet/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(INET_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/internet/stackcall.o: Sources/Networking/Inet/Internet/s/aarch32/stackcall.S
	@mkdir -p $(dir $@)
	$(CC) --target=arm-none-eabi $(ARCH_MACHINE) -c $< -o $@

$(BUILD)/modules/Internet.elf: $(INET_OBJS) $(INET_RENAME_STAMP) \
    $(INET_WORKSPACE_OBJ) $(CLIB) $(INET_RW_LD) tools/build/rumprelocs.py \
    tools/check/rumplink.py Makefile
	@mkdir -p $(dir $@)
	$(LD) $(INET_RW_LDFLAGS) -o $@.measured $(INET_OBJS) $(INET_RENAMED_OBJS) \
	  $(INET_WORKSPACE_OBJ) $(CLIB)
	@python3 tools/build/rumprelocs.py $@.measured --asm $@.relocs.S --quiet
	$(CC) --target=arm-none-eabi -c $@.relocs.S -o $@.relocs.o
	$(LD) $(INET_RW_LDFLAGS) -o $@.tmp $(INET_OBJS) $(INET_RENAMED_OBJS) \
	  $(INET_WORKSPACE_OBJ) $@.relocs.o $(CLIB)
	@python3 tools/build/rumprelocs.py $@.tmp --asm $@.again.S
	@cmp $@.relocs.S $@.again.S
	@python3 tools/check/rumplink.py $@.tmp
	@mv $@.tmp $@

$(INET_MOD): $(BUILD)/modules/Internet.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Internet)"

-include $(INET_OBJS:.o=.d)

internetmodule: $(INET_MOD)
# Isolated RPCEmu networking: never changes the shared emulator's discs/config.
internetmachinetest: internetmodule
	python3 Sources/Networking/Inet/Internet/test/internet/machine/run.py nat

# Name lookups against the emulator's name server: the reference Resolver,
# then ours loaded over it, in the reference ROM; the two reports must match.
# See Sources/Networking/Inet/Resolver/test/resolver/machine/run.py.
resolvermachinetest: resolvermodule
	python3 Sources/Networking/Inet/Resolver/test/resolver/machine/run.py reference > $(BUILD)/resolver-machine-reference.txt || true
	python3 Sources/Networking/Inet/Resolver/test/resolver/machine/run.py ours > $(BUILD)/resolver-machine-ours.txt || true
	@sed -n '/what the machine reported/,/^Evidence/p' $(BUILD)/resolver-machine-reference.txt | grep -v '^Evidence' > $(BUILD)/resolver-machine-reference.cmp
	@sed -n '/what the machine reported/,/^Evidence/p' $(BUILD)/resolver-machine-ours.txt | grep -v '^Evidence' > $(BUILD)/resolver-machine-ours.cmp
	@grep -q '^PASS' $(BUILD)/resolver-rpcemu-reference/result.txt && grep -q '^PASS' $(BUILD)/resolver-rpcemu-ours/result.txt
	diff $(BUILD)/resolver-machine-reference.cmp $(BUILD)/resolver-machine-ours.cmp
.PHONY: resolvermachinetest

# DHCP against the emulator's libslirp DHCP server, with the same isolation
# and its own runtime directory. See Sources/Networking/Inet/DHCP/test/dhcp/machine/run.py.
dhcpmachinetest: internetmodule dhcpmodule
	python3 Sources/Networking/Inet/DHCP/test/dhcp/machine/run.py
.PHONY: dhcpmachinetest
netlivetest: netmodule internetmodule
	python3 Sources/Networking/Inet/Internet/test/internet/machine/run.py aun
.PHONY: netlivetest

internetloopbacktest: internetmodule
	python3 Sources/Networking/Inet/Internet/test/internet/machine/run.py off
.PHONY: internetmachinetest internetloopbacktest

.PHONY: internetmodule

# The Socket chunk's argument handling, checked on the host: the two socket
# address shapes and how they are told apart, the two message header shapes,
# error numbering, scatter-list arithmetic and the select bitmap's width; and
# the routing socket's link-level addresses and expiry times.
INET_DIR := Sources/Networking/Inet/Internet
INET_TEST_GEN := $(BUILD)/tests/internet
INET_TEST_FLAGS := $(HOST_CFLAGS) -I$(INET_DIR)/c -Itests/lib -Iinclude

$(INET_TEST_GEN)/%.o: Sources/Networking/Inet/Internet/test/internet/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(INET_TEST_FLAGS) -c $< -o $@

$(INET_TEST_GEN)/%.o: $(INET_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(INET_TEST_FLAGS) -c $< -o $@

$(INET_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(INET_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/internet-checks: $(addprefix $(INET_TEST_GEN)/,cases.o \
                                    sockabi.o aunwire.o routewire.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The machine half's atomics on the host. INET_ATOMICS_HOST replaces the CPSR
# with a counted critical section provided by the checks, which verify each
# operation's result and that each leaves the section exactly once.
$(INET_TEST_GEN)/atomics.o: INET_TEST_FLAGS += -DINET_ATOMICS_HOST

$(BUILD)/tests/internet-atomic-checks: $(addprefix $(INET_TEST_GEN)/, \
                                           atomic_cases.o atomics.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The two-piece image's workspace arithmetic. INET_WORKSPACE_HOST leaves out
# the OS half of workspace.c: the RMA claim and the linker's marks.
$(INET_TEST_GEN)/workspace.o: INET_TEST_FLAGS += -DINET_WORKSPACE_HOST

$(BUILD)/tests/internet-workspace-checks: $(addprefix $(INET_TEST_GEN)/, \
                                            workspace_cases.o workspace.o \
                                            check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# The hypercall layer's arithmetic only. INET_HYPERCALL_HOST leaves out the
# rest of hypercall.c, which needs NetBSD's headers, an RMA and a clock.
$(INET_TEST_GEN)/hypercall.o: INET_TEST_FLAGS += -DINET_HYPERCALL_HOST

$(BUILD)/tests/internet-hypercall-checks: $(addprefix $(INET_TEST_GEN)/, \
                                            hypercall_cases.o hypercall.o \
                                            check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

# Run the actual scheduler on host stacks; only the context switch, clock,
# allocator and rump CPU upcalls are replaced by the test harness.
$(INET_TEST_GEN)/scheduler.o $(INET_TEST_GEN)/scheduler_cases.o: \
    INET_TEST_FLAGS += -DINET_SCHEDULER_HOST -I$(RUMP_DIR)/sys/rump/include

$(BUILD)/tests/internet-scheduler-checks: $(addprefix $(INET_TEST_GEN)/, \
                                            scheduler_cases.o scheduler.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(INET_TEST_GEN)/scheduler.o $(INET_TEST_GEN)/scheduler_cases.o: \
    $(INET_DIR)/c/scheduler.h $(INET_DIR)/c/context.h \
    $(RUMP_DIR)/sys/rump/include/rump/rumpuser.h $(RUMP_DIR)/sys/sys/errno.h

# Optional execution check for the real ARM register switch, using qemu-arm.
$(INET_TEST_GEN)/context-arm: Sources/Networking/Inet/Internet/test/internet/context_arm.S \
                            Sources/Networking/Inet/Internet/s/aarch32/context.S
	@mkdir -p $(dir $@)
	$(CC) --target=arm-linux-gnueabi -mcpu=strongarm -nostdlib -static \
	  -fuse-ld=lld -Wl,-e,_start $^ -o $@

internetcontextcheck: $(INET_TEST_GEN)/context-arm
	timeout 10 qemu-arm $<
.PHONY: internetcontextcheck

# Full ARM library smoke test. Replace the OS adapter with Linux syscalls
# and the interrupt masking (s/aarch32/irqs.S) with the test's own pair; run
# the same renamed rump, scheduler and atomics objects in qemu-arm.
$(INET_TEST_GEN)/rump-arm.o: Sources/Networking/Inet/Internet/test/internet/rump_arm.c $(RUMP_STAGE)/.made
	@mkdir -p $(dir $@)
	$(CC) $(INET_MACHINE_CFLAGS) -c $< -o $@

$(INET_TEST_GEN)/rump-checks.o: Sources/Networking/Inet/Internet/test/internet/rump_checks.c $(RUMP_STAGE)/.made
	@mkdir -p $(dir $@)
	$(CC) $(INET_MACHINE_CFLAGS) -c $< -o $@

$(INET_TEST_GEN)/rump-start.o: Sources/Networking/Inet/Internet/test/internet/rump_start.S
	@mkdir -p $(dir $@)
	$(CC) --target=arm-none-eabi -mcpu=strongarm -c $< -o $@

$(INET_TEST_GEN)/rump-arm.ld: $(INET_RW_LD)
	@mkdir -p $(dir $@)
	@python3 -c 'from pathlib import Path; s = Path("$<").read_text(); Path("$@").write_text(s.replace(". = 0;", ". = 0x10000;").replace("ADDR(.text) == 0,", "ADDR(.text) == 0x10000,"))'

$(INET_TEST_GEN)/relocate-arm.o: Sources/Networking/Inet/Internet/test/internet/relocate_arm.c
	$(CC) $(INET_CFLAGS) -c $< -o $@

$(INET_TEST_GEN)/workspace-arm.o: Sources/Networking/Inet/Internet/c/workspace.c
	$(CC) $(INET_CFLAGS) -DINET_WORKSPACE_HOST -c $< -o $@

INET_RUN_OBJS = $(filter-out $(INET_RENAME_DIR)/machine/side/hypmachine.o $(INET_RENAME_DIR)/machine/side/dcimachine.o $(INET_RENAME_DIR)/machine/irqs.o,$(INET_RENAMED_OBJS)) \
  $(INET_TEST_GEN)/rump-arm.o $(INET_TEST_GEN)/rump-checks.o \
  $(INET_TEST_GEN)/rump-start.o \
  $(INET_TEST_GEN)/relocate-arm.o $(INET_TEST_GEN)/workspace-arm.o

$(INET_TEST_GEN)/rump-arm: $(INET_RENAME_STAMP) $(CLIB) \
    $(INET_TEST_GEN)/rump-arm.o $(INET_TEST_GEN)/rump-checks.o \
    $(INET_TEST_GEN)/rump-start.o $(INET_TEST_GEN)/rump-arm.ld \
    $(INET_TEST_GEN)/relocate-arm.o $(INET_TEST_GEN)/workspace-arm.o
	$(LD) --emit-relocs -T $(INET_TEST_GEN)/rump-arm.ld -e test_start \
	  -o $@.measured $(INET_RUN_OBJS) $(CLIB)
	python3 tools/build/rumprelocs.py $@.measured --asm $@.relocs.S --quiet
	$(CC) --target=arm-none-eabi -c $@.relocs.S -o $@.relocs.o
	$(LD) --emit-relocs -T $(INET_TEST_GEN)/rump-arm.ld -e test_start \
	  -o $@ $(INET_RUN_OBJS) $@.relocs.o $(CLIB)
	python3 tools/build/rumprelocs.py $@ --asm $@.again.S --quiet
	cmp $@.relocs.S $@.again.S

internetruncheck: $(INET_TEST_GEN)/rump-arm
	timeout 30 qemu-arm $<
.PHONY: internetruncheck
hostchecks: internetcontextcheck internetruncheck

internetchecks: $(BUILD)/tests/internet-checks \
                $(BUILD)/tests/internet-atomic-checks \
                $(BUILD)/tests/internet-workspace-checks \
                $(BUILD)/tests/internet-hypercall-checks \
                $(BUILD)/tests/internet-scheduler-checks
	@$(BUILD)/tests/internet-checks
	@$(BUILD)/tests/internet-atomic-checks
	@$(BUILD)/tests/internet-workspace-checks
	@$(BUILD)/tests/internet-hypercall-checks
	@$(BUILD)/tests/internet-scheduler-checks
.PHONY: internetchecks

# WindowUtils: a header, a title and a help string, and no code. See
# Sources/Desktop/WimpUtils/cmhg/modhead for why the ROM carries it. Its one object is
# the header, so there is no C, no veneers and no library.
WU_MOD  := $(BUILD)/Desktop/WimpUtils.mod

$(BUILD)/Sources/Desktop/WimpUtils/modhead.S: Sources/Desktop/WimpUtils/cmhg/modhead \
                                      Sources/Desktop/WimpUtils/VersionNum \
                                      tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/Desktop/WimpUtils -Ihdr \
	  -ISources/Kernel/hdr/Global

$(BUILD)/Sources/Desktop/WimpUtils/modhead.o: $(BUILD)/Sources/Desktop/WimpUtils/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(MOD_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/Desktop/WimpUtils.elf: $(BUILD)/Sources/Desktop/WimpUtils/modhead.o \
                                mk/ld/module.ld tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(BUILD)/Sources/Desktop/WimpUtils/modhead.o
	@python3 tools/build/modulecheck.py $@.tmp --header-only
	@if python3 tools/build/modulecheck.py $@.tmp >/dev/null 2>&1; then \
	  echo "modulecheck: a header-only module passed WITHOUT the flag, so"; \
	  echo "  the flag is not load-bearing and a dropped --emit-relocs"; \
	  echo "  would go unnoticed on every other module"; \
	  exit 1; \
	fi
	@mv $@.tmp $@

$(WU_MOD): $(BUILD)/Desktop/WimpUtils.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, WindowUtils)"

wimputilsmodule: $(WU_MOD)
.PHONY: wimputilsmodule

# ShellCLI's C on the host, against a table of environment handlers: what
# Shell_Destroy leaves installed. Sources/Desktop/ShellCLI/test/shellcli/host precedes include/ so the
# SWI wrappers are functions the checks define.
SHELL_TEST_GEN := $(BUILD)/tests/shellcli
SHELL_TEST_FLAGS := -ISources/Desktop/ShellCLI/test/shellcli/host $(HOST_CFLAGS) -I$(SHELL_DIR)/c \
                    -I$(BUILD)/Sources/Desktop/ShellCLI -Wno-unused-parameter

$(SHELL_TEST_GEN)/checks.o: Sources/Desktop/ShellCLI/test/shellcli/checks.c \
                            $(BUILD)/Sources/Desktop/ShellCLI/shell_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SHELL_TEST_FLAGS) -c $< -o $@

$(SHELL_TEST_GEN)/shellcli.o: $(SHELL_DIR)/c/shellcli.c \
                              $(BUILD)/Sources/Desktop/ShellCLI/shell_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SHELL_TEST_FLAGS) -c $< -o $@

$(SHELL_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SHELL_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/shellcli-checks: $(addprefix $(SHELL_TEST_GEN)/,checks.o \
                                  shellcli.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

-include $(SHELL_TEST_GEN)/checks.d $(SHELL_TEST_GEN)/shellcli.d

shellclichecks: $(BUILD)/Desktop/ShellCLI.elf $(BUILD)/tests/shellcli-checks
	@python3 tools/build/modulecheck.py $<
	@python3 Sources/Desktop/ShellCLI/test/shellcli/buildcheck.py
	@$(BUILD)/tests/shellcli-checks

.PHONY: shellclichecks
hostchecks: shellclichecks

.PHONY: all clean verify regen-headers modules

# The ported modules are in `all` so that a shared-header change that breaks
# one fails the build.
all: $(HAL_IOMD_BIN) $(MOD_VIDC20_MOD) $(MOD_KRES_MOD) \
     $(SHELL_MOD) $(WS_MOD) $(FA_MOD) $(FS_MOD) $(DAO_MOD) $(DSK_MOD) \
     $(DSP_MOD) $(MSG_MOD) $(RFS_MOD) $(TERR_MOD) $(FSW_MOD) $(OBEY_MOD) \
     $(MOUSE_MOD) $(BT_MOD) $(CTM_MOD) $(MM_MOD) $(RTC_MOD) $(FM_MOD) \
     $(SQ_MOD) $(RAMFS_MOD) $(RFL_MOD) $(SAV_MOD) $(DRAW_MOD) $(FC_MOD) \
     $(WU_MOD) $(NS_MOD)

modules: $(MOD_VIDC20_MOD) $(MOD_KRES_MOD) $(MSG_MOD) \
         $(RFS_MOD) $(TERR_MOD) $(FSW_MOD) $(OBEY_MOD) $(MOUSE_MOD) \
         $(BT_MOD) $(CTM_MOD) $(MM_MOD) $(RTC_MOD) $(FM_MOD) $(SQ_MOD) \
         $(RAMFS_MOD) $(RFL_MOD) $(SAV_MOD) $(DRAW_MOD) $(FC_MOD) \
         $(WU_MOD) $(NS_MOD)

# Records the -D flags and ROM_SIZE so objects rebuild when they change; the
# .d files track headers, not the command line. ROM_SIZE is the HAL's word at
# &60. The file is rewritten only when its contents differ.
$(BUILD)/defines.stamp: FORCE
	@mkdir -p $(dir $@)
	@printf '%s\n' '$(DEFINES) ROM_SIZE=$(ROM_SIZE)' | cmp -s - $@ \
	  || printf '%s\n' '$(DEFINES) ROM_SIZE=$(ROM_SIZE)' > $@

FORCE:
.PHONY: FORCE

$(BUILD)/%.o: %.S $(BUILD)/defines.stamp
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(INCLUDES) $(DEFINES) -c $< -o $@

$(BUILD)/%.o: %.c $(BUILD)/defines.stamp
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(INCLUDES) $(DEFINES) -c $< -o $@

$(BUILD)/lib/%.o: lib/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_MACHINE) $(MOD_PIFLAGS) $(MOD_INCLUDES) \
	  $(MOD_DEFINES) -c $< -o $@

include mk/kernelres-rules.mk

# The fonts are not in this block: Sources/Video/Render/Fonts/ROMFonts registers them,
# under the reference's registered names rather than their file names.

# Checks the two message files in the KernelResources block that were taken
# from the reference against their originals, expanding the reference's
# dictionary compression and comparing the words. If the MsgTrans file is
# missing, MessageTrans recurses looking up TokNFnd until the SVC stack is
# exhausted: a black screen with no message.
kreschecks: $(MOD_KRES_BLOCK)
	@python3 tools/check/kresscheck.py $< $(REF_ROM)

$(MOD_KRES_OBJ): $(MOD_KRES_DIR)/module.S $(MOD_KRES_BLOCK)
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_MACHINE) $(MOD_INCLUDES) \
	  -DMODULE_HELP_VERSION='"$(MOD_KRES_VERSION)"' \
	  -DRESOURCE_BLOCK='"$(MOD_KRES_BLOCK)"' -c $< -o $@

$(MOD_KRES_ELF): $(MOD_KRES_OBJ) $(MOD_KRES_DIR)/module.ld
	$(LD) $(LDFLAGS) -T $(MOD_KRES_DIR)/module.ld -o $@ $(MOD_KRES_OBJ)

$(MOD_KRES_MOD): $(MOD_KRES_ELF)
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes)"

$(BUILD)/Sources/Hardware/IOMD/halvideo.o: Sources/Hardware/IOMD/halvideo.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(MOD_PIFLAGS) $(INCLUDES) $(DEFINES) -c $< -o $@

$(HAL_IOMD_ELF): $(HAL_IOMD_OBJ) Sources/Hardware/IOMD/hal.ld
	$(LD) $(LDFLAGS) --no-check-sections \
	  --defsym=VIDC20_WS_SIZE=$(VIDC20_WS_SIZE) \
	  -T Sources/Hardware/IOMD/hal.ld -o $@ $(HAL_IOMD_OBJ)

$(HAL_IOMD_BIN): $(HAL_IOMD_ELF)
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes)"

# The bytes below &68 are fixed by the hardware and the ROM format, so they
# are compared against a real ROM. The HAL is made again by a make of its own:
# a recursion at another ROM_SIZE earlier in the same run can have rebuilt it
# after this make last looked.
verify: $(HAL_IOMD_BIN)
	@$(MAKE) --no-print-directory $(HAL_IOMD_BIN)
	@if [ ! -f "$(REF_ROM)" ]; then \
		echo "verify: no reference ROM at $(REF_ROM)"; \
		echo "        run legacy/setup.sh to fetch one"; \
		exit 1; \
	fi
	@python3 tools/check/bincmp.py $(HAL_IOMD_BIN) $(REF_ROM) --length 0x68
	@echo
	@python3 tools/check/halcmp.py $(HAL_IOMD_ELF) $(REF_ROM) --hal-size $(HAL_SIZE) \
	  $(addprefix --object ,$(patsubst %.S,$(BUILD)/%.o,$(HAL_IOMD_SRC))) \
	  $(addprefix --c-object ,$(patsubst %.c,$(BUILD)/%.o,$(HAL_IOMD_CSRC))) \
	  $(addprefix --c-object ,$(HAL_VIDEO_OBJ)) \
	  --added "$(BUILD)/Sources/Hardware/IOMD/videoentries.o=$(HAL_VIDEO_WHY)" \
	  --added "$(BUILD)/Sources/Hardware/IOMD/iicentries.o=$(HAL_IIC_WHY)" \
	  $(foreach r,$(DIFFERS_SHORTER),--differs "$(r)=$(SHORTER_WHY)") \
	  $(foreach r,$(DIFFERS_ADRFAR),--differs "$(r)=$(ADRFAR_WHY)") \
	  $(foreach r,$(DIFFERS_POOL),--differs "$(r)=$(POOL_WHY)") \
	  $(foreach r,$(DIFFERS_DEBUGTX),--differs "$(r)=$(DEBUGTX_WHY)") \
	  $(foreach r,$(DIFFERS_IIC),--differs "$(r)=$(IIC_WHY)") \
	  $(foreach r,$(DIFFERS_DESC),--differs "$(r)=$(DESC_WHY)") \
	  $(foreach r,$(DIFFERS_TABLE),--differs "$(r)=$(TABLE_WHY)") \
	  $(foreach r,$(DIFFERS_HALINIT),--differs "$(r)=$(HALINIT_WHY)") \
	  $(foreach r,$(DIFFERS_VIDEODEV),--differs "$(r)=$(VIDEODEV_WHY)") \
	  $(foreach r,$(DIFFERS_IDEINFO),--differs "$(r)=$(IDEINFO_WHY)")

ROM := $(BUILD)/riscos-c-hal

# A ROM made of our HAL and the reference ROM's unmodified OS image. Booting
# this in RPCEmu tests the ported HAL against the real OS.
rom: $(ROM)

$(ROM): $(HAL_IOMD_BIN)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) --hal-size $(HAL_SIZE)

# Our module in place of the reference one, over our HAL. romtool rebuilds
# the chain, but some later modules call each other by address, so moving
# them breaks it; see "Why there is no full-ROM test" in README.md. Use
# vidcminrom to test a module.
MANIFEST := $(LEGACY)/romparts/rom.manifest
VIDC_ROM := $(BUILD)/riscos-c-video

vidcrom: $(VIDC_ROM)

$(VIDC_ROM): $(HAL_IOMD_BIN) $(MOD_VIDC20_MOD)
	@python3 $(LEGACY)/tools/romtool.py join $(MANIFEST) $(BUILD)/rom-vidc.tmp \
	  --replace VIDC20Video=$(abspath $(MOD_VIDC20_MOD))
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(BUILD)/rom-vidc.tmp \
	  --hal-size $(HAL_SIZE)
	@rm -f $(BUILD)/rom-vidc.tmp

# The smallest ROM that shows anything: the CLI and this driver, which is what
# reaches a supervisor prompt. A full 121-module ROM has no spare room in the
# chain, so a module larger than the one it replaces will not fit there.
VIDC_MINROM := $(BUILD)/riscos-c-video-min
VIDC_MINMODS := UtilityModule,VIDC20Video,PS2Driver

vidcminrom: $(VIDC_MINROM)

$(VIDC_MINROM): $(HAL_IOMD_BIN) $(MOD_VIDC20_MOD)
	@python3 $(LEGACY)/tools/romtool.py join $(MANIFEST) $(BUILD)/rom-vidcmin.tmp \
	  --modules $(VIDC_MINMODS) \
	  --replace VIDC20Video=$(abspath $(MOD_VIDC20_MOD))
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(BUILD)/rom-vidcmin.tmp \
	  --hal-size $(HAL_SIZE)
	@rm -f $(BUILD)/rom-vidcmin.tmp

vidcminboot: $(VIDC_MINROM)
	$(LEGACY)/tools/try-rom.sh $(abspath $(VIDC_MINROM)) $(abspath $(BUILD))/vidcminboot.png 18
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/vidcminboot.png

.PHONY: vidcminrom vidcminboot

# The smallest ROM that shows the kernel working: our HAL, kernel and display
# driver, with the chain after the kernel built from these alone.
#
# The prompt cannot be typed at: the kernel has no key handler (KeyInit leaves
# KeyVec at -1), and InternationalKeyboard, which installs one, is omitted.
# PS2Driver is the reference's; without a keyboard registering, reset spends
# three seconds scanning for one.
MIN_ROM  := $(BUILD)/riscos-c-min
MIN_MODS := $(MOD_VIDC20_MOD) $(LEGACY)/romparts/015-PS2Driver.mod

minrom: $(MIN_ROM)

$(MIN_ROM): $(HAL_IOMD_BIN) $(KERNEL_BIN) $(MOD_VIDC20_MOD)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  $(addprefix --module ,$(MIN_MODS))

# Fails unless the machine draws something: screen-state.py exits 0 either
# way, so the recipe checks its output.
minboot: $(MIN_ROM)
	$(LEGACY)/tools/try-rom.sh $(abspath $(MIN_ROM)) $(abspath $(BUILD))/minboot.png 18
	@state=$$(python3 $(LEGACY)/tools/screen-state.py $(BUILD)/minboot.png); \
	 echo "$$state"; \
	 case "$$state" in OUTPUT*) ;; *) \
	   echo "minboot: the machine drew nothing in 18 seconds. The screen is"; \
	   echo "  $(BUILD)/minboot.png and the telemetry is $(BUILD)/../telemetry.log."; \
	   exit 1;; esac

.PHONY: minrom minboot

# Builds every object, host binary, test ROM and the five matrix rows without
# running anything. `test` runs it first and the emulator targets wait on it.
# Run it alone after any change to a header, include path or build
# structure.
EVERYTHING := $(KERNEL_BIN) $(MIN_ROM) \
              $(TESTS:%=$(BUILD)/tests/%-ours.rom) \
              $(TESTS:%=$(BUILD)/tests/%-ref.rom) \
              $(CHAIN_TESTS:%=$(BUILD)/tests/%-ours.rom) \
              $(CHECKED_TESTS:%=$(BUILD)/tests/%-checks) \
              $(HOST_TESTS:%=$(BUILD)/tests/%-host) \
              $(FILE_TESTS:%=$(BUILD)/tests/%-file) \
              $(POD_FILES)

everything: $(EVERYTHING) matrix premmu
	@echo "everything: $(words $(EVERYTHING)) artifacts and $(words $(MATRIX)) further machines build; nothing was run"
.PHONY: everything


# The reference OS modules on our kernel.
#
# Needs the 8M ROM, as our kernel is larger than the one it replaces and the
# reference ROM is full, so the recipe recurses to set ROM_SIZE. Uses mkrom's
# --relocate, which is a hack: read relocate() in tools/build/mkrom.py before
# trusting this ROM.
FULL_ROM := $(BUILD)/riscos-full

fullrom:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(FULL_ROM)

$(FULL_ROM): $(HAL_IOMD_BIN) $(KERNEL_BIN) $(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  --reference-modules --relocate

# The full ROM with our builds of modules in the reference's places. A
# smaller replacement is padded to its slot; a larger one goes past the chain
# with a placeholder in its slot. Replacements must be position independent.
# Entries use the name the module registers, which is not always its
# directory name.
REPLACED_MODULES := ShellCLI=$(SHELL_MOD) WindowScroll=$(WS_MOD) \
                    Filer_Action=$(FA_MOD) FilerSWIs=$(FS_MOD) \
                    DragAnObject=$(DAO_MOD) Desktop=$(DSK_MOD) \
                    DragASprite=$(DSP_MOD) FilterManager=$(FLT_MOD) \
                    Free=$(FRE_MOD) TaskWindow=$(TWN_MOD) \
                    TaskManager=$(SWI_MOD) Pinboard=$(PBD_MOD) \
                    Filer=$(FLR_MOD) RAMFSFiler=$(RFL_MOD) \
                    ResourceFiler=$(RSF_MOD) \
                    Squash=$(SQ_MOD) ScrSaver=$(SAV_MOD) \
                    WindowUtils=$(WU_MOD) NetStatus=$(NS_MOD) \
                    International=$(BUILD)/modules/International.mod \
                    InternationalKeyboard=$(BUILD)/modules/InternationalKeyboard.mod \
                    DisplayManager=$(BUILD)/modules/DisplayManager.mod

# Not in REPLACED_MODULES:
#
# Draw (26,168 bytes against an 11,108-byte slot) goes past the chain, and
# fits in the 8M ROM. Every desktop machine test here boots $(REPLACED_ROM),
# so adding it waits for a batched machine run. It has booted past the chain
# on its own and answers *Help Draw; its drawing has not been compared with
# the reference's. A module past the chain can OS_Claim a software vector.
#
# SpriteExtend (153,164 bytes against a 151,876-byte slot) goes past the
# chain and has booted to a desktop. The progressive JPEG transcoder must not
# be dropped to save space; Sources/Video/Render/SprExtend/cmhg/modhead has the
# size breakdown.
#
# FileCore and RamFS are larger than their slots, as each carries Sources/Lib/CLib.
# They have their own targets against this ROM: filecorereplacedtest and
# filecorereplacedreftest, below.
REPLACED_ROM     := $(BUILD)/riscos-replaced

# The Wimp is kept out of the list above and has its own targets. It is three
# times the size of the module it replaces (it carries the desktop's sprite
# file), so it needs `--replace-at-end`; and every other replaced module runs
# under it, so a failure with it in place means nothing until the same ROM
# has been seen to work without it.
WIMP_REPLACED   := $(REPLACED_MODULES) WindowManager=$(WIMP_MOD)

# Extra mkrom options that put a module past the chain instead of in its
# slot. endboot uses it to test the redirect: against portedrom, only the
# placement differs.
REPLACED_AT_END ?=

portedrom:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)

endboot:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) \
	  REPLACED_AT_END='--replace-at-end ShellCLI' \
	  TYPE_SHOT=endtest portedtest

# REPLACED_MODULES is a variable, whose changes make cannot see, so it is
# written to a stamp file the ROM depends on. Without it, a control run that
# overrides the list reuses the ROM already built. The stamp is rewritten only
# when the list changes.
REPLACED_STAMP := $(BUILD)/riscos-replaced.modules

.PHONY: replaced-stamp
replaced-stamp:
	@mkdir -p $(dir $(REPLACED_STAMP))
	@printf '%s\n' '$(REPLACED_MODULES)' > $(REPLACED_STAMP).tmp
	@if cmp -s $(REPLACED_STAMP).tmp $(REPLACED_STAMP); then \
	  rm -f $(REPLACED_STAMP).tmp; \
	else \
	  mv $(REPLACED_STAMP).tmp $(REPLACED_STAMP); \
	fi

$(REPLACED_STAMP): replaced-stamp ;

# Every module named in REPLACED_MODULES, so that rebuilding one rebuilds the
# ROM: the stamp catches the list changing, not a module in it.
$(REPLACED_ROM): $(HAL_IOMD_BIN) $(KERNEL_BIN) \
                 $(foreach m,$(REPLACED_MODULES),$(lastword $(subst =, ,$(m)))) \
                 $(REPLACED_STAMP) \
                 $(REF_ROM) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  --reference-modules --relocate $(REPLACED_AT_END) \
	  $(addprefix --replace ,$(REPLACED_MODULES))

# Desktop runs on the machine.
#
# Scripted input starts from the desktop, not from a guess at how long a boot
# takes: RPCEmu raises cue 254 once the Desktop has started every task and
# taken its banner down, 253 when the typed keys are done and 252 when the
# pointer script is (legacy/rpcemu/src/telemetry.h). The delays count from
# those cues. A run ends as soon as its work is done - the test's own check
# passing, or the scripts finishing - and a *_WAIT is only how long a run
# that never gets there is given.
#
#   KEYS_AFTER        ms from the desktop to the first key
#   MOUSE_AFTER       ms from the desktop to the first pointer step
#   MOUSE_AFTER_KEYS  ms from the last key to the first pointer step, for a
#                     pointer script that acts on what the keys opened
#   MACHINE_SETTLE    s from the end of the work to the screenshot
KEYS_AFTER       ?= 0
MOUSE_AFTER      ?= 0
MOUSE_AFTER_KEYS ?= 1500
MACHINE_SETTLE   ?= 2
export KEYS_AFTER MOUSE_AFTER MOUSE_AFTER_KEYS MACHINE_SETTLE
KEYS_AT_DESKTOP   = RPCEMU_AUTOTYPE_CUE=254 RPCEMU_AUTOTYPE_DELAY=$(KEYS_AFTER)
MOUSE_AT_DESKTOP  = RPCEMU_AUTOMOUSE_CUE=254 RPCEMU_AUTOMOUSE_DELAY=$(MOUSE_AFTER)
MOUSE_AFTER_TYPED = RPCEMU_AUTOMOUSE_CUE=253 \
                    RPCEMU_AUTOMOUSE_DELAY=$(MOUSE_AFTER_KEYS)
# The end of a run whose work is the input itself, or the desktop alone.
UNTIL_SCRIPTS     = TRY_ROM_UNTIL=SCRIPTS-DONE TRY_ROM_SETTLE=$(MACHINE_SETTLE)
UNTIL_DESKTOP     = TRY_ROM_UNTIL=DESKTOP-READY TRY_ROM_SETTLE=$(MACHINE_SETTLE)
# The end of a run whose work is a driver's: its check passes, or it wrote
# that it crashed. <check.py and its arguments>, <the HostFS directory>.
machine_done = python3 $(1) || grep -aqs crash $(2)/Report*

# Boots the replaced ROM and exercises ShellCLI: F12 opens it, and
# *WimpScroll reports WindowScroll's settings, changes all three and reads
# them back (its OS_ReadArgs path).
#
# `fulltest` types the same keys against the ROM with the reference ShellCLI;
# if neither shows anything, the keys are not arriving.
TYPE_ROM  ?= $(BUILD)/riscos-replaced
TYPE_SHOT ?= portedtest

portedtest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@printf '<F12>WimpScroll\nWimpScroll -Type Focus -LineScroll -Speed 8\nWimpScroll\n' > $(BUILD)/shellcli.keys
	RPCEMU_AUTOTYPE=$(abspath $(BUILD))/shellcli.keys \
	$(KEYS_AT_DESKTOP) RPCEMU_AUTOTYPE_GAP=25 $(UNTIL_SCRIPTS) \
	RPCEMU_MOUSE_FOLLOWING=0 \
	  $(LEGACY)/tools/try-rom.sh $(abspath $(TYPE_ROM)) $(abspath $(BUILD))/$(TYPE_SHOT).png 45
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/$(TYPE_SHOT).png

# A ROM of this tree's modules only; reference modules not yet ported are
# absent.
#
# The order is the reference chain's, since module initialisation runs in
# chain order. KernelResources, which carries the kernel's messages through
# ResourceFS, sits where the reference's Messages does: after ResourceFS and
# before MessageTrans.
#
# Every module links Sources/Lib/CLib. SharedCLibrary is here for software loaded
# from disc: it is our facade over Sources/Lib/CLib and Sources/Lib/Desktop, not the
# reference's library.
#
# SoundDMA follows DMAManager: activating IOMD's DMA controller stops every
# channel, sound included.
#
# The Toolbox modules: Toolbox goes first, since the class modules register
# with it; ToolAction and TextGadgets register gadgets with Window, and
# ColourMenu needs Menu. ColourPicker precedes them, as in the reference; the
# ColourDbox class wraps its SWIs.
#
# Modules whose rules are included at the end of this file are named here by
# path, because OURS_MODULES is simply expanded and their variables (such as
# $(CP_MOD) and $(TBM_MOD)) are still empty at this point.
COLOURPICKER_MOD := $(BUILD)/modules/ColourPicker.mod

# Resolver's rules are at the end of this file, so RESOLV_MOD is declared here
# for the simply expanded list below; otherwise it expands to nothing and the
# ROM silently builds without the module.
RESOLV_MOD := $(BUILD)/modules/Resolver.mod

# Freeway's rules are in Sources/Networking/Freeway/freeway.mk, included at the
# end of this file; named here for the list. 089 in the reference chain,
# after BootNet.
FREEWAY_MOD := $(BUILD)/modules/Freeway.mod

# LanManFS's are in Sources/Networking/LanManFS/lanmanfs.mk, for the same
# reason; 092 in the reference chain, after MimeMap.
LANMANFS_MOD := $(BUILD)/modules/LanManFS.mod

# ShareFS, SharedCLibrary and BASICTrans have their rules further down, so they
# are named by path. The reference ROM has no BASICTrans; it goes after
# BASIC64.
OURS_SHAREFS_MOD := $(BUILD)/modules/ShareFS.mod
OURS_SCL_MOD := $(BUILD)/modules/SharedCLibrary.mod
OURS_BTR_MOD := $(BUILD)/modules/BASICTrans.mod

# The ROM's applications, named here for the same reason; their rules are in
# mk/apps.mk, and !Draw's in Sources/Apps/Draw/module.mk. In the
# reference's order, between DHCP and the Toolbox.
APP_MODS := $(addprefix $(BUILD)/modules/,Edit.mod DrawApp.mod Paint.mod Alarm.mod Chars.mod Help.mod)

TOOLBOX_MODS := $(addprefix $(BUILD)/modules/,\
                  Toolbox.mod Window.mod Menu.mod \
                  ToolAction.mod TextGadgets.mod Iconbar.mod \
                  ColourDbox.mod ColourMenu.mod DCS.mod FileInfo.mod \
                  FontDbox.mod FontMenu.mod PrintDbox.mod ProgInfo.mod \
                  SaveAs.mod Scale.mod)

# Podule's rules are in Sources/HWSupport/Podule/module.mk; named here for the
# list. It is first, as in the reference chain: the kernel starts it before
# the rest and then asks it for the modules on the expansion cards.
OURS_PODULE_MOD := $(BUILD)/modules/Podule.mod

# The same for BBCEconet, Joystick and UnSqueezeAIF, each in its module.mk.
OURS_BBCECONET_MOD := $(BUILD)/modules/BBCEconet.mod
OURS_JOYSTICK_MOD := $(BUILD)/modules/Joystick.mod
OURS_UNSQUEEZEAIF_MOD := $(BUILD)/modules/UnSqueezeAIF.mod

# DMAManager, 025 in the reference chain: after DisplayManager.
OURS_DMA_MOD := $(BUILD)/modules/DMAManager.mod

# The parallel and serial device drivers, 050 and 059.
OURS_PARALLEL_MOD := $(BUILD)/modules/ParallelDeviceDriver.mod
OURS_SERIAL_MOD := $(BUILD)/modules/SerialDeviceDriver.mod

# Debugger, 022 in the reference chain: after ColourTrans.
OURS_DEBUGGER_MOD := $(BUILD)/modules/Debugger.mod

OURS_MODULES := $(OURS_PODULE_MOD) $(FSW_MOD) $(RFS_MOD) $(TERR_MOD) $(MOD_KRES_MOD) \
                $(UKT_MOD) \
                $(BUILD)/modules/International.mod $(BUILD)/modules/InternationalKeyboard.mod \
                $(MSG_MOD) $(FM_MOD) $(FPE_MOD) $(WIMP_MOD) $(SWI_MOD) \
                $(DSK_MOD) $(OURS_SCL_MOD) $(SM_MOD) \
                $(MOD_VIDC20_MOD) \
                $(MOUSE_MOD) $(PS2_MOD) $(BT_MOD) \
                $(BUFS_MOD) $(CTM_MOD) $(OURS_DEBUGGER_MOD) $(DEVFS_MOD) \
                $(BUILD)/modules/DisplayManager.mod \
                $(OURS_DMA_MOD) $(DSP_MOD) $(DAO_MOD) $(DRAW_MOD) $(DRAWFILE_MOD) $(OURS_BBCECONET_MOD) $(FC_MOD) $(ADFS_MOD) \
                $(RAMFS_MOD) \
                $(FLR_MOD) $(FS_MOD) $(FSLK_MOD) \
                $(FRE_MOD) $(HOUR_MOD) $(IIC_MOD) $(ITAB_MOD) $(OURS_JOYSTICK_MOD) $(NETFS_MOD) $(NFL_MOD) $(NETPRINT_MOD) \
                $(OBEY_MOD) $(OURS_PARALLEL_MOD) $(PBD_MOD) $(AFL_MOD) $(RFL_MOD) $(RSF_MOD) \
                $(ROMFONTS_MOD) $(RTC_MOD) \
                $(BLNK_MOD) $(SAV_MOD) \
                $(OURS_SERIAL_MOD) $(SERSPT_MOD) $(SERM_MOD) $(SHELL_MOD) \
                $(BUILD)/modules/SoundDMA.mod $(BUILD)/modules/SoundChannels.mod \
                $(BUILD)/modules/SoundScheduler.mod \
                $(SYSDEV_MOD) $(PIPE_MOD) \
                $(SPRX_MOD) $(SPU_MOD) $(SUPS_MOD) $(SQ_MOD) \
                $(TWN_MOD) \
                $(FLT_MOD) \
                $(BUILD)/modules/WaveSynth.mod $(BUILD)/modules/StringLib.mod \
                $(BUILD)/modules/Percussion.mod $(BUILD)/modules/SharedSound.mod \
                $(FA_MOD) $(DOSFS_MOD) \
                $(BCMD_MOD) $(WS_MOD) $(MBUF_MOD) $(INET_MOD) $(RESOLV_MOD) \
                $(NET_MOD) $(BNET_MOD) $(FREEWAY_MOD) $(OURS_SHAREFS_MOD) \
                $(MM_MOD) $(LANMANFS_MOD) $(DHCP_MOD) \
                $(WU_MOD) \
                $(NS_MOD) $(BASIC_MOD) $(BASIC64_MOD) $(OURS_BTR_MOD) \
                $(APP_MODS) \
                $(COLOURPICKER_MOD) $(TOOLBOX_MODS) \
                $(CDD_MOD) $(ATAPI_MOD) $(CDFS_MOD) $(CFL_MOD) \
                $(OURS_UNSQUEEZEAIF_MOD)

# BASIC and BASIC64 are in that list. They are larger than the reference's
# slots, so mkrom puts them past the chain.
OURS_ROM     := $(BUILD)/riscos-ours

# `make ourstest OURS_UPTO=n` takes only the first n modules, in
# initialisation order, to find the module a boot dies in.
OURS_UPTO ?= $(words $(OURS_MODULES))
OURS_TAKEN := $(wordlist 1,$(OURS_UPTO),$(OURS_MODULES))

.PHONY: oursrom ourstest
oursrom:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(OURS_ROM) \
	  OURS_UPTO=$(OURS_UPTO)

# Our parts only: no reference ROM, so mkrom writes the padding and footer.
# The build date is the last commit's, so a rebuild of the same tree is the
# same ROM.
ROM_BUILD_DATE ?= $(shell git log -1 --format=%ct 2>/dev/null)
$(OURS_ROM): $(HAL_IOMD_BIN) $(KERNEL_BIN) $(OURS_TAKEN) tools/build/mkrom.py
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ \
	  $(if $(ROM_BUILD_DATE),--build-date $(ROM_BUILD_DATE)) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  $(addprefix --module ,$(OURS_TAKEN))
	@echo "oursrom: $(words $(OURS_TAKEN)) modules, all ours"

OURS_WAIT ?= 45
# The configured RAM disc size, in pages at CMOS &90, written for the run
# only. At zero, RamFS creates no disc and RAMFSFiler shows no icon.
OURS_CMOS ?= 90=20

# The run opens a window to show the Wimp's window furniture. The pointer is
# driven directly, which needs mouse following (the guest is given absolute
# positions). `OURS_MOUSE=` empty gives a bare boot.
OURS_MOUSE ?= Sources/Desktop/Switcher/test/switcher/machine/tasks.mouse
# The screenshot path and the IDE drive images; `make adfsfilertest`
# overrides them.
OURS_SHOT ?= $(abspath $(BUILD))/oursrom.png
ADFSFILER_HD ?=

ourstest:
	@rm -f $(OURS_ROM)
	@$(MAKE) --no-print-directory oursrom OURS_UPTO=$(OURS_UPTO)
	RPCEMU_CMOS='$(OURS_CMOS)' \
	$(if $(ADFSFILER_HD),RPCEMU_HD4=$(ADFSFILER_HD)/hd4.hdf \
	RPCEMU_HD5=$(ADFSFILER_HD)/hd5.hdf) \
	$(if $(OURS_DISC0),RPCEMU_DISC0=$(abspath $(OURS_DISC0))) \
	$(if $(OURS_MOUSE),RPCEMU_AUTOMOUSE=$(abspath $(OURS_MOUSE)) \
	$(MOUSE_AT_DESKTOP) RPCEMU_AUTOMOUSE_GAP=400 \
	RPCEMU_MOUSE_FOLLOWING=1 $(UNTIL_SCRIPTS),$(UNTIL_DESKTOP)) \
	python3 tools/check/ours-test.py \
	  $(abspath $(OURS_ROM)) $(OURS_SHOT) $(OURS_WAIT)
	@python3 $(LEGACY)/tools/screen-state.py $(OURS_SHOT)

# The bell, rung on the reference ROM and on ours: F12, then Ctrl-G twice,
# with RPCEmu capturing what each sound system played. The start-up beep
# and both bells must last as long and peak as high as the reference's.
BELL_DIR := $(abspath $(BUILD))/bell

bellmachinetest:
	@$(MAKE) --no-print-directory oursrom
	@mkdir -p $(BELL_DIR)
	for rom in ref:$(REF_ROM) ours:$(OURS_ROM); do \
	  name=$${rom%%:*}; \
	  RPCEMU_SOUND_WAV=$(BELL_DIR)/$$name.wav \
	  RPCEMU_AUTOTYPE=$(abspath Sources/Kernel/test/bell/bell.keys) \
	  $(KEYS_AT_DESKTOP) RPCEMU_AUTOTYPE_GAP=40 $(UNTIL_SCRIPTS) \
	  RPCEMU_MOUSE_FOLLOWING=0 \
	    $(LEGACY)/tools/try-rom.sh $$(realpath $${rom#*:}) \
	    $(BELL_DIR)/$$name.png 45 || exit 1; \
	done
	python3 Sources/Kernel/test/bell/compare.py $(BELL_DIR)/ref.wav $(BELL_DIR)/ours.wav
.PHONY: bellmachinetest

# Our Wimp in place of WindowManager, booted for real (Sources/Desktop/Wimp/test/wimp runs its C
# only against stand-ins). A desktop coming up means every task in it ran
# through our Wimp_Initialise, Wimp_Poll, Wimp_OpenWindow and redraw loop.
#
# F12 needs Wimp_CommandWindow, so typing at all shows the desktop is
# polling; `*Help WindowManager` then shows whose Wimp answered.
#
# The control, wimpreftest, is the same ROM without the replacement; run it
# first.
WIMP_KEYS  ?= <F12>*Help WindowManager\n
WIMP_WAIT  ?= 45

wimpkeys:
	@printf '$(WIMP_KEYS)' > $(BUILD)/wimp.keys
	RPCEMU_AUTOTYPE=$(abspath $(BUILD))/wimp.keys \
	$(KEYS_AT_DESKTOP) RPCEMU_AUTOTYPE_GAP=25 $(UNTIL_SCRIPTS) \
	RPCEMU_MOUSE_FOLLOWING=0 \
	  $(LEGACY)/tools/try-rom.sh $(abspath $(WIMP_ROM)) \
	  $(abspath $(BUILD))/$(WIMP_SHOT).png $(WIMP_WAIT)
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/$(WIMP_SHOT).png

# The control: the same ROM, the reference's own WindowManager.
wimpreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(REPLACED_MODULES)'
	@$(MAKE) --no-print-directory NODEPS=1 wimpkeys WIMP_ROM=$(REPLACED_ROM) \
	  WIMP_SHOT=wimpref

# Ours in its place. Larger than its slot, so it goes past the chain.
wimpmachinetest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory WIMP_MODULE_TELEMETRY=1 $(WIMP_MOD)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  WIMP_MODULE_TELEMETRY=1 \
	  REPLACED_MODULES='$(WIMP_REPLACED)' \
	  REPLACED_AT_END='--replace-at-end WindowManager'
	@$(MAKE) --no-print-directory NODEPS=1 wimpkeys WIMP_ROM=$(REPLACED_ROM) \
	  WIMP_SHOT=wimpmachine

.PHONY: wimpkeys wimpreftest wimpmachinetest

# Wimp checks on a real boot: `make wimpbatchtest`.
#
# Covers what wimpmachinetest cannot: task handlers across task switches,
# Wimp_SlotSize and Wimp_TransferBlock in a grown slot, DomainId and a
# TaskWindow, Wimp_Extend 9 and 10, ClickSubmenus and the icon bar's pointer
# scroll. Sources/Desktop/Wimp/test/wimpmachine/batch.c describes each check.
#
# Two boots, differing only in WindowManager and built under their own names:
# the reference's is the control for every check, and ours must match its
# Wimp_Extend 9 and 10 output line for line.
#
# Each boot runs one program in two parts. The first is started with
# `*WimpTask` from F12 and starts its partner; the second is typed at the
# prompt after the first has ended the desktop. Sequencing uses the
# emulator's telemetry cues (legacy/rpcemu/src/telemetry.h), so the emulator
# must be built with them.
#
# The same boots check marshal's generated `task-start`, which claims each
# task's stack and must release it however the task ends: the Filer quits,
# four task modules are killed and restarted, starts are refused with the
# desktop up and at the prompt, and a reset is measured by RMA use and task
# stack guards. batch.c's "the task-start changes" says what each expects.
# Both ROMs carry our ADFSFiler and CDFSFiler past the chain for this.
#
# Each boot lasts at most W2B_WAIT seconds and ends when the guest reports it
# has finished. `W2B_DRY_RUN=1` builds and prepares both runs without
# starting the emulator.
W2B_OUT      := $(BUILD)/tests/wimpmachine
W2B_PROGRAM  := $(W2B_OUT)/W2Batch
W2B_REF_ROM  := $(W2B_OUT)/wimpbatch-ref.rom
W2B_OURS_ROM := $(W2B_OUT)/wimpbatch-ours.rom
W2B_WAIT     ?= 240
W2B_MODULES  := $(REPLACED_MODULES) ADFSFiler=$(AFL_MOD) CDFSFiler=$(CFL_MOD)
W2B_REF_MODS := $(foreach m,$(W2B_MODULES),$(word 2,$(subst =, ,$(m))))

# A first task must keep its code and writable state outside application space.
W2F_DIR := Sources/Desktop/Wimp/test/wimpmachine/first
W2F_OUT := $(W2B_OUT)/first
W2F_MODULE := $(W2B_OUT)/W2First
W2F_OBJECTS := $(addprefix $(W2F_OUT)/,modhead.o veneers.o module.o batch.o entry.o format.o)

$(W2F_OUT)/modhead.S: $(W2F_DIR)/cmhg/modhead tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@

$(W2F_OUT)/veneers.S: $(W2F_DIR)/s/boundaries $(W2F_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(W2F_DIR) --asm $@ --header $(W2F_OUT)/w2f_veneers.h

$(W2F_OUT)/w2f_veneers.h: $(W2F_OUT)/veneers.S
	@:

$(W2F_OUT)/modhead.o $(W2F_OUT)/veneers.o: $(W2F_OUT)/%.o: $(W2F_OUT)/%.S
	$(CC) --target=arm-none-eabi $(ARCH_MACHINE) -Iinclude -x assembler-with-cpp -c $< -o $@

$(W2F_OUT)/module.o: $(W2F_DIR)/module.c $(W2F_OUT)/w2f_veneers.h
	$(CC) $(MOD_CFLAGS) $(RWPI_CPPFLAGS) -I$(W2F_OUT) -c $< -o $@

$(W2F_OUT)/batch.o: Sources/Desktop/Wimp/test/wimpmachine/batch.c
	@mkdir -p $(dir $@)
	$(CC) $(RWPI_CFLAGS) $(TEST_CFLAGS) -DW2B_FIRST_MODULE -c $< -o $@

$(W2F_OUT)/entry.o: Sources/Desktop/Wimp/test/wimpmachine/entry.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(TEST_CFLAGS) -DW2B_FIRST_MODULE -c $< -o $@

$(W2F_OUT)/format.o: tests/lib/format.c
	@mkdir -p $(dir $@)
	$(CC) $(MOD_CFLAGS) -Itests/lib -c $< -o $@

$(W2F_MODULE).elf: $(W2F_OBJECTS) $(BUILD)/Sources/Lib/RWPI/rwpi.o $(CLIB) \
                   Sources/Lib/RWPI/rwpi.ld tools/build/rumprelocs.py
	$(call rwpi-link,$@,$(W2F_OBJECTS) $(RWPI_OBJ) $(CLIB))

$(W2F_MODULE): $(W2F_MODULE).elf
	$(OBJCOPY) -O binary $< $@

$(W2B_OUT)/%.o: Sources/Desktop/Wimp/test/wimpmachine/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(ARCH_iomd) $(TEST_CFLAGS) -c $< -o $@

$(W2B_OUT)/%.o: Sources/Desktop/Wimp/test/wimpmachine/%.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(TEST_CFLAGS) -c $< -o $@

$(W2B_OUT)/W2Batch.elf: $(W2B_OUT)/entry.o $(W2B_OUT)/batch.o \
                        $(BUILD)/tests/lib/abs/format.o \
                        Sources/Desktop/Wimp/test/wimpmachine/task.ld
	$(LD) $(LDFLAGS) -T Sources/Desktop/Wimp/test/wimpmachine/task.ld -o $@ \
	  $(W2B_OUT)/entry.o $(W2B_OUT)/batch.o $(BUILD)/tests/lib/abs/format.o

$(W2B_PROGRAM): $(W2B_OUT)/W2Batch.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes)"

$(W2B_REF_ROM): $(HAL_IOMD_BIN) $(KERNEL_BIN) $(W2B_REF_MODS) $(REF_ROM) \
                tools/build/mkrom.py
	@mkdir -p $(dir $@)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  --reference-modules --relocate \
	  $(addprefix --replace ,$(W2B_MODULES))

$(W2B_OURS_ROM): $(HAL_IOMD_BIN) $(KERNEL_BIN) $(W2B_REF_MODS) $(WIMP_MOD) \
                 $(REF_ROM) tools/build/mkrom.py
	@mkdir -p $(dir $@)
	@python3 tools/build/mkrom.py $(HAL_IOMD_BIN) $@ --reference $(REF_ROM) \
	  --hal-size $(HAL_SIZE) --kernel $(KERNEL_BIN) \
	  --reference-modules --relocate --replace-at-end WindowManager \
	  $(addprefix --replace ,$(W2B_MODULES) WindowManager=$(WIMP_MOD))

# Both ROMs are deleted first, so a refused build cannot leave an old ROM to
# be booted.
wimpbatchtest:
	@rm -f $(W2B_REF_ROM) $(W2B_OURS_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) \
	  $(W2B_REF_ROM) $(W2B_OURS_ROM) $(W2B_PROGRAM) $(W2F_MODULE) $(CDFS_ISO)
	@echo "the reference's WindowManager:"
	@W2B_DRY_RUN='$(W2B_DRY_RUN)' W2B_CDROM=$(abspath $(CDFS_ISO)) \
	  Sources/Desktop/Wimp/test/wimpmachine/run.sh \
	  $(abspath $(W2B_REF_ROM)) $(abspath $(W2B_PROGRAM)) \
	  $(abspath $(W2B_OUT)) ref $(W2B_WAIT)
	@echo "ours:"
	@W2B_DRY_RUN='$(W2B_DRY_RUN)' W2B_CDROM=$(abspath $(CDFS_ISO)) \
	  Sources/Desktop/Wimp/test/wimpmachine/run.sh \
	  $(abspath $(W2B_OURS_ROM)) $(abspath $(W2B_PROGRAM)) \
	  $(abspath $(W2B_OUT)) ours $(W2B_WAIT)
	@if [ -n '$(W2B_DRY_RUN)' ]; then \
	  echo "wimpbatchtest: dry run, nothing booted"; \
	else \
	  python3 Sources/Desktop/Wimp/test/wimpmachine/check.py $(abspath $(W2B_OUT)); \
	fi

.PHONY: wimpbatchtest

# The reference OS on our FileCore and RamFS.
#
# FileCore is kept out of REPLACED_MODULES, like the Wimp, because every
# other ported module's test runs on it. RamFS comes with it as its client;
# RAMFSFiler is already in the list. Both are larger than their slots (each
# carries Sources/Lib/CLib), so mkrom puts them past the chain with placeholders,
# which also means a reply to *Help FileCore can only be ours.
#
# The keys catalogue the RAM disc and its free space, then do the same on the
# hard disc. The hard disc must be named, because the current drive at the
# prompt is the empty floppy, and supplied, because the emulator's own
# hd4.hdf is empty: each run uses a copy of the image from
# `make filecoreimages`. The control is the same ROM with the reference
# FileCore and RamFS. The last line is VDU 23,1,0, which turns the cursor off:
# the screenshots are compared, and a flashing cursor is on in one and off in
# the other by chance.
FILECORE_REPLACED := $(REPLACED_MODULES) FileCore=$(FC_MOD) RamFS=$(RAMFS_MOD)

FILECORE_RKEYS ?= <F12>RAM\nCat\nFree\nDir ADFS::4.$$\nFree\nCat\nEcho |W|A|@|@|@|@|@|@|@|@\n
FILECORE_RWAIT ?= 90
FILECORE_RSHOT ?= filecorereplaced
# The RAM disc's configured size in pages, as ourstest sets it; at zero
# RamFS creates no disc.
FILECORE_RCMOS ?= 90=20

filecorereplacedkeys:
	@test -f $(FC_IMAGES)/hd4.hdf \
	  || { echo "filecorereplaced: no filled hard disc under $(FC_IMAGES); make filecoreimages first"; exit 1; }
	@cp $(FC_IMAGES)/hd4.hdf $(BUILD)/filecorereplaced-hd4.hdf
	@printf '$(FILECORE_RKEYS)' > $(BUILD)/filecorereplaced.keys
	RPCEMU_CMOS='$(FILECORE_RCMOS)' \
	RPCEMU_HD4=$(abspath $(BUILD))/filecorereplaced-hd4.hdf \
	RPCEMU_AUTOTYPE=$(abspath $(BUILD))/filecorereplaced.keys \
	$(KEYS_AT_DESKTOP) RPCEMU_AUTOTYPE_GAP=25 $(UNTIL_SCRIPTS) \
	RPCEMU_MOUSE_FOLLOWING=0 \
	  $(LEGACY)/tools/try-rom.sh $(abspath $(REPLACED_ROM)) \
	  $(abspath $(BUILD))/$(FILECORE_RSHOT).png $(FILECORE_RWAIT)
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/$(FILECORE_RSHOT).png

# The control: the same ROM, the reference's own FileCore and RamFS.
filecorereplacedreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(REPLACED_MODULES)'
	@$(MAKE) --no-print-directory NODEPS=1 filecorereplacedkeys \
	  FILECORE_RSHOT=filecorereplacedref

# And ours in their place.
filecorereplacedtest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(FILECORE_REPLACED)'
	@$(MAKE) --no-print-directory NODEPS=1 filecorereplacedkeys \
	  FILECORE_RSHOT=filecorereplaced
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/filecorereplaced.png \
	  $(BUILD)/filecorereplacedref.png FileCore

.PHONY: filecorereplacedkeys filecorereplacedreftest filecorereplacedtest

# The ported Filer_Action in the replaced ROM: *Help, its two files in
# ResourceFS, and the path variable that finds them, in one screenshot.
FILER_ROM  ?= $(BUILD)/riscos-replaced
FILER_SHOT ?= filertest

filertest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(FILER_ROM) \
	  FILER_SHOT=$(FILER_SHOT)

# What to type. Overridable per run.
FILER_KEYS ?= <F12>Help Filer_Action\nCat Resources:$$.Resources.FilerAct\nShow FilerAct$$Path\n
# The most seconds a run is given. Runs that copy files need longer.
FILER_WAIT ?= 45
# A command that succeeds once the work is done, for a run whose work goes on
# after the keys: usually the test's own check. Without one the run ends when
# the keys are typed, or with none when the desktop is up.
FILER_DONE ?=
FILER_SETTLE ?= $(MACHINE_SETTLE)

filerkeys:
	@printf '$(FILER_KEYS)' > $(BUILD)/filer.keys
	RPCEMU_AUTOTYPE=$(abspath $(BUILD))/filer.keys \
	$(KEYS_AT_DESKTOP) RPCEMU_AUTOTYPE_GAP=25 \
	$(if $(FILER_DONE),TRY_ROM_DONE='$(FILER_DONE)',\
	TRY_ROM_UNTIL=$(if $(FILER_KEYS),SCRIPTS-DONE,DESKTOP-READY)) \
	TRY_ROM_SETTLE=$(FILER_SETTLE) \
	$(if $(FILER_DISC0),RPCEMU_DISC0=$(abspath $(FILER_DISC0))) \
	$(if $(FILER_HD),RPCEMU_HD4=$(abspath $(FILER_HD))/hd4.hdf \
	RPCEMU_HD5=$(abspath $(FILER_HD))/hd5.hdf) \
	RPCEMU_MOUSE_FOLLOWING=0 \
	  $(LEGACY)/tools/try-rom.sh $(abspath $(FILER_ROM)) $(abspath $(BUILD))/$(FILER_SHOT).png $(FILER_WAIT)
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/$(FILER_SHOT).png

# Copies a small tree with the ported module and checks what arrived. A
# drag cannot be made from a keys file, so the copy is driven through
# FilerSWIs by a BASIC program run with *Exec. See
# Sources/Desktop/FilerAct/test/fileract/machine/README.md.
FILER_HOSTFS := $(LEGACY)/rpcemu/hostfs/FAtest
# The most a copy run is given. It ends as soon as the check passes, so a
# half-finished copy is never read as the result.
FILER_COPY_WAIT ?= 60

filermachinetest: FILER_ROM = $(REPLACED_ROM)
filermachinetest: FILER_SHOT = filermachine
filermachinetest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@$(MAKE) --no-print-directory NODEPS=1 filermachine \
	  FILER_ROM=$(REPLACED_ROM) FILER_SHOT=filermachine

filerfullmachinetest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(FULL_ROM)
	@$(MAKE) --no-print-directory NODEPS=1 filermachine \
	  FILER_ROM=$(FULL_ROM) FILER_SHOT=filerfullmachine

# The files both drivers work over. One task per run of the emulator: this
# module refuses a second start while one is running, and a single-tasking
# program has no way to wait for the first to finish.
filerfiles:
	@rm -rf $(FILER_HOSTFS)
	@mkdir -p $(FILER_HOSTFS)/From/inner $(FILER_HOSTFS)/To \
	          $(FILER_HOSTFS)/To3 $(FILER_HOSTFS)/Many
	@printf 'the first file, which should arrive unchanged\n' \
	  > "$(FILER_HOSTFS)/From/one,fff"
	@printf 'the second, a little longer so the two are told apart\n' \
	  > "$(FILER_HOSTFS)/From/two,fff"
	@printf 'inside a directory, so the walk has to descend\n' \
	  > "$(FILER_HOSTFS)/From/inner/three,fff"
	@i=1; while [ $$i -le 200 ]; do \
	   printf 'one of many\n' > "$(FILER_HOSTFS)/Many/f$$i,fff"; \
	   i=$$((i+1)); \
	 done

filermachine: filerfiles
	@cp Sources/Desktop/FilerAct/test/fileract/machine/Drive "$(FILER_HOSTFS)/Drive,fff"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(FILER_ROM) \
	  FILER_SHOT=$(FILER_SHOT) FILER_WAIT=$(FILER_COPY_WAIT) \
	  FILER_KEYS='<F12>BASIC\n*Exec HostFS::HostFS.$$$$.FAtest.Drive\n*Quit\n\n' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/FilerAct/test/fileract/machine/check.py $(FILER_HOSTFS),$(FILER_HOSTFS))'
	@python3 Sources/Desktop/FilerAct/test/fileract/machine/check.py $(FILER_HOSTFS)

# A selection of 200 files, all of which must arrive: Filer_Action has no
# limit on the size of a selection. FILER_BIG_ROM=$(REF_ROM) is the control.
filerbigmachinetest: FILER_BIG_ROM ?= $(REPLACED_ROM)
filerbigmachinetest: filerfiles
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@cp Sources/Desktop/FilerAct/test/fileract/machine/Big "$(FILER_HOSTFS)/Drive,fff"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(FILER_BIG_ROM) \
	  FILER_SHOT=filerbigmachine FILER_WAIT=$(FILER_COPY_WAIT) \
	  FILER_KEYS='<F12>BASIC\n*Exec HostFS::HostFS.$$$$.FAtest.Drive\n*Quit\n\n' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/FilerAct/test/fileract/machine/check.py --big $(FILER_HOSTFS),$(FILER_HOSTFS))'
	@python3 Sources/Desktop/FilerAct/test/fileract/machine/check.py --big $(FILER_HOSTFS)

# DragAnObject on the machine. A BASIC program runs a drag with each of the
# module's three kinds of renderer and writes to HostFS how often each was
# called: twice per drag, over backgrounds of zeros and of ones, which give
# the mask. Each drag must also start without error; the pointer never
# moves, so the following Stop ends it.
DAO_HOSTFS := $(LEGACY)/rpcemu/hostfs/DAOtest
DAO_WAIT   ?= 60

dragobjmachinetest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@$(MAKE) --no-print-directory NODEPS=1 dragobjmachine FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=dragobjmachine

# DragASprite on the machine: a drag started and stopped for each way a
# sprite can be placed and drawn. The routines the Wimp calls as the pointer
# moves are not exercised, as the harness does not move the pointer.
DAS_HOSTFS := $(LEGACY)/rpcemu/hostfs/DAStest
DAS_WAIT   ?= 60

dragsprmachinetest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@rm -rf $(DAS_HOSTFS)
	@mkdir -p $(DAS_HOSTFS)
	@cp Sources/Desktop/DragASprit/test/dragspr/machine/Drive "$(DAS_HOSTFS)/Drive,fff"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=dragsprmachine FILER_WAIT=$(DAS_WAIT) \
	  FILER_KEYS='<F12>BASIC\n*Exec HostFS::HostFS.$$$$.DAStest.Drive\n*Quit\n\n' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/DragASprit/test/dragspr/machine/check.py $(DAS_HOSTFS),$(DAS_HOSTFS))'
	@python3 Sources/Desktop/DragASprit/test/dragspr/machine/check.py $(DAS_HOSTFS)

# Squash on the machine: the two SWIs' register interface (compression
# itself is checked on the host). A BASIC program asks both SWIs for
# workspace sizes, compresses a buffer and reads it back, repeats with bit 2
# and an output buffer that fills exactly, compresses sixteen output bytes at
# a time and compares the stream, and passes four inputs that must be
# refused. Results go to HostFS for the host to check.
SQ_HOSTFS := $(LEGACY)/rpcemu/hostfs/SQtest
SQ_WAIT   ?= 60

squashmachinetest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@rm -rf $(SQ_HOSTFS)
	@mkdir -p $(SQ_HOSTFS)
	@cp Sources/Utilities/Squash/test/squash/machine/Drive "$(SQ_HOSTFS)/Drive,fff"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=squashmachine FILER_WAIT=$(SQ_WAIT) \
	  FILER_KEYS='<F12>BASIC\n*Exec HostFS::HostFS.$$$$.SQtest.Drive\n*Quit\n\n' \
	  FILER_DONE='$(call machine_done,Sources/Utilities/Squash/test/squash/machine/check.py $(SQ_HOSTFS),$(SQ_HOSTFS))'
	@python3 Sources/Utilities/Squash/test/squash/machine/check.py $(SQ_HOSTFS)

# The control: the same driver against the reference ROM's Squash.
squashreftest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(filter-out Squash=%,$(REPLACED_MODULES))'
	@rm -rf $(SQ_HOSTFS)
	@mkdir -p $(SQ_HOSTFS)
	@cp Sources/Utilities/Squash/test/squash/machine/Drive "$(SQ_HOSTFS)/Drive,fff"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=squashref FILER_WAIT=$(SQ_WAIT) \
	  FILER_KEYS='<F12>BASIC\n*Exec HostFS::HostFS.$$$$.SQtest.Drive\n*Quit\n\n' \
	  FILER_DONE='$(call machine_done,Sources/Utilities/Squash/test/squash/machine/check.py $(SQ_HOSTFS),$(SQ_HOSTFS))'
	@python3 Sources/Utilities/Squash/test/squash/machine/check.py $(SQ_HOSTFS)

.PHONY: squashmachinetest squashreftest

# FilterManager on the machine. A BASIC program becomes a task, registers
# three counting filters in machine code, polls twenty times and writes the
# counts out. *Filters output is redirected to a second file.
FLT_HOSTFS := $(LEGACY)/rpcemu/hostfs/FLTtest
FLT_WAIT   ?= 60

filtermachinetest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@rm -rf $(FLT_HOSTFS)
	@mkdir -p $(FLT_HOSTFS)
	@cp Sources/Desktop/Filter/test/filter/machine/Drive "$(FLT_HOSTFS)/Drive,fff"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=filtermachine FILER_WAIT=$(FLT_WAIT) \
	  FILER_KEYS='<F12>BASIC\n*Exec HostFS::HostFS.$$$$.FLTtest.Drive\n*Quit\n\n' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/Filter/test/filter/machine/check.py $(FLT_HOSTFS),$(FLT_HOSTFS))'
	@python3 Sources/Desktop/Filter/test/filter/machine/check.py $(FLT_HOSTFS)

# Free on the machine. The driver registers its own entry point over Free's
# one for the RAM filing system and counts the reason codes it receives: one
# name request and one size request mean a window was made, brought to the
# front and filled in.
FRE_HOSTFS := $(LEGACY)/rpcemu/hostfs/FREtest
FRE_WAIT   ?= 60
# The driver runs in the command window and is not a Wimp task. *ShowFree is
# typed at the prompt last, because it sets the poll word Free's task waits
# on and that task cannot run while the command window is open. The
# screenshot shows the window it builds.
FRE_KEYS   ?= <F12>BASIC\n*Exec HostFS::HostFS.$$$$.FREtest.Drive\n*Quit\nShowFree -FS Null 0\n\n

# TaskWindow on the machine. The driver sets TaskWindow$Server and starts
# one task; with no editor answering its broadcast, the module runs that
# server, which acts as !Edit and records every character the task prints.
# Output reaching the server exercises the co-routine switch through WrchV.
TWN_HOSTFS := $(LEGACY)/rpcemu/hostfs/TWNtest
TWN_WAIT   ?= 60
TWN_KEYS   ?= <F12>BASIC\n*Exec HostFS::HostFS.$$$$.TWNtest.Drive\n*Quit\n\n

# The control: the same harness against the reference TaskWindow, with only
# our module left out of the replacements.
TWN_REFERENCE := ShellCLI=$(SHELL_MOD) WindowScroll=$(WS_MOD) \
                 Filer_Action=$(FA_MOD) FilerSWIs=$(FS_MOD) \
                 DragAnObject=$(DAO_MOD) Desktop=$(DSK_MOD) \
                 DragASprite=$(DSP_MOD) FilterManager=$(FLT_MOD) \
                 Free=$(FRE_MOD)

taskwinreftest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(TWN_REFERENCE)'
	@rm -rf $(TWN_HOSTFS)
	@mkdir -p $(TWN_HOSTFS)
	@cp Sources/Desktop/TaskWindow/test/taskwin/machine/Drive "$(TWN_HOSTFS)/Drive,fff"
	@cp Sources/Desktop/TaskWindow/test/taskwin/machine/Server "$(TWN_HOSTFS)/Server,ffb"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=taskwinreference FILER_WAIT=$(TWN_WAIT) \
	  FILER_KEYS='$(TWN_KEYS)' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/TaskWindow/test/taskwin/machine/check.py $(TWN_HOSTFS),$(TWN_HOSTFS))'
	@python3 Sources/Desktop/TaskWindow/test/taskwin/machine/check.py $(TWN_HOSTFS)

taskwinmachinetest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@rm -rf $(TWN_HOSTFS)
	@mkdir -p $(TWN_HOSTFS)
	@cp Sources/Desktop/TaskWindow/test/taskwin/machine/Drive "$(TWN_HOSTFS)/Drive,fff"
	@cp Sources/Desktop/TaskWindow/test/taskwin/machine/Server "$(TWN_HOSTFS)/Server,ffb"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=taskwinmachine FILER_WAIT=$(TWN_WAIT) \
	  FILER_KEYS='$(TWN_KEYS)' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/TaskWindow/test/taskwin/machine/check.py $(TWN_HOSTFS),$(TWN_HOSTFS))'
	@python3 Sources/Desktop/TaskWindow/test/taskwin/machine/check.py $(TWN_HOSTFS)

# The Task Manager on the machine. The driver runs in the command window with
# the desktop behind it and queries the tasks the module knows; the server is
# a Wimp task, whose name and slot the module learns from Wimp_Initialise's
# messages.
#
# The run ends with TaskManager_Shutdown. The server records each shutdown
# step (PreQuit, the broadcast, Quit, leaving the desktop) as it arrives and
# rewrites its reports after every line, so later events cannot lose earlier
# ones.
SWI_HOSTFS := $(LEGACY)/rpcemu/hostfs/SWItest
SWI_WAIT   ?= 60
SWI_KEYS   ?= <F12>BASIC\n*Exec HostFS::HostFS.$$$$.SWItest.Drive\n*Quit\n\n

# The control: the same harness against the reference Task Manager, with
# only our module left out of the replacements.
SWI_REFERENCE := ShellCLI=$(SHELL_MOD) WindowScroll=$(WS_MOD) \
                 Filer_Action=$(FA_MOD) FilerSWIs=$(FS_MOD) \
                 DragAnObject=$(DAO_MOD) Desktop=$(DSK_MOD) \
                 DragASprite=$(DSP_MOD) FilterManager=$(FLT_MOD) \
                 Free=$(FRE_MOD) TaskWindow=$(TWN_MOD)

switcherfiles:
	@rm -rf $(SWI_HOSTFS)
	@mkdir -p $(SWI_HOSTFS)
	@cp Sources/Desktop/Switcher/test/switcher/machine/Drive "$(SWI_HOSTFS)/Drive,fff"
	@cp Sources/Desktop/Switcher/test/switcher/machine/Server "$(SWI_HOSTFS)/Server,ffb"

# The ROM is deleted first: it is one file whatever it contains, so a stale
# one would make the control run the port.
switcherreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(SWI_REFERENCE)'
	@$(MAKE) --no-print-directory NODEPS=1 switcherfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=switcherreference FILER_WAIT=$(SWI_WAIT) \
	  FILER_KEYS='$(SWI_KEYS)' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/Switcher/test/switcher/machine/check.py $(SWI_HOSTFS),$(SWI_HOSTFS))'
	@python3 Sources/Desktop/Switcher/test/switcher/machine/check.py $(SWI_HOSTFS)

switchermachinetest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@$(MAKE) --no-print-directory NODEPS=1 switcherfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=switchermachine FILER_WAIT=$(SWI_WAIT) \
	  FILER_KEYS='$(SWI_KEYS)' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/Switcher/test/switcher/machine/check.py $(SWI_HOSTFS),$(SWI_HOSTFS))'
	@python3 Sources/Desktop/Switcher/test/switcher/machine/check.py $(SWI_HOSTFS)

# The Pinboard on the machine. The driver types the commands of a settings
# file, each of which only queues a job, so the final screen shows whether
# they worked; the test checks the screenshot as well as the report.
PBD_HOSTFS := $(LEGACY)/rpcemu/hostfs/PBtest
PBD_WAIT   ?= 60
# Seconds from the driver's last report line to the screenshot: the commands
# only queue jobs, which the Pinboard's task carries out once the command
# window has gone.
PBD_SETTLE ?= 4
PBD_KEYS   ?= <F12>BASIC\n*Exec HostFS::HostFS.$$$$.PBtest.Drive\n*Quit\n\n

# The control: the same harness against the reference Pinboard, with every
# other port still in, so only one thing differs.
PBD_REFERENCE := $(filter-out Pinboard=%,$(REPLACED_MODULES))

pinboardfiles:
	@rm -rf $(PBD_HOSTFS)
	@mkdir -p $(PBD_HOSTFS)
	@cp Sources/Desktop/Pinboard/test/pinboard/machine/Drive "$(PBD_HOSTFS)/Drive,fff"
	@cp Sources/Desktop/Pinboard/test/pinboard/machine/Drive "$(PBD_HOSTFS)/Thing,fff"

pinboardreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(PBD_REFERENCE)'
	@$(MAKE) --no-print-directory NODEPS=1 pinboardfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=pinboardreference FILER_WAIT=$(PBD_WAIT) \
	  FILER_KEYS='$(PBD_KEYS)' FILER_SETTLE=$(PBD_SETTLE) \
	  FILER_DONE='$(call machine_done,Sources/Desktop/Pinboard/test/pinboard/machine/check.py $(PBD_HOSTFS),$(PBD_HOSTFS))'
	@python3 Sources/Desktop/Pinboard/test/pinboard/machine/check.py $(PBD_HOSTFS) \
	  $(BUILD)/pinboardreference.png

# The Filer on the machine. The control substitutes the reference Filer and
# keeps the other replacements. The runs are compared by screenshot.
FLR_HOSTFS := $(LEGACY)/rpcemu/hostfs/FLRtest
FLR_WAIT   ?= 45
FLR_KEYS   ?= <F12>*Filer_OpenDir HostFS::HostFS.$$$$.FLRtest\n*Filer_Run HostFS::HostFS.$$$$.FLRtest.Work\n\n

# The ported Filer, as in the normal replacement ROM, and its control: every
# port but this one.
FLR_REFERENCE := $(filter-out Filer=%,$(REPLACED_MODULES))
FLR_REPLACED  := $(REPLACED_MODULES)

# The files a directory viewer is opened on. Their timestamps are fixed so
# that full information lines match between runs.
filerdirfiles:
	@rm -rf $(FLR_HOSTFS)
	@mkdir -p $(FLR_HOSTFS)/Work
	@printf 'hello\n' > "$(FLR_HOSTFS)/Notes,fff"
	@printf 'data\n' > "$(FLR_HOSTFS)/Data,ffd"
	@printf 'x\n' > "$(FLR_HOSTFS)/Work/Inner,fff"
	@printf 'y\n' > "$(FLR_HOSTFS)/Work/Second,ffd"
	@touch -d '2020-01-02 03:04:05' "$(FLR_HOSTFS)/Notes,fff" \
	  "$(FLR_HOSTFS)/Data,ffd" "$(FLR_HOSTFS)/Work/Inner,fff" \
	  "$(FLR_HOSTFS)/Work/Second,ffd" "$(FLR_HOSTFS)/Work" \
	  "$(FLR_HOSTFS)"

filerportedtest: $(FLR_MOD)
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(FLR_REPLACED)'
	@$(MAKE) --no-print-directory NODEPS=1 filerdirfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=filerported FILER_WAIT=$(FLR_WAIT) \
	  FILER_KEYS='$(FLR_KEYS)'
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/filerported.png \
	  $(BUILD)/filerdirref.png

filerdirreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(FLR_REFERENCE)'
	@$(MAKE) --no-print-directory NODEPS=1 filerdirfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=filerdirref FILER_WAIT=$(FLR_WAIT) \
	  FILER_KEYS='$(FLR_KEYS)'

# The menu test. The pointer is driven directly, which needs mouse following
# (the guest is given absolute positions); both runs use it.
FLR_MOUSE      ?= Sources/Desktop/Filer/test/filer/machine/menu.mouse
FLR_MENU_WAIT  ?= 60

filerpointer:
	@printf '$(FILER_KEYS)' > $(BUILD)/filer.keys
	RPCEMU_AUTOTYPE=$(abspath $(BUILD))/filer.keys \
	$(KEYS_AT_DESKTOP) RPCEMU_AUTOTYPE_GAP=25 \
	RPCEMU_AUTOMOUSE=$(abspath $(FLR_MOUSE)) \
	$(if $(FILER_KEYS),$(MOUSE_AFTER_TYPED),$(MOUSE_AT_DESKTOP)) \
	RPCEMU_AUTOMOUSE_GAP=400 $(UNTIL_SCRIPTS) \
	RPCEMU_MOUSE_FOLLOWING=1 \
	  $(LEGACY)/tools/try-rom.sh $(abspath $(FILER_ROM)) $(abspath $(BUILD))/$(FILER_SHOT).png $(FILER_WAIT)
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/$(FILER_SHOT).png

filermenureftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(FLR_REFERENCE)'
	@$(MAKE) --no-print-directory NODEPS=1 filerdirfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerpointer FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=filermenuref FILER_WAIT=$(FLR_MENU_WAIT) \
	  FILER_KEYS='$(FLR_KEYS)'

filermenutest: $(FLR_MOD)
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(FLR_REPLACED)'
	@$(MAKE) --no-print-directory NODEPS=1 filerdirfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerpointer FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=filermenu FILER_WAIT=$(FLR_MENU_WAIT) \
	  FILER_KEYS='$(FLR_KEYS)'
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/filermenu.png \
	  $(BUILD)/filermenuref.png

# The full information display: access, size, type and date, drawn with the
# desktop font.
FLR_FULL_KEYS ?= <F12>*Filer_OpenDir HostFS::HostFS.$$$$.FLRtest -fi\n\n

filerfullreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(FLR_REFERENCE)'
	@$(MAKE) --no-print-directory NODEPS=1 filerdirfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=filerfullref FILER_WAIT=$(FLR_WAIT) \
	  FILER_KEYS='$(FLR_FULL_KEYS)'

filerfullinfotest: $(FLR_MOD)
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(FLR_REPLACED)'
	@$(MAKE) --no-print-directory NODEPS=1 filerdirfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=filerfullinfo FILER_WAIT=$(FLR_WAIT) \
	  FILER_KEYS='$(FLR_FULL_KEYS)'
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/filerfullinfo.png \
	  $(BUILD)/filerfullref.png

.PHONY: filerfullreftest filerfullinfotest
.PHONY: filerdirfiles filerportedtest filerdirreftest
.PHONY: filerpointer filermenureftest filermenutest
# RAMFSFiler on the machine: one screenshot compares its icon bar icon,
# sprite, text and position with the reference's. The menu is not tested.
#
# It is larger than its slot (it carries Sources/Lib/Desktop), so mkrom puts it past
# the chain with a placeholder in the slot. The control is the same ROM with
# the reference RAMFSFiler in its slot.
RFL_WAIT      ?= 45
RFL_REFERENCE := $(filter-out RAMFSFiler=%,$(REPLACED_MODULES))
RFL_REPLACED  := $(REPLACED_MODULES)

# The machine needs a RAM disc or neither module shows an icon. RPCEMU_CMOS
# sets CMOS &90 (RAM disc size in pages) for the run only; 32 pages is 128K,
# more than the two pages RamFS needs.
RFL_CMOS      ?= 90=20

ramfsfilerreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(RFL_REFERENCE)'
	RPCEMU_CMOS='$(RFL_CMOS)' $(MAKE) --no-print-directory NODEPS=1 filerkeys \
	  FILER_ROM=$(REPLACED_ROM) FILER_SHOT=ramfsfilerref \
	  FILER_WAIT=$(RFL_WAIT) FILER_KEYS=''

ramfsfilerportedtest: $(RFL_MOD)
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(RFL_REPLACED)'
	RPCEMU_CMOS='$(RFL_CMOS)' $(MAKE) --no-print-directory NODEPS=1 filerkeys \
	  FILER_ROM=$(REPLACED_ROM) FILER_SHOT=ramfsfilerported \
	  FILER_WAIT=$(RFL_WAIT) FILER_KEYS=''
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/ramfsfilerported.png \
	  $(BUILD)/ramfsfilerref.png RAMFSFiler

.PHONY: ramfsfilerreftest ramfsfilerportedtest

# The Display Manager on the machine: the icon on the bar, and the dialogue
# behind it.
#
# displaymanagermodtest measures the module's command, services and words;
# only this pair reaches the Wimp task. `displaymanagerreftest` keeps the
# reference module in its slot, `displaymanagerdesktoptest` puts ours in, and
# Sources/Desktop/Filer/test/filer/machine/check.py compares the two screenshots.
#
# Ours is larger than the reference's slot, so mkrom puts it past the chain
# and leaves a placeholder in the slot.
DM_WAIT      ?= 45
DM_REFERENCE := $(filter-out DisplayManager=%,$(REPLACED_MODULES))
DM_REPLACED  := $(REPLACED_MODULES)

displaymanagerreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(DM_REFERENCE)'
	$(MAKE) --no-print-directory NODEPS=1 filerkeys \
	  FILER_ROM=$(REPLACED_ROM) FILER_SHOT=displaymanagerref \
	  FILER_WAIT=$(DM_WAIT) FILER_KEYS=''

displaymanagerdesktoptest: $(BUILD)/modules/DisplayManager.mod
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(DM_REPLACED)'
	$(MAKE) --no-print-directory NODEPS=1 filerkeys \
	  FILER_ROM=$(REPLACED_ROM) FILER_SHOT=displaymanagerported \
	  FILER_WAIT=$(DM_WAIT) FILER_KEYS=''
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py \
	  $(BUILD)/displaymanagerported.png $(BUILD)/displaymanagerref.png \
	  DisplayManager

.PHONY: displaymanagerreftest displaymanagerdesktoptest

# The dialogue: clicks the icon and compares the two dialogues (resolution,
# colours, frame rate, monitor name in the title bar, window position).
#
# The pointer is driven through the emulated mouse, so both runs need mouse
# following on (the guest is told the pointer's position, not its motion).
DM_MOUSE       ?= Sources/Desktop/DisplayManager/test/displaymanager/machine/open.mouse
DM_DIALOG_WAIT ?= 60

displaymanagerdialogreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(DM_REFERENCE)'
	@$(MAKE) --no-print-directory NODEPS=1 filerpointer FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=displaymanagerdialogref FILER_WAIT=$(DM_DIALOG_WAIT) \
	  FILER_KEYS='' FLR_MOUSE=$(DM_MOUSE)
	@python3 Sources/Desktop/DisplayManager/test/displaymanager/machine/check.py $(BUILD)/displaymanagerdialogref.png

displaymanagerdialogtest: $(BUILD)/modules/DisplayManager.mod
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(DM_REPLACED)'
	@$(MAKE) --no-print-directory NODEPS=1 filerpointer FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=displaymanagerdialog FILER_WAIT=$(DM_DIALOG_WAIT) \
	  FILER_KEYS='' FLR_MOUSE=$(DM_MOUSE)
	@python3 Sources/Desktop/DisplayManager/test/displaymanager/machine/check.py \
	  $(BUILD)/displaymanagerdialog.png $(BUILD)/displaymanagerdialogref.png

.PHONY: displaymanagerdialogreftest displaymanagerdialogtest

# Both pop-ups must open; a pair of closed dialogues is not a menu test.
displaymanagermenutest:
	@set -e; for menu in resolution rate; do \
	  $(MAKE) --no-print-directory displaymanagerdialogreftest \
	    DM_MOUSE=Sources/Desktop/DisplayManager/test/displaymanager/machine/$$menu.mouse; \
	  python3 Sources/Desktop/DisplayManager/test/displaymanager/machine/check.py --menu \
	    $(BUILD)/displaymanagerdialogref.png; \
	  $(MAKE) --no-print-directory displaymanagerdialogtest \
	    DM_MOUSE=Sources/Desktop/DisplayManager/test/displaymanager/machine/$$menu.mouse; \
	  python3 Sources/Desktop/DisplayManager/test/displaymanager/machine/check.py --menu \
	    $(BUILD)/displaymanagerdialog.png $(BUILD)/displaymanagerdialogref.png; \
	done
.PHONY: displaymanagermenutest

# The ADFS hard discs on the icon bar.
#
# The all-ours ROM boots with two hard disc images laid out and filled by
# Sources/FileSys/FileCore/test/filecore's layimages, and the pointer clicks the first hard disc
# icon. Expected: two hard disc icons and a directory viewer of drive four.
#
# `make adfsfilerbaretest` boots the same ROM with empty drives;
# Sources/Desktop/ADFSFiler/test/adfsfiler/machine/check.py compares the two screenshots.
ADFSFILER_IMAGES := $(BUILD)/tests/adfsfiler

adfsfilerimages: $(BUILD)/tests/filecore-layimages
	@mkdir -p $(ADFSFILER_IMAGES)
	@rm -f $(ADFSFILER_IMAGES)/hd4.hdf $(ADFSFILER_IMAGES)/hd5.hdf
	@$(BUILD)/tests/filecore-layimages $(ADFSFILER_IMAGES) winnies filled \
	  > $(ADFSFILER_IMAGES)/laid.txt || { cat $(ADFSFILER_IMAGES)/laid.txt; exit 1; }
	@mkdir -p $(ADFSFILER_IMAGES)/empty
	@: > $(ADFSFILER_IMAGES)/empty/hd4.hdf
	@: > $(ADFSFILER_IMAGES)/empty/hd5.hdf
.PHONY: adfsfilerimages

adfsfilertest: adfsfilerimages
	@$(MAKE) --no-print-directory ourstest \
	  OURS_MOUSE=Sources/Desktop/ADFSFiler/test/adfsfiler/machine/disc.mouse \
	  ADFSFILER_HD=$(abspath $(ADFSFILER_IMAGES)) \
	  OURS_SHOT=$(abspath $(BUILD))/adfsfiler.png OURS_WAIT=45
.PHONY: adfsfilertest

adfsfilerbaretest: adfsfilerimages
	@$(MAKE) --no-print-directory ourstest \
	  OURS_MOUSE= \
	  ADFSFILER_HD=$(abspath $(ADFSFILER_IMAGES))/empty \
	  OURS_SHOT=$(abspath $(BUILD))/adfsfilerbare.png OURS_WAIT=45
.PHONY: adfsfilerbaretest

# Both boots and the comparison.
adfsfilerdiscs: adfsfilerbaretest adfsfilertest
	@python3 Sources/Desktop/ADFSFiler/test/adfsfiler/machine/check.py $(BUILD)/adfsfiler.png \
	  $(BUILD)/adfsfilerbare.png
.PHONY: adfsfilerdiscs

# A floppy on the bar beside them.
#
# The two runs differ in the number of floppy drives, not in whether a disc
# is inserted: an empty drive still gets an icon (":0"), so a disc-in/disc-out
# pair would look the same. The drive count is the low three bits of CMOS &87,
# set for the run only by RPCEMU_CMOS. Both runs have the same two hard discs
# and a disc in drive 0.
#
# The floppy icon should appear at WimpPriority_FloppyDiscs, to the right of
# the fixed discs. The pointer is parked in the same place both times and
# nothing is clicked: clicking a floppy would mount it.
ADFSFILER_CMOS_FLOPPY ?= 90=20,87=41
ADFSFILER_CMOS_NONE   ?= 90=20,87=40

adfsfilerfloppy: adfsfilerimages
	@mkdir -p $(ADFSFILER_IMAGES)/floppy
	@$(BUILD)/tests/filecore-layimages $(ADFSFILER_IMAGES)/floppy floppies \
	  > $(ADFSFILER_IMAGES)/floppy/laid.txt
	@$(MAKE) --no-print-directory ourstest \
	  OURS_MOUSE=Sources/Desktop/ADFSFiler/test/adfsfiler/machine/parked.mouse \
	  ADFSFILER_HD=$(abspath $(ADFSFILER_IMAGES)) \
	  OURS_DISC0=$(ADFSFILER_IMAGES)/floppy/F.adf \
	  OURS_CMOS='$(ADFSFILER_CMOS_FLOPPY)' \
	  OURS_SHOT=$(abspath $(BUILD))/adfsfilerfloppy.png OURS_WAIT=45
	@$(MAKE) --no-print-directory ourstest \
	  OURS_MOUSE=Sources/Desktop/ADFSFiler/test/adfsfiler/machine/parked.mouse \
	  ADFSFILER_HD=$(abspath $(ADFSFILER_IMAGES)) \
	  OURS_DISC0=$(ADFSFILER_IMAGES)/floppy/F.adf \
	  OURS_CMOS='$(ADFSFILER_CMOS_NONE)' \
	  OURS_SHOT=$(abspath $(BUILD))/adfsfilernofloppy.png OURS_WAIT=45
	@python3 Sources/Desktop/ADFSFiler/test/adfsfiler/machine/floppy.py \
	  $(BUILD)/adfsfilerfloppy.png $(BUILD)/adfsfilernofloppy.png
.PHONY: adfsfilerfloppy

# The icon menu: two runs differing only in which drive the pointer is over.
# Over a floppy, Share is shaded and Format and Backup are live; over a fixed
# disc it is the reverse. The floppy's icon is wherever the rest of the bar
# puts it, so its pointer script is written from where `adfsfilerfloppy`
# found it.
adfsfilermenu: adfsfilerfloppy
	@python3 Sources/Desktop/ADFSFiler/test/adfsfiler/machine/floppy.py \
	  $(BUILD)/adfsfilerfloppy.png $(BUILD)/adfsfilernofloppy.png \
	  Sources/Desktop/ADFSFiler/test/adfsfiler/machine/floppymenu.mouse \
	  $(ADFSFILER_IMAGES)/floppymenu.mouse > /dev/null
	@$(MAKE) --no-print-directory ourstest \
	  OURS_MOUSE=$(ADFSFILER_IMAGES)/floppymenu.mouse \
	  ADFSFILER_HD=$(abspath $(ADFSFILER_IMAGES)) \
	  OURS_DISC0=$(ADFSFILER_IMAGES)/floppy/F.adf \
	  OURS_CMOS='$(ADFSFILER_CMOS_FLOPPY)' \
	  OURS_SHOT=$(abspath $(BUILD))/adfsfilermenufloppy.png OURS_WAIT=45
	@$(MAKE) --no-print-directory ourstest \
	  OURS_MOUSE=Sources/Desktop/ADFSFiler/test/adfsfiler/machine/discmenu.mouse \
	  ADFSFILER_HD=$(abspath $(ADFSFILER_IMAGES)) \
	  OURS_DISC0=$(ADFSFILER_IMAGES)/floppy/F.adf \
	  OURS_CMOS='$(ADFSFILER_CMOS_FLOPPY)' \
	  OURS_SHOT=$(abspath $(BUILD))/adfsfilermenudisc.png OURS_WAIT=45
	@python3 Sources/Desktop/ADFSFiler/test/adfsfiler/machine/menu.py \
	  $(BUILD)/adfsfilermenufloppy.png $(BUILD)/adfsfilermenudisc.png
.PHONY: adfsfilermenu

# The two-ROM comparison for ADFSFiler.
#
# Reference kernel and our desktop modules, the same two hard discs and a
# disc in drive 0; the `-ref` run keeps legacy/romparts/016-ADFSFiler.mod in
# its slot and the `-ported` run puts ours there. No input is driven, so the
# screenshots show each module's icon bar. Sources/Desktop/Filer/test/filer/machine/check.py
# reports how many pixels differ and where.
AFL_WAIT      ?= 45
AFL_REFERENCE := $(REPLACED_MODULES)
AFL_REPLACED  := $(REPLACED_MODULES) ADFSFiler=$(AFL_MOD)

adfsfilerreftest: adfsfilerimages
	@mkdir -p $(ADFSFILER_IMAGES)/floppy
	@$(BUILD)/tests/filecore-layimages $(ADFSFILER_IMAGES)/floppy floppies \
	  > $(ADFSFILER_IMAGES)/floppy/laid.txt
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(AFL_REFERENCE)'
	RPCEMU_CMOS='$(ADFSFILER_CMOS_FLOPPY)' $(MAKE) --no-print-directory NODEPS=1 \
	  filerkeys FILER_ROM=$(REPLACED_ROM) FILER_SHOT=adfsfilerref \
	  FILER_WAIT=$(AFL_WAIT) FILER_KEYS='' \
	  FILER_DISC0=$(ADFSFILER_IMAGES)/floppy/F.adf \
	  FILER_HD=$(ADFSFILER_IMAGES)

adfsfilerportedtest: adfsfilerimages $(AFL_MOD)
	@$(MAKE) --no-print-directory adfsfilerreftest
	@mkdir -p $(ADFSFILER_IMAGES)/floppy
	@$(BUILD)/tests/filecore-layimages $(ADFSFILER_IMAGES)/floppy floppies \
	  > $(ADFSFILER_IMAGES)/floppy/laid.txt
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(AFL_REPLACED)'
	RPCEMU_CMOS='$(ADFSFILER_CMOS_FLOPPY)' $(MAKE) --no-print-directory NODEPS=1 \
	  filerkeys FILER_ROM=$(REPLACED_ROM) FILER_SHOT=adfsfilerported \
	  FILER_WAIT=$(AFL_WAIT) FILER_KEYS='' \
	  FILER_DISC0=$(ADFSFILER_IMAGES)/floppy/F.adf \
	  FILER_HD=$(ADFSFILER_IMAGES)
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/adfsfilerported.png \
	  $(BUILD)/adfsfilerref.png ADFSFiler

.PHONY: adfsfilerreftest adfsfilerportedtest

# The same ROM twice, differing only in CDFSFiler.
#
# Two screenshots, reference module in its slot and ours in its place,
# compared by Sources/Desktop/Filer/test/filer/machine/check.py.
#
# The drive holds the ISO from tools/build/mkiso.py (the one `make
# cdfsmodtest` reads), and CDFS's CMOS byte configures one drive with an 8K
# cache. The reference CDFS, CDFSDriver and CDFSSoftATAPI are under both runs.
#
# `filerkeys` passes neither the CD nor the CMOS and `ourstest` builds the
# wrong ROM, so this rule is the same shape with both added.
CFL_WAIT        ?= 45
CFL_REFERENCE   := $(REPLACED_MODULES)
CFL_REPLACED    := $(REPLACED_MODULES) CDFSFiler=$(CFL_MOD)

# Ours is larger than the reference's slot, so mkrom.py places it past the
# chain; it still receives Service_StartFiler there.
cdfsfilershot: $(CDFS_ISO)
	RPCEMU_CDROM=$(abspath $(CDFS_ISO)) RPCEMU_CMOS='$(CDFS_CMOS)' \
	$(if $(CFL_MOUSE),RPCEMU_AUTOMOUSE=$(abspath $(CFL_MOUSE)) \
	$(MOUSE_AT_DESKTOP) RPCEMU_AUTOMOUSE_GAP=400 \
	RPCEMU_MOUSE_FOLLOWING=1 $(UNTIL_SCRIPTS),$(UNTIL_DESKTOP)) \
	  $(LEGACY)/tools/try-rom.sh $(abspath $(CFL_ROM)) \
	  $(abspath $(BUILD))/$(CFL_SHOT).png $(CFL_WAIT)
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/$(CFL_SHOT).png
.PHONY: cdfsfilershot

# The control, run first: the same ROM with the reference module in place.
cdfsfilerreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(CFL_REFERENCE)'
	@$(MAKE) --no-print-directory NODEPS=1 cdfsfilershot CFL_ROM=$(REPLACED_ROM) \
	  CFL_SHOT=cdfsfilerref CFL_MOUSE=$(CFL_MOUSE)

cdfsfilerportedtest: $(CFL_MOD)
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(CFL_REPLACED)'
	@$(MAKE) --no-print-directory NODEPS=1 cdfsfilershot CFL_ROM=$(REPLACED_ROM) \
	  CFL_SHOT=cdfsfilerported CFL_MOUSE=$(CFL_MOUSE)
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/cdfsfilerported.png \
	  $(BUILD)/cdfsfilerref.png CDFSFiler

# The icon bar alone, with no pointer driven.
cdfsfilerbartest:
	@$(MAKE) --no-print-directory cdfsfilerreftest CFL_MOUSE=
	@$(MAKE) --no-print-directory cdfsfilerportedtest CFL_MOUSE=

# The icon menu: Share shaded because this ROM has no ShareFS, Eject live
# because the drawer is not locked.
CFL_MOUSE ?= Sources/Desktop/CDFSFiler/test/cdfsfiler/machine/menu.mouse

cdfsfilertest:
	@$(MAKE) --no-print-directory cdfsfilerreftest
	@$(MAKE) --no-print-directory cdfsfilerportedtest

# A click on the icon: the disc's root opened, the icon still ":0".
cdfsfileropentest:
	@$(MAKE) --no-print-directory cdfsfilerreftest \
	  CFL_MOUSE=Sources/Desktop/CDFSFiler/test/cdfsfiler/machine/open.mouse
	@$(MAKE) --no-print-directory cdfsfilerportedtest \
	  CFL_MOUSE=Sources/Desktop/CDFSFiler/test/cdfsfiler/machine/open.mouse

.PHONY: cdfsfilerreftest cdfsfilerportedtest cdfsfilerbartest cdfsfilertest \
	cdfsfileropentest

# The same ROM twice, differing only in NetFiler.
#
# Sources/Desktop/NetFiler/test/netfiler/machine/run.py boots the reference ROM in its own RPCEmu
# with the AUN test peer on (the file server NetFS's machine test uses). An
# Obey file in !Boot.Choices.Boot.PreDesk brings the network up, replaces the
# ROM's NetFiler with the one under test, and logs on before the desktop
# starts.
#
# The reference NetFS is under both runs; this tree's Internet and Net are
# too. Sources/Desktop/Filer/test/filer/machine/check.py compares the two screenshots.
#
# `NFL_MOUSE` drives the pointer; without it the picture is the icon bar.
NFL_MOUSE ?=

netfilerreftest: netmodule internetmodule
	python3 Sources/Desktop/NetFiler/test/netfiler/machine/run.py reference $(NFL_MOUSE)

netfilerportedtest: $(NFL_MOD) netmodule internetmodule
	python3 Sources/Desktop/NetFiler/test/netfiler/machine/run.py ours $(NFL_MOUSE)
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/netfilerported.png \
	  $(BUILD)/netfilerref.png NetFiler

netfilertest:
	@$(MAKE) --no-print-directory netfilerreftest
	@$(MAKE) --no-print-directory netfilerportedtest

.PHONY: netfilerreftest netfilerportedtest netfilertest

# Hard disc images for the machine, and booting from one.
#
# `make adfsdiscimage` writes a host directory into a hard disc image using
# Sources/FileSys/FileCore/test/filecore/putimage.c; RPCEMU_HD4 and RPCEMU_HD5 attach it. The defaults
# turn the emulator's HostFS tree into a 256MB ADFS disc.
#
#   make adfsdiscimage ADFS_FROM=<dir> ADFS_IMAGE=<file> ADFS_MB=<megabytes>
#
# `make adfsbootdisc` writes the image and boots the all-ours ROM from it,
# with the configured filing system set to ADFS for the run; OS_FSControl 19
# runs the disc's `!Boot`. Expected: a desktop with a pinboard backdrop.
ADFS_FROM  ?= $(LEGACY)/rpcemu/hostfs
ADFS_IMAGE ?= $(BUILD)/tests/adfsboot/hd4.hdf
ADFS_MB    ?= 256

adfsdiscimage: $(BUILD)/tests/filecore-putimage
	@mkdir -p $(dir $(ADFS_IMAGE))
	@$(BUILD)/tests/filecore-putimage $(ADFS_IMAGE) $(ADFS_MB) \
	  $(ADFS_FROM) HardDisc4 | tail -1
.PHONY: adfsdiscimage

# CMOS &5 (FileLangCMOS) = 8 selects ADFS for the run; the machine's own
# value is &99 (HostFS). Boot and start-up drive are already set in the
# machine's CMOS and are left alone.
ADFS_BOOT_CMOS := 5=8,90=20

adfsbootdisc: adfsdiscimage
	@$(MAKE) --no-print-directory ourstest \
	  OURS_MOUSE= \
	  OURS_CMOS='$(ADFS_BOOT_CMOS)' \
	  ADFSFILER_HD=$(abspath $(dir $(ADFS_IMAGE))) \
	  OURS_SHOT=$(abspath $(BUILD))/adfsboot.png OURS_WAIT=120
.PHONY: adfsbootdisc

# ResourceFiler on the machine: the icon bar.
#
# One screenshot shows whether the sprite, caption and bar position match the
# reference module's. The control is the same ROM with the reference
# ResourceFiler left in its slot.
#
# Ours is larger than the reference's slot, so mkrom puts it past the chain
# and leaves a placeholder in the slot. No CMOS is needed: ResourceFS is
# always in the ROM.
RSF_WAIT      ?= 45
RSF_REFERENCE := $(filter-out ResourceFiler=%,$(REPLACED_MODULES))
RSF_REPLACED  := $(REPLACED_MODULES)

resourcefilerreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(RSF_REFERENCE)'
	$(MAKE) --no-print-directory NODEPS=1 filerkeys \
	  FILER_ROM=$(REPLACED_ROM) FILER_SHOT=resourcefilerref \
	  FILER_WAIT=$(RSF_WAIT) FILER_KEYS=''

resourcefilerportedtest: $(RSF_MOD)
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(RSF_REPLACED)'
	$(MAKE) --no-print-directory NODEPS=1 filerkeys \
	  FILER_ROM=$(REPLACED_ROM) FILER_SHOT=resourcefilerported \
	  FILER_WAIT=$(RSF_WAIT) FILER_KEYS=''
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/resourcefilerported.png \
	  $(BUILD)/resourcefilerref.png ResourceFiler

.PHONY: resourcefilerreftest resourcefilerportedtest

# The menu behind the icon: the pointer clicks Menu on the Apps icon, showing
# the title and its one entry from the message file. Both runs read the
# reference ROM's message file.
RSF_MOUSE      ?= Sources/Desktop/ResourceFiler/test/resfiler/machine/menu.mouse
RSF_MENU_WAIT  ?= 60

resourcefilermenureftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(RSF_REFERENCE)'
	$(MAKE) --no-print-directory NODEPS=1 filerpointer FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=resourcefilermenuref FILER_WAIT=$(RSF_MENU_WAIT) \
	  FILER_KEYS='' FLR_MOUSE=$(RSF_MOUSE)

resourcefilermenutest: $(RSF_MOD)
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(RSF_REPLACED)'
	$(MAKE) --no-print-directory NODEPS=1 filerpointer FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=resourcefilermenu FILER_WAIT=$(RSF_MENU_WAIT) \
	  FILER_KEYS='' FLR_MOUSE=$(RSF_MOUSE)
	@python3 Sources/Desktop/Filer/test/filer/machine/check.py $(BUILD)/resourcefilermenu.png \
	  $(BUILD)/resourcefilermenuref.png ResourceFiler

.PHONY: resourcefilermenureftest resourcefilermenutest

pinboardmachinetest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@$(MAKE) --no-print-directory NODEPS=1 pinboardfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=pinboardmachine FILER_WAIT=$(PBD_WAIT) \
	  FILER_KEYS='$(PBD_KEYS)' FILER_SETTLE=$(PBD_SETTLE) \
	  FILER_DONE='$(call machine_done,Sources/Desktop/Pinboard/test/pinboard/machine/check.py $(PBD_HOSTFS),$(PBD_HOSTFS))'
	@python3 Sources/Desktop/Pinboard/test/pinboard/machine/check.py $(PBD_HOSTFS) \
	  $(BUILD)/pinboardmachine.png

freemachinetest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@rm -rf $(FRE_HOSTFS)
	@mkdir -p $(FRE_HOSTFS)
	@cp Sources/Desktop/Free/test/free/machine/Drive "$(FRE_HOSTFS)/Drive,fff"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=freemachine FILER_WAIT=$(FRE_WAIT) \
	  FILER_KEYS='$(FRE_KEYS)' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/Free/test/free/machine/check.py $(FRE_HOSTFS),$(FRE_HOSTFS))'
	@python3 Sources/Desktop/Free/test/free/machine/check.py $(FRE_HOSTFS)

dragobjmachine:
	@rm -rf $(DAO_HOSTFS)
	@mkdir -p $(DAO_HOSTFS)
	@cp Sources/Desktop/DragAnObj/test/dragobj/machine/Drive "$(DAO_HOSTFS)/Drive,fff"
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(FILER_ROM) \
	  FILER_SHOT=$(FILER_SHOT) FILER_WAIT=$(DAO_WAIT) \
	  FILER_KEYS='<F12>BASIC\n*Exec HostFS::HostFS.$$$$.DAOtest.Drive\n*Quit\n\n' \
	  FILER_DONE='$(call machine_done,Sources/Desktop/DragAnObj/test/dragobj/machine/check.py $(DAO_HOSTFS),$(DAO_HOSTFS))'
	@python3 Sources/Desktop/DragAnObj/test/dragobj/machine/check.py $(DAO_HOSTFS)

# The control: the same three questions against the ROM with the reference
# Filer_Action in it.
filerfulltest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(FULL_ROM)
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(FULL_ROM) \
	  FILER_SHOT=filerfulltest

fulltest:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(FULL_ROM)
	@$(MAKE) --no-print-directory TYPE_ROM=$(BUILD)/riscos-full \
	  TYPE_SHOT=fulltest portedtest

portedboot:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	$(LEGACY)/tools/try-rom.sh $(abspath $(REPLACED_ROM)) $(abspath $(BUILD))/portedboot.png 60
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/portedboot.png

.PHONY: portedrom portedboot portedtest filertest filerkeys filerfulltest
.PHONY: filermachine filermachinetest filerfullmachinetest fulltest endboot
.PHONY: filerfiles filerbigmachinetest
.PHONY: dragobjmachine dragobjmachinetest dragsprmachinetest \
        filtermachinetest freemachinetest taskwinmachinetest \
        taskwinreftest switchermachinetest switcherreftest switcherfiles \
        pinboardmachinetest pinboardreftest pinboardfiles

fullboot:
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(FULL_ROM)
	$(LEGACY)/tools/try-rom.sh $(abspath $(FULL_ROM)) $(abspath $(BUILD))/fullboot.png 60
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/fullboot.png

.PHONY: fullrom fullboot

# The control for fullboot: the reference ROM, unmodified, on the same disc.
# A difference between the two runs is our kernel and the module addresses
# it moves. Nothing is built for it.
refboot:
	$(LEGACY)/tools/try-rom.sh $(abspath $(REF_ROM)) $(abspath $(BUILD))/refboot.png 60
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/refboot.png

.PHONY: refboot

vidcboot: $(VIDC_ROM)
	$(LEGACY)/tools/try-rom.sh $(abspath $(VIDC_ROM)) $(abspath $(BUILD))/vidcboot.png 18
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/vidcboot.png

.PHONY: vidcrom vidcboot

boot: $(ROM)
	$(LEGACY)/tools/try-rom.sh $(abspath $(ROM)) $(abspath $(BUILD))/boot.png 18
	@python3 $(LEGACY)/tools/screen-state.py $(BUILD)/boot.png

.PHONY: rom boot

# The C-form headers this tree owns.
#
# Each is the C form of an ObjAsm header, generated by
# tools/check/objasm-hdr.py and read through cpp by the C, the HAL and the
# assembly. The file in the tree is the source; `make hdrcheck` regenerates
# each into $(BUILD)/hdrcheck and compares it with the owned copy, and `make
# regen-headers` rewrites the owned copies from the same table.
#
# Nine origins are ObjAsm headers under Sources/Kernel/hdr, which the ObjAsm unit
# still reads directly; the check keeps both forms in step.
OWNED_HDRS := include/hal/hal_entries.h include/hal/os_entries.h \
              include/hal/devnos.h include/hal/dma.h include/hal/dma_device.h \
              include/hal/hal_device.h include/hal/ide_device.h \
              include/hal/rtc_device.h include/hal/video_device.h \
              include/hal/io_iomd.h \
              include/riscos/errors.h include/riscos/services.h \
              include/riscos/upcall.h include/riscos/osbytes.h \
              include/riscos/oswords.h include/riscos/cmos.h \
              include/riscos/heap.h include/riscos/pointer.h \
              include/riscos/podule.h include/riscos/keyboard.h \
              include/riscos/countries.h include/riscos/sprite.h \
              include/riscos/internatio.h \
              include/riscos/osrsi6.h \
              include/riscos/graphicsv.h \
                                          include/riscos/palettev.h include/riscos/highfsi.h \
                                          include/riscos/lowfsi.h \
                                          include/riscos/territory.h \
              include/riscos/fsnumbers.h include/riscos/portable.h \
              include/riscos/rtc.h include/riscos/econet.h \
              include/riscos/mimemap.h include/riscos/serialop.h \
              include/riscos/devicefs.h include/riscos/serial.h \
              include/riscos/rs423.h \
              include/riscos/messages.h include/riscos/filetypes.h \
              include/riscos/filecore.h include/riscos/multifs.h \
              include/riscos/adfs.h \
              include/riscos/buffer.h \
              include/riscos/filecoreerr.h include/riscos/colourtran.h \
              include/arch/arm/memm_arm600.h include/arch/arm/memm_vmsav6.h \
              Sources/Hardware/IOMD/include/io_generic.h Sources/Hardware/IOMD/include/iomd_regs.h \
              Sources/Hardware/IOMD/include/hal_ws.h

# Where each comes from, and the options its conversion needs.
HDR_SRC_include/hal/hal_entries.h   := $(KERNEL)/HALEntries
HDR_GUARD_include/hal/hal_entries.h := HAL_ENTRIES_H
HDR_SRC_include/hal/os_entries.h    := $(KERNEL)/OSEntries
HDR_GUARD_include/hal/os_entries.h  := OS_ENTRIES_H
HDR_SRC_include/hal/devnos.h        := $(HDRSRC)/DevNos
HDR_GUARD_include/hal/devnos.h      := DEVNOS_H
HDR_SRC_include/hal/dma.h           := $(RO_SRC)/HWSupport/DMA/hdr/DMA
HDR_GUARD_include/hal/dma.h         := DMA_H
HDR_OPTS_include/hal/dma.h          := --symbols-from $(HDRSRC)/SWIs -D Auto_Error_SWI_bit=0x20000
HDR_SRC_include/hal/dma_device.h    := $(RO_SRC)/HWSupport/DMA/hdr/DMADevice
HDR_GUARD_include/hal/dma_device.h  := DMA_DEVICE_H
HDR_SRC_include/hal/hal_device.h    := $(KERNEL)/HALDevice
HDR_GUARD_include/hal/hal_device.h  := HAL_DEVICE_H
HDR_SRC_include/hal/ide_device.h    := $(RO_SRC)/FileSys/ADFS/ADFS/hdr/IDEDevice
HDR_GUARD_include/hal/ide_device.h  := IDE_DEVICE_H
HDR_SRC_include/hal/rtc_device.h    := $(RO_SRC)/HWSupport/RTC/hdr/RTCDevice
HDR_GUARD_include/hal/rtc_device.h  := RTC_DEVICE_H
HDR_SRC_include/hal/video_device.h  := $(KERNEL)/VideoDevice
HDR_GUARD_include/hal/video_device.h := VIDEO_DEVICE_H
# The IOMD register map comes from HdrSrc, not the HAL: the kernel's IIC code
# reads the chip's identity registers through it, so it sits beside devnos.h.
HDR_SRC_include/hal/io_iomd.h       := $(HDRSRC)/IO/IOMD
HDR_GUARD_include/hal/io_iomd.h     := IO_IOMD_H
HDR_SRC_include/riscos/errors.h     := $(HDRSRC)/NewErrors
HDR_GUARD_include/riscos/errors.h   := RISCOS_ERRORS_H
HDR_OPTS_include/riscos/errors.h    := --symbols-from $(HDRSRC)/FSNumbers
HDR_SRC_include/riscos/services.h   := $(HDRSRC)/Services
HDR_GUARD_include/riscos/services.h := RISCOS_SERVICES_H
HDR_SRC_include/riscos/upcall.h     := $(HDRSRC)/UpCall
HDR_GUARD_include/riscos/upcall.h   := RISCOS_UPCALL_H
HDR_SRC_include/riscos/osbytes.h    := $(HDRSRC)/OsBytes
HDR_GUARD_include/riscos/osbytes.h  := RISCOS_OSBYTES_H
HDR_SRC_include/riscos/oswords.h    := $(HDRSRC)/OsWords
HDR_GUARD_include/riscos/oswords.h  := RISCOS_OSWORDS_H
HDR_SRC_include/riscos/cmos.h       := $(HDRSRC)/CMOS
HDR_GUARD_include/riscos/cmos.h     := RISCOS_CMOS_H
HDR_SRC_include/riscos/heap.h       := $(HDRSRC)/Heap
HDR_GUARD_include/riscos/heap.h     := RISCOS_HEAP_H
HDR_SRC_include/riscos/pointer.h    := $(HDRSRC)/Pointer
HDR_GUARD_include/riscos/pointer.h  := RISCOS_POINTER_H
HDR_SRC_include/riscos/podule.h     := $(RO_SRC)/HWSupport/Podule/hdr/Podule
HDR_SRC_include/riscos/serialop.h   := $(RO_SRC)/HWSupport/SerialSpt/hdr/SerialOp
HDR_GUARD_include/riscos/serialop.h := RISCOS_SERIALOP_H
# DeviceFS's header: the SWI numbers, parent and device registration flags,
# and the reason codes a driver's entry is called with.
HDR_SRC_include/riscos/devicefs.h   := $(RO_SRC)/HWSupport/DeviceFS/hdr/DeviceFS
HDR_GUARD_include/riscos/devicefs.h := RISCOS_DEVICEFS_H
HDR_OPTS_include/riscos/devicefs.h  := --symbols-from $(HDRSRC)/SWIs \
                                       -D Auto_Error_SWI_bit=0x20000
# The serial device driver's reasons above DeviceCall_ExternalBase that
# SerialDeviceSupport turns OS_SerialOp into, and the baud codes (FX 7 and
# FX 8 numbers, not rates). Needs the DeviceFS header for the base.
HDR_SRC_include/riscos/serial.h     := $(RO_SRC)/HWSupport/Serial/hdr/Serial
HDR_GUARD_include/riscos/serial.h   := RISCOS_SERIAL_H
HDR_OPTS_include/riscos/serial.h    := --symbols-from \
                                       $(RO_SRC)/HWSupport/DeviceFS/hdr/DeviceFS
# The serial flags: the bits OS_SerialOp 0 reads and writes, the 6850 control
# register SerialDeviceSupport emulates, and word lengths and stop bits.
HDR_SRC_include/riscos/rs423.h      := $(RO_SRC)/HWSupport/Serial/hdr/RS423
HDR_GUARD_include/riscos/rs423.h    := RISCOS_RS423_H
HDR_GUARD_include/riscos/podule.h   := RISCOS_PODULE_H
HDR_SRC_include/riscos/keyboard.h   := $(HDRSRC)/Keyboard
HDR_GUARD_include/riscos/keyboard.h := RISCOS_KEYBOARD_H
HDR_SRC_include/riscos/countries.h  := $(HDRSRC)/Countries
HDR_GUARD_include/riscos/countries.h := RISCOS_COUNTRIES_H
HDR_SRC_include/riscos/sprite.h     := $(HDRSRC)/Sprite
HDR_GUARD_include/riscos/sprite.h   := RISCOS_SPRITE_H
HDR_SRC_include/riscos/osrsi6.h     := $(KERNEL)/OSRSI6
HDR_GUARD_include/riscos/osrsi6.h   := RISCOS_OSRSI6_H
HDR_GUARD_include/riscos/envnumbers.h := RISCOS_ENVNUMBERS_H
HDR_SRC_include/riscos/graphicsv.h  := $(HDRSRC)/GraphicsV
HDR_GUARD_include/riscos/graphicsv.h := GRAPHICSV_H
HDR_GUARD_include/riscos/modhand.h  := MODHAND_H
HDR_SRC_    := Sources/Kernel/hdr/VduExt
HDR_GUARD_include/riscos/vduext.h   := VDUEXT_H
HDR_GUARD_:= VIDCLIST_H
# PaletteV reasons, FileSwitch's FSControl reasons, filing system numbers
# and the Portable module's feature bits.
HDR_SRC_include/riscos/palettev.h   := $(HDRSRC)/PaletteV
HDR_GUARD_include/riscos/palettev.h := RISCOS_PALETTEV_H
HDR_SRC_include/riscos/highfsi.h    := $(RO_SRC)/FileSys/FileSwitch/hdr/HighFSI
HDR_GUARD_include/riscos/highfsi.h  := RISCOS_HIGHFSI_H
# The low-level filing system interface: what a filing system's entry points
# are called with.
HDR_SRC_include/riscos/lowfsi.h     := $(RO_SRC)/FileSys/FileSwitch/hdr/LowFSI
HDR_GUARD_include/riscos/lowfsi.h   := RISCOS_LOWFSI_H
# The Territory Manager's header: error numbers, time formats and date
# conversion flags.
HDR_SRC_include/riscos/territory.h  := $(RO_SRC)/Internat/Territory/TerritoryManager/hdr/Territory
HDR_GUARD_include/riscos/territory.h := RISCOS_TERRITORY_H
# The International module's header, for the Service_International
# sub-reason the manager issues to define a character range for an alphabet.
HDR_SRC_include/riscos/internatio.h := $(RO_SRC)/Internat/Inter/hdr/Internatio
HDR_GUARD_include/riscos/internatio.h := RISCOS_INTERNATIO_H
HDR_SRC_include/riscos/fsnumbers.h  := $(HDRSRC)/FSNumbers
HDR_GUARD_include/riscos/fsnumbers.h := RISCOS_FSNUMBERS_H
HDR_SRC_include/riscos/portable.h   := $(RO_SRC)/HWSupport/Portable/hdr/Portable
HDR_GUARD_include/riscos/portable.h := RISCOS_PORTABLE_H
# The RTC module's SWIs, issued by s/io/rtcswi.S to read and write the
# clock.
HDR_SRC_include/riscos/rtc.h        := $(RO_SRC)/HWSupport/RTC/hdr/RTC
HDR_GUARD_include/riscos/rtc.h      := RISCOS_RTC_H
# MimeMap's SWI and the formats it translates between.
HDR_SRC_include/riscos/mimemap.h    := $(RO_SRC)/Networking/MimeMap/hdr/MimeMap
HDR_GUARD_include/riscos/mimemap.h  := RISCOS_MIMEMAP_H
HDR_OPTS_include/riscos/mimemap.h   := --symbols-from $(HDRSRC)/SWIs \
                                       -D Auto_Error_SWI_bit=0x20000
# File server function codes and the file server enumeration layout, which
# Free reads to find the free space on an Econet disc.
HDR_SRC_include/riscos/econet.h     := $(RO_SRC)/Networking/Econet/hdr/Econet
HDR_GUARD_include/riscos/econet.h   := RISCOS_ECONET_H
# Every Wimp message number.
HDR_SRC_include/riscos/messages.h   := $(HDRSRC)/Messages
HDR_GUARD_include/riscos/messages.h := RISCOS_MESSAGES_H
# Message numbers past &40000 are a SWI chunk base times a chunk size, so the
# two headers that define those are read too; otherwise the output carries
# the expression rather than the number.
HDR_OPTS_include/riscos/messages.h  := --symbols-from $(HDRSRC)/SWIs \
                                       --symbols-from hdr/ModHand
# The file types.
HDR_SRC_include/riscos/filetypes.h   := $(HDRSRC)/FileTypes
HDR_GUARD_include/riscos/filetypes.h := RISCOS_FILETYPES_H
# FileCore's header: the disc record, the reason codes for FileCore's two
# callback entry points, and the descriptor FileCore_Create takes.
HDR_SRC_include/riscos/buffer.h      := $(RO_SRC)/HWSupport/Buffers/hdr/Buffer
HDR_GUARD_include/riscos/buffer.h    := RISCOS_BUFFER_H
HDR_OPTS_include/riscos/buffer.h     := --symbols-from $(HDRSRC)/SWIs \
                                        -D Auto_Error_SWI_bit=0x20000

HDR_SRC_include/riscos/filecore.h    := $(RO_SRC)/FileSys/FileCore/hdr/FileCore
HDR_GUARD_include/riscos/filecore.h  := RISCOS_FILECORE_H
HDR_OPTS_include/riscos/filecore.h   := --symbols-from $(HDRSRC)/SWIs \
                                        -D Auto_Error_SWI_bit=0x20000
# FileCore's two companion headers. MultiFS is the block layout the format
# services and SWIs exchange with ADFSFiler and the format tools;
# FileCoreErr is the error numbers FileCore raises at an offset into its
# client's error block.
# ADFS's header: its SWI numbers, and the reason codes and flags of
# ADFS_IDEDeviceInfo and ADFS_ATAPIOp.
HDR_SRC_include/riscos/adfs.h          := $(RO_SRC)/FileSys/ADFS/ADFS/hdr/ADFS
HDR_GUARD_include/riscos/adfs.h        := RISCOS_ADFS_H
HDR_OPTS_include/riscos/adfs.h         := --symbols-from $(HDRSRC)/SWIs \
                                          -D Auto_Error_SWI_bit=0x20000

HDR_SRC_include/riscos/multifs.h       := $(RO_SRC)/FileSys/FileCore/hdr/MultiFS
HDR_GUARD_include/riscos/multifs.h     := RISCOS_MULTIFS_H
HDR_SRC_include/riscos/filecoreerr.h   := $(RO_SRC)/FileSys/FileCore/hdr/FileCoreErr
HDR_GUARD_include/riscos/filecoreerr.h := RISCOS_FILECOREERR_H
# ColourTrans's header: the SWI numbers and the header layout of a "32K+"
# translation table, which SpriteExtend validates before plotting through
# one.
HDR_SRC_include/riscos/colourtran.h   := $(RO_SRC)/Video/Render/Colours/hdr/ColourTran
HDR_GUARD_include/riscos/colourtran.h := RISCOS_COLOURTRAN_H
# --no-swis: ColourTrans's SWI numbers are already in include/riscos/swis.h;
# only the structure offsets are wanted from here.
HDR_OPTS_include/riscos/colourtran.h  := --symbols-from $(HDRSRC)/SWIs \
                                         -D Auto_Error_SWI_bit=0x20000 \
                                         --no-swis
HDR_OPTS_include/riscos/rtc.h       := --symbols-from $(HDRSRC)/SWIs -D Auto_Error_SWI_bit=0x20000
# Portable's SWI numbers need the X bit from Sources/Kernel/hdr/RISCOS as well as
# the chunk base; without it the converter reports every AddSWI instead of
# emitting it.
HDR_OPTS_include/riscos/portable.h  := --symbols-from $(HDRSRC)/SWIs \
                                       --symbols-from $(KERNEL_DIR)/hdr/Global/RISCOS.h
# Descriptor and control register bits for the two page table formats, read
# by the arch assembly. The coprocessor register names those headers also
# declare are assembler aliases and are not converted.
HDR_SRC_include/arch/arm/memm_arm600.h   := $(HDRSRC)/MEMM/ARM600
HDR_GUARD_include/arch/arm/memm_arm600.h := ARCH_ARM_MEMM_ARM600_H
HDR_SRC_include/arch/arm/memm_vmsav6.h   := $(HDRSRC)/MEMM/VMSAv6
HDR_GUARD_include/arch/arm/memm_vmsav6.h := ARCH_ARM_MEMM_VMSAV6_H
HDR_SRC_hal/iomd/include/io_generic.h   := $(HDRSRC)/IO/GenericIO
HDR_GUARD_hal/iomd/include/io_generic.h := IO_GENERIC_H
HDR_SRC_hal/iomd/include/iomd_regs.h    := $(HALSRC)/IOMD
HDR_GUARD_hal/iomd/include/iomd_regs.h  := IOMD_REGS_H
HDR_SRC_hal/iomd/include/hal_ws.h       := $(HALSRC)/StaticWS
HDR_GUARD_hal/iomd/include/hal_ws.h     := HAL_WS_H
HDR_OPTS_hal/iomd/include/hal_ws.h      := -D HighestOSEntry=7

# One conversion: $(call hdr_convert,owned path,where to write it).
hdr_convert = python3 tools/check/objasm-hdr.py $(HDR_SRC_$(1)) $(2) \
                --guard $(HDR_GUARD_$(1)) $(HDR_OPTS_$(1))

regen-headers:
	@$(foreach h,$(OWNED_HDRS),$(call hdr_convert,$(h),$(h)) > /dev/null 2>&1 || exit 1;) \
	echo "regen-headers: $(words $(OWNED_HDRS)) owned headers rewritten from their origins"

# Regenerate every owned header into $(BUILD)/hdrcheck and compare with the
# owned copy; also check the hand-written include/arch/arm/psr.h against
# Hdr:CPU.Generic32 on every name the two share.
hdrcheck:
	@fail=0; \
	$(foreach h,$(OWNED_HDRS), \
	  mkdir -p $(BUILD)/hdrcheck/$(dir $(h)); \
	  if $(call hdr_convert,$(h),$(BUILD)/hdrcheck/$(h)) > /dev/null 2>&1; then \
	    cmp -s $(h) $(BUILD)/hdrcheck/$(h) \
	      || { echo "hdrcheck: $(h) differs from its origin $(HDR_SRC_$(h)); make regen-headers if the origin is right"; \
	           diff $(h) $(BUILD)/hdrcheck/$(h) | head -8; fail=1; }; \
	  else echo "hdrcheck: $(h): its origin $(HDR_SRC_$(h)) no longer converts"; fail=1; fi;) \
	python3 tools/check/objasm-hdr.py $(HDRSRC)/CPU/Generic32 \
	  $(BUILD)/hdrcheck/generic32.h --guard HDRCHECK_GENERIC32_H > /dev/null 2>&1; \
	python3 tools/check/hdrsubset.py include/arch/arm/psr.h $(BUILD)/hdrcheck/generic32.h \
	  || fail=1; \
	python3 tools/check/objasm-hdr.py $(HDRSRC)/CPU/Generic26 \
	  $(BUILD)/hdrcheck/generic26.h --guard HDRCHECK_GENERIC26_H > /dev/null 2>&1; \
	python3 tools/check/hdrsubset.py include/arch/arm/psr.h $(BUILD)/hdrcheck/generic26.h \
	  || fail=1; \
	[ $$fail = 0 ] && echo "hdrcheck: $(words $(OWNED_HDRS)) owned headers match their origins, and psr.h matches Hdr:CPU.Generic32 and Generic26"; \
	exit $$fail

# Compiler-generated dependency files, so a changed header rebuilds what
# includes it. Another build kept inside this one (BUILD=build/<name>, marked
# by its own defines.stamp) is left out: its files name only its own targets.
# NODEPS=1 skips them all, for a make that only runs something already built
# - the machine tests' own sub-makes - since reading them takes seconds.
ifeq ($(NODEPS),)
NESTED_BUILDS := $(patsubst %/defines.stamp,%,$(shell find $(BUILD) -mindepth 2 \
                   -maxdepth 4 -name defines.stamp 2>/dev/null))
-include $(shell find $(BUILD) $(foreach d,$(NESTED_BUILDS),-path $(d) -prune -o) \
           -name '*.d' -print 2>/dev/null)
endif

clean:
	rm -rf $(BUILD)


include Sources/Lib/CLib/clib.mk
include Sources/Lib/Posix/posix.mk
include Sources/Lib/Desktop/desktop.mk
include Sources/Lib/Toolbox/toolbox.mk
include Sources/Lib/SMB/smb.mk
include Sources/Lib/Fonts/fonts.mk
include Sources/Lib/LanMan/lanman.mk
include Sources/Networking/ShareFS/sharefs.mk
include Sources/Networking/LanManFS/lanmanfs.mk
include Sources/Networking/Freeway/freeway.mk
include Sources/Lib/Virtio/virtio.mk
include Sources/Lib/RWPI/rwpi.mk
include Sources/Desktop/DesktopBoot/module.mk
include Sources/Video/HWVideo/VIDC20Video/module.mk
include Sources/Video/HWVideo/RamfbVideo/module.mk
include Sources/HWSupport/Virtio/VirtioInput/module.mk
include Sources/HWSupport/Virtio/VirtioRNG/module.mk
include Sources/Networking/Ethernet/VirtioNet/module.mk
include Sources/Networking/WLanB/module.mk
include Sources/Networking/NetManager/wifichecks.mk
include Sources/Networking/Ethernet/EtherUSB/module.mk
include Sources/Networking/Bluetooth/module.mk
# The SCSI system: the switcher, a filing system and the virt bus driver.
include Sources/HWSupport/SCSI/SCSISwitch/module.mk
include Sources/FileSys/SCSIFS/module.mk
include Sources/HWSupport/Virtio/VirtioSCSI/module.mk
include Sources/Desktop/SCSIFiler/module.mk
include Sources/FileSys/CDFS/CDFSSoftSCSI/module.mk
include Sources/HWSupport/USB/USBDriver/module.mk
include Sources/HWSupport/USB/XHCIDriver/module.mk
include Sources/HWSupport/SCSI/SCSISoftUSB/module.mk
include Sources/HWSupport/PCI/module.mk
include Sources/HWSupport/Podule/module.mk
include Sources/HWSupport/DMA/module.mk
include Sources/HWSupport/Parallel/module.mk
include Sources/HWSupport/Serial/module.mk
include Sources/Programmer/Debugger/module.mk
include Sources/HWSupport/Joystick/module.mk
include Sources/Networking/Econet/BBCEconet/module.mk
include Sources/Networking/Inet/InetRes/module.mk
include Sources/Utilities/UnSqzAIF/module.mk
include Sources/Hardware/Virt/virt.mk

# Every module either ROM carries, whether or not a test ROM does.
everything: $(OURS_MODULES) $(VIRT_MODULES)

# Each Toolbox module brings its own module.mk, so adding one is adding a
# directory.
include $(sort $(wildcard Sources/Toolbox/*/module.mk))

# ColourPicker brings its own rules too, included by name: everything else
# else that has no module.mk has its rules in this file.
include Sources/Video/UserI/Picker/module.mk

# The Display Manager also brings its own rules, resources and host checks.
include Sources/Desktop/DisplayManager/module.mk
include mk/unicorn.mk
include Sources/Programmer/ARM32/module.mk
include Sources/HWSupport/VFPSupport/module.mk
include Sources/Programmer/SharedCLibrary/sharedclibrary.mk

# Host VDU policy tests supply configurable mode tables. Test the actual ROM
# tables separately, with native host pointers and sanitizers.
$(BUILD)/tests/modetables-checks: Sources/Kernel/test/modetables/checks.c $(KERNEL_DIR)/c/vdu/hardfont.c $(KERNEL_DIR)/c/vdu/modetables.c $(KERNEL_DIR)/c/vdu/modetimings.inc $(KERNEL_DIR)/c/vdu/vdulegacy.h $(KERNEL_DIR)/c/vdu/vduvars.h $(KERNEL_DIR)/hdr/kernel-ws.h $(KERNEL_DIR)/hdr/vduws.h $(KERNEL_GLOBALS)
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) Sources/Kernel/test/modetables/checks.c $(KERNEL_DIR)/c/vdu/hardfont.c $(KERNEL_DIR)/c/vdu/modetables.c -o $@

modetableschecks: $(BUILD)/tests/modetables-checks
	@$(BUILD)/tests/modetables-checks
.PHONY: modetableschecks

# Exercise production runtime adapters separately from the general host
# harness, whose configurable stand-ins use these same public names.
RESIDUE_TEST_C := vdu/teletextfont vdu/vdusupport vdu/vduservices cli/cliworkspace cli/utilitydata \
                  mem/memworkspace mem/pmpclaim io/rtcswi io/cmosinit cli/climetadata cli/clierrors cli/sysvarpresets
RESIDUE_TEST_SRC := $(RESIDUE_TEST_C:%=$(KERNEL_DIR)/c/%.c)
$(BUILD)/tests/residue-checks: tests/residue/checks.c tests/residue/cmoschecks.c $(RESIDUE_TEST_SRC) $(KERNEL_GLOBALS) \
                              $(BUILD)/Sources/Kernel/kernel-version.h $(KERNEL_BLOCK_HDRS) \
                              $(KERNEL_DIR)/hdr/kernel-ws.h $(KERNEL_DIR)/hdr/zeropage.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) tests/residue/checks.c tests/residue/cmoschecks.c $(RESIDUE_TEST_SRC) -o $@
residuechecks: $(BUILD)/tests/residue-checks
	@$(BUILD)/tests/residue-checks
.PHONY: residuechecks

# Check target pointers, relative module offsets and exported error/text data.
residueromchecks: $(KERNEL_ELF)
	@python3 tests/residue/romdata.py $(KERNEL_ELF)
.PHONY: residueromchecks

# ============================================================== BASIC ======
#
# ARM BBC BASIC V and VI, one C interpreter built twice: Sources/Programmer/BASIC
# is BASIC (five-byte reals) and Sources/Programmer/BASIC64 is BASIC64 (doubles,
# using the floating point emulator's arithmetic, whose C it links). Host
# checks against a stand-in OS are in Sources/Programmer/BASIC/test/basic; the machine test runs a
# BASIC program under the reference module and ours, in Sources/Programmer/BASIC/test/basic/machine.
#
# The VFP assembler's tables, as extracted from the reference ROM's Messages
# module, match the copy in the tree.
basicvfpcheck: $(LEGACY)/romparts/005-Messages.mod tools/build/resfsextract.py
	@mkdir -p $(BUILD)/modules/basic
	@python3 tools/build/resfsextract.py $< Resources.BASIC.VFPData \
	  $(BUILD)/modules/basic/VFPData.gen
	@cmp $(BUILD)/modules/basic/VFPData.gen $(MOD_BASIC_VFPDATA)
.PHONY: basicvfpcheck
hostchecks: basicvfpcheck

# Check the tables generated from BASIC's sources against them.
basicgencheck:
	@python3 tools/build/basicgen.py
.PHONY: basicgencheck

BASIC_DIR    := Sources/Programmer/BASIC
# 1 builds 64-bit integers (`%%`, `]`) into the interpreter. basic.h refuses
# 1 for the ARM32 modules; the host checks build both ways (below).
BASIC_INT64  ?= 0
# The ARM-only C (the VFP assembler) is in c/arch/aarch32.
BASIC_CSRC   := $(sort $(wildcard $(BASIC_DIR)/c/*.c $(BASIC_DIR)/c/arch/aarch32/*.c))
BASIC_CNAMES := $(sort $(basename $(notdir $(BASIC_CSRC))))
# What BASIC V links: everything but the double implementation.
BASIC5_CNAMES := $(filter-out bfloatd,$(BASIC_CNAMES))
# What BASIC VI links: everything but the five-byte one, plus the emulator's
# arithmetic.
BASIC64_CNAMES := $(filter-out bfloat5,$(BASIC_CNAMES))
FPE_SHARED_CNAMES := fpnum fpround fpnan fparith fptrans fpconv fppacked
# On the host there is no OS to call, so bos.c is left out and
# Sources/Programmer/BASIC/test/basic/hostos.c replaces it.
BASIC_HOST_CNAMES   := $(filter-out bos,$(BASIC5_CNAMES))
BASIC64_HOST_CNAMES := $(filter-out bos,$(BASIC64_CNAMES))

# ---- on the build machine
BASIC_TEST_GEN   := $(BUILD)/tests/basic
BASIC64_TEST_GEN := $(BUILD)/tests/basic64
BASIC_TEST_FLAGS := $(HOST_CFLAGS) -DBASIC_HOST -I$(BASIC_DIR)/c -Itests/lib \
                    -Iinclude -ISources/Programmer/FPEmulator/c -ISources/Lib/Desktop/text
BASIC_TEST_SRC   := $(sort $(wildcard Sources/Programmer/BASIC/test/basic/*.c))
# a64asm_checks links the A64 assembler instead of the ARM one (below).
BASIC_A64_CHECK  := a64asm_checks
# int64_checks runs only where BASIC_INT64 is 1 (basicint64checks below).
BASIC_INT64_CHECK := int64_checks
BASIC_CHECKS     := $(filter-out $(BASIC_A64_CHECK) $(BASIC_INT64_CHECK),$(basename $(notdir $(filter %_checks.c,$(BASIC_TEST_SRC)))))
BASIC_STANDINS   := $(basename $(notdir $(filter-out %_checks.c,$(BASIC_TEST_SRC))))

$(BASIC_TEST_GEN)/%.o: $(BASIC_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BASIC_TEST_FLAGS) -c $< -o $@
$(BASIC_TEST_GEN)/%.o: $(BASIC_DIR)/c/arch/aarch32/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BASIC_TEST_FLAGS) -c $< -o $@
$(BASIC_TEST_GEN)/%.o: Sources/Programmer/BASIC/test/basic/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BASIC_TEST_FLAGS) -c $< -o $@
$(BASIC_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BASIC_TEST_FLAGS) -c $< -o $@
$(BASIC64_TEST_GEN)/%.o: $(BASIC_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BASIC_TEST_FLAGS) -DBASIC_FPOINT=1 -c $< -o $@
$(BASIC64_TEST_GEN)/%.o: $(BASIC_DIR)/c/arch/aarch32/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BASIC_TEST_FLAGS) -DBASIC_FPOINT=1 -c $< -o $@
$(BASIC64_TEST_GEN)/%.o: Sources/Programmer/BASIC/test/basic/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BASIC_TEST_FLAGS) -DBASIC_FPOINT=1 -c $< -o $@
$(BASIC64_TEST_GEN)/%.o: Sources/Programmer/FPEmulator/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BASIC_TEST_FLAGS) -DBASIC_FPOINT=1 -c $< -o $@
$(BASIC64_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(BASIC_TEST_FLAGS) -DBASIC_FPOINT=1 -c $< -o $@

# `make basicchecks` links every checks file against the whole interpreter
# and every stand-in. `make basicchecks BASIC_CHECK_ONLY=lex
# BASIC_CHECK_ALSO="var expr"` links lex_checks against lex.o, the named
# objects and the stand-ins only.
# BASIC_CHECKS_FP5 assume five-byte reals and run only in the BASIC V build;
# BASIC_CHECKS_FP8 runs only in the BASIC VI build.
BASIC_CHECKS_FP5 := bfloat5_checks funct_checks stmt_checks var_checks
BASIC_CHECKS_FP8 := bfloatd_checks

BASIC_CHECK_ONLY ?=
BASIC_CHECK_ALSO ?=
ifeq ($(BASIC_CHECK_ONLY),)
BASIC_CHECK_LINK  := $(BASIC_HOST_CNAMES) $(BASIC_STANDINS)
BASIC_CHECK_RUN   := $(filter-out $(BASIC_CHECKS_FP8),$(BASIC_CHECKS)) $(BASIC_A64_CHECK)
BASIC64_CHECK_LINK := $(BASIC64_HOST_CNAMES) $(FPE_SHARED_CNAMES) $(BASIC_STANDINS)
BASIC64_CHECK_RUN := $(filter-out $(BASIC_CHECKS_FP5),$(BASIC_CHECKS)) $(BASIC_A64_CHECK)
else
BASIC_CHECK_LINK  := $(BASIC_CHECK_ONLY) $(BASIC_CHECK_ALSO) $(BASIC_STANDINS)
BASIC_CHECK_RUN   := $(BASIC_CHECK_ONLY)_checks
BASIC64_CHECK_LINK := $(BASIC_CHECK_ONLY) $(BASIC_CHECK_ALSO) $(BASIC_STANDINS)
BASIC64_CHECK_RUN := $(BASIC_CHECK_ONLY)_checks
# The binary path is the same either way, and a narrower link is not older
# than the wide one, so force a relink.
BASIC_CHECK_FORCE := FORCE
endif

# The checks file is linked first and duplicate definitions are allowed, so a
# checks file's own stand-in for a routine wins over the interpreter's; a
# stand-in marked weak gives way to the real one. Unresolved symbols are
# still an error.
BASIC_LINK_FLAGS := -Wl,--allow-multiple-definition

$(BUILD)/tests/basic-%: $(BASIC_TEST_GEN)/%.o $(addprefix $(BASIC_TEST_GEN)/,$(addsuffix .o,$(BASIC_CHECK_LINK)) check.o) $(BASIC_CHECK_FORCE)
	$(HOSTCC) $(HOST_CFLAGS) $(BASIC_LINK_FLAGS) -o $@ $(filter %.o,$^)
$(BUILD)/tests/basic64-%: $(BASIC64_TEST_GEN)/%.o $(addprefix $(BASIC64_TEST_GEN)/,$(addsuffix .o,$(BASIC64_CHECK_LINK)) check.o) $(BASIC_CHECK_FORCE)
	$(HOSTCC) $(HOST_CFLAGS) $(BASIC_LINK_FLAGS) -o $@ $(filter %.o,$^)

# The A64 assembler, as the AArch64 build has it: asm.c built for A64 with
# c/arch/aarch64, in place of the ARM encoders, against llvm-mc's encodings
# of Sources/Programmer/BASIC/test/basic/a64cases.txt.
BASIC_A64_OBJS = asm-a64 a64asm $(filter-out asm vfpasm,$(1)) check
$(BASIC_TEST_GEN)/a64cases.h: Sources/Programmer/BASIC/test/basic/a64cases.txt Sources/Programmer/BASIC/test/basic/a64cases.py
	@mkdir -p $(dir $@)
	python3 Sources/Programmer/BASIC/test/basic/a64cases.py $< $@
define BASIC_A64_RULES
$(1)/asm-a64.o: $(BASIC_DIR)/c/asm.c
	@mkdir -p $$(dir $$@)
	$$(HOSTCC) $$(BASIC_TEST_FLAGS) $(2) -DBASIC_ASM_A64=1 -c $$< -o $$@
$(1)/a64asm.o: $(BASIC_DIR)/c/arch/aarch64/a64asm.c
	@mkdir -p $$(dir $$@)
	$$(HOSTCC) $$(BASIC_TEST_FLAGS) $(2) -DBASIC_ASM_A64=1 -c $$< -o $$@
$(1)/$(BASIC_A64_CHECK).o: Sources/Programmer/BASIC/test/basic/$(BASIC_A64_CHECK).c $(BASIC_TEST_GEN)/a64cases.h
	@mkdir -p $$(dir $$@)
	$$(HOSTCC) $$(BASIC_TEST_FLAGS) $(2) -I$(BASIC_TEST_GEN) -c $$< -o $$@
$(BUILD)/tests/$(3)-$(BASIC_A64_CHECK): $(1)/$(BASIC_A64_CHECK).o \
    $$(addprefix $(1)/,$$(addsuffix .o,$$(call BASIC_A64_OBJS,$(4))))
	$$(HOSTCC) $$(HOST_CFLAGS) $$(BASIC_LINK_FLAGS) -o $$@ $$^
endef
$(eval $(call BASIC_A64_RULES,$(BASIC_TEST_GEN),,basic,$(BASIC_HOST_CNAMES) $(BASIC_STANDINS)))
$(eval $(call BASIC_A64_RULES,$(BASIC64_TEST_GEN),-DBASIC_FPOINT=1,basic64,$(BASIC64_HOST_CNAMES) $(FPE_SHARED_CNAMES) $(BASIC_STANDINS)))

basicchecks: basicgencheck $(addprefix $(BUILD)/tests/basic-,$(BASIC_CHECK_RUN))
	@for c in $(BASIC_CHECK_RUN); do $(BUILD)/tests/basic-$$c || exit 1; done
basic64checks: $(addprefix $(BUILD)/tests/basic64-,$(BASIC64_CHECK_RUN))
	@for c in $(BASIC64_CHECK_RUN); do $(BUILD)/tests/basic64-$$c || exit 1; done
.PHONY: basicchecks basic64checks
-include $(wildcard $(BASIC_TEST_GEN)/*.d) $(wildcard $(BASIC64_TEST_GEN)/*.d)

# The same suites built with BASIC_INT64=1, in both real formats, each in a
# directory of its own, with int64_checks added: `%` must behave as it does
# without 64-bit integers.
BASICI64_TEST_GEN   := $(BUILD)/tests/basicint64
BASIC64I64_TEST_GEN := $(BUILD)/tests/basic64int64
BASIC_INT64_RUN   := $(BASIC_CHECK_RUN) $(if $(BASIC_CHECK_ONLY),,$(BASIC_INT64_CHECK))
BASIC64_INT64_RUN := $(BASIC64_CHECK_RUN) $(if $(BASIC_CHECK_ONLY),,$(BASIC_INT64_CHECK))

# $(1) the objects' directory, $(2) the flags beyond BASIC_TEST_FLAGS, $(3)
# the binaries' prefix, $(4) what each checks file links.
define BASIC_HOST_RULES
$(1)/%.o: $(BASIC_DIR)/c/%.c
	@mkdir -p $$(dir $$@)
	$$(HOSTCC) $$(BASIC_TEST_FLAGS) $(2) -c $$< -o $$@
$(1)/%.o: $(BASIC_DIR)/c/arch/aarch32/%.c
	@mkdir -p $$(dir $$@)
	$$(HOSTCC) $$(BASIC_TEST_FLAGS) $(2) -c $$< -o $$@
$(1)/%.o: Sources/Programmer/BASIC/test/basic/%.c
	@mkdir -p $$(dir $$@)
	$$(HOSTCC) $$(BASIC_TEST_FLAGS) $(2) -c $$< -o $$@
$(1)/%.o: Sources/Programmer/FPEmulator/c/%.c
	@mkdir -p $$(dir $$@)
	$$(HOSTCC) $$(BASIC_TEST_FLAGS) $(2) -c $$< -o $$@
$(1)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $$(dir $$@)
	$$(HOSTCC) $$(BASIC_TEST_FLAGS) $(2) -c $$< -o $$@
$(BUILD)/tests/$(3)-%: $(1)/%.o $$(addprefix $(1)/,$$(addsuffix .o,$(4)) check.o) $$(BASIC_CHECK_FORCE)
	$$(HOSTCC) $$(HOST_CFLAGS) $$(BASIC_LINK_FLAGS) -o $$@ $$(filter %.o,$$^)
endef
$(eval $(call BASIC_HOST_RULES,$(BASICI64_TEST_GEN),-DBASIC_INT64=1,basicint64,$(BASIC_CHECK_LINK)))
$(eval $(call BASIC_HOST_RULES,$(BASIC64I64_TEST_GEN),-DBASIC_FPOINT=1 -DBASIC_INT64=1,basic64int64,$(BASIC64_CHECK_LINK)))
$(eval $(call BASIC_A64_RULES,$(BASICI64_TEST_GEN),-DBASIC_INT64=1,basicint64,$(BASIC_HOST_CNAMES) $(BASIC_STANDINS)))
$(eval $(call BASIC_A64_RULES,$(BASIC64I64_TEST_GEN),-DBASIC_FPOINT=1 -DBASIC_INT64=1,basic64int64,$(BASIC64_HOST_CNAMES) $(FPE_SHARED_CNAMES) $(BASIC_STANDINS)))

basicint64checks: $(addprefix $(BUILD)/tests/basicint64-,$(BASIC_INT64_RUN))
	@for c in $(BASIC_INT64_RUN); do $(BUILD)/tests/basicint64-$$c || exit 1; done
basic64int64checks: $(addprefix $(BUILD)/tests/basic64int64-,$(BASIC64_INT64_RUN))
	@for c in $(BASIC64_INT64_RUN); do $(BUILD)/tests/basic64int64-$$c || exit 1; done
.PHONY: basicint64checks basic64int64checks
hostchecks: basicint64checks basic64int64checks
-include $(wildcard $(BASICI64_TEST_GEN)/*.d) $(wildcard $(BASIC64I64_TEST_GEN)/*.d)

# ---- the modules
BASIC_ASM    := $(sort $(basename $(notdir $(wildcard $(BASIC_DIR)/s/aarch32/*.S))))
BASIC_OBJS   := $(addprefix $(BUILD)/modules/basic/,modhead.o veneers.o \
                  $(addsuffix .o,$(BASIC5_CNAMES) $(BASIC_ASM)))
BASIC64_OBJS := $(addprefix $(BUILD)/modules/basic64/,modhead.o veneers.o \
                  $(addsuffix .o,$(BASIC64_CNAMES) $(BASIC_ASM))) \
                $(addprefix $(BUILD)/modules/fpemulator/,$(addsuffix .o,$(FPE_SHARED_CNAMES)))
BASIC_CFLAGS := $(MOD_CFLAGS) -DBASIC_INT64=$(BASIC_INT64) -ISources/Lib/CLib/include -I$(BASIC_DIR)/c \
                -ISources/Programmer/FPEmulator/c -ISources/Lib/Desktop/text -I$(BUILD)/modules/basic
BASIC64_CFLAGS := $(MOD_CFLAGS) -DBASIC_FPOINT=1 -DBASIC_INT64=$(BASIC_INT64) -ISources/Lib/CLib/include -I$(BASIC_DIR)/c \
                -ISources/Programmer/FPEmulator/c -ISources/Lib/Desktop/text -I$(BUILD)/modules/basic64

define BASIC_MODULE_RULES
$(BUILD)/modules/$(1)/modhead.S: $(COMPONENT_DIR_$(1))/cmhg/modhead $(COMPONENT_DIR_$(1))/VersionNum tools/build/cmunge.py
	@mkdir -p $$(dir $$@)
	@python3 tools/build/cmunge.py $$< $$@ -I$(COMPONENT_DIR_$(1)) -Ihdr -ISources/Kernel/hdr/Global
$(BUILD)/modules/$(1)/%.o: $(BASIC_DIR)/c/%.c Makefile $(BUILD)/modules/$(1)/basic_veneers.h
	@mkdir -p $$(dir $$@)
	$$(CC) $$($(2)_CFLAGS) -c $$< -o $$@
$(BUILD)/modules/$(1)/%.o: $(BASIC_DIR)/c/arch/aarch32/%.c Makefile $(BUILD)/modules/$(1)/basic_veneers.h
	@mkdir -p $$(dir $$@)
	$$(CC) $$($(2)_CFLAGS) -c $$< -o $$@
$(BUILD)/modules/$(1)/veneers.S: $(COMPONENT_DIR_$(1))/s/boundaries $(COMPONENT_DIR_$(1))/cmhg/modhead tools/build/marshal.py
	@mkdir -p $$(dir $$@)
	@python3 tools/build/marshal.py $(COMPONENT_DIR_$(1)) --asm $$@ --header $(BUILD)/modules/$(1)/basic_veneers.h
$(BUILD)/modules/$(1)/basic_veneers.h: $(BUILD)/modules/$(1)/veneers.S
$(BUILD)/modules/$(1)/veneers.o: $(BUILD)/modules/$(1)/veneers.S
	@mkdir -p $$(dir $$@)
	$$(CC) $$($(2)_CFLAGS) -x assembler-with-cpp -c $$< -o $$@
$(BUILD)/modules/$(1)/%.o: $(BASIC_DIR)/s/aarch32/%.S
	@mkdir -p $$(dir $$@)
	$$(CC) $$($(2)_CFLAGS) -x assembler-with-cpp -c $$< -o $$@
$(BUILD)/modules/$(1)/modhead.o: $(BUILD)/modules/$(1)/modhead.S
	@mkdir -p $$(dir $$@)
	$$(CC) $$($(2)_CFLAGS) -x assembler-with-cpp -c $$< -o $$@
$(BUILD)/modules/$(3).elf: $$($(2)_OBJS) $(CLIB) mk/ld/module.ld tools/build/modulecheck.py
	@mkdir -p $$(dir $$@)
	$$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld -o $$@.tmp $$($(2)_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $$@.tmp
	@mv $$@.tmp $$@
$$($(2)_MOD): $(BUILD)/modules/$(3).elf
	$$(OBJCOPY) -O binary $$< $$@
	@echo "built $$@ ($$$$(stat -c%s $$@) bytes, $(3))"
-include $$($(2)_OBJS:.o=.d)
endef
$(eval $(call BASIC_MODULE_RULES,basic,BASIC,BASIC))
$(eval $(call BASIC_MODULE_RULES,basic64,BASIC64,BASIC64))

# The same two built as modules of their own names, BASIC32 and BASICFPA32,
# for the AArch64 ROM to run 32-bit programs in under ARM32
# (Sources/Programmer/BASIC/c/handover.c): they are the ARM build, in ARM32's
# own build directory, beside the AArch64 modules of the same interpreter.
BASIC32_OBJS := $(addprefix $(BUILD)/modules/basic32/,modhead.o veneers.o \
                  $(addsuffix .o,$(BASIC5_CNAMES) $(BASIC_ASM)))
BASICFPA32_OBJS := $(addprefix $(BUILD)/modules/basicfpa32/,modhead.o veneers.o \
                  $(addsuffix .o,$(BASIC64_CNAMES) $(BASIC_ASM))) \
                $(addprefix $(BUILD)/modules/fpemulator/,$(addsuffix .o,$(FPE_SHARED_CNAMES)))
BASIC32_CFLAGS := $(MOD_CFLAGS) -DBASIC_INT64=0 -DBASIC_NAME='"BASIC32"' -ISources/Lib/CLib/include \
		-DBASIC_ROOM_TRACE \
                -I$(BASIC_DIR)/c -ISources/Programmer/FPEmulator/c -ISources/Lib/Desktop/text \
                -I$(BUILD)/modules/basic32
BASICFPA32_CFLAGS := $(MOD_CFLAGS) -DBASIC_INT64=0 -DBASIC_FPOINT=1 -DBASIC_NAME='"BASICFPA32"' \
		-DBASIC_ROOM_TRACE \
                -ISources/Lib/CLib/include -I$(BASIC_DIR)/c -ISources/Programmer/FPEmulator/c \
                -ISources/Lib/Desktop/text -I$(BUILD)/modules/basicfpa32
BASIC32_MOD := $(BUILD)/modules/BASIC32.mod
BASICFPA32_MOD := $(BUILD)/modules/BASICFPA32.mod
$(eval $(call BASIC_MODULE_RULES,basic32,BASIC32,BASIC32))
$(eval $(call BASIC_MODULE_RULES,basicfpa32,BASICFPA32,BASICFPA32))
basic32modules: $(BASIC32_MOD) $(BASICFPA32_MOD)
.PHONY: basic32modules

# ---- BASICTrans
#
# The three SWIs at &42C80 an internationalised BASIC calls for its text.
# Two answer "not done" (the module header says why); the third reads the
# interpreter's own message file, so this module and Sources/Programmer/BASIC
# share their text.
BTR_DIR   := Sources/Programmer/BASICTrans
BTR_MOD   := $(BUILD)/modules/BASICTrans.mod
BTR_OBJS  := $(addprefix $(BUILD)/modules/basictrans/,modhead.o veneers.o \
               basictrans.o bttoken.o)
BTR_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(BTR_DIR)/c \
              -I$(BUILD)/modules/basictrans

$(BUILD)/modules/basictrans/modhead.S: $(BTR_DIR)/cmhg/modhead \
                                       $(BTR_DIR)/VersionNum \
                                       tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(BTR_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/basictrans/%.o: $(BTR_DIR)/c/%.c Makefile \
                                 $(BUILD)/modules/basictrans/bt_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(BTR_CFLAGS) -c $< -o $@

$(BUILD)/modules/basictrans/veneers.S: $(BTR_DIR)/s/boundaries \
                                       $(BTR_DIR)/cmhg/modhead \
                                       tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(BTR_DIR) --asm $@ \
	  --header $(BUILD)/modules/basictrans/bt_veneers.h

$(BUILD)/modules/basictrans/bt_veneers.h: $(BUILD)/modules/basictrans/veneers.S

$(BUILD)/modules/basictrans/veneers.o: $(BUILD)/modules/basictrans/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(BTR_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/basictrans/modhead.o: $(BUILD)/modules/basictrans/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(BTR_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/BASICTrans.elf: $(BTR_OBJS) $(CLIB) mk/ld/module.ld \
                                 tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(BTR_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(BTR_MOD): $(BUILD)/modules/BASICTrans.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, BASICTrans)"

-include $(BTR_OBJS:.o=.d)

basictransmodule: $(BTR_MOD)
.PHONY: basictransmodule

# Token/message consistency and the real module entries with OS calls
# replaced, including allocation, lazy opening, lookup and cleanup failures.
BTR_TEST_GEN := $(BUILD)/tests/basictrans

$(BTR_TEST_GEN)/%.o: Sources/Programmer/BASICTrans/test/basictrans/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(BTR_DIR)/c -Itests/lib -Iinclude -c $< -o $@
$(BTR_TEST_GEN)/bttoken.o: $(BTR_DIR)/c/bttoken.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(BTR_DIR)/c -Itests/lib -Iinclude -c $< -o $@
$(BTR_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Itests/lib -c $< -o $@

$(BUILD)/tests/basictrans-checks: $(BTR_TEST_GEN)/checks.o \
                                  $(BTR_TEST_GEN)/bttoken.o \
                                  $(BTR_TEST_GEN)/check.o
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

$(BTR_TEST_GEN)/module_checks.o: Sources/Programmer/BASICTrans/test/basictrans/module_checks.c $(BTR_DIR)/c/basictrans.c $(BTR_DIR)/c/bttables.h $(BUILD)/modules/basictrans/bt_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(BTR_DIR)/c -I$(BUILD)/modules/basictrans -Itests/lib -Iinclude -c $< -o $@

$(BUILD)/tests/basictrans-module-checks: $(BTR_TEST_GEN)/module_checks.o $(BTR_TEST_GEN)/bttoken.o $(BTR_TEST_GEN)/check.o
	$(HOSTCC) $(HOST_CFLAGS) -o $@ $^

basictranschecks: $(BUILD)/tests/basictrans-checks $(BUILD)/tests/basictrans-module-checks
	@python3 tools/build/basictransgen.py
	@$< Sources/Programmer/BASIC/Resources/UK/Messages
	@$(BUILD)/tests/basictrans-module-checks
.PHONY: basictranschecks
-include $(wildcard $(BTR_TEST_GEN)/*.d)

basicmodule: $(BASIC_MOD)
basic64module: $(BASIC64_MOD)
.PHONY: basicmodule basic64module

# BASIC and BASIC64, for ARM32 and for AArch64 with BASIC_INT64=0, built from
# revision BASE and from this tree and compared with cmp
# (tools/check/basicident.sh). The AArch64 default, BASIC_INT64=1, differs by
# design and is not compared. It builds in a worktree and in directories
# beside BUILD, so it is not a host check. Equal only against a BASE that has
# the interpreter's other changes (see docs/basic-int64.md).
BASE ?= HEAD
basicidentcheck:
	@tools/check/basicident.sh $(BASE) $(abspath $(BUILD))-basicident
.PHONY: basicidentcheck

# ---- on the machine
#
# `Sources/Programmer/BASIC/test/basic/machine/Drive` spools its output to a file, compared between
# two boots differing only in which interpreter is in the ROM.
#
# Ours is larger than the reference's slot, so it goes past the chain; the
# control leaves it out and runs the reference's. Check the program with
# `make basicdrive` first.
BASIC_HOSTFS := $(LEGACY)/rpcemu/hostfs/BAStest
BASIC_WAIT   ?= 90
# The interpreter to enter. BASIC64 runs the same program and is compared
# with the reference BASIC64.
BASIC_ENTER  ?= BASIC
# *Quit leaves the interpreter; the echo after it appears only if the exit
# worked. The interpreter is entered as a star command so the keys work at
# either a command line or a BASIC prompt, where a bare "BASIC64" would be
# a variable.
BASIC_KEYS   ?= <F12>*$(BASIC_ENTER)\n*Exec HostFS::HostFS.$$$$.BAStest.Drive\n*Quit\n*Echo left the interpreter\n\n
BASIC_REPLACED := $(REPLACED_MODULES) BASIC=$(BASIC_MOD) BASIC64=$(BASIC64_MOD)
# The spool is complete once the program has printed its last heading, or
# stopped (Sources/Programmer/BASIC/test/basic/machine/check.py). Its lines end LF CR.
BASIC_DONE = cat $(BASIC_HOSTFS)/Report* | tr -d "\r" | grep -aq -e "^-- done" -e "^CRASH "
# The control's spool, kept outside the emulator directory, which is emptied
# before each run.
BASIC_KEPT    = $(BUILD)/tests/basic/Reference-$(BASIC_ENTER).txt

# Copy the program in and remove any previous report, so a run that dies
# before writing is not read as agreeing with the previous one.
basicfiles:
	@rm -rf $(BASIC_HOSTFS)
	@mkdir -p $(BASIC_HOSTFS)
	@cp Sources/Programmer/BASIC/test/basic/machine/Drive "$(BASIC_HOSTFS)/Drive,fff"

# Run the program on the host interpreter to check it before a boot.
basicdrive: $(BUILD)/tests/basic-driverun
	@$<

# The control: the reference interpreter with every other module of ours in
# place.
basicreftest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM)
	@$(MAKE) --no-print-directory NODEPS=1 basicfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=basicreference FILER_WAIT=$(BASIC_WAIT) \
	  FILER_KEYS='$(BASIC_KEYS)' FILER_DONE='$(BASIC_DONE)'
	@python3 Sources/Programmer/BASIC/test/basic/machine/check.py $(BASIC_HOSTFS) --keep $(BASIC_KEPT)

basicmachinetest:
	@rm -f $(REPLACED_ROM)
	@$(MAKE) --no-print-directory ROM_SIZE=$(BIG_ROM_SIZE) $(REPLACED_ROM) \
	  REPLACED_MODULES='$(BASIC_REPLACED)' \
	  REPLACED_AT_END='--replace-at-end BASIC --replace-at-end BASIC64'
	@$(MAKE) --no-print-directory NODEPS=1 basicfiles
	@$(MAKE) --no-print-directory NODEPS=1 filerkeys FILER_ROM=$(REPLACED_ROM) \
	  FILER_SHOT=basicmachine FILER_WAIT=$(BASIC_WAIT) \
	  FILER_KEYS='$(BASIC_KEYS)' FILER_DONE='$(BASIC_DONE)'
	@python3 Sources/Programmer/BASIC/test/basic/machine/check.py $(BASIC_HOSTFS) --against $(BASIC_KEPT)

basic64reftest:   BASIC_ENTER = BASIC64
basic64reftest:   basicreftest
basic64machinetest: BASIC_ENTER = BASIC64
basic64machinetest: basicmachinetest

.PHONY: basicfiles basicdrive basicreftest basicmachinetest \
        basic64reftest basic64machinetest

# An isolated comparison on the unmodified reference ROM. All other modules
# are original; the runner loads only BASIC/BASIC64 replacements from HostFS.
BASIC_DIFFERENTIAL_WAIT ?= 480
basicdifferential: basicmodule basic64module
	@python3 Sources/Programmer/BASIC/test/basic/machine/differential.py --build $(BUILD) --wait $(BASIC_DIFFERENTIAL_WAIT)
.PHONY: basicdifferential

$(BUILD)/tests/adfsdcb/mod/post.o: Sources/FileSys/ADFS/test/adfsdcb/post.S
	@mkdir -p $(dir $@)
	$(AS) $(ASFLAGS) $(ARCH_iomd) $(TEST_CFLAGS) -c $< -o $@

# ADFS_FlpProcessDCB and ADFS_ATAPIOp, on a machine with a floppy in drive 0
# and a CD in the drive beside the hard discs. Neither goes through FileCore,
# so no other test reaches them.
#
# Both images are written here: the floppy is laid out by layimages, and the
# ISO is 2048-byte sectors filled with a known pattern. The CD is attached
# with RPCEMU_CDROM, which enables the drive for the run without changing the
# machine's configuration.
ADFSDCB_DIR := $(BUILD)/tests/adfsdcb
ADFSDCB_ISO := $(ADFSDCB_DIR)/test.iso

$(ADFSDCB_ISO):
	@mkdir -p $(dir $@)
	@python3 -c "import sys; \
	  a=bytes((i*7+3)&255 for i in range(2048)); \
	  b=bytes((i*11+5)&255 for i in range(2048)); \
	  c=bytes(2048)*14; \
	  open(sys.argv[1],'wb').write(a+b+c)" $@
	@echo "wrote $@ (16 sectors of 2048, the first two patterned)"

adfsdcbtest: $(BUILD)/tests/adfsdcb-ref.rom $(BUILD)/tests/filecore-layimages \
             $(ADFSDCB_ISO)
	@mkdir -p $(ADFSDCB_DIR)/images
	@$(BUILD)/tests/filecore-layimages $(ADFSDCB_DIR)/images floppies \
	  > $(ADFSDCB_DIR)/laid.txt
	RPCEMU_DISC0=$(abspath $(ADFSDCB_DIR))/images/F.adf \
	RPCEMU_CDROM=$(abspath $(ADFSDCB_ISO)) \
	tools/build/rom-test.sh $(abspath $(BUILD))/tests/adfsdcb-ref.rom \
	  $(abspath $(ADFSDCB_DIR))/report.txt 180
	@cat $(ADFSDCB_DIR)/report.txt
	@if grep -aq '^ *FAIL ' $(ADFSDCB_DIR)/report.txt; then exit 1; fi
.PHONY: adfsdcbtest

# Machine targets for ADFS and its filer (and others) that `test` does not
# collect through TESTS and CHAIN_TESTS.
#
#   adfsdcbtest          the two interfaces underneath the filing system
#   adfsfloppytest       the 765 driver over six floppy formats
#   adfsfilerdiscs       the fixed discs on the bar, and one of them opened
#   adfsfilerfloppy      a floppy on the bar beside them
#   adfsfilermenu        the menu, and which entries the drive leaves live
#   adfsfilerportedtest  the reference ADFSFiler and this one, held to each
#                        other on the same machine
#   msgtransmodtest      MessageTrans against the reference's own
#   territorymodtest     the Territory Manager against the reference's own
#   ukmodtest            the UK territory against the reference's own, with
#                        our manager above it in both ROMs
test: adfsdcbtest adfsfloppytest adfsfilerdiscs adfsfilerfloppy \
      adfsfilermenu adfsfilerportedtest msgtransmodtest territorymodtest \
      ukmodtest cdfsdrivermodtest cdatapimodtest

# Floppy read/write and mount checks on a fresh disposable image.
adfsfloppytest: $(BUILD)/tests/adfsfloppy-ref.rom $(BUILD)/tests/filecore-layimages
	@mkdir -p $(BUILD)/tests/adfsfloppy/images
	@$(BUILD)/tests/filecore-layimages $(BUILD)/tests/adfsfloppy/images floppies > $(BUILD)/tests/adfsfloppy/laid.txt
	@for image in L.adl D.adf E.adf F.adf Ep.adf Fp.adf; do \
	  RPCEMU_DISC0=$(abspath $(BUILD))/tests/adfsfloppy/images/$$image \
	  tools/build/rom-test.sh $(abspath $(BUILD))/tests/adfsfloppy-ref.rom \
	    $(abspath $(BUILD))/tests/adfsfloppy/$$image.txt 120 || exit 1; \
	  cat $(BUILD)/tests/adfsfloppy/$$image.txt; \
	  if grep -aq '^ *FAIL ' $(BUILD)/tests/adfsfloppy/$$image.txt; then exit 1; fi; \
	done
.PHONY: adfsfloppytest

include Sources/Internat/International/module.mk

# The ROM's applications, and the two build tools the BASIC ones need.
include mk/apps.mk
include Sources/Apps/Draw/module.mk
include Sources/Internat/IntKey/module.mk

all modules: $(BUILD)/modules/International.mod $(BUILD)/modules/InternationalKeyboard.mod

# SoundDMA's portable queue is host-checked independently of any hardware;
# the IOMD backend is assembled separately so an accidental ARMv4-incompatible
# instruction (notably BX) fails before it reaches a ROM link.
# One hardware backend is linked: virtio-sound on the virt machine, IOMD's
# SD0 DMA everywhere else.
SOUNDDMA_DIR := Sources/HWSupport/Sound/Sound0
# SoundDMA's errors and command help, at the path its header names.
MOD_DESKTOP_RES += Resources.SoundDMA.Messages=$(SOUNDDMA_DIR)/Resources/UK/Messages
SOUNDDMA_TEST := $(BUILD)/tests/sounddma-checks
SOUNDDMA_IOMD_OBJ := $(BUILD)/modules/sounddma/iomd.o
SOUNDDMA_TEST_FLAGS := $(HOST_CFLAGS) -I$(SOUNDDMA_DIR)/c -Itests/lib
ifeq ($(MACHINE_NAME),Virt)
SOUNDDMA_HW_OBJS := $(BUILD)/modules/sounddma/virtiohw.o \
                    $(BUILD)/modules/sounddma/vsndcalc.o
SOUNDDMA_HW_LIBS := $(VIRTIO_LIB)
else
SOUNDDMA_HW_OBJS := $(BUILD)/modules/sounddma/iomdhw.o $(SOUNDDMA_IOMD_OBJ)
SOUNDDMA_HW_LIBS :=
endif
SOUNDDMA_OBJS := $(BUILD)/modules/sounddma/modhead.o \
                 $(BUILD)/modules/sounddma/sounddma.o \
                 $(BUILD)/modules/sounddma/sounddmamodule.o \
                 $(BUILD)/modules/sounddma/veneers.o $(SOUNDDMA_HW_OBJS)
SOUNDDMA_MOD := $(BUILD)/modules/SoundDMA.mod
SOUNDDMA_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(SOUNDDMA_DIR)/c \
                   -I$(BUILD)/modules/sounddma $(VIRTIO_CPPFLAGS) -MMD -MP

$(BUILD)/modules/sounddma/modhead.S: $(SOUNDDMA_DIR)/cmhg/modhead \
                                       $(SOUNDDMA_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(SOUNDDMA_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/sounddma/veneers.S: $(SOUNDDMA_DIR)/s/boundaries \
                                      $(SOUNDDMA_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(SOUNDDMA_DIR) --asm $@ \
	  --header $(BUILD)/modules/sounddma/sounddma_veneers.h

$(BUILD)/modules/sounddma/sounddma_veneers.h: $(BUILD)/modules/sounddma/veneers.S

$(BUILD)/modules/sounddma/%.o: $(SOUNDDMA_DIR)/c/%.c Makefile \
                                $(BUILD)/modules/sounddma/sounddma_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDDMA_CFLAGS) -c $< -o $@

$(BUILD)/modules/sounddma/iomdhw.o: $(SOUNDDMA_DIR)/c/arch/aarch32/iomdhw.c Makefile \
                                    $(BUILD)/modules/sounddma/sounddma_veneers.h \
                                    Sources/Kernel/hdr/Global/PublicWS.h
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDDMA_CFLAGS) -c $< -o $@
-include $(SOUNDDMA_OBJS:.o=.d)

$(BUILD)/modules/sounddma/veneers.o: $(BUILD)/modules/sounddma/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDDMA_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/sounddma/modhead.o: $(BUILD)/modules/sounddma/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDDMA_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(SOUNDDMA_IOMD_OBJ): $(SOUNDDMA_DIR)/s/aarch32/iomd.S \
                       $(SOUNDDMA_DIR)/c/sounddma_iomd.h include/hal/io_iomd.h
	@mkdir -p $(dir $@)
	$(CC) $(MOD_CFLAGS) -x assembler-with-cpp -c $< -o $@

sounddmaiomdgas: $(SOUNDDMA_IOMD_OBJ)
.PHONY: sounddmaiomdgas

$(BUILD)/modules/SoundDMA.elf: $(SOUNDDMA_OBJS) $(SOUNDDMA_HW_LIBS) $(CLIB) \
                                mk/ld/module.ld tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SOUNDDMA_OBJS) $(SOUNDDMA_HW_LIBS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SOUNDDMA_MOD): $(BUILD)/modules/SoundDMA.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, SoundDMA)"

sounddmamodule: $(SOUNDDMA_MOD)
.PHONY: sounddmamodule

$(SOUNDDMA_TEST): Sources/HWSupport/Sound/Sound0/test/sounddma/checks.c $(SOUNDDMA_DIR)/c/sounddma.c \
                   $(SOUNDDMA_DIR)/c/sounddma.h $(SOUNDDMA_DIR)/c/vsndcalc.c \
                   $(SOUNDDMA_DIR)/c/vsndcalc.h tests/lib/check.c tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SOUNDDMA_TEST_FLAGS) Sources/HWSupport/Sound/Sound0/test/sounddma/checks.c \
	  $(SOUNDDMA_DIR)/c/sounddma.c $(SOUNDDMA_DIR)/c/vsndcalc.c \
	  tests/lib/check.c -o $@

sounddmachecks: $(SOUNDDMA_TEST) sounddmaiomdgas
	@$(SOUNDDMA_TEST)
	@python3 Sources/HWSupport/Sound/Sound0/test/sounddma/check_vidc.py $(SOUNDDMA_TEST)
.PHONY: sounddmachecks

hostchecks: sounddmachecks

SOUNDSCHEDULER_DIR := Sources/HWSupport/Sound/Sound2
# SoundScheduler's errors and command help, at the path its header names.
MOD_DESKTOP_RES += Resources.SoundSched.Messages=$(SOUNDSCHEDULER_DIR)/Resources/UK/Messages
SOUNDSCHEDULER_TEST := $(BUILD)/tests/soundscheduler-checks
SOUNDSCHEDULER_TEST_FLAGS := $(HOST_CFLAGS) -I$(SOUNDSCHEDULER_DIR)/c -Itests/lib
SOUNDSCHEDULER_MOD := $(BUILD)/modules/SoundScheduler.mod
SOUNDSCHEDULER_OBJS := $(BUILD)/modules/soundscheduler/modhead.o \
                       $(BUILD)/modules/soundscheduler/soundscheduler.o \
                       $(BUILD)/modules/soundscheduler/soundschedulermodule.o \
                       $(BUILD)/modules/soundscheduler/veneers.o
SOUNDSCHEDULER_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(SOUNDSCHEDULER_DIR)/c -ISources/HWSupport/Sound/Sound0/c \
                         -I$(BUILD)/modules/soundscheduler

$(BUILD)/modules/soundscheduler/modhead.S: $(SOUNDSCHEDULER_DIR)/cmhg/modhead \
                                             $(SOUNDSCHEDULER_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(SOUNDSCHEDULER_DIR) -Ihdr -ISources/Kernel/hdr/Global

$(BUILD)/modules/soundscheduler/veneers.S: $(SOUNDSCHEDULER_DIR)/s/boundaries \
                                            $(SOUNDSCHEDULER_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(SOUNDSCHEDULER_DIR) --asm $@ \
	  --header $(BUILD)/modules/soundscheduler/soundscheduler_veneers.h

$(BUILD)/modules/soundscheduler/soundscheduler_veneers.h: $(BUILD)/modules/soundscheduler/veneers.S

$(BUILD)/modules/soundscheduler/%.o: $(SOUNDSCHEDULER_DIR)/c/%.c Makefile \
                                      $(BUILD)/modules/soundscheduler/soundscheduler_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDSCHEDULER_CFLAGS) -c $< -o $@

$(BUILD)/modules/soundscheduler/veneers.o: $(BUILD)/modules/soundscheduler/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDSCHEDULER_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/soundscheduler/modhead.o: $(BUILD)/modules/soundscheduler/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDSCHEDULER_CFLAGS) -x assembler-with-cpp -c $< -o $@

$(BUILD)/modules/SoundScheduler.elf: $(SOUNDSCHEDULER_OBJS) $(CLIB) mk/ld/module.ld \
                                      tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld \
	  -o $@.tmp $(SOUNDSCHEDULER_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@

$(SOUNDSCHEDULER_MOD): $(BUILD)/modules/SoundScheduler.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, SoundScheduler)"

soundschedulermodule: $(SOUNDSCHEDULER_MOD)
.PHONY: soundschedulermodule

$(SOUNDSCHEDULER_TEST): Sources/HWSupport/Sound/Sound2/test/soundscheduler/checks.c \
                         $(SOUNDSCHEDULER_DIR)/c/soundscheduler.c \
                         $(SOUNDSCHEDULER_DIR)/c/soundscheduler.h tests/lib/check.c tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SOUNDSCHEDULER_TEST_FLAGS) Sources/HWSupport/Sound/Sound2/test/soundscheduler/checks.c \
	  $(SOUNDSCHEDULER_DIR)/c/soundscheduler.c tests/lib/check.c -o $@

soundschedulerchecks: $(SOUNDSCHEDULER_TEST)
	@$(SOUNDSCHEDULER_TEST)
.PHONY: soundschedulerchecks

hostchecks: soundschedulerchecks

SOUNDCHANNELS_DIR := Sources/HWSupport/Sound/Sound1
SOUNDCHANNELS_TEST := $(BUILD)/tests/soundchannels-checks
SOUNDCHANNELS_MOD := $(BUILD)/modules/SoundChannels.mod
SOUNDCHANNELS_OBJS := $(BUILD)/modules/soundchannels/modhead.o $(BUILD)/modules/soundchannels/soundchannels.o \
                      $(BUILD)/modules/soundchannels/soundchannelsmodule.o $(BUILD)/modules/soundchannels/veneers.o
SOUNDCHANNELS_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(SOUNDCHANNELS_DIR)/c -ISources/HWSupport/Sound/Sound0/c -I$(BUILD)/modules/soundchannels

$(BUILD)/modules/soundchannels/modhead.S: $(SOUNDCHANNELS_DIR)/cmhg/modhead $(SOUNDCHANNELS_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(SOUNDCHANNELS_DIR) -Ihdr -ISources/Kernel/hdr/Global
$(BUILD)/modules/soundchannels/veneers.S: $(SOUNDCHANNELS_DIR)/s/boundaries $(SOUNDCHANNELS_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(SOUNDCHANNELS_DIR) --asm $@ --header $(BUILD)/modules/soundchannels/soundchannels_veneers.h
$(BUILD)/modules/soundchannels/soundchannels_veneers.h: $(BUILD)/modules/soundchannels/veneers.S
$(BUILD)/modules/soundchannels/%.o: $(SOUNDCHANNELS_DIR)/c/%.c Makefile $(BUILD)/modules/soundchannels/soundchannels_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDCHANNELS_CFLAGS) -c $< -o $@
$(BUILD)/modules/soundchannels/veneers.o: $(BUILD)/modules/soundchannels/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDCHANNELS_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/soundchannels/modhead.o: $(BUILD)/modules/soundchannels/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(SOUNDCHANNELS_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/SoundChannels.elf: $(SOUNDCHANNELS_OBJS) $(CLIB) mk/ld/module.ld tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld -o $@.tmp $(SOUNDCHANNELS_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@
$(SOUNDCHANNELS_MOD): $(BUILD)/modules/SoundChannels.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, SoundChannels)"
soundchannelsmodule: $(SOUNDCHANNELS_MOD)
.PHONY: soundchannelsmodule

$(SOUNDCHANNELS_TEST): Sources/HWSupport/Sound/Sound1/test/soundchannels/checks.c $(SOUNDCHANNELS_DIR)/c/soundchannels.c \
                       $(SOUNDCHANNELS_DIR)/c/soundchannels.h tests/lib/check.c tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Iinclude -I$(SOUNDCHANNELS_DIR)/c -Itests/lib $< \
	  $(SOUNDCHANNELS_DIR)/c/soundchannels.c tests/lib/check.c -o $@

soundchannelschecks: $(SOUNDCHANNELS_TEST)
	@$(SOUNDCHANNELS_TEST)
.PHONY: soundchannelschecks

hostchecks: soundchannelschecks

WAVESYNTH_DIR := Sources/HWSupport/Sound/WaveSynth
WAVESYNTH_TEST := $(BUILD)/tests/wavesynth-checks
WAVESYNTH_MOD := $(BUILD)/modules/WaveSynth.mod
WAVESYNTH_OBJS := $(BUILD)/modules/wavesynth/modhead.o $(BUILD)/modules/wavesynth/wavesynth.o $(BUILD)/modules/wavesynth/wavesynthmodule.o $(BUILD)/modules/wavesynth/veneers.o
WAVESYNTH_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -I$(WAVESYNTH_DIR)/c -I$(SOUNDCHANNELS_DIR)/c -I$(BUILD)/modules/wavesynth
$(BUILD)/modules/wavesynth/modhead.S: $(WAVESYNTH_DIR)/cmhg/modhead $(WAVESYNTH_DIR)/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(WAVESYNTH_DIR) -Ihdr -ISources/Kernel/hdr/Global
$(BUILD)/modules/wavesynth/veneers.S: $(WAVESYNTH_DIR)/s/boundaries $(WAVESYNTH_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(WAVESYNTH_DIR) --asm $@ --header $(BUILD)/modules/wavesynth/wavesynth_veneers.h
$(BUILD)/modules/wavesynth/wavesynth_veneers.h: $(BUILD)/modules/wavesynth/veneers.S
$(BUILD)/modules/wavesynth/%.o: $(WAVESYNTH_DIR)/c/%.c Makefile $(BUILD)/modules/wavesynth/wavesynth_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(WAVESYNTH_CFLAGS) -c $< -o $@
$(BUILD)/modules/wavesynth/veneers.o: $(BUILD)/modules/wavesynth/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(WAVESYNTH_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/wavesynth/modhead.o: $(BUILD)/modules/wavesynth/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(WAVESYNTH_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/WaveSynth.elf: $(WAVESYNTH_OBJS) $(CLIB) mk/ld/module.ld tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld -o $@.tmp $(WAVESYNTH_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@
$(WAVESYNTH_MOD): $(BUILD)/modules/WaveSynth.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, WaveSynth)"
wavesynthmodule: $(WAVESYNTH_MOD)
.PHONY: wavesynthmodule
$(WAVESYNTH_TEST): Sources/HWSupport/Sound/WaveSynth/test/wavesynth/checks.c $(WAVESYNTH_DIR)/c/wavesynth.c \
                   $(WAVESYNTH_DIR)/c/wavesynth.h $(SOUNDCHANNELS_DIR)/c/soundchannels.c \
                   $(SOUNDCHANNELS_DIR)/c/soundchannels.h tests/lib/check.c tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Iinclude -I$(WAVESYNTH_DIR)/c -I$(SOUNDCHANNELS_DIR)/c -Itests/lib $< \
	  $(WAVESYNTH_DIR)/c/wavesynth.c $(SOUNDCHANNELS_DIR)/c/soundchannels.c tests/lib/check.c -o $@

wavesynthchecks: $(WAVESYNTH_TEST)
	@$(WAVESYNTH_TEST)
.PHONY: wavesynthchecks

hostchecks: wavesynthchecks

STRINGLIB_DIR := Sources/HWSupport/Sound/StringLib
STRINGLIB_TEST := $(BUILD)/tests/stringlib-checks
# The engine StringLib and Percussion share, with SoundChannels' tables.
$(STRINGLIB_TEST): Sources/HWSupport/Sound/StringLib/test/stringlib/checks.c $(STRINGLIB_DIR)/c/pluck.c $(STRINGLIB_DIR)/c/pluck.h \
                  $(SOUNDCHANNELS_DIR)/c/soundchannels.c $(SOUNDCHANNELS_DIR)/c/soundchannels.h tests/lib/check.c tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Iinclude -I$(STRINGLIB_DIR)/c -I$(SOUNDCHANNELS_DIR)/c -Itests/lib $< \
	  $(STRINGLIB_DIR)/c/pluck.c $(SOUNDCHANNELS_DIR)/c/soundchannels.c tests/lib/check.c -o $@

stringlibchecks: $(STRINGLIB_TEST)
	@$(STRINGLIB_TEST)
.PHONY: stringlibchecks

hostchecks: stringlibchecks

PERCUSSION_DIR := Sources/HWSupport/Sound/Percussion
PERCUSSION_TEST := $(BUILD)/tests/percussion-checks
$(PERCUSSION_TEST): Sources/HWSupport/Sound/Percussion/test/percussion/checks.c $(STRINGLIB_DIR)/c/pluck.c $(STRINGLIB_DIR)/c/pluck.h \
                   $(SOUNDCHANNELS_DIR)/c/soundchannels.c $(SOUNDCHANNELS_DIR)/c/soundchannels.h tests/lib/check.c tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Iinclude -I$(STRINGLIB_DIR)/c -I$(SOUNDCHANNELS_DIR)/c -Itests/lib $< \
	  $(STRINGLIB_DIR)/c/pluck.c $(SOUNDCHANNELS_DIR)/c/soundchannels.c tests/lib/check.c -o $@

percussionchecks: $(PERCUSSION_TEST)
	@$(PERCUSSION_TEST)
.PHONY: percussionchecks

hostchecks: percussionchecks

SHAREDSOUND_DIR := Sources/HWSupport/Sound/SharedSound
# SharedSound's errors and its handler and driver names.
MOD_DESKTOP_RES += Resources.SSound.Messages=$(SHAREDSOUND_DIR)/Resources/UK/Messages
SHAREDSOUND_TEST := $(BUILD)/tests/sharedsound-checks
$(SHAREDSOUND_TEST): Sources/HWSupport/Sound/SharedSound/test/sharedsound/checks.c $(SHAREDSOUND_DIR)/c/sharedsound.c $(SHAREDSOUND_DIR)/c/sharedsound.h \
                    tests/lib/check.c tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(SHAREDSOUND_DIR)/c -Itests/lib $< \
	  $(SHAREDSOUND_DIR)/c/sharedsound.c tests/lib/check.c -o $@

SHAREDSOUND_MODULE_TEST := $(BUILD)/tests/sharedsound-module-checks
$(SHAREDSOUND_MODULE_TEST): Sources/HWSupport/Sound/SharedSound/test/sharedsound/module_checks.c $(SHAREDSOUND_DIR)/c/sharedsoundmodule.c \
                          $(SHAREDSOUND_DIR)/c/sharedsound.c $(SHAREDSOUND_DIR)/c/sharedsound.h \
                          $(SOUNDDMA_DIR)/c/sounddma.c $(SOUNDDMA_DIR)/c/sounddma.h \
                          tests/lib/check.c \
                          $(BUILD)/modules/sharedsound/sharedsound_veneers.h
	@mkdir -p $(dir $@)
	$(HOSTCC) -ISources/HWSupport/Sound/SharedSound/test/sharedsound/host $(HOST_CFLAGS) -I$(SHAREDSOUND_DIR)/c \
	  -I$(SOUNDDMA_DIR)/c -I$(BUILD)/modules/sharedsound -Itests/lib $< \
	  $(SHAREDSOUND_DIR)/c/sharedsound.c \
	  $(SOUNDDMA_DIR)/c/sounddma.c tests/lib/check.c -o $@

sharedsoundchecks: $(SHAREDSOUND_TEST) $(SHAREDSOUND_MODULE_TEST)
	@$(SHAREDSOUND_TEST)
	@$(SHAREDSOUND_MODULE_TEST)
.PHONY: sharedsoundchecks

hostchecks: sharedsoundchecks

SHAREDSOUND_MOD := $(BUILD)/modules/SharedSound.mod
SHAREDSOUND_OBJS := $(BUILD)/modules/sharedsound/modhead.o $(BUILD)/modules/sharedsound/sharedsound.o $(BUILD)/modules/sharedsound/sharedsoundmodule.o $(BUILD)/modules/sharedsound/veneers.o
SHAREDSOUND_CFLAGS := -ISources/HWSupport/Sound/Sound0/c $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/Sound/SharedSound/c -I$(BUILD)/modules/sharedsound
$(BUILD)/modules/sharedsound/modhead.S: Sources/HWSupport/Sound/SharedSound/cmhg/modhead Sources/HWSupport/Sound/SharedSound/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/Sound/SharedSound -Ihdr -ISources/Kernel/hdr/Global
$(BUILD)/modules/sharedsound/veneers.S: Sources/HWSupport/Sound/SharedSound/s/boundaries Sources/HWSupport/Sound/SharedSound/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/Sound/SharedSound --asm $@ --header $(BUILD)/modules/sharedsound/sharedsound_veneers.h
$(BUILD)/modules/sharedsound/sharedsound_veneers.h: $(BUILD)/modules/sharedsound/veneers.S
$(BUILD)/modules/sharedsound/%.o: Sources/HWSupport/Sound/SharedSound/c/%.c Makefile $(BUILD)/modules/sharedsound/sharedsound_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(SHAREDSOUND_CFLAGS) -c $< -o $@
$(BUILD)/modules/sharedsound/veneers.o: $(BUILD)/modules/sharedsound/veneers.S
	$(CC) $(SHAREDSOUND_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/sharedsound/modhead.o: $(BUILD)/modules/sharedsound/modhead.S
	$(CC) $(SHAREDSOUND_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/SharedSound.elf: $(SHAREDSOUND_OBJS) $(CLIB) mk/ld/module.ld tools/build/modulecheck.py
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld -o $@.tmp $(SHAREDSOUND_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@
$(SHAREDSOUND_MOD): $(BUILD)/modules/SharedSound.elf
	$(OBJCOPY) -O binary $< $@
sharedsoundmodule: $(SHAREDSOUND_MOD)
.PHONY: sharedsoundmodule

AUDIO_TEST := $(BUILD)/tests/audio-checks
$(AUDIO_TEST): tests/audio/checks.c $(SOUNDDMA_DIR)/c/sounddma.c $(SOUNDDMA_DIR)/c/sounddma.h \
               $(SOUNDCHANNELS_DIR)/c/soundchannels.c $(SOUNDCHANNELS_DIR)/c/soundchannels.h \
               $(SOUNDSCHEDULER_DIR)/c/soundscheduler.c $(SOUNDSCHEDULER_DIR)/c/soundscheduler.h \
               $(WAVESYNTH_DIR)/c/wavesynth.c $(WAVESYNTH_DIR)/c/wavesynth.h tests/lib/check.c tests/lib/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -Iinclude -I$(SOUNDDMA_DIR)/c -I$(SOUNDCHANNELS_DIR)/c -I$(SOUNDSCHEDULER_DIR)/c -I$(WAVESYNTH_DIR)/c -Itests/lib $< \
	  $(SOUNDDMA_DIR)/c/sounddma.c $(SOUNDCHANNELS_DIR)/c/soundchannels.c \
	  $(SOUNDSCHEDULER_DIR)/c/soundscheduler.c $(WAVESYNTH_DIR)/c/wavesynth.c tests/lib/check.c -o $@

audiochecks: $(AUDIO_TEST)
	@$(AUDIO_TEST)
.PHONY: audiochecks

hostchecks: audiochecks

# The two no-SWI voice modules share the same module shape: lifecycle veneers,
# a portable voice core, and registration through Sound_InstallVoice.
STRINGLIB_MOD := $(BUILD)/modules/StringLib.mod
STRINGLIB_OBJS := $(BUILD)/modules/stringlib/modhead.o $(BUILD)/modules/stringlib/pluck.o $(BUILD)/modules/stringlib/stringlibmodule.o $(BUILD)/modules/stringlib/veneers.o
STRINGLIB_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/Sound/StringLib/c -ISources/HWSupport/Sound/Sound1/c -I$(BUILD)/modules/stringlib
$(BUILD)/modules/stringlib/modhead.S: Sources/HWSupport/Sound/StringLib/cmhg/modhead Sources/HWSupport/Sound/StringLib/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/Sound/StringLib -Ihdr -ISources/Kernel/hdr/Global
$(BUILD)/modules/stringlib/veneers.S: Sources/HWSupport/Sound/StringLib/s/boundaries Sources/HWSupport/Sound/StringLib/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/Sound/StringLib --asm $@ --header $(BUILD)/modules/stringlib/stringlib_veneers.h
$(BUILD)/modules/stringlib/stringlib_veneers.h: $(BUILD)/modules/stringlib/veneers.S
$(BUILD)/modules/stringlib/%.o: Sources/HWSupport/Sound/StringLib/c/%.c Makefile $(BUILD)/modules/stringlib/stringlib_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(STRINGLIB_CFLAGS) -c $< -o $@
$(BUILD)/modules/stringlib/veneers.o: $(BUILD)/modules/stringlib/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(STRINGLIB_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/stringlib/modhead.o: $(BUILD)/modules/stringlib/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(STRINGLIB_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/StringLib.elf: $(STRINGLIB_OBJS) $(CLIB) mk/ld/module.ld tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld -o $@.tmp $(STRINGLIB_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@
$(STRINGLIB_MOD): $(BUILD)/modules/StringLib.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, StringLib)"
stringlibmodule: $(STRINGLIB_MOD)
.PHONY: stringlibmodule

PERCUSSION_MOD := $(BUILD)/modules/Percussion.mod
# Percussion plays on StringLib's engine, built into each module.
PERCUSSION_OBJS := $(BUILD)/modules/percussion/modhead.o $(BUILD)/modules/percussion/pluck.o $(BUILD)/modules/percussion/percussionmodule.o $(BUILD)/modules/percussion/veneers.o
PERCUSSION_CFLAGS := $(MOD_CFLAGS) -ISources/Lib/CLib/include -ISources/HWSupport/Sound/Percussion/c -ISources/HWSupport/Sound/StringLib/c -ISources/HWSupport/Sound/Sound1/c -I$(BUILD)/modules/percussion
$(BUILD)/modules/percussion/pluck.o: Sources/HWSupport/Sound/StringLib/c/pluck.c Makefile $(BUILD)/modules/percussion/percussion_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(PERCUSSION_CFLAGS) -c $< -o $@
$(BUILD)/modules/percussion/modhead.S: Sources/HWSupport/Sound/Percussion/cmhg/modhead Sources/HWSupport/Sound/Percussion/VersionNum tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -ISources/HWSupport/Sound/Percussion -Ihdr -ISources/Kernel/hdr/Global
$(BUILD)/modules/percussion/veneers.S: Sources/HWSupport/Sound/Percussion/s/boundaries Sources/HWSupport/Sound/Percussion/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py Sources/HWSupport/Sound/Percussion --asm $@ --header $(BUILD)/modules/percussion/percussion_veneers.h
$(BUILD)/modules/percussion/percussion_veneers.h: $(BUILD)/modules/percussion/veneers.S
$(BUILD)/modules/percussion/%.o: Sources/HWSupport/Sound/Percussion/c/%.c Makefile $(BUILD)/modules/percussion/percussion_veneers.h
	@mkdir -p $(dir $@)
	$(CC) $(PERCUSSION_CFLAGS) -c $< -o $@
$(BUILD)/modules/percussion/veneers.o: $(BUILD)/modules/percussion/veneers.S
	@mkdir -p $(dir $@)
	$(CC) $(PERCUSSION_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/percussion/modhead.o: $(BUILD)/modules/percussion/modhead.S
	@mkdir -p $(dir $@)
	$(CC) $(PERCUSSION_CFLAGS) -x assembler-with-cpp -c $< -o $@
$(BUILD)/modules/Percussion.elf: $(PERCUSSION_OBJS) $(CLIB) mk/ld/module.ld tools/build/modulecheck.py
	@mkdir -p $(dir $@)
	$(LD) --gc-sections --emit-relocs -T mk/ld/module.ld -o $@.tmp $(PERCUSSION_OBJS) $(CLIB)
	@python3 tools/build/modulecheck.py $@.tmp
	@mv $@.tmp $@
$(PERCUSSION_MOD): $(BUILD)/modules/Percussion.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Percussion)"
percussionmodule: $(PERCUSSION_MOD)
.PHONY: percussionmodule

# ---------------------------------------------------------------------------
# Resolver: the DNS SWI chunk, implemented with NetBSD 10.1's libc resolver.
#
# Built the same way as the Internet module: vendored C with writable globals
# becomes a two-piece image, the writable part a template copied into a
# claimed block at initialisation and reached through the static base.
#
# One difference: the resolver's vendored names do not clash with Sources/Lib/CLib's,
# so `--no-rename` moves sections without renaming symbols. Renaming would
# break c/module.c's calls such as `gethostbyname`.
RESOLV_DIR    := Sources/Networking/Inet/Resolver
RESOLV_TREE   := $(RESOLV_DIR)/netbsd
RESOLV_LOCAL  := $(RESOLV_DIR)/netbsd-local
RESOLV_BUILD  := $(BUILD)/modules/resolver
RESOLV_STAGE  := $(RESOLV_BUILD)/stage

include $(RESOLV_LOCAL)/sources.mk

# Must match the flags tools/build/vendor-resolv.py compiled with: the set of
# vendored headers depends on them.
# -nostdinc removes the compiler's stdarg.h and stddef.h too; their directory
# is asked of the compiler, as vendor-resolv.py does, because the path
# contains the compiler version.
RESOLV_BUILTIN := -isystem $(shell $(CC) -print-resource-dir)/include

RESOLV_CFLAGS := --target=arm-none-eabi $(ARCH_MACHINE) -mno-movt -ffreestanding \
                 -fno-strict-aliasing -fropi -frwpi -fno-jump-tables \
                 -fno-stack-protector -mno-unaligned-access \
                 -fno-unwind-tables -fno-asynchronous-unwind-tables \
                 -Os -g -std=gnu99 -MMD -MP -nostdinc $(RESOLV_BUILTIN) \
                 $(RESOLV_DEFINES) $(RESOLV_INCLUDES)

# Stage `machine` (the architecture headers) and the sys/sys headers userland
# includes without a directory, as symlinks; NetBSD's own build makes these.
$(RESOLV_STAGE)/.made: $(RESOLV_TREE)/MANIFEST Makefile
	@mkdir -p $(RESOLV_STAGE)
	@ln -sfn $(abspath $(RESOLV_TREE)/sys/arch/arm/include) $(RESOLV_STAGE)/arm
	@ln -sfn $(abspath $(RESOLV_TREE)/sys/arch/evbarm/include) $(RESOLV_STAGE)/machine
	@ln -sfn $(abspath $(RESOLV_TREE)/sys/arch/evbarm/include) $(RESOLV_STAGE)/evbarm
	@for h in $(RESOLV_STAGED_HEADERS); do \
	   ln -sfn $(abspath $(RESOLV_TREE))/sys/sys/$$h $(RESOLV_STAGE)/$$h; \
	 done
	@touch $@

RESOLV_VENDOR_SRCS := $(sort $(foreach c,$(RESOLV_COMPONENTS),\
                                        $(RESOLV_SRCS_$(c))))
RESOLV_VENDOR_OBJS := $(patsubst %.c,$(RESOLV_BUILD)/netbsd/%.o,\
                                  $(RESOLV_VENDOR_SRCS))

$(RESOLV_BUILD)/netbsd/%.o: $(RESOLV_TREE)/%.c $(RESOLV_STAGE)/.made Makefile
	@mkdir -p $(dir $@)
	$(CC) $(RESOLV_CFLAGS) -Wno-everything -c $< -o $@

# Our sources, compiled with the vendored code's flags because they call it
# and read its headers, but with warnings on.
RESOLV_OWN_SRCS := aliases cache cells config context debug dispatch \
                   fparseln hosts module send syslog
RESOLV_OWN_OBJS := $(addprefix $(RESOLV_BUILD)/own/,\
                               $(addsuffix .o,$(RESOLV_OWN_SRCS)))
RESOLV_OWN_CFLAGS := $(RESOLV_CFLAGS) -Iinclude -ISources/Kernel/hdr -I$(RESOLV_DIR)/c \
                     -I$(RESOLV_DIR)/c/arch/aarch32 -I$(RESOLV_BUILD) -Wall -Wextra

$(RESOLV_BUILD)/own/%.o: $(RESOLV_DIR)/c/%.c $(RESOLV_STAGE)/.made \
                         $(RESOLV_BUILD)/resolver_veneers.h Makefile
	@mkdir -p $(dir $@)
	$(CC) $(RESOLV_OWN_CFLAGS) -c $< -o $@

# The workspace is linked with the image because it reads module-rw.ld's
# marks.
#
# It is the one file not built -frwpi: under -frwpi a reference to an image
# mark (`__module_base`, `__rump_rw_start`) becomes an SBREL relocation,
# which lld rejects. With the ordinary module flags they are PC-relative.
# `-ffixed-r9` keeps the static base register untouched.
RESOLV_WORKSPACE_OBJ := $(RESOLV_BUILD)/own/workspace.o
RESOLV_WORKSPACE_CFLAGS := $(MOD_CFLAGS) -ffixed-r9 -ISources/Lib/CLib/include \
                           -I$(RESOLV_DIR)/c -Iinclude -ISources/Kernel/hdr

$(RESOLV_WORKSPACE_OBJ): $(RESOLV_DIR)/c/workspace.c Makefile
	@mkdir -p $(dir $@)
	$(CC) $(RESOLV_WORKSPACE_CFLAGS) -c $< -o $@

$(RESOLV_BUILD)/modhead.S: $(RESOLV_DIR)/cmhg/modhead $(RESOLV_DIR)/VersionNum \
                           tools/build/cmunge.py
	@mkdir -p $(dir $@)
	@python3 tools/build/cmunge.py $< $@ -I$(RESOLV_DIR) -Ihdr \
	  -ISources/Kernel/hdr/Global

$(RESOLV_BUILD)/veneers.S: $(RESOLV_DIR)/s/boundaries \
                           $(RESOLV_DIR)/cmhg/modhead tools/build/marshal.py
	@mkdir -p $(dir $@)
	@python3 tools/build/marshal.py $(RESOLV_DIR) --asm $@ \
	  --header $(RESOLV_BUILD)/resolver_veneers.h

$(RESOLV_BUILD)/resolver_veneers.h: $(RESOLV_BUILD)/veneers.S

$(RESOLV_BUILD)/modhead.o: $(RESOLV_BUILD)/modhead.S
	@mkdir -p $(dir $@)
	$(CC) --target=arm-none-eabi $(ARCH_MACHINE) -x assembler-with-cpp \
	  -c $< -o $@

$(RESOLV_BUILD)/veneers.o: $(RESOLV_BUILD)/veneers.S
	@mkdir -p $(dir $@)
	$(CC) --target=arm-none-eabi $(ARCH_MACHINE) -c $< -o $@

# A const table holding an address cannot stay in the read-only image;
# tools/build/rumprename.py finds those sections and moves them into the
# template.
RESOLV_MOVED_DIR   := $(RESOLV_BUILD)/moved
RESOLV_MOVED_LIST  := $(RESOLV_BUILD)/renamed.syms
RESOLV_MOVED_STAMP := $(RESOLV_MOVED_DIR)/moved.stamp
RESOLV_MOVED_OBJS  := $(patsubst $(RESOLV_BUILD)/%,$(RESOLV_MOVED_DIR)/%,\
                                 $(RESOLV_VENDOR_OBJS) $(RESOLV_OWN_OBJS))

$(RESOLV_MOVED_STAMP): $(RESOLV_VENDOR_OBJS) $(RESOLV_OWN_OBJS) $(CLIB) \
                       tools/build/rumprename.py tools/build/rumprwrefs.py \
                       tools/build/rumprelocs.py Makefile
	@mkdir -p $(RESOLV_MOVED_DIR)
	@python3 tools/build/rumprename.py --no-rename \
	  --out $(RESOLV_MOVED_DIR) --clib $(CLIB) --list $(RESOLV_MOVED_LIST) \
	  $(RESOLV_VENDOR_OBJS) $(RESOLV_OWN_OBJS)
	@touch $@

# Two passes: the relocation table is part of the image and cannot be written
# until the image is laid out. The first link is measured and discarded; the
# second includes the table.
RESOLV_RW_LD      := $(RESOLV_DIR)/module-rw.ld
RESOLV_RW_LDFLAGS := --emit-relocs -T $(RESOLV_RW_LD)
RESOLV_LINK_OBJS  := $(RESOLV_BUILD)/modhead.o $(RESOLV_BUILD)/veneers.o \
                     $(RESOLV_MOVED_OBJS)
# RESOLV_MOD is defined beside OURS_MODULES, not here: that list is simply
# expanded, so a name defined at this point would be empty in it.

$(BUILD)/modules/Resolver.elf: $(RESOLV_BUILD)/modhead.o \
    $(RESOLV_BUILD)/veneers.o $(RESOLV_MOVED_STAMP) $(RESOLV_WORKSPACE_OBJ) \
    $(CLIB_NOFLOAT) $(CLIB) $(RESOLV_RW_LD) tools/build/rumprelocs.py Makefile
	@mkdir -p $(dir $@)
	$(LD) $(RESOLV_RW_LDFLAGS) -o $@.measured $(RESOLV_LINK_OBJS) \
	  $(RESOLV_WORKSPACE_OBJ) $(CLIB_NOFLOAT) $(CLIB)
	@python3 tools/build/rumprelocs.py $@.measured --asm $@.relocs.S --quiet
	$(CC) --target=arm-none-eabi -c $@.relocs.S -o $@.relocs.o
	$(LD) $(RESOLV_RW_LDFLAGS) -o $@.tmp $(RESOLV_LINK_OBJS) \
	  $(RESOLV_WORKSPACE_OBJ) $@.relocs.o $(CLIB_NOFLOAT) $(CLIB)
	@python3 tools/build/rumprelocs.py $@.tmp --asm $@.again.S
	@cmp $@.relocs.S $@.again.S || { echo "resolvermodule: the table the \
	  first link produced is not the table the second one wants"; exit 1; }
	@python3 tools/check/rumplink.py $@.tmp
	@rm -f $@.measured $@.again.S
	@mv $@.tmp $@

# tools/build/modulecheck.py does not apply: it expects a single read-only
# image and rejects the .rump_relocs section. tools/check/rumplink.py checks
# the two-piece image instead.


$(RESOLV_MOD): $(BUILD)/modules/Resolver.elf
	$(OBJCOPY) -O binary $< $@
	@echo "built $@ ($$(stat -c%s $@) bytes, Resolver)"

resolvermodule: $(RESOLV_MOD)
.PHONY: resolvermodule

# Host checks.
#
# -m32 and -funsigned-char are required: c/cache.h asserts the offsets
# Resolver_GetCache publishes, and on a 64-bit build those assertions fail.
#
# netbsd-local is a quote path only: its stdio.h forwards to Sources/Lib/CLib for the
# module, and the checks feed fparseln the host's FILE.
RESOLV_TEST_GEN := $(BUILD)/tests/resolver
RESOLV_TEST_BASE := -m32 -funsigned-char $(HOST_CFLAGS) -I$(RESOLV_DIR)/c \
                    -iquote $(RESOLV_LOCAL) -Itests/lib -Iinclude
RESOLV_TEST_FLAGS := $(RESOLV_TEST_BASE) -I$(RESOLV_DIR)/c/arch/aarch32

$(RESOLV_TEST_GEN)/%.o: Sources/Networking/Inet/Resolver/test/resolver/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RESOLV_TEST_FLAGS) -c $< -o $@

$(RESOLV_TEST_GEN)/%.o: $(RESOLV_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RESOLV_TEST_FLAGS) -c $< -o $@

# The workspace's OS half is left out (no RMA to claim from); the address
# arithmetic is what is checked.
$(RESOLV_TEST_GEN)/workspace.o: RESOLV_TEST_FLAGS += -DRESOLVER_WORKSPACE_HOST

$(RESOLV_TEST_GEN)/check.o: $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RESOLV_TEST_FLAGS) -c $< -o $@

$(BUILD)/tests/resolver-checks: $(addprefix $(RESOLV_TEST_GEN)/,cases.o \
                                     cache.o fparseln.o hosts.o workspace.o \
                                     check.o)
	$(HOSTCC) -m32 $(HOST_SANITISE) -o $@ $^

# The layout AArch64 publishes (c/arch/aarch64/cacheref.h), from the same
# c/cache.c and AArch64's c/arch/aarch64/publish.c; -m32 so that its words
# hold the host's addresses.
RESOLV_PUB_GEN := $(BUILD)/tests/resolver-published
RESOLV_PUB_FLAGS := $(RESOLV_TEST_BASE) -I$(RESOLV_DIR)/c/arch/aarch64

$(RESOLV_PUB_GEN)/%.o: Sources/Networking/Inet/Resolver/test/resolver/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RESOLV_PUB_FLAGS) -c $< -o $@

$(RESOLV_PUB_GEN)/%.o: $(RESOLV_DIR)/c/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RESOLV_PUB_FLAGS) -c $< -o $@

$(RESOLV_PUB_GEN)/%.o: $(RESOLV_DIR)/c/arch/aarch64/%.c
	@mkdir -p $(dir $@)
	$(HOSTCC) $(RESOLV_PUB_FLAGS) -c $< -o $@

$(BUILD)/tests/resolver-published-checks: $(addprefix $(RESOLV_PUB_GEN)/, \
                                            published.o cache.o publish.o) \
                                          $(RESOLV_TEST_GEN)/check.o
	$(HOSTCC) -m32 $(HOST_SANITISE) -o $@ $^

# The hosts file reader publishes no layout, so it is checked at the build
# machine's own width; the file it reads is a scratch file in the build tree.
$(BUILD)/tests/resolver-hosts-checks: Sources/Networking/Inet/Resolver/test/resolver/hostsfile.c \
        $(RESOLV_DIR)/c/hosts.c $(RESOLV_DIR)/c/hosts.h \
        $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(HOST_CFLAGS) -I$(RESOLV_DIR)/c -Itests/lib -Iinclude \
	  Sources/Networking/Inet/Resolver/test/resolver/hostsfile.c $(RESOLV_DIR)/c/hosts.c $(TEST_LIB)/check.c \
	  -o $@

resolverchecks: $(BUILD)/tests/resolver-checks $(BUILD)/tests/resolver-transport-checks \
                $(BUILD)/tests/resolver-published-checks \
                $(BUILD)/tests/resolver-hosts-checks
	@$(BUILD)/tests/resolver-checks
	@$(BUILD)/tests/resolver-transport-checks
	@$(BUILD)/tests/resolver-published-checks
	@$(BUILD)/tests/resolver-hosts-checks $(BUILD)/tests/resolver-hosts.scratch
.PHONY: resolverchecks

$(BUILD)/tests/resolver-transport-checks: Sources/Networking/Inet/Resolver/test/resolver/transport.c \
        $(RESOLV_DIR)/c/send.c $(wildcard Sources/Networking/Inet/Resolver/test/resolver/host/riscos/*.h) \
        $(TEST_LIB)/check.c $(TEST_LIB)/check.h
	@mkdir -p $(dir $@)
	$(HOSTCC) -m32 -ISources/Networking/Inet/Resolver/test/resolver/host $(HOST_CFLAGS) -Iinclude \
	  -Itests/lib Sources/Networking/Inet/Resolver/test/resolver/transport.c $(TEST_LIB)/check.c -lresolv -o $@

# Check the vendored tree against what the vendoring tool says it vendored.
resolvcheck:
	@python3 tools/build/vendor-resolv.py --check
.PHONY: resolvcheck

# Added to `hostchecks` as a separate rule rather than on its main line, to
# avoid merge conflicts.
hostchecks: resolverchecks resolvcheck

-include $(RESOLV_VENDOR_OBJS:.o=.d) $(RESOLV_OWN_OBJS:.o=.d)

# Pinboard's production selection and save paths, with typed OS stand-ins.
PBD_PLATFORM_FLAGS := $(PBD_TEST_FLAGS) -ISources/Kernel/hdr -ISources/Lib/CLib/include \
 -ISources/Lib/Desktop/include -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast \
 -ffunction-sections -fdata-sections
$(PBD_TEST_GEN)/platform.o: Sources/Desktop/Pinboard/test/pinboard/platform.c Sources/Desktop/Pinboard/test/pinboard/platform-shim.h Sources/Desktop/Pinboard/c/pbmod.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PBD_PLATFORM_FLAGS) -c $< -o $@
$(PBD_TEST_GEN)/pbtask.o $(PBD_TEST_GEN)/pbfile.o: $(PBD_TEST_GEN)/%.o: Sources/Desktop/Pinboard/c/%.c Sources/Desktop/Pinboard/test/pinboard/platform-shim.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(PBD_PLATFORM_FLAGS) -include Sources/Desktop/Pinboard/test/pinboard/platform-shim.h -c $< -o $@
$(BUILD)/tests/pinboard-platform-checks: $(addprefix $(PBD_TEST_GEN)/,platform.o pbtask.o pbfile.o pbsave.o pbname.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -Wl,--gc-sections -o $@ $^
pinboard-platformchecks: $(BUILD)/tests/pinboard-platform-checks
	@$<
pinboardchecks: pinboard-platformchecks
.PHONY: pinboard-platformchecks

# Execute the format/verify/backup state machine at a substituted OS boundary.
AFL_PLATFORM_FLAGS := $(AFL_TEST_FLAGS) -ISources/Kernel/hdr -ISources/Lib/CLib/include \
 -ISources/Lib/Desktop/include -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast \
 -Wno-unused-variable -Wno-unused-but-set-variable -ffunction-sections -fdata-sections -fno-pie
$(AFL_TEST_GEN)/platform.o: Sources/Desktop/ADFSFiler/test/adfsfiler/platform.c Sources/Desktop/ADFSFiler/test/adfsfiler/platform-shim.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(AFL_PLATFORM_FLAGS) -c $< -o $@
$(AFL_TEST_GEN)/afsformat.o: Sources/Desktop/ADFSFiler/c/afsformat.c Sources/Desktop/ADFSFiler/test/adfsfiler/platform-shim.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(AFL_PLATFORM_FLAGS) -include Sources/Desktop/ADFSFiler/test/adfsfiler/platform-shim.h -c $< -o $@
$(BUILD)/tests/adfsfiler-platform-checks: $(addprefix $(AFL_TEST_GEN)/,platform.o afsformat.o afsmenu.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -no-pie -Wl,--gc-sections -o $@ $^
adfsfiler-platformchecks: $(BUILD)/tests/adfsfiler-platform-checks
	@$<
adfsfilerchecks: adfsfiler-platformchecks
.PHONY: adfsfiler-platformchecks
-include $(wildcard $(AFL_TEST_GEN)/*.d)


# Task Manager save completion and boot configuration through production code.
SWI_PLATFORM_FLAGS := $(SWI_TEST_FLAGS) -ISources/Kernel/hdr -ISources/Lib/CLib/include \
 -ISources/Lib/Desktop/include -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast \
 -ffunction-sections -fdata-sections
$(SWI_TEST_GEN)/platform.o: Sources/Desktop/Switcher/test/switcher/platform.c Sources/Desktop/Switcher/test/switcher/platform-shim.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SWI_PLATFORM_FLAGS) -c $< -o $@
$(SWI_TEST_GEN)/swsave.o: Sources/Desktop/Switcher/c/swsave.c Sources/Desktop/Switcher/test/switcher/platform-shim.h
	@mkdir -p $(dir $@)
	$(HOSTCC) $(SWI_PLATFORM_FLAGS) -include Sources/Desktop/Switcher/test/switcher/platform-shim.h -c $< -o $@
$(BUILD)/tests/switcher-platform-checks: $(addprefix $(SWI_TEST_GEN)/,platform.o swsave.o swcalc.o swlist.o swmenu.o swpath.o check.o)
	$(HOSTCC) $(HOST_CFLAGS) -Wl,--gc-sections -o $@ $^
switcher-platformchecks: $(BUILD)/tests/switcher-platform-checks
	@$<
switcherchecks: switcher-platformchecks
.PHONY: switcher-platformchecks

# Failure-injected production paths for the independent Desktop review.
desktopreviewchecks: cdfsfilermodule netfilermodule freemodule resourcefilermodule dragasprite windowscroll
	@HOSTCC="$(HOSTCC)" BUILD="$(BUILD)" python3 tests/desktopreview/run.py
.PHONY: desktopreviewchecks
hostchecks: desktopreviewchecks

# mkfilecore.py, the FileCore disc image maker, and its checks: after the
# FileCore host-check block, whose objects its second reader links.
include tools/build/mkfilecore.mk

# Defects found against RISC OS and fixed, kept as checks (part of hostchecks).
include tests/defects/defects.mk

# ARM32 builds of the modules declared with `a64-module` (the Unified
# Toolbox), read before mk/app32.mk so its zips can name them.
include mk/mod32.mk

# 32-bit application builds and zips (after every module.mk above).
include mk/app32.mk

# ARM32 builds of the AppKit applications (!ResEd and the others).
include mk/appkit32.mk

endif # CROSS_ARCH
