== Building Your Program JoeyLib does not install as a system library: each target needs its own cross compiler, link step, packaging and disk image, and JoeyLib's makefiles own all of that. Your program lives in a directory of its own -- its own repository, usually -- next to a JoeyLib checkout. Its Makefile describes the program and includes two of JoeyLib's makefiles, and that is enough to build it for every target with exactly the flags, link order and packaging JoeyLib's own examples get. JoeyLib's examples are described the same way and built by the same rules; there is one set of them. === A Project ---- games/ joeylib/ a JoeyLib checkout, set up with ./install.sh mygame/ Makefile mygame.c world.c assets/ tiles.png ---- The whole Makefile for a program with no data files: [source,makefile] ---- JOEYLIB ?= ../joeylib include $(JOEYLIB)/make/project.mk PROGRAMS := MYGAME MYGAME_SRCS := mygame.c world.c MYGAME_TITLE := My Game MYGAME_HELP := One line on what it is, for its DVX help file. include $(JOEYLIB)/make/programs.mk ---- `make/project.mk` comes first and sets up the target being built; the description comes next and may use what it set up (see <<_data_files>>); `make/programs.mk` comes last and makes the rules. Run make in the project's directory -- the paths in the Makefile are relative to it -- or with `make -C`: [source,console] ---- $ make # every target whose toolchain is installed $ make dos # one target: iigs amiga atarist dos x68000 dvx $ make clean # the project's build/ (clean-dos: one target's) $ make help ---- A target without its toolchain is skipped with a note. `JOEYLIB=PATH` on the command line points at a checkout somewhere else. ==== Where things go The project's outputs land in its own `build/`, laid out like JoeyLib's: [cols="1,4", options="header"] |=== |Target |Output |Apple IIgs |`build/iigs/bin/MYGAME` (an S16 OMF), its data staged in `build/iigs/data/MYGAME/`, and with `MYGAME_IIGS_GAME_DISK` the disks `build/iigs/bin/MYGAME.2mg` and `MYGAME-data.2mg` |Amiga |`build/amiga/bin/Mygame` |Atari ST |`build/atarist/bin/MYGAME.PRG` |MS-DOS |`build/dos/bin/MYGAME.EXE`, with the DPMI server built in |Sharp X68000 |`build/x68000/bin/MYGAME.X` |DVX |`build/dvx/bin/apps/joeylib/mygame/`: `mygame.app`, `mygame.hlp` and its `DATA/` |=== A program with `MYGAME_FOLDER` set ships in a folder of its own with its `DATA/` beside it (`build/dos/bin/MYGAME/MYGAME.EXE`); otherwise programs share `bin/DATA/`. JoeyLib's library for each target, and the host tools such as `spritebake`, are built on demand where JoeyLib keeps them, in the checkout's `build/`, and shared with JoeyLib's own build and every other project: the first project to need the DOS library builds it, the rest reuse it. That is the only thing a project writes into the JoeyLib checkout. To keep even that out, give it another place: [source,console] ---- $ make JOEY_BUILD=$PWD/build/joeylib ---- `PROJECT_BUILD=DIR` likewise moves the project's own `build/`. Both belong on the command line (or before the `include` of `make/project.mk`). [NOTE] ==== How it works: every target has its own compiler and its own make variables, so one make cannot hold two of them. `make` in a project only reads the description, then runs the same Makefile again once per target with `JOEY_PORT` set, and in that run `make/project.mk` includes the target's makefile, `make/.mk`. That makefile holds JoeyLib's library rules and `programRules`, the target's rules for one program, which `make/programs.mk` makes for every program. So any target of one port can be built directly: `make JOEY_PORT=dos build/dos/bin/MYGAME.EXE`, and `make iigs-game-disk` is passed to the IIgs run for you. ==== === Describing a Program A program is a set of variables named after its id: an upper-case name of up to eight letters and digits, which is also its file name unless `_NAME` says otherwise. `PROGRAMS` lists the ids. A project may describe several programs. [cols="2,5", options="header"] |=== |Variable |Meaning |`PROGRAMS` |The ids that `make` builds. |`TEST_PROGRAMS` |Ids with rules that are built only when asked for (test probes for gates). |`ID_SRCS` |Its C sources, in link order. |`ID_CFLAGS` |Extra compiler flags: `-I` folders, `-D` defines. |`ID_LDFLAGS` |Extra link flags (`-s` to strip). |`ID_PORTS` |The targets it builds for. Every target when unset. |`ID_NAME` |Its file name without the extension (see <<_naming>>). |`ID_FOLDER` |Set: it ships in a folder of its own with its `DATA/` (Amiga, ST, DOS, X68000; on the IIgs and DVX every program is packaged apart anyway). |`ID_BINDIR` |Build it there instead of `bin/`. |`ID_DATA` |Its data files (<<_data_files>>). |`ID_TITLE` |DVX: its window title. |`ID_HELP` |DVX: the line its help file says about it. No apostrophes. |`ID_IIGS_OVERFLOW_CAP` |IIgs: link large overflow segments (`0xF000`), which a big program needs to leave the Memory Manager room (see `make/support/iigs/clang-build.sh`). |`ID_IIGS_GAME_DISK` |IIgs: set: `make` also builds its bootable volume. |=== Any variable can be given for one target only as `ID__`, which wins there even when empty: [source,makefile] ---- MYGAME_AMIGA_NAME := MyGame MYGAME_X68000_LDFLAGS := -s MYGAME_IIGS_DATA := # nothing staged on the IIgs ---- DVX's help and resource texts call a program "a `$(DVX_KIND)` program" and name `$(DVX_AUTHOR)` as its author (both `JoeyLib` by default); `DVX_VENDOR` (`joeylib`) is the folder it installs in under `APPS\`. Set them in the description. === Data Files A program opens its files through `DATA/` (`jlDataOpen`), and its Makefile puts them there. `ID_DATA` lists the staged files, and the project writes the rules that make them, below its description, using what `make/project.mk` set up: [cols="2,5", options="header"] |=== |Name |Meaning |`$(call programDataDir,ID)` |Where its `DATA/` files go on this target. |`$(call programOptionalDir,ID)` |Where the files go that it can run without (<<_disk_images_and_packaging>>). |`PLATFORM` |The target: `iigs`, `amiga`, `atarist`, `dos`, `x68000`, `dvx`. |`ASSET_PLATFORM` |The target its bakes are for. DVX shows the DOS stage and runs x86 code, so it takes the DOS bakes. |`SPRITEBAKE` |The host `spritebake` for this target (its rule is JoeyLib's). |`PROJECT_DIR`, `PROJECT_BUILD` |The project and its `build/`. |`REPO_DIR` |The JoeyLib checkout, for its tools: `$(REPO_DIR)/tools/assetbake/assetbake.py`. |`HOST_CC`, `HOST_CFLAGS` |For a host tool of the project's own. |=== A copy, a tile-bank bake and a pre-compiled sprite bank: [source,makefile] ---- MYGAME_RUN := $(call programDataDir,MYGAME) MYGAME_GEN := $(PROJECT_BUILD)/generated/$(ASSET_PLATFORM) MYGAME_DATA := $(MYGAME_RUN)/theme.jym $(MYGAME_RUN)/tiles.tbk $(MYGAME_RUN)/player.spc $(MYGAME_RUN)/theme.jym: assets/theme.jym @mkdir -p $(dir $@) cp $< $@ $(MYGAME_RUN)/tiles.tbk: assets/tiles.png @mkdir -p $(dir $@) python3 $(REPO_DIR)/tools/assetbake/assetbake.py --type tile --target $(ASSET_PLATFORM) $< $@ $(MYGAME_GEN)/player.spr: assets/player.png @mkdir -p $(dir $@) python3 $(REPO_DIR)/tools/assetbake/assetbake.py --type sprite --cell 16x16 $< $@ $(MYGAME_RUN)/player.spc: $(MYGAME_GEN)/player.spr $(SPRITEBAKE) @mkdir -p $(dir $@) $(SPRITEBAKE) $< $@ ---- One rule serves every target: tile banks and `.spc` files differ per target, and `ASSET_PLATFORM` and `SPRITEBAKE` say which. The Assets chapter (<<_creating_assets>>) covers the formats and tools. === Naming File naming conventions differ per platform, and the rules apply them to the program's id: [cols="2,2,4", options="header"] |=== |Platform |Convention |Example |Apple IIgs |Uppercase, 8 characters |`MYGAME` |Atari ST |Uppercase 8.3, `.PRG` |`MYGAME.PRG` |MS-DOS |Uppercase 8.3, `.EXE` |`MYGAME.EXE` |Amiga |Mixed case, no extension: the id Capitalised unless `ID_AMIGA_NAME` says otherwise |`Mygame` |Sharp X68000 |Uppercase 8.3, `.X` |`MYGAME.X` |DVX |Lowercase, 8 characters, a folder of its own |`apps/joeylib/mygame/mygame.app` |=== Keep the id to 8 characters and letters and digits only, so the same name works everywhere. === Disk Images and Packaging *Apple IIgs.* Programs run from ProDOS volumes. With `MYGAME_IIGS_GAME_DISK := yes`, `make iigs` also builds a self-booting volume, `build/iigs/bin/MYGAME.2mg`: a stripped GS/OS that launches the program straight from its own folder, which can be copied to a hard drive and run from there. It is an 800 KB floppy when the program fits beside the system, otherwise a 2048 KB volume that MAME boots off a CFFA2 card. `make iigs-game-disk GAME=NAME` builds one for any program, `GAME_DISK_SIZE=2048KB` forces the size, and `make iigs-verify-game-disk` boots it headlessly and checks it draws. The data the program needs goes on with it; files listed under `programOptionalDir` (a program's optional data, which it can run without) go on last, only while the volume has room. *Sharp X68000.* Programs ship on a Human68k floppy; `joey-run` builds one for each run from the program and its `DATA/`. A floppy is 1232 KB, so a large program strips its binary (`ID_X68000_LDFLAGS := -s`) and ships in a folder of its own (`ID_FOLDER`), so its data never rides onto another program's disk. A stale image can hide hundreds of kilobytes in subdirectories that a directory listing does not show; rebuild from empty rather than deleting files if space seems to vanish. *MS-DOS.* The CWSDPMI DPMI server is built into every `.EXE`, so nothing else needs to be on the disk. *Amiga.* `joey-run` writes an `s/startup-sequence` that invokes your binary. *DVX.* Every program is a folder laid out as it installs in DVX: `build/dvx/bin/apps/joeylib/mygame/` holds `mygame.app`, its help file `mygame.hlp` (DVX gives every program one; the rules write a one-topic help from `MYGAME_HELP`) and its `DATA/`. Copy that folder into a DVX installation's `APPS\JOEYLIB\` and start it from DVX, or let `joey-run` do it (<<_dvx_programs>>). === Verifying It Actually Runs Build success is weak evidence on retro targets. Run it with `joey-run` (see <>) on every machine, by its path or by its name in the project: [source,console] ---- $ ../joeylib/joey-run build/dos/bin/MYGAME.EXE $ ../joeylib/joey-run --project . --port amiga mygame $ ../joeylib/joey-run build/iigs/bin/MYGAME.2mg ---- Without a window, which is what you want while iterating, run it headless and look at the snapshot; the program's log lands next to it as usual: [source,console] ---- $ ../joeylib/joey-run --headless --seconds 90 --snapshot mygame.png --project . --port atarist mygame ---- === JoeyLib's Own Examples JoeyLib's examples are programs like any other: `make/examples.mk` describes them, and each target's makefile includes it and `make/programs.mk`. Built from the JoeyLib checkout, everything lands in its `build/`: [source,console] ---- $ make # the library and the examples, every installed target $ make iigs-disk # the IIgs examples on build/iigs/bin/joey.2mg $ make -f make/iigs.mk iigs-game-disk GAME=DRAW ---- `make iigs` builds only the IIgs library; `make iigs-disk` builds the examples and packs them. Space Taxi and JoeyAGI, two complete games, are projects of their own built exactly as this chapter describes: each repository's Makefile includes its `program.mk` description between JoeyLib's two makefiles. === The Gates The repository carries automated checks. They exist because several classes of bug on these machines are invisible until measured, and they are worth running against your own program too: `scripts/verify-iigs-game-disk.sh IMAGE` boots any game disk, `scripts/verify-iigs-audio.sh` and `scripts/verify-x68000-audio.sh` read the sound chips under any program given its path, `joey-run --headless` runs any program, and the emulator setup in `scripts/lib/` (DVX staging, Human68k floppies, environment checks) takes paths. [cols="2,4", options="header"] |=== |Command |What it proves |`make verify-audio` |Every port's audio gate: the IIgs DOC and X68000 chips are read directly, the ST's YM2149 register writes are traced, and the Amiga, DOS and DVX output is recorded and graded. Exits 3, not 0, if a gate had to be skipped. |`make iigs-verify` |The IIgs actually reached the program and drew something. |`make iigs-verify-game-disk` |A program's bootable IIgs volume boots, launches the program and draws. |`scripts/verify--tilebank.sh` |The port's own tile-bank bake loads pixel-exact both ways, with the same surface hash as the host check `scripts/check-tilebank.sh`, and banks baked for the other machines are refused. `scripts/verify-all.sh tilebank` runs every port's. |`scripts/bench-.sh` |Per-operation throughput for that port. |`tools/diff-uber-hashes` |Compares a port's rendered output against the IIgs golden reference, operation by operation, by surface hash. |=== That last one is the strongest tool in the repository. `jlSurfaceHash` produces the same value for the same logical pixels on every platform regardless of internal storage format, so a hash mismatch means one port is genuinely drawing different pixels from the others. === Logging from the Target Debugging a program running on an emulated 1988 machine is awkward. JoeyLib provides a logger that writes to a file next to the executable (on DVX, in the program's settings folder): [source,c] ---- jlLogReset(); jlLogF("level %d loaded, %u cels", levelNum, celCount); jlLogFlush(); ---- The headless runners retrieve that file automatically. [NOTE] ==== The logger needs a multi-kilobyte ring buffer, which some targets cannot afford. Space Taxi compiles it out entirely on the IIgs and X68000 for that reason. If you are debugging a memory-tight program, expect to do the same, and fall back to observing hardware state through an emulator instead. ====