diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index ff0c696..1b0373c 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -56,17 +56,12 @@ emulator, and disk-image tool. From the repository root, build those tools first make ``` -Then build CosmOS and all of its applications: +That builds CosmOS, all of its applications, and a disk to boot them from - one makefile +covers the machine and the system. To rebuild only part of it: ```sh -cd Programs make cosmos -``` - -Build a fresh SBFS application disk as well: - -```sh -make cosmos-disk +make disk ``` To boot CosmOS with that disk attached: @@ -383,7 +378,7 @@ without stopping the machine. ## What Is On The Disk: -`make -C Programs cosmos-disk` builds the disk this system is meant to be met on, and it is +`make disk` builds the disk this system is meant to be met on, and it is laid out in three directories: | Where | What | @@ -551,7 +546,7 @@ which assembles its own now. ### A Clean Install: -`make cosmos-disk` in `Programs/` lays down a disk the machine can start itself from, and +`make disk` lays down a disk the machine can start itself from, and `make run-cosmos` starts it - with no boot image named, so the emulator shadows its ROM and reads the disk for everything else. diff --git a/Programs/makefile b/Programs/makefile deleted file mode 100644 index 0f8eed2..0000000 --- a/Programs/makefile +++ /dev/null @@ -1,269 +0,0 @@ -# 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 diff --git a/README.md b/README.md index 8ac1405..5974854 100644 --- a/README.md +++ b/README.md @@ -136,10 +136,12 @@ Assemble something and run it: Or boot the operating system, with a disk of programs and all of its own source on it: ``` -cd Programs -make run-cosmos +make run-voyager ``` +`make` builds a disk as well as the tools, so there is one to boot. `make run-cosmos` is the +same system in the terminal, for a machine with no graphics library. + Then `dir` to see what is there, `load Snake.sbx` and `run` to play something, or `load Asm.sbx` and `run cosmos.asm` to watch the machine build itself. Every source in `Programs/` is on that disk, under `/Source`, so anything not shipped as a @@ -578,11 +580,10 @@ A disk is at the lowest version that describes what is on it, so `format` makes The assembler is built to work with make. `-o` puts the output where the build system wants it, and `-M` writes out which libraries went into it, so that editing a library reassembles everything that includes it. -`Programs/makefile` does this for the programs in this repository: +The makefile does this for the programs in this repository: ``` -cd Programs -make +make programs ``` The rule it uses is small enough to copy into your own projects: diff --git a/SplitBit Assembler Manual.md b/SplitBit Assembler Manual.md index 328cf69..4367753 100644 --- a/SplitBit Assembler Manual.md +++ b/SplitBit Assembler Manual.md @@ -436,7 +436,7 @@ $(BUILD)/%.bin: %.asm -include $(BINARIES:.bin=.d) ``` -Programs/makefile in this repository builds every program that way, if you would like a longer example to copy. +The makefile in this repository builds every program that way, if you would like a longer example to copy. ## An Example SplitBit Assembly Program: diff --git a/makefile b/makefile index c41178d..bb1b1a5 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,25 @@ -# SplitBit Emulator and Assembler Makefile +# SplitBit Makefile # Anachronaut # 10/16/2024 +# +# ---- One makefile, three groups of target ---- +# +# make The tools, CosmOS, and a disk to boot it from. +# make run-voyager ... and start the machine that has a screen. +# make test The whole suite. +# make clean Everything any of it built. +# +# THE PLATFORM AND THE SYSTEM ARE SEPARATE TARGETS, NOT SEPARATE MAKEFILES. SplitBit is a +# machine and CosmOS is a program for it, and somebody who wants to write their own system +# should be able to build the tools and ignore the rest - which is what 'make SplitBit +# Assembler' is for. That separation lives in the target graph, where make enforces it. +# +# It used to live in a second makefile under Programs/, and that did not work: nothing at +# this level ever ran it, so it rotted. It named two source files that had been renamed +# months earlier and simply failed; the disk did not depend on the tree it mirrors, so new +# files silently were not on it; and the disk was not in any default target, so 'make clean' +# threw it away and 'make' did not bring it back. Three bugs, one cause - a makefile nothing +# exercises is a makefile nobody notices is broken. # Compiler and flags CC ?= gcc @@ -81,16 +100,86 @@ HAVE_RAYLIB := $(shell printf '#include \nint main(void){return (int)G # Default target: the machine, its three host-side tools, and Voyager where it can be built. # +# ===================================================================================== +# Where the SplitBit programs are, and what they build into. +# ===================================================================================== +# +# ---- These are up here because 'all' below needs them ---- +# +# A prerequisite list is expanded WHEN MAKE READS THE LINE, so a variable defined further +# down expands to nothing and takes its target with it, silently. That is not a hypothetical: +# these sat at the bottom with the rules that use them, 'all' asked for $(COSMOS_DISK), and +# 'make' on a clean tree built every tool, said "Nothing to be done", and left no disk. +# +# Every path is built from the first two, so moving the tree is a change to those lines +# rather than a search through this file. +# ---- Where things are, said once ---- +# +# Every path below is built from these two, so moving the tree is a change to these lines +# rather than a search through the file. +PROG_DIR = Programs +PROG_BUILD = $(PROG_DIR)/build + +# The tools, as commands. Built by the rules above, and named here so the assembly rules +# read the way the shell would. +ASM_RUN = ./$(ASM_TARGET) +EMU_RUN = ./$(EMU_TARGET) +VOY_RUN = ./$(VOY_TARGET) +DISKTOOL = ./$(DSK_TARGET) + +# 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 $(PROG_DIR)/Libraries -I $(PROG_DIR)/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'. +PROGRAM_SOURCES = \ + 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 = $(PROGRAM_SOURCES:%.asm=$(PROG_BUILD)/%.bin) +PROG_DEPS = $(BINARIES:.bin=.d) + +COSMOS = $(PROG_BUILD)/CosmOS/Source/cosmos.bin +APPS = $(patsubst $(PROG_DIR)/CosmOS/Apps/%.asm,$(PROG_BUILD)/CosmOS/Apps/%.sbx,\ + $(wildcard $(PROG_DIR)/CosmOS/Apps/*.asm)) +COSMOS_DISK = $(PROG_BUILD)/cosmos.img + + # VOYAGER IS NOT IN THE HARD LIST. Everything below it - the assembler, the disk tool, the # linter, the whole test suite - has to build on a machine with no graphics library at all, # because a project about a small understandable CPU should not need OpenGL to run its # tests. Where Raylib is missing, 'make' says so once and builds everything else. TOOLS = $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET) +# ---- What 'make' builds ---- +# +# The tools AND a bootable disk. A machine with nothing in the drive does not do anything, +# and the first thing anybody wants after building this is to see CosmOS come up - so the +# default build hands them one. It costs half a second: the C assembler goes through the +# whole system, twenty six apps and the native assembler in less time than the link. +# +# This is also the answer to 'make clean' leaving no disk behind, which is what a disk that +# no default target built did every time. ifeq ($(HAVE_RAYLIB),yes) -all: $(TOOLS) $(VOY_TARGET) +all: $(TOOLS) $(VOY_TARGET) $(COSMOS_DISK) else -all: $(TOOLS) +all: $(TOOLS) $(COSMOS_DISK) @echo "Raylib was not found, so Voyager was not built. Everything else is here." endif @@ -287,6 +376,7 @@ bless: $(EMU_TARGET) $(ASM_TARGET) clean: rm -rf $(OBJ_DIR) rm -rf Tests/build + rm -rf $(PROG_BUILD) rm -f $(SRC_DIR_EMU)/rom.c rm -f $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET) $(VOY_TARGET) @@ -295,5 +385,238 @@ install: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET) mkdir -p "$(PREFIX)/bin" install -m 755 $^ "$(PREFIX)/bin/" + +# ===================================================================================== +# The SplitBit programs: everything written in assembly rather than in C. +# ===================================================================================== +# + +programs: $(BINARIES) + +# ---- Assembling needs the assembler ---- +# +# An ORDER ONLY prerequisite, which is the bar after the pipe. It makes sure the assembler +# exists before anything is assembled with it - which the two makefile arrangement got for +# free, because you had already built the tools before you went into Programs. It is not an +# ordinary prerequisite because that would reassemble every program on every relink of the +# assembler, which is a rebuild of the whole system whenever a C file changes. +# +# -M writes out which source files went into the binary, in the form of a make rule. +$(PROG_BUILD)/%.bin: $(PROG_DIR)/%.asm | $(ASM_TARGET) + @mkdir -p $(@D) + $(ASM_RUN) $(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 the build +# directory, which worked while every program sat at the top of Programs/ 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 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 $(PROGRAM_SOURCES) | tr ' ' '\n' | sed 's|.*/||;s|\.asm$$||;s|^| |'; \ + exit 1; \ + fi; \ + $(MAKE) --no-print-directory "$$target" && $(EMU_RUN) "$$target" + +# ---- CosmOS ---- +# +# make cosmos Assemble the system and everything it can load. +# make 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. + + +# ---- 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. It is the ROM, built into the emulator by the rule further up from +# the same source, which is what makes it the one part of this that a disk cannot replace. +# That the two now sit in one file is an improvement on its own: the ROM rule and the boot +# slot rule are the same boot chain and used to be a directory apart. +STAGE2 = $(PROG_BUILD)/Boot/stage2.raw +PROG_DEPS += $(PROG_BUILD)/Boot/stage2.d + +$(STAGE2): $(PROG_DIR)/Boot/stage2.asm | $(ASM_TARGET) + @mkdir -p $(@D) + $(ASM_RUN) $(INCLUDES) -M $(PROG_BUILD)/Boot/stage2.d \ + -o $(PROG_BUILD)/Boot/stage2.sbx $< + tail -c +17 $(PROG_BUILD)/Boot/stage2.sbx > $@ + +PROG_DEPS += $(APPS:.sbx=.d) + +$(PROG_BUILD)/CosmOS/Apps/%.sbx: $(PROG_DIR)/CosmOS/Apps/%.asm | $(ASM_TARGET) + @mkdir -p $(@D) + $(ASM_RUN) $(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 = $(PROG_BUILD)/CosmOS/Assembler/Asm.sbx +PROG_DEPS += $(NATIVE_ASM:.sbx=.d) + +$(NATIVE_ASM): $(PROG_DIR)/CosmOS/Assembler/Asm.asm | $(ASM_TARGET) + @mkdir -p $(@D) + $(ASM_RUN) $(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 ---- +# +# 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 $(PROG_DIR) -name '*.asm' -not -path '$(PROG_BUILD)/*' 2>/dev/null) +$(COSMOS_DISK): $(MIRRORED) +$(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) \ + $(PROG_DIR)/testPrograms/stringKeyword.asm \ + $(wildcard $(PROG_DIR)/CosmOS/Apps/*.asm) \ + $(wildcard $(PROG_DIR)/CosmOS/Source/*.asm) \ + $(wildcard $(PROG_DIR)/CosmOS/Assembler/*.asm) | $(DSK_TARGET) + @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. + @# + @# THE DIRECTORY IT WALKS IS NAMED, and that is load bearing now that this file sits at + @# the top of the tree. It used to be "." and mean Programs/, because the makefile was + @# in there. Left as "." it would mean the whole repository - the C sources, the tests, + @# the manuals - mirrored onto a disk for an 8-bit machine. + @# + @# 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 $@ $(PROG_DIR) /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 $@ $(PROG_DIR)/Libraries /Lib + @for f in console fileStream sbfs services text config; do \ + $(DISKTOOL) put $@ $(PROG_DIR)/CosmOS/Source/$$f.asm /Lib/$$f.asm >/dev/null \ + || exit 1; done + @for f in classify labels numbers scratch source table token vectors; do \ + $(DISKTOOL) put $@ $(PROG_DIR)/CosmOS/Assembler/$$f.asm /Lib/$$f.asm >/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. +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_RUN) --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_RUN) --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) + $(VOY_RUN) --disk $(COSMOS_DISK) + +run-voyager-direct: $(COSMOS) $(COSMOS_DISK) + $(VOY_RUN) --disk $(COSMOS_DISK) $(COSMOS) + +# Pull in the dependency rules the assembler wrote with -M, so that touching a library +# reassembles every program that includes it. +-include $(PROG_DEPS) + # Phony targets -.PHONY: all clean install test bless sanitize +.PHONY: all clean install test bless sanitize strict \ + programs cosmos disk run-cosmos run-cosmos-direct run-voyager run-voyager-direct