# 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 -> --g; no git at # all -> ./VERSION verbatim, which is how a cgit snapshot / Homebrew build sees it). VERSION := $(shell contrib/version) CFLAGS := -std=c23 -O1 -g -Wall -Wextra -Wno-unused-parameter -MMD -MP \ -I$(VT_INC) -Iinclude -Ibuild 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 $(VT_LIB): @$(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= 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= 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=`: 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 is where the files will finally live. The binary is the only thing # slosh needs to run -- the rest is example configuration you may copy into # ~/.config/slosh, so it goes to share/ and nothing reads it from there. The # manpage goes where man(1) already looks, which is the whole point of one. PREFIX ?= /usr/local 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)