aboutsummaryrefslogtreecommitdiffstats
path: root/Makefile
blob: dd943649c2984ef54e39f30fe5e998be760efa79 (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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# slosh
#
# zig is the C compiler: it is already required to build libghostty-vt, and it
# gives us static musl linking and cross-compilation for free (D12).

# Honour an explicit ZIG=... on the command line or in the environment;
# otherwise use whatever `zig` is on $PATH.
ZIG      ?= $(shell command -v zig 2>/dev/null)
ifeq ($(ZIG),)
  $(error zig not found on $$PATH -- install it or pass ZIG=/path/to/zig)
endif
# TARGET=aarch64-linux-gnu (any zig triple) cross-compiles `make all` and
# `make vendor` -- zig is the C compiler precisely so this costs nothing.
# The arch PKGBUILD uses it to build the aarch64 package on the x86_64 box.
TARGET   ?=
CC       := $(ZIG) cc $(if $(TARGET),-target $(TARGET))
VT       := vendor/libghostty-vt
VT_OUT   := $(VT)/zig-out
VT_LIB   := $(VT_OUT)/lib/libghostty-vt.a
VT_INC   := $(VT_OUT)/include

# -MMD -MP emits a .d per object listing the headers it included, so editing a
# header rebuilds what actually uses it. Without this a header edit rebuilt
# nothing and the only safe move was `make clean`, which is how a 0.1s build
# turns into a habit of throwing the whole thing away.
#
# The version is baked in, because the first question when a session is
# behaving oddly is "which build is this?" -- and a session keeps the binary it
# started with, so the answer is not "whatever is in build/ now".
#
# It is derived from git tags (vX.Y.Z) by contrib/version -- a tag is the single
# source of truth and nothing here is bumped by hand. The same script names the
# release artifacts and is read by Makefile.macos, so a build, its file name and
# its baked-in --version can never disagree. See contrib/version for the ladder
# (on a tag -> 0.2.0; before any tag -> <VERSION>-<count>-g<short>; no git at
# all -> ./VERSION verbatim, which is how a cgit snapshot / Homebrew build sees it).
VERSION := $(shell contrib/version)

# Where `install` will put the shipped themes, compiled in so that a named
# theme (`theme_name "phosphor"`) can be found without being copied into your
# config directory first. PREFIX only, never DESTDIR: DESTDIR is a staging root
# a package is built in, not a path the program will ever see.
PREFIX   ?= /usr/local

CFLAGS   := -std=c23 -O1 -g -Wall -Wextra -Wno-unused-parameter -MMD -MP \
            -I$(VT_INC) -Iinclude -Ibuild -DSLOSH_DATADIR='"$(PREFIX)/share/slosh"'
LDFLAGS  :=

SRC      := $(wildcard src/*.c)
OBJ      := $(SRC:src/%.c=build/%.o)
DEPS     := $(OBJ:.o=.d)
BIN      := build/slosh

# One stamp per python test file. The stamp depends on the file, the harness and
# the binary, so an untouched test whose binary has not changed does not run
# again — and `make -j` runs the rest at once, because they are separate
# processes that share nothing (script mode opens no socket).
# test_session.py drives a real pty and belongs to test-live; it is only named
# test_* by history.
PY_TESTS  := $(filter-out test_session.py,$(notdir $(wildcard tests/test_*.py)))
PY_STAMPS := $(PY_TESTS:%.py=build/.pass-%)
JOBS      ?= $(shell nproc 2>/dev/null || echo 4)

# Quiet by default: `[CC] src/app.c` rather than a screenful of flags, which is
# what you want for the twenty seconds a build takes and never after. `make V=1`
# prints the command lines instead, which is what you want the moment one of
# them is wrong -- so the information is one flag away rather than gone.
V ?= 0
ifeq ($(V),0)
  Q   := @
  say  = @printf '  %-5s %s\n' '$(1)' '$(2)'
else
  Q   :=
  say  = @:
endif

.PHONY: all clean vendor run test retest test-live test-all smoke help coverage \
        docs www www-publish fmt fmt-check hooks tools install uninstall macos-dist \
        release release-check release-linux release-windows
.DEFAULT_GOAL := help

help: ## show this
	@grep -hE '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed 's/:.*##/\t/' | expand -t20
	@printf '\n%-19s %s\n' 'V=1' 'show the command lines rather than [CC] src/app.c'

all: $(BIN) ## build slosh

$(BIN): $(OBJ) $(VT_LIB)
	$(call say,[LD],$@)
	$(Q)$(CC) $(OBJ) $(VT_LIB) -o $@ $(LDFLAGS)

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

# stb_image comes with the vendored libghostty-vt, which is where the include
# path points. It is third-party and does not build clean under our warnings,
# so it gets its own rule rather than every file paying for it.
build/png.o: src/png.c build/version.h $(VT_LIB) | build
	$(call say,[CC],$<)
	$(Q)$(CC) $(CFLAGS) -I$(VT)/src/stb -Wno-unused-function -c $< -o $@

# Rewritten only when the value actually changes, so `make` after a commit
# rebuilds, and `make` twice in a row does not.
.PHONY: FORCE
# The logo, as a C string array: logo.txt is the source of truth and the
# splash includes this. Escaped through sed so art may contain anything.
build/logo.h: logo.txt | build
	$(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/$$/",/' logo.txt; \
	  printf '};\n'; } > $@

# The compiled xterm-ghostty terminfo entry, as bytes the binary carries so
# `slosh --install-terminfo` can hand it to a machine that lacks it. See
# contrib/terminfo/README.md for where the entry comes from.
build/terminfo.h: contrib/terminfo/xterm-ghostty.terminfo | build
	$(call say,[GEN],$@)
	$(Q){ printf '/* generated from contrib/terminfo/xterm-ghostty.terminfo */\n'; \
	  printf 'static const unsigned char TERMINFO_GHOSTTY[] = {\n'; \
	  od -An -v -tx1 $< | sed -e 's/ \([0-9a-f][0-9a-f]\)/0x\1,/g'; \
	  printf '};\n'; } > $@

build/version.h: FORCE | build
	@printf '/* generated by the Makefile */\n#define SLOSH_VERSION "%s"\n' \
	  '$(VERSION)' > $@.tmp
	@cmp -s $@.tmp $@ || mv $@.tmp $@
	@rm -f $@.tmp

build:
	@mkdir -p build

# The manifest is the prerequisite: contrib's re-vendor rewrites it every
# time, so a vendor bump rebuilds the library and its installed headers.
# Without it, an existing .a *was* the build -- and a machine whose zig-out
# predated a bump compiled new code against old headers (the v0.1.7 macOS
# leg failed exactly there: pane.c wanted GhosttySearch, the builder's
# stale include/ had never heard of it).
$(VT_LIB): $(VT).vendor.json
	@$(MAKE) vendor

# -Demit-xcframework=false: we only need libghostty-vt.a. In lib-vt mode the
# upstream build.zig auto-enables an xcframework whenever `xcodebuild` is on
# PATH -- but on a machine with only the Command Line Tools (no full Xcode)
# that xcodebuild is a stub that fails, so the auto-detect turns a working
# build into a broken one. We never consume the xcframework, so disable it.
#
# -Dversion-string: the vendored tree has no .git of its own, so upstream's
# version detection runs `git describe` in *our* repository and finds *our*
# tags -- and a slosh release tag (v0.1.0) is not a ghostty version, which
# upstream treats as a fatal mismatch. Passing the version from its own
# build.zig.zon is what the fallback would have computed, read from the one
# place a re-vendor updates.
VT_VERSION := $(shell sed -n 's/^[[:space:]]*\.version = "\([^"]*\)".*/\1/p' $(VT)/build.zig.zon | head -1)
VT_FLAGS   := -Demit-lib-vt -Demit-xcframework=false -Dversion-string=$(VT_VERSION) -Doptimize=ReleaseFast

vendor: ## build the vendored libghostty-vt (needs zig 0.16)
	cd $(VT) && PATH="$(dir $(ZIG)):$$PATH" zig build $(VT_FLAGS) $(if $(TARGET),-Dtarget=$(TARGET))

TEST_BIN := build/input_test

$(TEST_BIN): tests/input_test.c src/input.c $(VT_LIB) | build
	$(call say,[LD],$@)
	$(Q)$(CC) $(CFLAGS) tests/input_test.c src/input.c $(VT_LIB) -o $@

KDL_TEST := build/kdl_test

$(KDL_TEST): tests/kdl_test.c src/kdl.c | build
	$(call say,[LD],$@)
	$(Q)$(CC) $(CFLAGS) tests/kdl_test.c src/kdl.c -o $@

PATH_TEST := build/path_test

$(PATH_TEST): tests/path_test.c src/path.c | build
	$(call say,[LD],$@)
	$(Q)$(CC) $(CFLAGS) tests/path_test.c src/path.c -o $@

EXPR_TEST := build/expr_test

$(EXPR_TEST): tests/expr_test.c src/expr.c | build
	$(call say,[LD],$@)
	$(Q)$(CC) $(CFLAGS) tests/expr_test.c src/expr.c -o $@

# The real expression evaluator as a filter, so test_shadertoy.py can hold
# contrib/shadertoy.html's JavaScript to what the compiler actually does.
EXPR_EVAL := build/expr_eval

$(EXPR_EVAL): tests/expr_eval.c src/expr.c | build
	$(call say,[LD],$@)
	$(Q)$(CC) $(CFLAGS) tests/expr_eval.c src/expr.c -o $@

SHADER_TEST := build/shader_test

SCREEN_TEST := build/screen_test

$(SCREEN_TEST): tests/screen_test.c src/screen.c src/json.c $(VT_LIB) | build
	$(call say,[LD],$@)
	$(Q)$(CC) $(CFLAGS) tests/screen_test.c src/screen.c src/json.c $(VT_LIB) -o $@

$(SHADER_TEST): tests/shader_test.c src/shader.c src/screen.c src/json.c src/expr.c $(VT_LIB) | build
	$(call say,[LD],$@)
	$(Q)$(CC) $(CFLAGS) tests/shader_test.c src/shader.c src/screen.c src/json.c src/expr.c $(VT_LIB) -o $@

build/.pass-test_shadertoy: $(EXPR_EVAL) contrib/shadertoy.html

build/.pass-%: tests/%.py tests/harness.py $(BIN) | build
	@cd tests && timeout 300 python3 $(notdir $<) > ../build/.log-$* 2>&1 \
	  || { echo "FAIL $(notdir $<)"; tail -25 ../build/.log-$*; exit 1; }
	@printf '  ok   %-24s %s\n' "$(notdir $<)" "$$(grep -c '^ok' build/.log-$* 2>/dev/null || echo ?) checks"
	@touch $@

test: $(BIN) $(TEST_BIN) $(KDL_TEST) $(SHADER_TEST) $(SCREEN_TEST) $(EXPR_TEST) $(EXPR_EVAL) $(PATH_TEST) ## unit + headless checks (fast)
	@./$(TEST_BIN) >/dev/null && ./$(KDL_TEST) >/dev/null && ./$(SHADER_TEST) >/dev/null \
	  && ./$(SCREEN_TEST) >/dev/null \
	  && ./$(EXPR_TEST) >/dev/null && ./$(PATH_TEST) >/dev/null \
	  && printf '  ok   %-24s %s\n' "C unit tests" "6 binaries"
	@$(MAKE) --no-print-directory -j$(JOBS) $(PY_STAMPS)
	@printf '\nall green\n'

retest: ## force every python test to run again
	@rm -f build/.pass-*
	@$(MAKE) --no-print-directory test

test-live: $(BIN) ## the checks that need a real tty (slow)
	python3 tests/live_m0.py
	@echo
	python3 tests/live_input.py
	@echo
	python3 tests/test_session.py
	@echo
	python3 tests/live_reload.py
	@echo
	python3 tests/live_graphics.py

test-all: test test-live ## everything

smoke: $(BIN) ## compose a screen headlessly and print it
	./$(BIN) --headless --cols 40 --rows 12 -- /bin/sh -c 'printf "hello \033[1;32mslosh\033[0m\n"; echo "wide: 日本語"'

docs: ## render docs/ into build/docs (no dependencies; open build/docs/index.html)
	$(Q)python3 contrib/gen-docs build/docs

www: ## assemble the website into build/www (NO_DEMO=1 to skip the browser demo)
	$(Q)web/build-site

www-publish: ## build the site and rsync it to slosh.foo
	$(Q)web/publish

run: $(BIN) ## build and run
	./$(BIN)

# ── coverage ────────────────────────────────────────────────────────────────
#
# A second build, with gcc instead of zig, because the counters need a gcov
# that matches the compiler and gcov is what this machine has. It lives in
# build/cov and touches nothing else; $SLOSH_BIN is how the same suite drives
# it. `-O0` because optimised line counts are a work of fiction.
COV_DIR  := build/cov
COV_SRC  := $(wildcard src/*.c)
COV_OBJ  := $(COV_SRC:src/%.c=$(COV_DIR)/%.o)
COV_CC   ?= gcc
COV_FLAGS := -std=c23 -O0 -g --coverage -I$(VT_INC) -Iinclude -Ibuild

$(COV_DIR)/%.o: src/%.c build/version.h | $(COV_DIR)
	$(call say,[COV],$<)
	$(Q)$(COV_CC) $(COV_FLAGS) -c $< -o $@

$(COV_DIR):
	@mkdir -p $(COV_DIR)

$(COV_DIR)/slosh: $(COV_OBJ) $(VT_LIB)
	$(call say,[LD],$@)
	$(Q)$(COV_CC) --coverage $(COV_OBJ) $(VT_LIB) -o $@

# The C unit tests link the *same* coverage objects, so their counters land in
# the same place and input.c/kdl.c/shader.c/expr.c are credited for the tests
# that actually exercise them. Without this they read as half-tested, which is
# a lie about where the tests are.
COV_UNITS := $(COV_DIR)/input_test $(COV_DIR)/kdl_test $(COV_DIR)/shader_test \
             $(COV_DIR)/expr_test

$(COV_DIR)/input_test: tests/input_test.c $(COV_DIR)/input.o $(VT_LIB)
	$(call say,[LD],$@)
	$(Q)$(COV_CC) $(COV_FLAGS) $^ -o $@
$(COV_DIR)/kdl_test: tests/kdl_test.c $(COV_DIR)/kdl.o
	$(call say,[LD],$@)
	$(Q)$(COV_CC) $(COV_FLAGS) $^ -o $@
$(COV_DIR)/shader_test: tests/shader_test.c $(COV_DIR)/shader.o $(COV_DIR)/screen.o \
                        $(COV_DIR)/json.o $(COV_DIR)/expr.o $(VT_LIB)
	$(call say,[LD],$@)
	$(Q)$(COV_CC) $(COV_FLAGS) $^ -o $@
$(COV_DIR)/expr_test: tests/expr_test.c $(COV_DIR)/expr.o
	$(call say,[LD],$@)
	$(Q)$(COV_CC) $(COV_FLAGS) $^ -o $@

coverage: $(COV_DIR)/slosh $(COV_UNITS) ## how much of the code the tests run
	@rm -f $(COV_DIR)/*.gcda
	@for u in $(COV_UNITS); do ./$$u >/dev/null || echo "  (failed: $$u)"; done
	@echo "running the suite against the coverage build..."
	@for t in $(PY_TESTS); do \
	  (cd tests && SLOSH_BIN=../$(COV_DIR)/slosh timeout 300 python3 $$t >/dev/null 2>&1) \
	    || echo "  (failed: $$t)"; \
	done
	@for t in test_session.py live_m0.py live_input.py live_reload.py live_graphics.py; do \
	  (cd tests && SLOSH_BIN=../$(COV_DIR)/slosh timeout 300 python3 $$t >/dev/null 2>&1) \
	    || echo "  (failed: $$t)"; \
	done
	@contrib/coverage

# Everything we format, minus the vendored library: that one is pinned by commit
# (D12) and reformatting it would turn every re-vendor into a merge. The contrib
# scripts are Python with no extension, so they are named rather than globbed.
CSRC  := $(shell git ls-files '*.c' '*.h' 2>/dev/null | grep -v '^vendor/')
PYSRC := $(shell git ls-files '*.py' 2>/dev/null | grep -v '^vendor/') \
         contrib/coverage contrib/gen-docs contrib/shader-repl \
         web/demo/boot-check web/demo/build-rootfs web/demo/serve

# clang-format is a system package. ruff usually is not, and `pip install --user`
# is refused outright on a distro python (PEP 668), so `make tools` puts it in a
# venv here and everything looks there *first* -- before $$PATH, so that a system
# ruff of the wrong version cannot strand somebody whose venv has the right one.
# The version is pinned in ruff.toml as well, where ruff enforces it itself.
RUFF_VERSION := 0.16.3
VENV         := .venv
RUFF          = $(if $(wildcard $(VENV)/bin/ruff),$(VENV)/bin/ruff,$(shell command -v ruff 2>/dev/null))
no-ruff       = { echo "ruff not found. run: make tools"; exit 1; }

tools: ## install the formatters (a venv with ruff)
	$(Q)python3 -m venv $(VENV)
	$(Q)$(VENV)/bin/pip install --quiet --disable-pip-version-check \
	    ruff==$(RUFF_VERSION)
	@printf '  %s\n' "$$($(VENV)/bin/ruff --version)  in $(VENV)"
	@command -v clang-format >/dev/null 2>&1 \
	  || echo "  note: clang-format is missing (apt install clang-format)"
	@echo "  survives make clean; rm -rf $(VENV) to start over"

fmt: ## format the C and Python in place
	@test -n "$(RUFF)" || $(no-ruff)
	$(Q)clang-format -i $(CSRC)
	$(Q)$(RUFF) check -q --select I --fix $(PYSRC)
	$(Q)$(RUFF) format -q $(PYSRC)
	$(say) "  [FMT] $(words $(CSRC)) C, $(words $(PYSRC)) Python"

fmt-check: ## ...or just say what is not formatted
	@test -n "$(RUFF)" || $(no-ruff)
	@bad=$$(for f in $(CSRC); do \
	    clang-format "$$f" | cmp -s - "$$f" || echo "$$f"; \
	  done); \
	pybad=$$($(RUFF) format -q --check $(PYSRC) 2>&1 | sed -n 's/^Would reformat: //p'; \
	         $(RUFF) check -q --select I $(PYSRC) 2>/dev/null | sed -n 's/:.*//p' | sort -u); \
	all=$$(printf '%s\n%s\n' "$$bad" "$$pybad" | sed '/^$$/d' | sort -u); \
	if [ -n "$$all" ]; then \
	  echo "not formatted:"; echo "$$all" | sed 's/^/  /'; \
	  echo "run: make fmt"; exit 1; \
	else echo "all $(words $(CSRC)) C and $(words $(PYSRC)) Python files formatted"; fi

fuzz: $(BIN) ## build and run all fuzz targets (TIME=15)
	$(MAKE) -C tests/fuzz run-all TIME=$(or $(TIME),15)

fuzz-corpus: ## replay fuzz seed corpora (CI-friendly, no fuzzing)
	$(MAKE) -C tests/fuzz replay
	$(MAKE) -C tests/fuzz corpus

hooks: ## install the git hooks (format on commit, tag sanity on push)
	$(Q)git config core.hooksPath .githooks
	@echo "core.hooksPath -> .githooks   (git commit --no-verify to skip)"

# ── macOS releases ──────────────────────────────────────────────────────────
# A mac binary users can run without being told it is damaged has to be signed
# with a Developer ID and notarized by Apple, and that needs a mac holding a
# private key -- which is not necessarily this machine, and under `make all`
# never is. So this target does not build anything: it hands the job to the
# builder over ssh and brings back a signed, notarized zip. Works from Linux;
# all it needs here is ssh, rsync and something that can hash a file.
#
# The pipeline is Makefile.macos and contrib/macos-{builder-setup,remote-build},
# all in the repo. Everything specific to *you* -- which mac, whose certificate,
# which Apple team -- is the handful of lines in macos-release.mk, which is not.
# REF=<tag> builds a ref origin already has, which is what a release is;
# without it you get this working tree, dirty and honestly labelled.
macos-dist: ## signed, notarized macOS build on the builder mac (REF=<tag> for a release)
	@test -f macos-release.mk || { \
	  echo "no macos-release.mk here -- it names your signing identity and your"; \
	  echo "builder mac, so it is the one file kept out of the repo:"; \
	  echo "  cp macos-release.mk.example macos-release.mk   # then fill it in"; \
	  echo "To provision a builder mac: contrib/macos-builder-setup --help"; exit 1; }
	$(Q)$(MAKE) -f Makefile.macos remote-dist REF=$(REF)

# ── releases ────────────────────────────────────────────────────────────────
# Five artifacts for a tagged release, staged in dist/ with a SHA256SUMS beside
# them. The two linux tarballs and the two windows zips are cross-built right
# here: zig cc is already the compiler, so -target gets us x86_64 and aarch64
# for free -- statically linked against musl for linux, and one slosh.exe with
# no runtime dependency beyond the OS for windows (Makefile.windows). macOS is
# the one that cannot be built here -- it must be signed and notarized on a mac
# -- so `release` hands that leg to `macos-dist`, which drives the builder.
#
# The tag itself is cut by `contrib/release X.Y.Z`, which bumps ./VERSION,
# commits and tags as one step -- the two must agree, and doing them
# separately is how v0.1.1 shipped with a stale floor. VERSION here comes
# from the git tag (contrib/version), so the usual flow is contrib/release,
# then check out the tag and run `make release REF=<tag>`: the linux tarballs
# are named from the working tree, the macOS zip from the ref the builder
# checks out, and because both resolve the same tag the names agree.
# release-check re-verifies the floor/tag invariant before anything is built.
DIST          ?= dist
# Each tarball carries the binary plus the paperwork a redistributed build must
# ship (see THIRD-PARTY-LICENSES: libghostty-vt and stb_image are linked in),
# and the manpages, because a tarball is what a packager starts from.
REL_DOCS      := README.md LICENSE THIRD-PARTY-LICENSES docs/slosh.1 docs/slosh.5
# The target triples we cross-build. The arch label in the artifact name is the
# part before the first '-' (x86_64, aarch64).
LINUX_TARGETS := x86_64-linux-musl aarch64-linux-musl
# Release build flags: like CFLAGS but -O2, no -g (we strip at link with -s) and
# no depfiles (a one-shot build, not an incremental one). The per-target zig-out
# include is added in-recipe; the host VT_INC is deliberately left out.
REL_CFLAGS    := -std=c23 -O2 -Wall -Wextra -Wno-unused-parameter -Iinclude -Ibuild

release-linux: build/version.h build/logo.h ## cross-build the static linux tarballs into dist/
	$(Q)mkdir -p $(DIST)
	$(Q)set -e; for t in $(LINUX_TARGETS); do \
	  arch=$${t%%-*}; stem=slosh-$(VERSION)-linux-$$arch; \
	  vtpfx=build/vt/$$t; vta=$$vtpfx/lib/libghostty-vt.a; \
	  odir=build/obj/$$t; stage=build/stage/$$stem; \
	  printf '  %-5s %s\n' '[VT]' "$$t"; \
	  ( cd $(VT) && PATH="$(dir $(ZIG)):$$PATH" zig build $(VT_FLAGS) \
	      -Dtarget=$$t --prefix "$(CURDIR)/$$vtpfx" ); \
	  mkdir -p $$odir; \
	  for c in $(SRC); do \
	    o=$$odir/$$(basename $${c%.c}).o; \
	    if [ "$$(basename $$c)" = png.c ]; then \
	      $(CC) -target $$t $(REL_CFLAGS) -I$$vtpfx/include -I$(VT)/src/stb -Wno-unused-function -c "$$c" -o "$$o"; \
	    else \
	      $(CC) -target $$t $(REL_CFLAGS) -I$$vtpfx/include -c "$$c" -o "$$o"; \
	    fi; \
	  done; \
	  printf '  %-5s %s\n' '[LD]' "$$stem/slosh"; \
	  $(CC) -target $$t -s $$odir/*.o "$$vta" -o build/slosh-$$t; \
	  rm -rf $$stage; mkdir -p $$stage; \
	  cp build/slosh-$$t $$stage/slosh; cp $(REL_DOCS) $$stage/; \
	  ( cd build/stage && tar czf "$(CURDIR)/$(DIST)/$$stem.tar.gz" "$$stem" ); \
	  printf '  %-5s %s\n' '[TAR]' "$(DIST)/$$stem.tar.gz"; \
	done

release-windows: ## cross-build the windows zips into dist/
	$(Q)$(MAKE) --no-print-directory -f Makefile.windows ZIG=$(ZIG) ARCH=x86_64 dist
	$(Q)$(MAKE) --no-print-directory -f Makefile.windows ZIG=$(ZIG) ARCH=aarch64 dist

release-check: ## verify ./VERSION agrees with the newest tag
	$(Q)contrib/release --check

release: release-check release-linux release-windows ## full release: linux tarballs + windows zips + signed macOS zip + SHA256SUMS in dist/
	$(Q)$(MAKE) --no-print-directory macos-dist REF=$(REF)
	$(Q)cd $(DIST) && { sha256sum slosh-$(VERSION)-* 2>/dev/null \
	  || shasum -a 256 slosh-$(VERSION)-*; } > SHA256SUMS
	$(call say,[SUMS],$(DIST)/SHA256SUMS)
	$(Q)cat $(DIST)/SHA256SUMS

# ── installation ────────────────────────────────────────────────────────────
# For packagers (Homebrew's formula mirrors this layout) and for anyone doing
# `sudo make install`. DESTDIR is the staging root a package manager wants;
# PREFIX (set near CFLAGS, because the binary is compiled knowing it) is where
# the files will finally live. The binary is the only thing slosh needs to run;
# of the rest, share/slosh/themes is the one directory it reads by itself --
# `theme_name` looks there after your own themes directory -- and the example
# config and layout are still yours to copy. The manpage goes where man(1)
# already looks, which is the whole point of one.
DESTDIR  ?=
BINDIR   := $(DESTDIR)$(PREFIX)/bin
SHAREDIR := $(DESTDIR)$(PREFIX)/share/slosh
DOCDIR   := $(DESTDIR)$(PREFIX)/share/doc/slosh
MAN1DIR  := $(DESTDIR)$(PREFIX)/share/man/man1
MAN5DIR  := $(DESTDIR)$(PREFIX)/share/man/man5

install: $(BIN) ## install to $PREFIX (default /usr/local, honours DESTDIR)
	$(Q)install -d $(BINDIR) $(SHAREDIR) $(DOCDIR) $(MAN1DIR) $(MAN5DIR)
	$(Q)install -m 755 $(BIN) $(BINDIR)/slosh
	$(Q)install -m 644 docs/slosh.1 $(MAN1DIR)/slosh.1
	$(Q)install -m 644 docs/slosh.5 $(MAN5DIR)/slosh.5
	$(Q)install -m 644 config/config.kdl config/example.layout $(SHAREDIR)/
	$(Q)for d in themes chrome shaders; do \
	  install -d $(SHAREDIR)/$$d; \
	  install -m 644 contrib/$$d/*.kdl $(SHAREDIR)/$$d/ 2>/dev/null || true; \
	done
	$(Q)install -m 644 README.md $(DOCDIR)/
	$(Q)install -d $(DOCDIR)/docs && install -m 644 docs/*.md $(DOCDIR)/docs/
	$(call say,[INST],$(BINDIR)/slosh)

uninstall: ## remove what install put there
	$(Q)rm -f $(BINDIR)/slosh $(MAN1DIR)/slosh.1 $(MAN5DIR)/slosh.5
	$(Q)rm -rf $(SHAREDIR) $(DOCDIR)
	$(call say,[RM],$(BINDIR)/slosh)

clean: ## remove our build output (not the vendor lib)
	rm -rf build

-include $(DEPS)