== Building Your Program JoeyLib does not install as a system library: each target needs its own cross compiler, link step, packaging, and disk image; 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 ste 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/`, its Finder icon in `build/iigs/icon/MYGAME/`, and with `MYGAME_IIGS_GAME_DISK` the disks `build/iigs/bin/MYGAME.2mg` and `MYGAME-data.2mg` |Amiga |`build/amiga/bin/Mygame` and its Workbench icon, `Mygame.info` |Atari ST |`build/atarist/bin/MYGAME.PRG`, which runs on every ST |Atari STe |`build/ste/bin/MYGAME.PRG`, the STe build: STe and Mega STe only (<<_the_atari_st_family>>); same toolchain and baked data as the ST |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/` |SDL3 |`build/sdl3/bin/MYGAME`, a native program with SDL 3 linked in |Web |`build/web/bin/MYGAME/`: `MYGAME.html`, `.js`, `.wasm`, and `.data` (its `DATA/`, packed in), and `icon.png` |Win32 |`build/win32/bin/MYGAME.EXE`, a 32-bit Windows program |Win16 |`build/win16/bin/MYGAME.EXE`, a Windows 3.x program |PSP |`build/psp/bin/MYGAME/`: `EBOOT.PBP` and its `DATA/`, a folder for the memory stick's `PSP/GAME/`; the ELF it is packed from in `build/psp/elf/MYGAME.elf` |=== 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/`. A program's icon (<<_icons>>) is baked in `build//icon/MYGAME/` on its way into the program. 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, SDL3, Win32, Win16; on the IIgs, DVX, the web, and the PSP 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. PSP: its title in the PSP's menu (by default its name). |`ID_ICON` |Its icon: a picture (a PNG, best square, transparent around the figure), baked for every target whose system shows programs by an icon (<<_icons>>). JoeyLib's kangaroo by default. |`ID_HELP` |DVX: the line its help file says about it. |`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`, `ste`, `dos`, `x68000`, `dvx`, `sdl3`, `web`, `win32`, `win16`, `psp`, `haiku`, `haiku32`, `beos`. |`ASSET_PLATFORM` |The target its bakes are for. DVX shows the DOS stage and runs x86 code, so it takes the DOS bakes; the hosted ports' stage (SDL3, the web, Win32, Win16, the PSP, Haiku, BeOS) is the same chunky one, so they take them too: Win16 runs a pre-compiled sprite bank's x86 code as DVX does, and the others take its cels without their code. The STe build takes the ST's. |`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` |SDL3 |The id, no extension |`MYGAME` |Web |The id, a folder of its own |`MYGAME/MYGAME.html` |Win32 |Uppercase 8.3, `.EXE` |`MYGAME.EXE` |Win16 |Uppercase 8.3, `.EXE` |`MYGAME.EXE` |PSP |The id, a folder of its own holding `EBOOT.PBP` |`MYGAME/EBOOT.PBP` |=== 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, and the programs' Finder icons last of all (<<_icons>>). *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. `Name.info` beside it is its Workbench icon, from which it starts too. *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>>). *SDL3.* A program is one native executable with SDL 3 linked in statically, so the folder it is in, with its `DATA/`, is all it needs on a machine like the one that built it. *Web.* A program is its folder, `build/web/bin/MYGAME/`, with its `DATA/` packed into `MYGAME.data` at link time (so changing a DATA file relinks it). Copy the folder to any web server; `MYGAME.html` is the page. *Win32.* A program is one `.EXE` linked statically against the system's own DLLs, so its folder, with its `DATA/`, is all it needs; it ships the way a DOS program does. *Win16.* A program is one `.EXE`, its 32-bit code bound to Open Watcom's Win386 supervisor and calling only what Windows 3.1 has (USER, GDI, KERNEL, and MMSYSTEM), so its folder, with its `DATA/`, is all it needs; it ships the way a DOS program does. *PSP.* A program is its folder, `build/psp/bin/MYGAME/`: `EBOOT.PBP` (the program, SDL 3 linked in, packed as the PSPSDK packs it, with its icon and no other pictures) and its `DATA/`. Copy the folder into `PSP/GAME/` on a memory stick; the PSP shows it by its icon and its `ID_TITLE`. The unstripped ELF stays in `build/psp/elf/`, for `psp-addr2line` on an address a crash reports. === Icons A program has an icon on every system that shows programs by one. `ID_ICON` names a picture, a PNG, best square and transparent around the figure, and `make` bakes it for each target with `tools/iconBake.py`; a program without one gets JoeyLib's kangaroo, `assets/joeyIcon.png`. A picture of 64 pixels or less is taken for pixel art and enlarged by whole pixels; a bigger one is resampled smoothly. Where an icon has few colours, each pixel takes the nearest. [cols="1,5", options="header"] |=== |Target |The icon |Apple IIgs |The Finder's: an icon file (type `$CA`) for each program, which the disk scripts put in the volume's `Icons` folder as `NAME.ICONS`, where the Finder reads icon files when it starts. 24x20 and 12x10 colour icons, which the Finder shows as they are: in its 640 mode each icon pixel is two screen pixels, black, red, green, or white beside black, blue, yellow, or white, seen together as one of 16 colours (an icon drawn at an odd screen column would pair them with the other halves of the palette; the Finder drew every one at an even column). A data disk takes the icons last, while there is room; a game disk keeps its program's at its root. A program copied to another volume takes its icon file along, into that volume's `Icons` folder. |Amiga |Workbench's: `Name.info` beside the program, a tool's icon in Workbench's four colours, 48x24 hires pixels, which starts it with `AMIGA_ICON_STACK` (32768) bytes of stack. Started from it, the program runs in its own folder, as it does from a shell. |DVX |Program Manager's: the program's `icon32` resource, 32x32 on DVX's grey. |Win16 |Program Manager's and the minimised window's: 32x32 in the 16 VGA colours, bound into the `.EXE`. |Win32 |Explorer's and the window's: 16, 32, and 48 pixels, each in 256 colours and in 32-bit, linked into the `.EXE`. |Haiku, BeOS |Tracker's and the Deskbar's: 32x32 and 16x16 in the BeOS palette, in the program's resources. Tracker shows a file by the icons in its attributes, which a program built on another machine does not have; the program copies them across the first time it runs, so Tracker shows it by its icon from then on (or at once, after `mimeset -F NAME`). |PSP |The menu's: `ICON0` in the EBOOT, 144x80. |SDL3 |The window's, 64x64, which the desktop shows where it shows one (title bar, task bar, dock). |Web |The page's: `icon.png`, 32x32, beside it. |=== MS-DOS, the Atari ST, and the X68000 show no program by an icon of its own, so their programs get none. === 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 for the STe build the samples the DMA sound chip plays), the Amiga, DOS, and DVX output is recorded and graded, SDL3's mix is written to a file by SDL's disk driver and graded, and the web page's sound is recorded in headless Chromium and graded, as is the Win32 program's under Wine, the Win16 program's on Windows 3.11 in DOSBox, and what reaches the PSP's audio output under PPSSPP. 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/verify-all.sh icon` |Every program carries its icon where its system looks (`scripts/check-icons.sh`, no emulator), and each system shows it: the IIgs Finder's window, the Amiga's icon.library (and a Workbench start), DVX's Program Manager, Windows 3.11's desktop with the program minimised, the Win32 and SDL3 windows' icons on X, and Tracker on Haiku and BeOS. |`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. ====