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]
OptionEffect
--cc65-home DIRThe installed cc65 2.19 SDK. The default is CC65_HOME, then the SDK of the cc65 on PATH.
--game NAMEBuild the library, the diagnostic, this game and the programs that its disk lists. Without this option, the build uses all manifests.
--no-diskLink only the programs and the library. No ProDOS input is necessary.
--privateCompile with TI_PRIVATE_TRANSPORT=1 and link the optional AMEM transport. The build does not include games whose manifest sets "private": false.
--build-dir DIRThe output folder in the project. The default is build. Use build/private for the AMEM variant.
--test-targetAlso link the 65C02 test programs that the checks run.
--prodos-image FILEUse a different installed copy of ProDOS 8 2.4.3. It must have the pinned SHA-256.

The build steps

  1. The build reads and checks all examples/*/game.json and games/*/game.json files before it builds.
  2. It runs the generate steps of the selected games. Then it compiles the score and phrases of the audio demo.
  3. It compiles all src/*.c files except the diagnostic. It assembles all src/target/*.s files except the startup. It puts them in the libtini.lib archive.
  4. 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.lib and the cc65 none.lib.
  5. It checks each linked layout. The SYS starts at $2000. Zero page stays in $80-$9F. NEARBSS stays in the near arena. Low segments stay in the readable low range. No other segment is outside the application area.
  6. It writes the ProDOS disks, layout.json, memory.json and manifest.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}
}
KeyMeaning
nameNecessary. 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.
titleThe build summary shows it. The default is the name.
sourcesNecessary. C files relative to the manifest. The first file defines main.
assemblyca65 files relative to the manifest.
configThe linker layout, relative to the libtini folder. The default is cfg/libtini.cfg.
cflagsMore cc65 options, for example --static-locals for foreground code.
privatefalse removes the game from --private builds. The default is true.
generateCommands that run in sequence before the compiler. See below.
diskA 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:

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

FileContents
build/libtini.libThe library archive
build/NAME.systemEach program as a raw ProDOS SYS file, loaded at $2000
build/NAME.poEach bootable disk: the ProDOS boot blocks and kernel, then the program
build/NAME.map, .lbl, .dbgThe 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.jsonAll linked segments of all programs
build/memory.jsonThe used and free bytes of each memory area of each program
build/manifest.jsonThe 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:

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.