# SplitBit Programs Makefile # Anachronaut # # Builds every SplitBit program into build/, and keeps track of which libraries # each one includes so that editing a library reassembles whatever depends on it. # # make Assemble everything. # make clean Throw away build/. # make run-hello Assemble and run one program. ASM ?= ../Assembler EMU ?= ../SplitBit BUILD ?= build # Libraries are included by bare name, so the assembler is told where to find them. # CosmOS owns the filesystem library and the service names, so it is a place to look too. INCLUDES = -I Libraries -I CosmOS/Source # The programs worth building. Every one lives in a directory that says what kind it is: # Examples/ is what you read to learn, Loader/ is the standalone loader CosmOS grew out of, # CosmOS/ is the system. Files in Libraries/ are left out because they have no entry point # of their own, and the ones in testPrograms/ are covered by 'make test' in the parent # directory. PROGRAMS = \ CosmOS/Source/cosmos.asm \ Examples/hello.asm \ Examples/printHello.asm \ Examples/inputTest.asm \ Examples/replCalculator.asm \ Examples/Fibonacci/8bitFibonacci.asm \ Examples/Fibonacci/16bitFibonacci.asm \ Examples/Fibonacci/32bitFibonacci.asm \ Examples/primeSieve/8bitSieve.asm \ Examples/primeSieve/16bitSieve.asm \ Examples/primeSieve/16bitSieveModern.asm \ Examples/gameOfLife/16x16Life.asm \ Examples/gameOfLife/16x16LifeModern.asm \ Loader/loader.asm \ Loader/loadable.asm BINARIES = $(PROGRAMS:%.asm=$(BUILD)/%.bin) DEPENDENCIES = $(BINARIES:.bin=.d) all: $(BINARIES) # -M writes out which source files went into the binary, in the form of a make rule. $(BUILD)/%.bin: %.asm @mkdir -p $(@D) $(ASM) $(INCLUDES) -M $(@:.bin=.d) -o $@ $< # Assemble and run a single program, as in 'make run-hello'. # # THE NAME IS THE PROGRAM'S, NOT ITS PATH. This used to be a pattern rule against # $(BUILD)/%.bin, which worked while every program sat at the top of this directory and # stopped working the moment they were filed into Examples/ - 'make run-hello' had nothing # to match. Somebody trying the one command the header advertises should not be the way # that is discovered, so the name is now looked up among the programs instead. run-%: @target=`echo $(BINARIES) | tr ' ' '\n' | grep -E "(^|/)$*\.bin$$" | head -1`; \ if [ -z "$$target" ]; then \ echo "There is no program called '$*'. What there is:"; \ echo $(PROGRAMS) | tr ' ' '\n' | sed 's|.*/||;s|\.asm$$||;s|^| |'; \ exit 1; \ fi; \ $(MAKE) --no-print-directory "$$target" && $(EMU) "$$target" # ---- CosmOS ---- # # make cosmos Assemble the system and everything it can load. # make cosmos-disk ... and put the loadable programs on a disk image. # make run-cosmos ... and boot the machine with that disk in the drive. # make run-voyager ... and boot the machine that has a screen instead of a terminal. # # Programs in Apps/ say where they live with #Base, so the assembler writes them out as # loadable programs rather than as boot images. They are named .sbx to keep that # difference visible: a .bin is something the machine boots, a .sbx is something a # running system loads. DISKTOOL ?= ../SplitDisk COSMOS = $(BUILD)/CosmOS/Source/cosmos.bin APPS = $(patsubst CosmOS/Apps/%.asm,$(BUILD)/CosmOS/Apps/%.sbx,$(wildcard CosmOS/Apps/*.asm)) COSMOS_DISK = $(BUILD)/cosmos.img # ---- What starts the machine ---- # # Stage two goes into a boot slot as RAW BYTES: stage one reads blocks into Program Memory # and jumps to the first one, so a sixteen byte header would be sixteen bytes of nonsense # executed first. Its Data Segment travels with it and it copies that down itself. # # Stage one is not here at all. It is the ROM, built into the emulator by the top level # makefile from the same source, which is what makes it the one part of this that a disk # cannot replace. STAGE2 = $(BUILD)/Boot/stage2.raw DEPENDENCIES += $(BUILD)/Boot/stage2.d $(STAGE2): Boot/stage2.asm @mkdir -p $(@D) $(ASM) $(INCLUDES) -M $(BUILD)/Boot/stage2.d -o $(BUILD)/Boot/stage2.sbx $< tail -c +17 $(BUILD)/Boot/stage2.sbx > $@ DEPENDENCIES += $(APPS:.sbx=.d) $(BUILD)/CosmOS/Apps/%.sbx: CosmOS/Apps/%.asm @mkdir -p $(@D) $(ASM) $(INCLUDES) -M $(@:.sbx=.d) -o $@ $< # The assembler that runs on the machine. It is not in Apps/ because it is not one file: # it has a directory of its own, the way the C assembler does. Its own pieces are found # beside it without being told, since an include is looked for next to the file that asked # for it before anywhere else; only services.asm needs the include path. NATIVE_ASM = $(BUILD)/CosmOS/Assembler/Asm.sbx DEPENDENCIES += $(NATIVE_ASM:.sbx=.d) $(NATIVE_ASM): CosmOS/Assembler/Asm.asm @mkdir -p $(@D) $(ASM) $(INCLUDES) -M $(@:.sbx=.d) -o $@ $< cosmos: $(COSMOS) $(APPS) $(NATIVE_ASM) # Made from scratch every time, so that what is on it is what is in Apps/ now and not # also whatever used to be. # # TWENTY FOUR DIRECTORY BLOCKS, WHICH IS ONE HUNDRED AND NINETY TWO NAMES. It was eight, # and that is sixty four, of which thirty nine were already spoken for. The two ceilings a # disk has were nowhere near each other: at the average file on here, twenty six blocks, # sixty four names run out with the disk forty one per cent full. Names were going to be # gone long before space was. # # A directory block is 256 bytes and holds eight entries, so the difference costs sixteen # blocks of four thousand and ninety six - three tenths of one per cent - to buy a hundred # and twenty eight more names. The superblock has carried this number per disk since the # format was written, so nothing but this line knows what it is. # ---- And on the recipe that lays it out ---- # # Changing HOW the disk is built has to rebuild the disk. It did not, so adding the libraries # to /Lib left an image that had been made without them, and the next run said the same thing # was still missing - which sends you looking at the change you just made rather than at the # stale thing in front of you. $(COSMOS_DISK): makefile # ---- And on everything it mirrors ---- # # Found the same way as the note above, one layer up. The mirror exists so that adding a file # is the whole of putting it on the disk, and that only works if adding a file also REBUILDS # the disk - but the prerequisites were as hand-maintained as the list the mirror replaced. # tune.asm went into Examples, the image was not remade, and it was simply not there to # assemble on the machine. Nothing said so, which is the failure a mirror is for. # # Found rather than wildcarded, because the tree the mirror walks is a tree and $(wildcard) # does not recurse. build is left out for the reason the mirror leaves it out. MIRRORED := $(shell find . -name '*.asm' -not -path './build/*' 2>/dev/null) $(COSMOS_DISK): $(MIRRORED) $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) testPrograms/stringKeyword.asm \ $(wildcard CosmOS/Apps/*.asm) \ $(wildcard CosmOS/Source/*.asm) $(wildcard CosmOS/Assembler/*.asm) @mkdir -p $(@D) rm -f $@ @# A BOOT AREA, so that this is a disk the machine can start itself from rather than @# one it has to be handed. Forty blocks a slot and two slots: stage two is about @# eight thousand bytes, and the second slot is what makes replacing it survivable, @# since raw blocks have no name and so nothing to rename. @# ---- Room for the whole source tree ---- @# @# Sixteen thousand blocks is four megabytes, which is absurd for a machine with 128K @# of memory and exactly right for a disk: the sources alone are 2,850 blocks and the @# point of mirroring them is that nobody has to think about it again when they add @# one. A hundred and twenty-eight directory blocks is 1,024 entries against the 149 @# the tree has now, for the same reason - it was 24, which is 192, and the mirror @# filled it on its first run. $(DISKTOOL) format $@ 16384 128 40 $(DISKTOOL) boot $@ $(STAGE2) 0 @# THREE DIRECTORIES, WHICH IS WHAT A CLEAN INSTALL LOOKS LIKE: what you run, what you @# assemble, and what those include. It was thirty nine files in one list with @# cosmos.asm sitting between fileStream.asm and sbfs.asm. @# @# The split is by ROLE rather than by which directory the host keeps them in. /Source @# holds the things you name to the assembler and /Lib the things they pull in, which is @# a distinction the host makes with -I and the machine now makes with a search path of @# its own: an include is looked for beside you and then in /Lib. Without that, every @# source that calls a service would have to sit in the same directory as services.asm @# and there would be nothing to organise. $(DISKTOOL) mkdir $@ /Apps $(DISKTOOL) mkdir $@ /Source $(DISKTOOL) mkdir $@ /Lib $(DISKTOOL) mkdir $@ /System $(DISKTOOL) mkdir $@ /System/Boot @# The system itself, as a file, which is the whole of what boot.cfg chooses between. @# No boot.cfg is written: stage two falls back to this name when there is none, and a @# clean install having nothing to configure is the right default. $(DISKTOOL) put $@ $(COSMOS) /System/Boot/cosmos.bin @# What you run. /Apps is the second place the shell looks when a word it does not know @# turns out to be a program, so anything in here starts by name from anywhere. @for app in $(APPS); do \ $(DISKTOOL) put $@ $$app /Apps/`basename $$app` >/dev/null || exit 1; done $(DISKTOOL) put $@ $(NATIVE_ASM) /Apps/Asm.sbx @# ---- What you assemble: all of it ---- @# @# MIRRORED RATHER THAN LISTED. A list in a makefile goes stale the moment somebody @# adds a program and forgets to name it here, and what they forgot is invisible until @# they go looking for it on the machine. Now putting a file where the others live is @# the whole of putting it on the disk. @# @# That matters most for the things nobody thought worth building an .sbx of. A demo @# that is not interesting enough to ship as a binary is still worth having the source @# of, because somebody curious can assemble it on the machine itself: @# @# > cd /Source/Examples @# /Source/Examples> Asm colours.asm @# @# build is left behind, because what a project builds is not what it wrote. Anything @# with a name longer than a directory entry holds is refused rather than skipped: a @# disk quietly missing a file is the failure a mirror exists to prevent. $(DISKTOOL) mirror $@ . /Source build @# ---- And the libraries proper ---- @# @# Programs/Libraries is what an #Include means when it is not a CosmOS source: print, @# the integer helpers, the maths. They were never here, because /Lib was a hand-written @# list and nobody thought of them - so Sieve-16.asm, Life.asm and Fib-16.asm could be @# read on the machine and not assembled on it, and the assembler said only "nothing was @# written". Mirrored, so that the next one nobody thinks of is here anyway. $(DISKTOOL) mirror $@ Libraries /Lib @for f in CosmOS/Source/console.asm CosmOS/Source/fileStream.asm \ CosmOS/Source/sbfs.asm CosmOS/Source/services.asm CosmOS/Source/text.asm \ CosmOS/Source/config.asm \ CosmOS/Assembler/classify.asm CosmOS/Assembler/labels.asm \ CosmOS/Assembler/numbers.asm CosmOS/Assembler/scratch.asm \ CosmOS/Assembler/source.asm CosmOS/Assembler/table.asm \ CosmOS/Assembler/token.asm CosmOS/Assembler/vectors.asm; do \ $(DISKTOOL) put $@ $$f /Lib/`basename $$f` >/dev/null || exit 1; done # The system as well as the disk. Building only the image leaves whatever cosmos.bin was # there before, or none at all, and then the disk is booted with a system that does not # match the programs on it. cosmos-disk: $(COSMOS) $(COSMOS_DISK) # THE MACHINE STARTS ITSELF. No image is named, so the emulator shadows its ROM into # Program Memory and that reads the disk for everything else - a boot slot, then a loader, # then whatever /System/Boot/boot.cfg names, or cosmos.bin when it names nothing. run-cosmos: $(COSMOS_DISK) $(EMU) --disk $(COSMOS_DISK) # The same disk with the system handed over directly instead, which is what a debugger # does: memory is placed from outside and nothing on the disk is consulted about it. Useful # when the thing being debugged is the boot chain itself, since it skips the boot chain. run-cosmos-direct: $(COSMOS) $(COSMOS_DISK) $(EMU) --disk $(COSMOS_DISK) $(COSMOS) # ---- The same disk, on the machine with a screen ---- # # Voyager rather than SplitBit, which is the only difference: same disk, same system, same # programs, presented through a window instead of a terminal. # # IT DEPENDS ON THE DISK, and that matters more than it looks. What is on a disk is whatever # was built when the disk was made, so a machine whose console has changed will happily boot # an image full of programs written for the old one - and they will draw whatever the old # way now means. Making the disk a dependency of running it is what stops that being a # puzzle. run-voyager: $(COSMOS_DISK) ../Voyager --disk $(COSMOS_DISK) run-voyager-direct: $(COSMOS) $(COSMOS_DISK) ../Voyager --disk $(COSMOS_DISK) $(COSMOS) clean: rm -rf $(BUILD) # Pull in the dependency rules written by -M above, so that touching a library # reassembles every program that includes it. -include $(DEPENDENCIES) .PHONY: all clean cosmos cosmos-disk run-cosmos run-cosmos-direct run-voyager run-voyager-direct