joeylib2/docs/BuildIntegration.adoc

503 lines
22 KiB
Text

== 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
DVX_VENDOR := acme
DVX_AUTHOR := Acme Games
MACOS_ID_PREFIX := com.example.acme
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.
The first three lines say whose the programs are, which only you can say:
JoeyLib never makes it up, and building for DVX or macOS stops until they
are set. `DVX_VENDOR` is the folder your programs install in under DVX's
`APPS\` (1 to 8 letters, digits, or `_`; DVX names a package `vendor.name`)
and `DVX_AUTHOR` the author DVX shows; `MACOS_ID_PREFIX`, a reverse-DNS name
of yours, starts each Mac app's identifier, which ends in the program's name
(`com.example.acme.MYGAME`). JoeyLib's own examples carry their author's
(`kpunch`, `com.kangaroopunch.joeylib`). 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/acme/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`
|macOS |`build/macos/bin/MYGAME.app`, a universal app: the program in
`Contents/MacOS/`, its `DATA/` and icon in `Contents/Resources/`; the program
it is packed from in `build/macos/link/MYGAME`
|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/<target>/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/<target>.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, macOS, 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.
macOS: its app's display name (`CFBundleDisplayName`). 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_<TARGET>_<VAR>`, 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"
(`JoeyLib` by default) and name `$(DVX_AUTHOR)` as its author; `DVX_VENDOR`
is the folder it installs in under `APPS\` (<<_a_project>>). 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`, `macos`, `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, macOS, 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/acme/mygame/mygame.app`
|SDL3 |The id, no extension |`MYGAME`
|Web |The id, a folder of its own |`MYGAME/MYGAME.html`
|macOS |The id, an app |`MYGAME.app`
|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/acme/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\ACME\` 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.
*macOS.* A program is its app, `build/macos/bin/MYGAME.app`, which holds
everything it needs: the program (SDL 3 linked in, for Apple silicon and
Intel Macs both), its `DATA/`, its icon, and the `Info.plist` that names
them. Copy the app to a Mac, or zip it to send it; a Mac opens it from
anywhere. Its identifier is `MACOS_ID_PREFIX` and its name
(`com.example.acme.MYGAME`, <<_a_project>>).
*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, and on Haiku the same two as one vector
icon, which draws each at its own size and the 32x32 cleanly at any larger
one (an icon with too many colour patches for the vector format goes
without). 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).
|macOS |Finder's and the Dock's: `icon.icns` in the app's `Contents/Resources/`,
16 to 128 pixels and twice that for Retina screens (up to 512, a picture that
big or pixel art).
|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
<<Running What You Built>>) 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-<port>-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-<port>.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.
====