# Makefile for pdp11xfer
#
# Targets:
#   make            - build the drivers and the CLI, then build and run
#                      the unit/integration tests
#   make test       - build and run all unit/integration tests
#   make drivers    - generate include/driver_<controller>.h for each
#                      controller from drivers/*.mac via macro11 +
#                      extract_code
#   make cli        - build the pdp11xfer CLI, obj/pdp11xfer (requires
#                      drivers)
#   make man        - generate the man page, obj/pdp11xfer.1, from
#                      docs/pdp11xfer.1.in (version from include/version.h)
#   make manual     - typeset docs/pdp11xfer-manual.tex into
#                      docs/pdp11xfer-manual.pdf via pdflatex
#   make test-e2e   - end-to-end driver tests against a SimH-simulated
#                      PDP-11 (real protocol/RLE/driver code, not faked)
#   make install    - install the pdp11xfer CLI into $(DESTDIR)$(BINDIR)
#                      and its man page into $(DESTDIR)$(MANDIR)/man1
#                      (default /usr/local/bin and /usr/local/share/man;
#                      override PREFIX, BINDIR or MANDIR)
#   make dist       - create pdp11xfer-<version>.tar.bz2, a source
#                      distribution (version from include/version.h)
#   make clangd     - generate compile_commands.json for clangd (needs bear)
#   make clean      - remove build artifacts (obj/, generated headers, .lst)
#
# See docs/BUILDING.md for the full list of build requirements.
#
# Driver generation requires `macro11` (https://gitlab.com/Rhialto/macro11)
# to be available in PATH. If it isn't, `make drivers`/`make cli` fail with
# a clear error. `make test` also needs it: src/test_main_modes.c #includes
# src/main.c directly to reach its static functions, which pulls in the
# generated driver_*.h headers.
#
# `make manual` requires `pdflatex` (any standard texlive install) to be
# available in PATH. It isn't part of `make`/`make all`, since the manual
# is checked in pre-built and most builds don't touch it.
#
# `make test-e2e` requires `socat` and a SimH pdp11 binary (see PDP11_SIMH)
# in PATH, plus the M9312 console ROM images in tests/. It isn't part of
# `make test`/`make all` (not every checkout has SimH/socat installed, and
# it's much slower - see tools/test-e2e.sh); `make test` prints a note
# pointing at it when both tools are detected in PATH.

CC      = gcc
CFLAGS  = -Wall -Wextra -O2 -I include
OBJDIR  = obj
DRVDIR  = drivers
GENDIR  = include

MACRO11 ?= macro11
PDP11_SIMH ?= pdp11

PREFIX  ?= /usr/local
BINDIR  ?= $(PREFIX)/bin
MANDIR  ?= $(PREFIX)/share/man
INSTALL ?= install

# ---- core library sources -------------------------------------------------

LIB_SRCS = src/protocol.c src/serial.c src/transfer.c src/loader.c src/device.c
LIB_OBJS = $(LIB_SRCS:src/%.c=$(OBJDIR)/%.o)

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

.PHONY: all test drivers cli man install manual test-e2e clangd dist clean

all: cli man test

test: $(OBJDIR)/test_protocol $(OBJDIR)/test_transfer $(OBJDIR)/test_loader $(OBJDIR)/test_device $(OBJDIR)/test_main_modes $(OBJDIR)/test_diskimage $(OBJDIR)/extract_code
	@echo "--- protocol ---";  ./$(OBJDIR)/test_protocol  | tail -1
	@echo "--- transfer ---";  ./$(OBJDIR)/test_transfer  | tail -1
	@echo "--- loader   ---";  ./$(OBJDIR)/test_loader    | tail -1
	@echo "--- device   ---";  ./$(OBJDIR)/test_device    | tail -1
	@echo "--- main modes ---"; ./$(OBJDIR)/test_main_modes | tail -1
	@echo "--- diskimage ---"; ./$(OBJDIR)/test_diskimage | tail -1
	@echo "--- extract_code ---"; ./tools/test_extract_code.sh
	@if command -v socat >/dev/null 2>&1 && command -v $(PDP11_SIMH) >/dev/null 2>&1; then \
	  echo; \
	  echo "SIMH and socat detected - run 'make test-e2e' for full end-to-end driver tests."; \
	fi

$(OBJDIR)/test_protocol: src/protocol.c src/test_protocol.c | $(OBJDIR)
	$(CC) $(CFLAGS) -o $@ $^

$(OBJDIR)/test_transfer: src/protocol.c src/serial.c src/transfer.c src/test_transfer.c | $(OBJDIR)
	$(CC) $(CFLAGS) -o $@ $^

$(OBJDIR)/test_loader: src/loader.c src/serial.c src/test_loader.c | $(OBJDIR)
	$(CC) $(CFLAGS) -o $@ $^

$(OBJDIR)/test_device: src/protocol.c src/serial.c src/transfer.c src/device.c src/test_device.c | $(OBJDIR)
	$(CC) $(CFLAGS) -o $@ $^

TEST_MAIN_MODES_SRCS = src/protocol.c src/serial.c src/transfer.c src/loader.c src/device.c src/diskimage.c src/progress.c src/test_main_modes.c

# test_main_modes.c #includes main.c directly (see top-of-file note), so it
# also needs the generated driver_*.h headers - see the dependency added
# below once DRIVERS is defined. They're deliberately not part of this
# rule's own prerequisite list, since this recipe names sources explicitly
# rather than using $^ (which would otherwise pass the headers to gcc as
# if they were compiler inputs in their own right).
$(OBJDIR)/test_main_modes: $(TEST_MAIN_MODES_SRCS) src/main.c | $(OBJDIR)
	$(CC) $(CFLAGS) -o $@ $(TEST_MAIN_MODES_SRCS)

$(OBJDIR)/test_diskimage: src/protocol.c src/serial.c src/transfer.c src/device.c src/diskimage.c src/progress.c src/test_diskimage.c | $(OBJDIR)
	$(CC) $(CFLAGS) -o $@ $^

$(OBJDIR)/extract_code: tools/extract_code.c | $(OBJDIR)
	$(CC) $(CFLAGS) -o $@ $^

$(OBJDIR):
	mkdir -p $(OBJDIR)

# ---- driver generation -------------------------------------------------
#
# Each controller's .mac file .includes pdp11gui_main.mac, pdp11gui_aux.mac,
# pdp11gui_serialxfer.mac (which in turn .includes
# pdp11gui_serialio_dl11.mac). macro11 must run from $(DRVDIR) for these
# relative .includes to resolve. Output .lst files are written there too,
# then extract_code turns them into headers under $(GENDIR)/.

DRIVERS = rl11 rk11 rk611 rx11 rx211 mscp

drivers: $(foreach d,$(DRIVERS),$(GENDIR)/driver_$(d).h)

$(GENDIR)/driver_%.h: $(DRVDIR)/pdp11gui_%.mac $(OBJDIR)/extract_code \
                      $(DRVDIR)/pdp11gui_main.mac $(DRVDIR)/pdp11gui_aux.mac \
                      $(DRVDIR)/pdp11gui_serialxfer.mac \
                      $(DRVDIR)/pdp11gui_serialio_dl11.mac
	@command -v $(MACRO11) >/dev/null 2>&1 || { \
	  echo "error: macro11 not found in PATH (set MACRO11=/path/to/macro11)"; \
	  echo "       see https://gitlab.com/Rhialto/macro11"; \
	  exit 1; }
	cd $(DRVDIR) && $(MACRO11) pdp11gui_$*.mac -l pdp11gui_$*.lst
	./$(OBJDIR)/extract_code $(DRVDIR)/pdp11gui_$*.lst $@ driver_$*

# ---- CLI ----------------------------------------------------------------

CLI_SRCS = src/main.c src/diskimage.c src/progress.c
CLI_OBJS = $(CLI_SRCS:src/%.c=$(OBJDIR)/%.o)

cli: $(OBJDIR)/pdp11xfer

$(OBJDIR)/pdp11xfer: $(LIB_OBJS) $(CLI_OBJS) | drivers
	$(CC) $(CFLAGS) -o $@ $(LIB_OBJS) $(CLI_OBJS)

# ---- man page -------------------------------------------------------------
#
# docs/pdp11xfer.1.in carries an @VERSION@ placeholder, filled in from
# include/version.h so the man page always matches the program.

VERSION := $(shell sed -n 's/^\#define PDP11XFER_VERSION "\(.*\)"/\1/p' $(GENDIR)/version.h)

man: $(OBJDIR)/pdp11xfer.1

$(OBJDIR)/pdp11xfer.1: docs/pdp11xfer.1.in $(GENDIR)/version.h | $(OBJDIR)
	@test -n "$(VERSION)" || { \
	  echo "error: could not read PDP11XFER_VERSION from $(GENDIR)/version.h"; \
	  exit 1; }
	sed 's/@VERSION@/$(VERSION)/g' docs/pdp11xfer.1.in > $@

# The driver programs are compiled into the binary, so it and the man
# page are the only files that need installing. DESTDIR is for staged
# installs (packaging).
install: $(OBJDIR)/pdp11xfer $(OBJDIR)/pdp11xfer.1
	$(INSTALL) -d $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
	$(INSTALL) -m 755 $(OBJDIR)/pdp11xfer $(DESTDIR)$(BINDIR)/pdp11xfer
	$(INSTALL) -m 644 $(OBJDIR)/pdp11xfer.1 $(DESTDIR)$(MANDIR)/man1/pdp11xfer.1

# Every object depends on the hand-written headers. The generated
# driver_*.h headers are left out: they are regenerated during the build,
# after most objects are compiled, so depending on them here would make
# the next make (e.g. `make install`) recompile everything again. Only
# the files that include them depend on them (see below).
SRC_HDRS = $(filter-out $(foreach d,$(DRIVERS),$(GENDIR)/driver_$(d).h), \
                        $(wildcard $(GENDIR)/*.h))

$(OBJDIR)/%.o: src/%.c $(SRC_HDRS) | $(OBJDIR)
	$(CC) $(CFLAGS) -c -o $@ $<

# main.c/diskimage.c #include the generated driver_*.h headers, so they
# depend on `drivers` (order-only via the cli rule above isn't enough for
# header deps - make them explicit). test_main_modes.c #includes main.c
# directly (see top-of-file note), so it needs the same headers:
$(OBJDIR)/main.o $(OBJDIR)/diskimage.o $(OBJDIR)/test_main_modes: $(foreach d,$(DRIVERS),$(GENDIR)/driver_$(d).h)

# ---- manual ---------------------------------------------------------------
#
# Two pdflatex passes: the first pass writes the .toc file that the second
# pass's \tableofcontents reads back, so the table of contents is correct.

DOCSDIR   = docs
PDFLATEX ?= pdflatex

manual: $(DOCSDIR)/pdp11xfer-manual.pdf

$(DOCSDIR)/pdp11xfer-manual.pdf: $(DOCSDIR)/pdp11xfer-manual.tex
	@command -v $(PDFLATEX) >/dev/null 2>&1 || { \
	  echo "error: pdflatex not found in PATH (set PDFLATEX=/path/to/pdflatex)"; \
	  echo "       any standard texlive install provides it"; \
	  exit 1; }
	cd $(DOCSDIR) && $(PDFLATEX) -interaction=nonstopmode -halt-on-error pdp11xfer-manual.tex >/dev/null
	cd $(DOCSDIR) && $(PDFLATEX) -interaction=nonstopmode -halt-on-error pdp11xfer-manual.tex >/dev/null

# ---- end-to-end tests (SimH) -----------------------------------------------
#
# src/test_e2e_driver.c drives the real driver image for a given
# controller/device pair (passed as argv) over a real serial_port_t - it
# doesn't fake the far end like the other test_*.c programs do.
# tools/test-e2e.sh brings up SimH+socat once per controller/device
# combination, points this binary at the resulting pty, and cleans up
# afterward. See that script and src/test_e2e_driver.c for why this
# transfers a few tracks, not a full image.

E2E_DRIVER_SRCS = src/protocol.c src/serial.c src/transfer.c src/loader.c src/device.c src/test_e2e_driver.c

$(OBJDIR)/test_e2e_driver: $(E2E_DRIVER_SRCS) $(foreach d,$(DRIVERS),$(GENDIR)/driver_$(d).h) | $(OBJDIR)
	$(CC) $(CFLAGS) -o $@ $(E2E_DRIVER_SRCS)

test-e2e: $(OBJDIR)/test_e2e_driver $(OBJDIR)/pdp11xfer
	PDP11_SIMH=$(PDP11_SIMH) ./tools/test-e2e.sh

clangd:
	bear -- make

# ---- source distribution ------------------------------------------------
#
# Everything needed to build, test and run pdp11xfer from a fresh copy,
# laid out as in the source tree under a top-level pdp11xfer-<version>/
# directory. Generated files (obj/, driver .lst files, include/driver_*.h)
# are left out; the recipient's `make` regenerates them, so building
# still requires macro11, exactly as it does from a checkout. Files are
# listed explicitly rather than copying whole directories, so .svn/,
# editor backups and other local clutter never get in.

DISTDIR  = pdp11xfer-$(VERSION)

SRCS = $(wildcard src/*.c) tools/extract_code.c
HDRS = $(filter-out $(foreach d,$(DRIVERS),$(GENDIR)/driver_$(d).h), \
                    $(wildcard $(GENDIR)/*.h))
DOCS = README.md \
       $(DOCSDIR)/pdp11xfer-manual.tex $(DOCSDIR)/pdp11xfer-manual.pdf \
       $(DOCSDIR)/pdp11xfer.1.in \
       $(wildcard $(DOCSDIR)/*.md)
DIST_EXTRA = $(wildcard $(DRVDIR)/*.mac) \
             tools/test-e2e.sh tools/test_extract_code.sh tools/test_sample.lst \
             $(wildcard tests/*.bin)

dist:
	@test -n "$(VERSION)" || { \
	  echo "error: could not read PDP11XFER_VERSION from $(GENDIR)/version.h"; \
	  exit 1; }
	@rm -rf $(DISTDIR)
	@rm -f $(DISTDIR).tar.bz2
	@mkdir $(DISTDIR)
	@tar -cf - $(SRCS) $(HDRS) Makefile $(DOCS) $(DIST_EXTRA) | tar -xf - -C $(DISTDIR)
	@tar -cjf $(DISTDIR).tar.bz2 $(DISTDIR)
	@rm -rf $(DISTDIR)
	@echo Distribution file $(DISTDIR).tar.bz2 created.

clean:
	rm -rf $(OBJDIR)
	rm -f $(DRVDIR)/*.lst
	rm -f $(foreach d,$(DRIVERS),$(GENDIR)/driver_$(d).h)
	rm -f $(DOCSDIR)/*.aux $(DOCSDIR)/*.log $(DOCSDIR)/*.out $(DOCSDIR)/*.toc
