Builds and game manifests
tools/build.py builds the library, the SDK programs and all games that it finds. It checks each linked layout and makes ProDOS disks. Each game has a game.json manifest. Thus you can add a game without a change to the build script.
Command line
python tools/build.py [--cc65-home DIR] [--game NAME] [--no-disk] [--private] [--build-dir DIR]
[--test-target] [--prodos-image FILE]
| Option | Effect |
|---|---|
--cc65-home DIR | The installed cc65 2.19 SDK. The default is CC65_HOME, then the SDK of the cc65 on PATH. |
--game NAME | Build the library, the diagnostic, this game and the programs that its disk lists. Without this option, the build uses all manifests. |
--no-disk | Link only the programs and the library. No ProDOS input is necessary. |
--private | Compile with TI_PRIVATE_TRANSPORT=1 and link the optional AMEM transport. The build does not include games whose manifest sets "private": false. |
--build-dir DIR | The output folder in the project. The default is build. Use build/private for the AMEM variant. |
--test-target | Also link the 65C02 test programs that the checks run. |
--prodos-image FILE | Use a different installed copy of ProDOS 8 2.4.3. It must have the pinned SHA-256. |
The build steps
- The build reads and checks all
examples/*/game.jsonandgames/*/game.jsonfiles before it builds. - It runs the
generatesteps of the selected games. Then it compiles the score and phrases of the audio demo. - It compiles all
src/*.cfiles except the diagnostic. It assembles allsrc/target/*.sfiles except the startup. It puts them in thelibtini.libarchive. - It links the diagnostic, the audio demo, each game and the test programs. Each link uses the startup object, the objects of the program,
libtini.liband the cc65none.lib. - It checks each linked layout. The SYS starts at $2000. Zero page stays in $80-$9F.
NEARBSSstays in the near arena. Low segments stay in the readable low range. No other segment is outside the application area. - It writes the ProDOS disks,
layout.json,memory.jsonandmanifest.json. It prints one memory line for each game.
The compiler commands are:
cc65 -t none --cpu 65c02 --standard c99 -O [cflags] -I SDK/include -I include -I src -I BUILD -I GAME -D TI_PRIVATE_TRANSPORT=0|1
ca65 --cpu 65c02 -t none -I SDK/asminc -I src/target
ld65 -C CONFIG -o NAME.system -m NAME.map -Ln NAME.lbl --dbgfile NAME.dbg startup.o OBJECTS libtini.lib SDK/lib/none.lib
Builds are deterministic. The same sources, options, installed SDK and ProDOS input give the same bytes. Object timestamps are fixed, and the build sets the informational timestamp of ca65 to a fixed value. No build uses the network. build/manifest.json records the hash of each input, tool, SDK header and output.
The game manifest
{
"name": "starfall",
"title": "STARFALL",
"sources": ["main.c", "game.c"],
"config": "examples/starfall/starfall.cfg",
"private": false,
"cflags": ["--static-locals"],
"generate": [
{
"command": ["{root}/tools/speech.py", "{game}/speech.json", "--output", "{build}/starfall_speech.h",
"--symbol", "sf_speech", "--no-numbers"],
"inputs": ["{root}/tools/speech.py", "speech.json"],
"outputs": ["{build}/starfall_speech.h"]
}
],
"disk": {"volume": "STARFALL", "system": "STARFALL.SYSTEM", "blocks": 1600}
}
| Key | Meaning |
|---|---|
name | Necessary. 1-15 characters: a lower-case letter, then lower-case letters, digits or _. The outputs are build/NAME.system, .po, .map, .lbl and .dbg. The name must be different from the names of other games and SDK programs. |
title | The build summary shows it. The default is the name. |
sources | Necessary. C files relative to the manifest. The first file defines main. |
assembly | ca65 files relative to the manifest. |
config | The linker layout, relative to the libtini folder. The default is cfg/libtini.cfg. |
cflags | More cc65 options, for example --static-locals for foreground code. |
private | false removes the game from --private builds. The default is true. |
generate | Commands that run in sequence before the compiler. See below. |
disk | A bootable ProDOS disk: volume (default: the name in capitals), system (default: NAME.SYSTEM), blocks (default: 1600, an 800 KiB disk) and files. |
The build stops for unknown keys, missing sources or a missing layout file.
Generate steps
Each step is {"command": [...], "inputs": [...], "outputs": [...]}. If the first element of a command ends in .py, the build runs it with its own Python.
{root} is the libtini folder, {game} is the folder of the manifest, and {build} is the build folder. Other relative paths in inputs, outputs and disk files are relative to the manifest.
The build puts the hashes of the inputs into build/manifest.json. For a folder, it uses all files in the folder. It records the outputs as build outputs. The build folder and the game folder are on the include path. Thus you include a generated header by its name only.
Extra disk files
Each entry of disk.files has a name and a maximum of one source:
pathcopies a file.programadds a different linked program, for example"libtini", the diagnostic.- An entry without a source makes an empty file.
The default ProDOS type is $06 for files, and $FF with auxiliary type $2000 for programs. type and aux change these values. The disk packager writes seedling and sapling files, each with a maximum of 131,072 bytes. It puts the SYS file of the game first, so that ProDOS starts it.
"disk": {"volume": "MYGAME", "system": "MYGAME.SYSTEM", "blocks": 280,
"files": [{"name": "RESOURCES.TPK", "path": "{build}/RESOURCES.TPK"},
{"name": "DIAG.SYSTEM", "program": "libtini"},
{"name": "SAVE.A"}]}
Outputs
| File | Contents |
|---|---|
build/libtini.lib | The library archive |
build/NAME.system | Each program as a raw ProDOS SYS file, loaded at $2000 |
build/NAME.po | Each bootable disk: the ProDOS boot blocks and kernel, then the program |
build/NAME.map, .lbl, .dbg | The ld65 map, the label file in VICE format and the debug file. The build does not include source-level debug data, because cc65 puts file timestamps in it. |
build/layout.json | All linked segments of all programs |
build/memory.json | The used and free bytes of each memory area of each program |
build/manifest.json | The profile, the firmware baseline, the tool versions and the hashes of all inputs and outputs |
build/asm/, build/obj/ | Staged assembler sources and objects |
A separate project
python examples/starter/create.py DESTINATION makes a project that contains all that it needs. In runtime/, it puts usual copies of:
- the public headers, the runtime sources and the assembly backends
cfg/libtini.cfg,tools/disk.py,tools/setup_inputs.py, the ProDOS lock file andtools/DEPENDENCIES.md.
It also writes the main.c of the starter, a build.py, a .gitignore, a README and SDK_INPUTS.json, which has the hash of each copied file. It does not copy objects, compilers, a ProDOS image or links.
python runtime/tools/setup_inputs.py
python build.py --cc65-home D:/Repos/cc65 [--no-disk]
The build.py of the project compiles all runtime sources into its own build/libtini.lib. It links main.c with runtime/cfg/libtini.cfg. It writes build/APP.SYSTEM, APP.map and APP.lbl, and APP.po if you do not give --no-disk. It has no manifest. To add sources and generated assets, edit the script.
Release archives
python tools/release.py --cc65-home D:/Repos/cc65 --verify-isolation
Before you run this command, build build/ and build/private/, each with --test-target. The command checks that no build input or output has changed since those builds.
With --verify-isolation, it copies the source files to a new folder in build/. It builds the two variants there with only installed tools and the pinned ProDOS input. The outputs must be identical.
Then it writes two archives. dist/libtini-source.zip contains the source without OS or ROM bytes. dist/libtini-local-release.zip also contains the built programs, the disks and the check records. The disks of the local release contain ProDOS. Read the ProDOS terms before you share it.