aboutsummaryrefslogtreecommitdiffstats
path: root/web/demo/Makefile
blob: 06d1f5b65e5889bc0c22e359e54ba0aaa741953b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# slosh in a browser tab.
#
# Bellard's TinyEMU compiled to wasm boots a riscv64 Linux; we hand it a root
# filesystem with a cross-compiled slosh in it. `make` builds the whole thing
# into build/site, `make serve` puts it on a port.
#
# Run this from web/demo (or `make -C web/demo`), not from the
# repository root: it is a separate build with a separate target triple and it
# deliberately shares nothing with the top-level one but the source files.

ROOT     := ../..
ZIG      ?= $(HOME)/zig-0.16.0/zig
CC       := $(ZIG) cc
TARGET   := riscv64-linux-musl

VT       := $(ROOT)/vendor/libghostty-vt
VT_OUT   := build/vt
VT_LIB   := $(VT_OUT)/lib/libghostty-vt.a
VT_INC   := $(VT_OUT)/include

SRC      := $(wildcard $(ROOT)/src/*.c)
OBJ      := $(patsubst $(ROOT)/src/%.c,build/obj/%.o,$(SRC))
DEPS     := $(OBJ:.o=.d)
GUEST    := build/slosh-riscv64

# -Os, no -g: every byte of this binary is pulled through a virtio block device
# by somebody's browser, 256 KiB at a time.
CFLAGS   := -target $(TARGET) -std=c23 -Os -Wall -Wextra -Wno-unused-parameter \
            -MMD -MP -I$(VT_INC) -I$(ROOT)/include -Ibuild
VERSION  := 0.1.0
GITDESC   = $(shell cd $(ROOT) && git describe --always --dirty 2>/dev/null || echo unknown)

SITE     := build/site
IMAGE    := build/rootfs.bin
IMAGE_MB ?= 48
PORT     ?= 8000

# What the browser downloads, beyond our own page. The emulator comes from
# emulator/dist -- ours, built from Bellard's source -- not from vendor/:
# his prebuilt blob wedges on disks larger than 16 MiB (see emulator/).
VENDOR_SITE := bbl64.bin kernel-riscv64.bin xterm.js xterm.css
EMU_SITE    := emulator/dist/riscvemu64-wasm.js emulator/dist/riscvemu64-wasm.wasm
WEB_SITE    := index.html demo.js demo.css slosh.cfg

V ?= 0
ifeq ($(V),0)
  Q   := @
  say  = @printf '  %-5s %s\n' '$(1)' '$(2)'
else
  Q   :=
  say  = @:
endif

.PHONY: all vendor guest rootfs site serve check clean distclean help
.DEFAULT_GOAL := help

help: ## show this
	@grep -hE '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed 's/:.*##/\t/' | expand -t14
	@printf '\n%-13s %s\n' 'PORT=8080' 'which port `make serve` uses'
	@printf '%-13s %s\n' 'IMAGE_MB=64' 'how big the guest filesystem is'

all: site ## build everything into build/site

# ── the third-party half ────────────────────────────────────────────────────

vendor: vendor/.stamp ## download the emulator, kernel, base image and xterm.js

vendor/.stamp: fetch-vendor
	$(Q)./fetch-vendor
	@touch $@

# ── slosh, for a machine nobody here owns ─────────────────────────────────
#
# -Dsimd=false is not a size choice: Google Highway, which libghostty-vt uses
# for SIMD, does not compile for rv64gc -- it emits RVV intrinsics for a vector
# extension this target does not have. The scalar paths are the same code
# without the hand-vectorised fast path, which for a demo VM is free.
$(VT_LIB):
	$(call say,[ZIG],libghostty-vt $(TARGET))
	$(Q)cd $(VT) && PATH="$(dir $(ZIG)):$$PATH" zig build \
	    -Demit-lib-vt -Dsimd=false -Doptimize=ReleaseFast -Dtarget=$(TARGET) \
	    --prefix $(abspath $(VT_OUT)) --cache-dir $(abspath build/vt-cache)

# FORCE, as at the top level: rewritten only when the value actually changes,
# so a new commit rebuilds and `make` twice in a row does not.
.PHONY: FORCE
build/version.h: FORCE | build
	@printf '/* generated by web/demo/Makefile */\n#define SLOSH_VERSION "%s+%s"\n' \
	  '$(VERSION)' '$(GITDESC)' > $@.tmp
	@cmp -s $@.tmp $@ || mv $@.tmp $@
	@rm -f $@.tmp

build build/obj:
	@mkdir -p build/obj

# The logo, exactly as the top-level Makefile bakes it: logo.txt is the
# source of truth and the splash includes this.
build/logo.h: $(ROOT)/logo.txt | build
	$(call say,[GEN],$@)
	$(Q){ printf '/* generated from logo.txt; edit that, not this */\n'; \
	  printf 'static const char *const LOGO[] = {\n'; \
	  sed -e 's/\\\\/\\\\\\\\/g' -e 's/"/\\\\"/g' -e 's/^/    "/' -e 's/$$/",/' $(ROOT)/logo.txt; \
	  printf '};\n'; } > $@

build/obj/%.o: $(ROOT)/src/%.c build/version.h build/logo.h $(VT_LIB) | build/obj
	$(call say,[CC],$(notdir $<))
	$(Q)$(CC) $(CFLAGS) -c $< -o $@

# stb_image is third-party and does not build clean under our warnings.
build/obj/png.o: $(ROOT)/src/png.c build/version.h $(VT_LIB) | build/obj
	$(call say,[CC],$(notdir $<))
	$(Q)$(CC) $(CFLAGS) -I$(VT)/src/stb -Wno-unused-function -c $< -o $@

# Static, because the guest has glibc 2.24 and we built against musl. It also
# means the binary is the only thing that has to land in the image.
$(GUEST): $(OBJ) $(VT_LIB)
	$(call say,[LD],$@)
	$(Q)$(CC) -target $(TARGET) -Os $(OBJ) $(VT_LIB) -o $@ -s

guest: $(GUEST) ## cross-compile slosh for riscv64

# ── the guest filesystem ────────────────────────────────────────────────────
#
# The kernel we use has no initramfs support (CONFIG_BLK_DEV_INITRD is off: it
# ignores an initrd and panics for want of a root), so the root filesystem is an
# ext2 image on a virtio disk, split into 256 KiB blocks that the emulator
# fetches over HTTP as the guest touches them. A visitor downloads the parts of
# the image they use, not all of it.
rootfs: $(IMAGE) ## build and split the guest filesystem

# find -L: overlay contains a symlink (the theme, pointing into contrib/themes)
# and -type f without -L skips symlinks -- editing the theme would not rebuild
# the image. With -L the prereq is the symlink path, and make stats through it,
# so the target's mtime is the one compared.
$(IMAGE): $(GUEST) build-rootfs vendor/.stamp $(shell find -L overlay -type f 2>/dev/null)
	$(Q)./build-rootfs --base vendor/alpine-minirootfs-riscv64.tar.gz --slosh $(GUEST) \
	    --apks vendor/apks --overlay overlay --out $@ --split $(SITE)/rootfs --size $(IMAGE_MB)M

# Everything except the page can be checked without a browser: same emulator,
# same kernel, same image, driven over a pty. `make check` before you open
# devtools, always.
check: $(IMAGE) ## boot the image in a native emulator and check slosh runs
	$(Q)./boot-check

# ── the page ────────────────────────────────────────────────────────────────

site: $(IMAGE) ## assemble build/site
	@mkdir -p $(SITE)
	$(Q)cp $(addprefix web/,$(WEB_SITE)) $(SITE)/
	$(Q)cp $(addprefix vendor/,$(VENDOR_SITE)) $(SITE)/
	$(Q)cp $(EMU_SITE) $(SITE)/
	$(Q)cp vendor/TINYEMU-MIT-LICENSE.txt $(SITE)/
	$(Q)for f in kernel-riscv64.bin bbl64.bin riscvemu64-wasm.js riscvemu64-wasm.wasm \
	             xterm.js xterm.css demo.js demo.css; do \
	    gzip -9 -n -c $(SITE)/$$f > $(SITE)/$$f.gz; \
	done
	@printf '  site  %s (%s; %s gzipped)\n' "$(SITE)" \
	  "$$(du -sh $(SITE) | cut -f1)" \
	  "$$(du -shc $(SITE)/*.gz $(SITE)/rootfs/*.gz 2>/dev/null | tail -1 | cut -f1)"

serve: site ## serve build/site on http://localhost:$(PORT)
	./serve --dir $(SITE) --port $(PORT)

clean: ## remove build/ (keeps the downloads)
	rm -rf build

distclean: clean ## also remove vendor/
	rm -rf vendor

-include $(DEPS)