From 53798d6ee1cd0c62f7888ae4afc124f6d9aa49a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Georges=20Dup=C3=A9ron?= <georges.duperon@gmail.com>
Date: Wed, 11 Jul 2018 17:53:34 +0200
Subject: [PATCH] Moved makefile one level up (part 2: modify), rearchitectured
 the offset calculation to use temporary files, so that dependencies are
 easier to express.

---
 .gitignore              | 130 ++++++++++++--
 .travis.yml             |   4 +-
 Makefile                | 387 ++++++++++++++++++++++++++++------------
 test/gui-sh.sh          |   5 +-
 test/qemu-system-arm.sh |   3 +-
 5 files changed, 388 insertions(+), 141 deletions(-)

diff --git a/.gitignore b/.gitignore
index e6df7cb..5a2e6ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,19 +1,113 @@
+/build/check_makefile
+/build/check_makefile_targets
+/build/check_makefile_w_arnings
+/build/iso_files/boot/iso_boot.sys
+/build/iso_files/os.zip
+/build/makefile_built_directories
+/build/makefile_built_files
+/build/makefile_database
+/build/makefile_database_files
+/build/makefile_file_targets
+/build/makefile_non_file_targets
+/build/makefile_phony
+/build/makefile_targets
+/build/makefile_w_arnings
+/build/offsets/bytes_fat12_end.dec
+/build/offsets/bytes_fat12_end.hex
+/build/offsets/bytes_fat12_size.dec
+/build/offsets/bytes_fat12_size.hex
+/build/offsets/bytes_fat12_start.dec
+/build/offsets/bytes_fat12_start.hex
+/build/offsets/bytes_gpt_mirror_end.dec
+/build/offsets/bytes_gpt_mirror_end.hex
+/build/offsets/bytes_gpt_mirror_size.dec
+/build/offsets/bytes_gpt_mirror_size.hex
+/build/offsets/bytes_gpt_mirror_start.dec
+/build/offsets/bytes_gpt_mirror_start.hex
+/build/offsets/bytes_header_32k_end.dec
+/build/offsets/bytes_header_32k_end.hex
+/build/offsets/bytes_header_32k_size.dec
+/build/offsets/bytes_header_32k_size.hex
+/build/offsets/bytes_header_32k_start.dec
+/build/offsets/bytes_header_32k_start.hex
+/build/offsets/bytes_iso_end.dec
+/build/offsets/bytes_iso_end.hex
+/build/offsets/bytes_iso_size.dec
+/build/offsets/bytes_iso_size.hex
+/build/offsets/bytes_iso_start.dec
+/build/offsets/bytes_iso_start.hex
+/build/offsets/bytes_mbr_end.dec
+/build/offsets/bytes_mbr_end.hex
+/build/offsets/bytes_mbr_start.dec
+/build/offsets/bytes_mbr_start.hex
+/build/offsets/bytes_os_size.dec
+/build/offsets/bytes_os_size.hex
+/build/offsets/bytes_zip_end.dec
+/build/offsets/bytes_zip_end.hex
+/build/offsets/bytes_zip_size.dec
+/build/offsets/bytes_zip_size.hex
+/build/offsets/bytes_zip_start.dec
+/build/offsets/bytes_zip_start.hex
+/build/offsets/sectors_fat12_size.dec
+/build/offsets/sectors_fat12_size.hex
+/build/offsets/sectors_fat12_start.dec
+/build/offsets/sectors_fat12_start.hex
+/build/offsets/sectors_gpt_mirror_size.dec
+/build/offsets/sectors_gpt_mirror_size.hex
+/build/offsets/sectors_iso_size.dec
+/build/offsets/sectors_iso_size.hex
+/build/offsets/sectors_os_size.dec
+/build/offsets/sectors_os_size.hex
+/build/offsets/sectors_zip_size.dec
+/build/offsets/sectors_zip_size.hex
+/build/offsets/tracks_fat12_size.dec
+/build/offsets/tracks_fat12_size.hex
+/build/offsets/tracks_gpt_mirror_size.dec
+/build/offsets/tracks_gpt_mirror_size.hex
+/build/offsets/tracks_iso_size.dec
+/build/offsets/tracks_iso_size.hex
+/build/offsets/tracks_os_size.dec
+/build/offsets/tracks_os_size.hex
+/build/offsets/tracks_zip_size.dec
+/build/offsets/tracks_zip_size.hex
+/build/os.32k
+/build/os.fat12
+/build/os.fdisk
+/build/os.file
+/build/os.hex_with_offsets
+/build/os.iso
+/build/os.ndisasm.disasm
+/build/os.offsets
+/build/os.reasm
+/build/os.reasm.asm
+/build/os.zip
+/build/os.zip.adjusted
+/build/test_pass/emu_bochs
+/build/test_pass/emu_dosbox
+/build/test_pass/emu_gui-sh
+/build/test_pass/emu_qemu-system-arm
+/build/test_pass/emu_qemu-system-i386-cdrom
+/build/test_pass/emu_qemu-system-i386-floppy
+/build/test_pass/emu_virtualbox
+/build/test_pass/noemu_fat12_contents
+/build/test_pass/noemu_os.reasm
+/build/test_pass/noemu_sizes
+/build/test_pass/noemu_zip
+/build/test_pass/sudo_fat12_mount
+/build/test_pass/sudo_iso_mount
+/deploy-screenshots
+/deploy-screenshots/bochs-anim.gif
+/deploy-screenshots/bochs.png
+/deploy-screenshots/dosbox-anim.gif
+/deploy-screenshots/dosbox.png
+/deploy-screenshots/gui-sh-anim.gif
+/deploy-screenshots/gui-sh.png
+/deploy-screenshots/qemu-system-arm-anim.gif
+/deploy-screenshots/qemu-system-arm.png
+/deploy-screenshots/qemu-system-i386-cdrom-anim.gif
+/deploy-screenshots/qemu-system-i386-cdrom.png
+/deploy-screenshots/qemu-system-i386-floppy-anim.gif
+/deploy-screenshots/qemu-system-i386-floppy.png
+/deploy-screenshots/virtualbox-anim.gif
+/deploy-screenshots/virtualbox.png
 /os.bat
-/os.ndisasm.disasm
-/os.reasm.asm
-/os.reasm
-/os.reasm.disasm
-/os.file
-/os.fdisk
-/os.arm.disasm
-/os.zip
-/os.zip.adjusted
-/os.iso
-/os.32k
-/os.fat12
-/os.offsets
-/os.hex_with_offsets
-/iso_files/os.zip
-/iso_files/boot/iso_boot.sys
-/build/offsets/fat12_start.hex
-/build/offsets/fat12_start.dec
diff --git a/.travis.yml b/.travis.yml
index a634f5e..2468061 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,7 +25,7 @@ addons:
 matrix:
   include:
     - env: MODE=self-test # Ensure that the Makefile works, especially with parallel builds.
-      script: (cd example-os && make -j 10 test)
+      script: make -j 10 test
       sudo: true
       addons:
         apt:
@@ -95,7 +95,7 @@ install:
   - if test "$MODE" = virtualbox -o "$MODE" = self-test; then ./utils/install-virtualbox.sh; fi
 
 script:
-  - (cd example-os && make test/${MODE})
+  - make test/${MODE}
   - |
     ./utils/auto-push.sh "https://github.com/jsmaniac/travis-os.git" \
                          "git@github.com:jsmaniac/travis-os-deploy-artifacts.git" \
diff --git a/Makefile b/Makefile
index 26c12d2..c67d32b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,86 @@
+MAKEFLAGS = --warn-undefined-variables
+SHELL = bash -euET -o pipefail -c
+.SECONDEXPANSION:
+
 os_filename = os.bat
 tests_emu = test/qemu-system-i386-floppy test/qemu-system-i386-cdrom test/qemu-system-arm test/virtualbox test/bochs test/gui-sh test/dosbox
 tests_requiring_sudo = test/fat12_mount test/iso_mount
-tests_noemu = test/zip os.reasm test/sizes test/fat12_contents
-built_files = $(os_filename) os.ndisasm.disasm os.reasm.asm os.reasm os.reasm.disasm os.file os.fdisk os.arm.disasm os.zip os.zip.adjusted os.iso os.32k os.fat12 os.offsets os.hex_with_offsets iso_files/os.zip iso_files/boot/iso_boot.sys build/offsets/fat12_start.hex build/offsets/fat12_start.dec
-# TODO: auto-add the .dec files for each .hex offset.
-built_directories = iso_files/boot iso_files build/offsets build/mnt_fat12 build/mnt_iso build
+tests_noemu = test/zip test/os.reasm test/sizes test/fat12_contents
+
+offset_names = bytes_os_size \
+               bytes_mbr_start \
+               bytes_mbr_end \
+               bytes_header_32k_start \
+               bytes_header_32k_end \
+               bytes_iso_start \
+               bytes_iso_end \
+               bytes_fat12_start \
+               bytes_fat12_end \
+               bytes_gpt_mirror_start \
+               bytes_gpt_mirror_end \
+               bytes_zip_start \
+               bytes_zip_end
+
+more_offset_names = ${offset_names} \
+                    bytes_fat12_size \
+                    bytes_gpt_mirror_size \
+                    bytes_header_32k_size \
+                    bytes_iso_size \
+                    bytes_zip_size \
+                    sectors_fat12_size \
+                    sectors_fat12_start \
+                    sectors_gpt_mirror_size \
+                    sectors_iso_size \
+                    sectors_os_size \
+                    sectors_zip_size \
+                    tracks_fat12_size \
+                    tracks_gpt_mirror_size \
+                    tracks_iso_size \
+                    tracks_os_size \
+                    tracks_zip_size
+
+more_offset_dec = ${more_offset_names:%=build/offsets/%.dec}
+more_offset_hex = ${more_offset_names:%=build/offsets/%.hex}
+
+# + os.arm.disasm
+# + os.reasm.disasm
+built_files = ${os_filename} \
+              build/check_makefile \
+              build/check_makefile_targets \
+              build/check_makefile_w_arnings \
+              build/makefile_built_directories \
+              build/makefile_built_files \
+              build/makefile_database \
+              build/makefile_database_files \
+              build/makefile_file_targets \
+              build/makefile_non_file_targets \
+              build/makefile_phony \
+              build/makefile_targets \
+              build/makefile_w_arnings \
+              build/os.ndisasm.disasm \
+              build/os.reasm.asm \
+              build/os.reasm \
+              build/os.file \
+              build/os.fdisk \
+              build/os.zip \
+              build/os.zip.adjusted \
+              build/os.iso \
+              build/os.32k \
+              build/os.fat12 \
+              build/os.offsets \
+              build/os.hex_with_offsets \
+              build/iso_files/os.zip \
+              build/iso_files/boot/iso_boot.sys \
+              ${more_offset_dec} \
+              ${more_offset_hex} \
+              ${tests_emu:test/%=build/test_pass/emu_%} \
+              ${tests_noemu:test/%=build/test_pass/noemu_%} \
+              ${tests_requiring_sudo:test/%=build/test_pass/sudo_%} \
+              ${tests_emu:test/%=deploy-screenshots/%.png} \
+              ${tests_emu:test/%=deploy-screenshots/%-anim.gif}
+
+built_directories = build/iso_files/boot build/iso_files build/offsets build/mnt_fat12 build/mnt_iso build/test_pass deploy-screenshots
+more_built_directories = ${built_directories} build
 
 os_image_size_kb = 1440
 os_partition_start_sectors = 3
@@ -15,19 +91,58 @@ os_floppy_chs_s = 9
 
 .PHONY: all
 # all: os.arm.disasm
-all: $(os_filename) os.ndisasm.disasm os.reasm.asm os.file os.fdisk os.offsets os.hex_with_offsets .gitignore Makefile
+all: ${os_filename} build/os.ndisasm.disasm build/os.reasm.asm build/os.file build/os.fdisk build/os.offsets build/os.hex_with_offsets .gitignore build/check_makefile ${more_offset_dec} ${more_offset_hex}
 
-../deploy-screenshots $(built_directories): Makefile
-	mkdir -p $@
-	touch $@
+build/makefile_w_arnings: | $${@D}
+${built_files}: | $${@D}
 
-iso_files/boot: iso_files
+build/makefile_w_arnings: Makefile
+	@make -n --warn-undefined-variables test 2>$@ 1>/dev/null || make -n --warn-undefined-variables test
+
+# Check that the file build/makefile_w_arnings is present, and that it does not contain the string "warn".
+build/check_makefile_w_arnings: build/makefile_w_arnings
+	@cat build/makefile_w_arnings > /dev/null && (! grep -i warn $<) && touch $@
+
+# Check that the declared list of built files matches the list of targets extracted from the Makefile.
+build/check_makefile_targets: build/makefile_built_files build/makefile_file_targets build/check_makefile_w_arnings
+	@diff build/makefile_built_files build/makefile_file_targets && touch $@
+
+build/check_makefile: build/check_makefile_w_arnings build/check_makefile_targets
+	@touch $@
+
+build/makefile_database: Makefile build/check_makefile_w_arnings
+	@make -rpn | sed -n -e '/^# Make data base,/,$$p' > $@
+
+build/makefile_database_files: build/makefile_database build/check_makefile_w_arnings
+	@sed -n -e '/^# Files$$/,/^# files hash-table stats:$$/p' $< > $@
+
+build/makefile_built_directories: build/check_makefile_w_arnings
+	@echo ${more_built_directories} | tr ' ' '\n' | grep -v '^\s*$$' | sort > $@
+
+build/makefile_built_files: build/check_makefile_w_arnings
+	@echo ${built_files} | tr ' ' '\n' | grep -v '^\s*$$' | sort > $@
+
+build/makefile_phony: build/makefile_database_files build/check_makefile_w_arnings
+	@sed -r -n -e 's/^.PHONY: (.*)$$/\1/p' $< | tr ' ' '\n' | grep -v '^\s*$$' | sort > $@
+
+build/makefile_targets: build/makefile_database_files build/check_makefile_w_arnings
+	@grep -E -v '^(\s|#|\.|$$|^[^:]*:$$)' $< | grep '^[^ :]*:' | sed -r -e 's|^([^:]*):.*$$|\1|' | sort > $@
+
+build/makefile_non_file_targets: build/makefile_phony build/makefile_built_directories build/check_makefile_w_arnings
+	@cat build/makefile_phony build/makefile_built_directories | sort > $@
+
+build/makefile_file_targets: build/makefile_non_file_targets build/makefile_targets build/check_makefile_w_arnings
+	@comm -23 build/makefile_targets build/makefile_non_file_targets > $@
+
+${built_directories}: build/check_makefile
+${more_built_directories}: Makefile
+	mkdir -p $@ && touch $@
 
 # 32k header of the ISO9660 image
-os.32k: os.asm Makefile
+build/os.32k: example-os/os.asm build/check_makefile
 	nasm -o $@ $<
 
-os.iso: iso_files/os.zip iso_files/boot/iso_boot.sys Makefile
+build/os.iso: build/iso_files/os.zip build/iso_files/boot/iso_boot.sys build/check_makefile
 	mkisofs \
 	 --input-charset utf-8 \
 	 -rock \
@@ -37,57 +152,79 @@ os.iso: iso_files/os.zip iso_files/boot/iso_boot.sys Makefile
 	 -no-emul-boot \
 	 -boot-load-size 4 \
 	 -pad \
-	 -output os.iso \
-	 ./iso_files/
+	 -output $@ \
+	 ./build/iso_files/
 
 # Layout:
 # MBR; GPT; UNIX sh & MS-DOS batch scripts; ISO9660; FAT12; GPT mirror; ZIP
 
+define offset
+tmp_${1} = ${3}
+build/offsets/${1}.dec: $${tmp_${1}:%=build/offsets/%.dec} build/check_makefile
+	echo $$$$(( ${2} )) > $$@
+${1} = $$$$(cat build/offsets/${1}.dec)
+dep_${1} = build/offsets/${1}.dec
+endef
+
+define div_round_up
+( ( ( ${1} ) + ( ${2} ) - 1 ) / ( ${2} ) )
+endef
+
 sector_size = 512
 # should be exact (TODO: make a check)
-os_total_size_sectors = ( $(os_image_size_kb)*1024 / $(sector_size) )
-os_total_size_tracks = ( $(os_total_size_sectors) / $(os_floppy_chs_s) )
+${eval ${call offset,bytes_os_size,     $${os_image_size_kb} * 1024,,                                 }}
+${eval ${call offset,sectors_os_size,   $${bytes_os_size}    / $${sector_size},                        bytes_os_size,}}
+${eval ${call offset,tracks_os_size,    $${sectors_os_size}  / $${os_floppy_chs_s},                    sectors_os_size,}}
+
 # round up
-iso_size_sectors = ( ( $$(wc -c os.iso | cut -d ' ' -f 1) + $(sector_size) - 1 ) / $(sector_size) )
-iso_size_tracks = ( ( $(iso_size_sectors) + $(os_floppy_chs_s) - 1 ) / $(os_floppy_chs_s) )
+${eval ${call offset,bytes_iso_size,    $$$$(wc -c build/os.iso | cut -d ' ' -f 1),                    ,build/os.iso}}
+${eval ${call offset,sectors_iso_size,  ${call div_round_up,$${bytes_iso_size},$${sector_size}},       bytes_iso_size,}}
+${eval ${call offset,tracks_iso_size,   ${call div_round_up,$${sectors_iso_size},$${os_floppy_chs_s}}, sectors_iso_size,}}
+
 # round up
-zip_size_sectors = ( ( $$(wc -c os.zip | cut -d ' ' -f 1) + $(sector_size) - 1 ) / $(sector_size) )
-zip_size_tracks = ( ( $(zip_size_sectors) + $(os_floppy_chs_s) - 1 ) / $(os_floppy_chs_s) )
-gpt_mirror_size_sectors = 33
-gpt_mirror_size_tracks = ( ( $(gpt_mirror_size_sectors) + $(os_floppy_chs_s) - 1 ) / $(os_floppy_chs_s) )
-# allocate the remaining sectors, aligned on tracks
-fat12_size_tracks = ( ( $(os_total_size_tracks) - $(iso_size_tracks) - $(gpt_mirror_size_tracks) - $(zip_size_tracks) ) )
-fat12_size_sectors = ( $(fat12_size_tracks) * $(os_floppy_chs_s) )
+${eval ${call offset,bytes_zip_size,    $$$$(wc -c build/os.zip | cut -d ' ' -f 1),                    ,build/os.zip}}
+${eval ${call offset,sectors_zip_size,  ${call div_round_up,$${bytes_zip_size},$${sector_size}},       bytes_zip_size,}}
+${eval ${call offset,tracks_zip_size,   ${call div_round_up,$${sectors_zip_size},$${os_floppy_chs_s}}, sectors_zip_size,}}
+
+# round up
+${eval ${call offset,sectors_gpt_mirror_size, 33,,                                                   }}
+${eval ${call offset,tracks_gpt_mirror_size,  ${call div_round_up,$${sectors_gpt_mirror_size},$${os_floppy_chs_s}}, sectors_gpt_mirror_size,}}
+
+# allocate the remaining sectors to the FAT, aligned on tracks
+${eval ${call offset,tracks_fat12_size, $${tracks_os_size} - $${tracks_iso_size} - $${tracks_gpt_mirror_size} - $${tracks_zip_size}, tracks_os_size tracks_iso_size tracks_gpt_mirror_size tracks_zip_size,}}
+${eval ${call offset,sectors_fat12_size,$${tracks_fat12_size} * $${os_floppy_chs_s},                   tracks_fat12_size,}}
+
 # zip should probably have its end aligned, not its start
-space_before_zip_bytes = ( $(os_image_size_kb)*1024 - $$(wc -c os.zip | cut -d ' ' -f 1) )
+${eval ${call offset,bytes_zip_start,   $${bytes_os_size} - $${bytes_zip_size},                        bytes_os_size bytes_zip_size,}}
 
-mbr_start        = 0
-mbr_end          = 512
-header_32k_start = 0
-header_32k_end   = ( 32 * 1024 )
-header_32k_size  = ( $(header_32k_end) - $(header_32k_start) )
-iso_start        = ( 32 * 1024 )
-iso_end          = ( $(iso_size_sectors) * $(sector_size) )
-fat12_start      = ( $(iso_size_tracks) * $(os_floppy_chs_s) * $(sector_size) )
-fat12_size       = ( $(fat12_size_sectors) * $(sector_size) )
-fat12_end        = ( $(fat12_start) + $(fat12_size) )
+${eval ${call offset,bytes_mbr_start,        0,,}}
+${eval ${call offset,bytes_mbr_end,          512,,}}
+${eval ${call offset,bytes_header_32k_start, 0,,}}
+${eval ${call offset,bytes_header_32k_end,   32 * 1024,,}}
+${eval ${call offset,bytes_header_32k_size,  $${bytes_header_32k_end} - $${bytes_header_32k_start},      bytes_header_32k_end bytes_header_32k_start,}}
+${eval ${call offset,bytes_iso_start,        32 * 1024,,}}
+${eval ${call offset,bytes_iso_end,          $${sectors_iso_size} * $${sector_size},                     sectors_iso_size,}}
+${eval ${call offset,bytes_fat12_start,      $${tracks_iso_size} * $${os_floppy_chs_s} * $${sector_size}, tracks_iso_size,}}
+${eval ${call offset,sectors_fat12_start,    $${bytes_fat12_start} / $${sector_size},                    bytes_fat12_start,}}
+${eval ${call offset,bytes_fat12_size,       $${sectors_fat12_size} * $${sector_size},                   sectors_fat12_size,}}
+${eval ${call offset,bytes_fat12_end,        $${bytes_fat12_start} + $${bytes_fat12_size},               bytes_fat12_start bytes_fat12_size,}}
 # It is probably not necessary to align the GPT mirror end on a track boundary.
-gpt_mirror_end   = ( $(fat12_end) + ( $(gpt_mirror_size_tracks) * $(os_floppy_chs_s) * $(sector_size) ) )
-gpt_mirror_start = ( $(gpt_mirror_end) - ( $(gpt_mirror_size_sectors) * $(sector_size) ) )
-zip_start        = $(space_before_zip_bytes)
-zip_end          = ( $(os_total_size_sectors) * $(sector_size) )
+${eval ${call offset,bytes_gpt_mirror_size,  $${sectors_gpt_mirror_size} + $${sector_size},              sectors_gpt_mirror_size,}}
+${eval ${call offset,bytes_gpt_mirror_end,   $${bytes_fat12_end} + $${bytes_gpt_mirror_size},            bytes_fat12_end bytes_gpt_mirror_size,}}
+${eval ${call offset,bytes_gpt_mirror_start, $${bytes_gpt_mirror_end} - $${bytes_gpt_mirror_size},       bytes_gpt_mirror_end bytes_gpt_mirror_size,}}
+${eval ${call offset,bytes_zip_end,          $${bytes_os_size},,                                         }}
 
-os_fat12_partition = "$@@@$$(( $(fat12_start) ))"
-os.fat12: os.zip os.iso Makefile
-	set -x; dd if=/dev/zero bs=$(sector_size) count=$$(( $(os_total_size_sectors) )) of=$@
+os_fat12_partition = "$@@@${bytes_fat12_start}"
+build/os.fat12: build/os.zip ${dep_bytes_fat12_size} ${dep_bytes_fat12_start} ${dep_sectors_os_size} build/check_makefile
+	set -x; dd if=/dev/zero bs=${sector_size} count=${sectors_os_size} of=$@
 	set -x; mformat -v "Example OS" \
-	 -T $$(( $(fat12_size_sectors) )) \
-	 -h $(os_floppy_chs_h) \
-	 -s $(os_floppy_chs_s) \
-	 -i $(os_fat12_partition)
-	set -x; mcopy -i $(os_fat12_partition) os.zip "::os.zip"
+	 -T ${sectors_fat12_size} \
+	 -h ${os_floppy_chs_h} \
+	 -s ${os_floppy_chs_s} \
+	 -i ${os_fat12_partition}
+	set -x; mcopy -i ${os_fat12_partition} build/os.zip "::os.zip"
 
-iso_files/os.zip: os.zip iso_files Makefile
+build/iso_files/os.zip: build/os.zip build/check_makefile
 # TODO: make it so that the various file formats are mutual quines:
 # * the ISO should contain the original file
 # * the ZIP should contain the original file
@@ -95,134 +232,146 @@ iso_files/os.zip: os.zip iso_files Makefile
 	cp $< $@
 
 # 4 sectors loaded when booting from optical media (CD-ROM, …):
-iso_files/boot/iso_boot.sys: os.32k iso_files/boot Makefile
+build/iso_files/boot/iso_boot.sys: build/os.32k build/check_makefile
+# TODO: this copy of the (or alternate) bootsector should contain a Boot Information Table,
+#       see https://wiki.osdev.org/El-Torito#A_BareBones_Boot_Image_with_Boot_Information_Table
 	dd if=$< bs=512 count=4 of=$@
 
-os.zip: os.32k Makefile
+build/os.zip: build/os.32k build/check_makefile
 	zip $@ $<
 
-os.zip.adjusted: os.zip Makefile
-	set -x; dd if=/dev/zero bs=1 count=$$(( $(space_before_zip_bytes) )) of=$@
+build/os.zip.adjusted: build/os.zip ${dep_bytes_zip_start} build/check_makefile
+# TODO: the ZIP file can end with a variable-length comment, this would allow us to hide the GPT mirrors.
+	set -x; dd if=/dev/zero bs=1 count=${bytes_zip_start} of=$@
 	cat $< >> $@
 	zip --adjust-sfx $@
 
-$(os_filename): os.32k os.iso os.fat12 os.zip os.zip.adjusted ../deploy-screenshots Makefile
+${os_filename}: build/os.32k build/os.iso build/os.fat12 build/os.zip.adjusted \
+                ${dep_bytes_header_32k_start} \
+                ${dep_bytes_header_32k_size} \
+                ${dep_bytes_fat12_start} \
+                ${dep_bytes_fat12_size} \
+                ${dep_bytes_gpt_mirror_end} \
+                ${dep_sectors_fat12_start} \
+                ${dep_sectors_fat12_size} \
+                ${dep_bytes_zip_start} \
+                build/check_makefile
 	rm -f $@
 # start with the .iso
-	cp os.iso $@
+	cp build/os.iso $@
 # splice in the first 32k (bootsector and partition table)
-	set -x; dd skip=$$(( $(header_32k_start) )) seek=$$(( $(header_32k_start) )) bs=1 count=$$(( $(header_32k_size) )) conv=notrunc if=os.32k of=$@
+	set -x; dd skip=${bytes_header_32k_start} seek=${bytes_header_32k_start} bs=1 count=${bytes_header_32k_size} conv=notrunc if=build/os.32k of=$@
 # splice in fat12
-	set -x; dd skip=$$(( $(fat12_start) )) seek=$$(( $(fat12_start) )) bs=1 count=$$(( $(fat12_size) )) conv=notrunc if=os.fat12 of=$@
+	set -x; dd skip=${bytes_fat12_start} seek=${bytes_fat12_start} bs=1 count=${bytes_fat12_size} conv=notrunc if=build/os.fat12 of=$@
 # pad with zeroes to prepare for GPT table
-	set -x; dd if=/dev/zero seek=$$(( $(gpt_mirror_end) - 1 )) bs=1 count=1 conv=notrunc of=$@
+	set -x; dd if=/dev/zero seek=$$((${bytes_gpt_mirror_end} - 1 )) bs=1 count=1 conv=notrunc of=$@
 # patch the partition table
-	printf "p\nd\nn\np\n1\n$$(( $(fat12_start) / $(sector_size) ))\n$$(( $(fat12_size_sectors) ))\nt\n01\na\n1\np\nw\nq\n" | fdisk $@
+	printf "p\nd\nn\np\n1\n${sectors_fat12_start}\n${sectors_fat12_size}\nt\n01\na\n1\np\nw\nq\n" | fdisk $@
 # Thanks to https://wiki.gentoo.org/wiki/Hybrid_partition_table for showing that gdisk can be used to make a hybrid MBR / GPT.
 # gdisk commands: recovery, make hybrid, add GPT partition #1 to the hybrid MBR, don't put the EFI partition first,
 #                 partition type=0x01, bootable=Y, don't add extra partitions, print GPT, print MBR, write, proceed, quit.
 	printf "r\nh\n1\nN\n01\nY\nN\np\no\nw\nY\nq\n" | gdisk $@
 # splice in zip at the end
-	set -x; dd skip=$$(( $(space_before_zip_bytes) )) seek=$$(( $(space_before_zip_bytes) )) bs=1 conv=notrunc if=os.zip.adjusted of=$@
+	set -x; dd skip=${bytes_zip_start} seek=${bytes_zip_start} bs=1 conv=notrunc if=build/os.zip.adjusted of=$@
 	chmod a+x-w $@
 
-os.file: $(os_filename) Makefile
+build/os.file: ${os_filename} build/check_makefile
 	file -kr $< > $@
 
-os.fdisk: $(os_filename) Makefile
+build/os.fdisk: ${os_filename} build/check_makefile
 	fdisk -l $< > $@
 
-os.offsets: $(os_filename) os.32k os.iso os.fat12 os.zip Makefile
-	printf 'total_size       = 0x%08x\n' $$(( $(os_total_size_sectors) * $(sector_size) ))  > $@
-	printf 'mbr_start        = 0x%08x\n' $$(( $(mbr_start) ))                              >> $@
-	printf 'mbr_end          = 0x%08x\n' $$(( $(mbr_end) ))                                >> $@
-	printf '32k_start        = 0x%08x\n' $$(( $(header_32k_start) ))                       >> $@
-	printf '32k_end          = 0x%08x\n' $$(( $(header_32k_end) ))                         >> $@
-	printf 'iso_start        = 0x%08x\n' $$(( $(iso_start) ))                              >> $@
-	printf 'iso_end          = 0x%08x\n' $$(( $(iso_end) ))                                >> $@
-	printf 'fat12_start      = 0x%08x\n' $$(( $(fat12_start) ))                            >> $@
-	printf 'fat12_end        = 0x%08x\n' $$(( $(fat12_end) ))                              >> $@
-	printf 'gpt_mirror_start = 0x%08x\n' $$(( $(gpt_mirror_start) ))                       >> $@
-	printf 'gpt_mirror_end   = 0x%08x\n' $$(( $(gpt_mirror_end) ))                         >> $@
-	printf 'zip_start        = 0x%08x\n' $$(( $(zip_start) ))                              >> $@
-	printf 'zip_end          = 0x%08x\n' $$(( $(zip_end) ))                                >> $@
+build/os.offsets: ${offset_names:%=build/offsets/%.hex} build/check_makefile
+	cat ${offset_names:%=build/offsets/%.hex} > $@
 
-# TODO: make each offset a separate file, dependencies will be simpler.
-build/offsets/fat12_start.hex: os.zip os.iso build/offsets Makefile
-	printf '0x%08x' $$(( $(fat12_start) ))                            > $@
+build/offsets/%.hex: build/offsets/%.dec
+	printf '%x\n' $$(cat $<) > $@
 
-build/offsets/%.dec: build/offsets/%.hex
-	printf '%d' $$(cat $<) > $@
-
-os.hex_with_offsets: $(os_filename) os.offsets
+build/os.hex_with_offsets: ${os_filename} build/os.offsets
 	hexdump -C $< \
-	 | grep -E -e "($$(cat os.offsets | cut -d '=' -f 2 | sed -r -e 's/^\s*0x(.*).$$/^\10/' | tr '\n' '|')^)" --color=yes > $@
+	 | grep -E -e "($$(cat build/os.offsets | cut -d '=' -f 2 | sed -r -e 's/^\s*0x(.*).$$/^\10/' | tr '\n' '|')^)" --color=yes > $@
 
-os.ndisasm.disasm: $(os_filename) Makefile ../utils/compact-ndisasm.sh
-	../utils/compact-ndisasm.sh $< $@
+build/os.ndisasm.disasm: ${os_filename} utils/compact-ndisasm.sh build/check_makefile
+	./utils/compact-ndisasm.sh $< $@
 
-os.reasm.asm: os.ndisasm.disasm Makefile
+build/os.reasm.asm: build/os.ndisasm.disasm build/check_makefile
 	sed -r -e 's/^[^ ]+ +[^ ]+ +//' $< > $@
 
-os.reasm: os.reasm.asm $(os_filename) Makefile
+build/test_pass/noemu_%.reasm build/%.reasm: build/%.reasm.asm ${os_filename} utils/compact-ndisasm.sh build/check_makefile
 # For now ignore this test, since we cannot have a reliable re-assembly of arbitrary data.
-	true
+	touch build/test_pass/noemu_$*.reasm build/$*.reasm
 #	nasm $< -o $@
-#	@echo "diff $@ $(os_filename)"
-#	@diff $@ $(os_filename) \
-#         && echo "Re-assembled file is identical to $(os_filename)" \
-#         || (../utils/compact-ndisasm.sh $@ os.reasm.disasm; \
-#	     echo "Re-assembled file is different from $(os_filename). Use meld os.ndisasm.disasm os.reasm.disasm to see differences."; \
+#	@echo "diff $@ ${os_filename}"
+#	@diff $@ ${os_filename} \
+#         && echo "Re-assembled file is identical to ${os_filename}" \
+#         || (./utils/compact-ndisasm.sh $@ build/os.reasm.disasm; \
+#	     echo "Re-assembled file is different from ${os_filename}. Use meld build/os.ndisasm.disasm build/os.reasm.disasm to see differences."; \
 #	     exit 0)
 
-#os.arm.disasm: $(os_filename) Makefile
+#os.arm.disasm: ${os_filename} build/check_makefile
 #	arm-none-eabi-objdump --endian=little -marm -b binary -D --adjust-vma=0x8000 $< > $@
 
 .PHONY: clean
-clean: Makefile
-	rm -f $(built_files)
-	for d in $(built_directories); do if test -e "$$d"; then rmdir "$$d"; fi; done
+clean: build/check_makefile
+	rm -f ${built_files}
+	for d in $$(echo ${more_built_directories} | tr ' ' '\n' | sort --reverse); do \
+          if test -e "$$d"; then \
+            rmdir "$$d"; \
+          fi; \
+        done
 
-.gitignore: Makefile
-	for f in $(built_files); do echo "/$$f"; done > $@
+.gitignore: build/check_makefile
+	for f in ${built_files}; do echo "/$$f"; done | sort > $@
 
 .PHONY: test
-test: $(tests_emu) $(tests_noemu) $(tests_requiring_sudo) all Makefile
+test: ${tests_emu:test/%=build/test_pass/emu_%} \
+      ${tests_noemu:test/%=build/test_pass/noemu_%} \
+      ${tests_requiring_sudo:test/%=build/test_pass/sudo_%} \
+      all \
+      build/check_makefile
 
-.PHONY: $(tests_emu)
-$(tests_emu): $(os_filename) ../deploy-screenshots Makefile
-	cd .. && ./utils/gui-wrapper.sh 800x600x24 ./$@.sh example-os/$<
+.PHONY: ${tests_emu}
+${tests_emu}: build/test_pass/emu_$$(@F)
+
+build/test_pass/emu_% deploy-screenshots/%.png deploy-screenshots/%-anim.gif: \
+ ${os_filename} utils/gui-wrapper.sh test/%.sh build/check_makefile \
+ | build/test_pass deploy-screenshots
+	./utils/gui-wrapper.sh 800x600x24 ./test/$*.sh $<
+	touch build/test_pass/emu_$*
 
 .PHONY: test/noemu
-test/noemu: $(tests_noemu) Makefile
+test/noemu: ${tests_noemu:test/%=build/test_pass/noemu_%} build/check_makefile
 
-.PHONY: test/zip
-test/zip: $(os_filename) Makefile
-	unzip -t $(os_filename)
+build/test_pass/noemu_zip: ${os_filename} build/check_makefile
+	unzip -t ${os_filename}
+	touch $@
 
-.PHONY: test/sizes
-test/sizes: os.32k $(os_filename) Makefile
-	test "$$(wc -c os.32k)" = "$$((32*1024)) os.32k"
-	test "$$(wc -c $(os_filename))" = "$$((1440*1024)) $(os_filename)"
+build/test_pass/noemu_sizes: build/os.32k ${os_filename} build/check_makefile
+	test "$$(wc -c build/os.32k)" = "$$((32*1024)) build/os.32k"
+	test "$$(wc -c ${os_filename})" = "$$((1440*1024)) ${os_filename}"
+	touch $@
 
 # check that the fat filesystem has the correct contents
-test/fat12_contents: $(os_filename)
-	mdir -i "$<@@$$(( $(fat12_start) ))" :: | grep -E "^os\s+zip\s+"
+build/test_pass/noemu_fat12_contents: ${os_filename} ${dep_bytes_fat12_start} build/check_makefile
+	mdir -i "$<@@${bytes_fat12_start}" :: | grep -E "^os\s+zip\s+"
+	touch $@
 
 .PHONY: test/requiring_sudo
-test/requiring_sudo: $(tests_requiring_sudo) Makefile
+test/requiring_sudo: ${tests_requiring_sudo:test/%=build/test_pass/sudo_%} build/check_makefile
 
 # check that the fat filesystem can be mounted and has the correct contents
-.PHONY: test/fat12_mount
-test/fat12_mount: $(os_filename) build/mnt_fat12 build/offsets/fat12_start.dec Makefile
+build/test_pass/sudo_fat12_mount: ${os_filename} ${dep_bytes_fat12_start} build/check_makefile | build/mnt_fat12
 	sudo umount build/mnt_fat12 || true
-	sudo mount -o loop,ro,offset=$$(cat build/offsets/fat12_start.dec) $< build/mnt_fat12
+	sudo mount -o loop,ro,offset=${bytes_fat12_start} $< build/mnt_fat12
 	ls -l build/mnt_fat12 | grep os.zip
 	sudo umount build/mnt_fat12
+	touch $@
 
-.PHONY: test/iso_mount
-test/iso_mount: $(os_filename) build/mnt_iso Makefile
+build/test_pass/sudo_iso_mount: ${os_filename} build/check_makefile | build/mnt_iso
 	sudo umount build/mnt_iso || true
 	sudo mount -o loop,ro $< build/mnt_iso
 	ls -l build/mnt_iso | grep os.zip
 	sudo umount build/mnt_iso
+	touch $@
+
+# See https://wiki.osdev.org/EFI#Emulation to emulate an UEFI system with qemu, to test the EFI boot from hdd / cd / fd (?).
diff --git a/test/gui-sh.sh b/test/gui-sh.sh
index a928da0..8326c52 100755
--- a/test/gui-sh.sh
+++ b/test/gui-sh.sh
@@ -4,7 +4,10 @@ set -e
 if test $# -ne 1 || test "$1" = '-h' -o "$1" = '--help'; then
     echo "Usage: $0 operating_system_file"
 fi
-os_filename="$1"
+# Force the path to be relative or absolute, but with at least one /
+# Otherwise, the command will be searched in the $PATH, instead of using the
+# given file.
+os_filename="$(dirname "$1")/$(basename "$1")"
 
 xterm -e ${os_filename} &
 pid=$!
diff --git a/test/qemu-system-arm.sh b/test/qemu-system-arm.sh
index 147d5c2..757c95f 100755
--- a/test/qemu-system-arm.sh
+++ b/test/qemu-system-arm.sh
@@ -1,4 +1,5 @@
 #!/bin/sh
 set -e
 
-qemu-system-arm -M help
\ No newline at end of file
+qemu-system-arm -M help
+sleep 5