# 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/16bitSegmentedSieve.asm \ Examples/primeSieve/16bitSegmentedSieveModern.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. $(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. $(DISKTOOL) format $@ 4096 24 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, because an assembler with nothing to assemble is a @# demonstration of nothing: @# @# > cd /Source @# /Source> Asm cosmos.asm the system it is running on @# /Source> Asm Asm.asm and the thing that built it @# @# Both come out byte for byte what the host tool makes from the same source. @# @# Keys.asm brings a vector of its own, so assembling it exercises the version two @# header and the Vector Segment: the loader installs its handler, the console @# interrupts into it, and the shell takes the vector back at exit. @# @# strings.asm is the odd one out on purpose. It has no #Include and no #Base, so it @# comes out as a boot image rather than a loadable program, and the difference between @# the two is visible on one disk. $(DISKTOOL) put $@ CosmOS/Source/cosmos.asm /Source/cosmos.asm $(DISKTOOL) put $@ CosmOS/Assembler/Asm.asm /Source/Asm.asm $(DISKTOOL) put $@ CosmOS/Assembler/readTest.asm /Source/readTest.asm $(DISKTOOL) put $@ CosmOS/Assembler/tokenTest.asm /Source/tokenTest.asm $(DISKTOOL) put $@ CosmOS/Apps/hello.asm /Source/hello.asm $(DISKTOOL) put $@ CosmOS/Apps/Say.asm /Source/Say.asm $(DISKTOOL) put $@ CosmOS/Apps/Keys.asm /Source/Keys.asm $(DISKTOOL) put $@ testPrograms/stringKeyword.asm /Source/strings.asm @# And the loader, so the machine can rebuild what starts it. Assembling stage2.asm on @# the machine and writing the result into the other boot slot is the whole of a @# self-hosted boot chain, and everything it includes is already in /Lib. $(DISKTOOL) put $@ Boot/stage1.asm /Source/stage1.asm $(DISKTOOL) put $@ Boot/stage2.asm /Source/stage2.asm @# And what those include. Everything here is named by an #Include somewhere and by @# nothing else, which is exactly what makes it a library rather than a source. @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