410 lines
16 KiB
Text
410 lines
16 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, 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 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/`, 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`, 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)
|
|
|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/`.
|
|
|
|
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 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_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_<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" 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`.
|
|
|`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) is the same chunky one, so they take them too
|
|
(the cels of a pre-compiled sprite bank, without their code).
|
|
|`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.
|
|
|
|
*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>>).
|
|
|
|
*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 no icon or
|
|
pictures) and its `DATA/`. Copy the folder into `PSP/GAME/` on a memory
|
|
stick; the PSP shows it by its `ID_TITLE`. The unstripped ELF stays in
|
|
`build/psp/elf/`, for `psp-addr2line` on an address a crash reports.
|
|
|
|
=== 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/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.
|
|
====
|