summaryrefslogtreecommitdiffstats
path: root/Makefile
diff options
context:
space:
mode:
authorEthan Werner <mail@ethanwerner.com>2026-10-10 20:55:33 +0000
committerEthan Werner <mail@ethanwerner.com>2026-10-10 20:56:50 +0000
commitf8423ebea413605b42de23f5dd06c5479d3fa1dd (patch)
tree2612a5eb026c59e7aae01f10a1cf62f2ee43042c /Makefile
initial commitHEADmaster
Diffstat (limited to 'Makefile')
-rw-r--r--Makefile250
1 files changed, 250 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..04edb8c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,250 @@
+# llsh — host-toolchain build with locally built static library archives.
+#
+# Supported target: x86_64 Linux. The bundled source archives are compiled
+# with the host GCC toolchain and linked against the system glibc/static C++
+# runtime. No musl toolchain or musl shim is used.
+
+SHELL ?= /bin/sh
+
+ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+VENDOR := $(ROOT)/vendor
+VEXT := $(VENDOR)/src/_extract
+BUILD := $(VENDOR)/build
+ZLIB_D := $(BUILD)/zlib
+OPENSSL_D := $(BUILD)/openssl
+CURL_D := $(BUILD)/curl
+CJSON_D := $(BUILD)/cjson
+NLOHMANN_D := $(BUILD)/nlohmann
+SYSRT := $(BUILD)/system-runtime
+SYSRT_LINKS := $(SYSRT)/linker-scripts
+GCC_TRIPLE := $(shell $(CXX) -dumpmachine)
+GCC_MAJOR := $(shell $(CXX) -dumpversion | cut -d. -f1)
+SYS_GCC_LIBDIR := $(SYSRT)/usr/lib/gcc/$(GCC_TRIPLE)/$(GCC_MAJOR)
+
+CC ?= gcc
+CXX ?= g++
+AR ?= ar
+RANLIB ?= ranlib
+STATIC ?= 1
+
+CFLAGS ?= -D_GNU_SOURCE -std=gnu11 -Os -g -Wall -Wextra \
+ -Wno-unused-parameter -ffunction-sections -fdata-sections
+CXXFLAGS ?= -D_GNU_SOURCE -std=gnu++20 -Os -g -Wall -Wextra \
+ -Wno-unused-parameter -Wno-write-strings \
+ -Wno-missing-field-initializers \
+ -ffunction-sections -fdata-sections
+
+CPPFLAGS += -I. -Isrc -Iinclude \
+ -I$(ZLIB_D)/include \
+ -I$(OPENSSL_D)/include \
+ -I$(CURL_D)/include \
+ -I$(CJSON_D)/include \
+ -I$(NLOHMANN_D)/include
+
+VLIBS := \
+ $(CURL_D)/lib/libcurl.a \
+ $(OPENSSL_D)/lib64/libssl.a \
+ $(OPENSSL_D)/lib64/libcrypto.a \
+ $(ZLIB_D)/lib/libz.a \
+ $(CJSON_D)/lib/libcjson.a
+
+LIBS := -lm -ldl -pthread
+ifeq ($(STATIC),1)
+LINK_MODE := -static
+RUNTIME_TARGET := static-runtime
+STATIC_LIB_DIRS := -L$(SYSRT_LINKS) -L$(SYSRT)/usr/lib64 \
+ -L$(SYS_GCC_LIBDIR)
+else
+LINK_MODE :=
+RUNTIME_TARGET :=
+STATIC_LIB_DIRS :=
+endif
+
+BIN := llsh
+MAIN := src/main.cpp
+MAIN_OBJ := src/main.o
+LIB_SRC_CPP := $(wildcard src/*.cpp)
+LIB_SRC_C := src/escape.c
+LIB_SRC := $(LIB_SRC_CPP) $(LIB_SRC_C)
+LIB_OBJ := $(LIB_SRC_CPP:.cpp=.o) $(LIB_SRC_C:.c=.o)
+LIB_OBJ_NO_MAIN := $(filter-out $(MAIN_OBJ),$(LIB_OBJ))
+
+TEST_SRC_CPP := $(wildcard tests/test_*.cpp)
+TEST_SRC_C := tests/test_frontmatter.c tests/test_dotagents.c \
+ tests/test_quine.c tests/test_clio.c
+TEST_SRC := $(TEST_SRC_CPP) $(TEST_SRC_C)
+TEST_BIN := $(TEST_SRC_CPP:.cpp=) $(TEST_SRC_C:.c=)
+LINKED_TESTS := $(TEST_SRC_CPP:.cpp=)
+
+HEADERS := $(wildcard src/*.h) $(wildcard include/*.h)
+
+ASAN_CC ?= clang
+ASAN_CXX ?= clang++
+ASAN_FLAGS := -fsanitize=address,undefined -fno-omit-frame-pointer
+ASANDIR := build-asan
+LIB_SRC_NO_MAIN := $(filter-out $(MAIN),$(LIB_SRC))
+
+.PHONY: all test valgrind asan clean vendor vendor-deps vendor-clean static-runtime
+
+all: $(BIN) $(TEST_BIN)
+
+# --------------------------------------------------------------- dependencies
+# Builds the bundled upstream sources with the host compiler. These are
+# ordinary glibc-targeting static archives; the final link uses host glibc.
+vendor-deps:
+ @set -e; \
+ mkdir -p "$(VEXT)"; \
+ for t in $(VENDOR)/src/*.tar.gz; do \
+ b=$$(basename $$t .tar.gz); \
+ [ -d "$(VEXT)/$$b" ] || tar xzf "$$t" -C "$(VEXT)"; \
+ done; \
+ if [ ! -f "$(NLOHMANN_D)/include/nlohmann/json.hpp" ]; then \
+ mkdir -p "$(NLOHMANN_D)"; \
+ cp -r "$(VEXT)/nlohmann-json-3.12.0/include" "$(NLOHMANN_D)/"; \
+ fi; \
+ if [ ! -f "$(ZLIB_D)/lib/libz.a" ]; then \
+ cd "$(VEXT)/zlib-1.3.1"; \
+ make distclean >/dev/null 2>&1 || true; \
+ CC="$(CC)" CFLAGS="-Os -ffunction-sections -fdata-sections" \
+ ./configure --prefix="$(ZLIB_D)" --static >/dev/null; \
+ make -j4 >/dev/null; make install >/dev/null; \
+ fi; \
+ if [ ! -f "$(OPENSSL_D)/lib64/libcrypto.a" ]; then \
+ cd "$(VEXT)/openssl-3.3.2"; \
+ CC="$(CC)" CFLAGS="-Os -fPIC" AR="$(AR)" RANLIB="$(RANLIB)" \
+ ./Configure linux-x86_64 no-shared no-tests no-dso \
+ --prefix="$(OPENSSL_D)" >/dev/null; \
+ make -j4 >/dev/null; make install_sw >/dev/null; \
+ fi; \
+ if [ ! -f "$(CJSON_D)/lib/libcjson.a" ]; then \
+ mkdir -p "$(CJSON_D)/include/cjson" "$(CJSON_D)/lib"; \
+ "$(CC)" -Os -ffunction-sections -fdata-sections -fPIC \
+ -c "$(VEXT)/cJSON-1.7.19/cJSON.c" -o "$(CJSON_D)/lib/cJSON.o"; \
+ "$(AR)" rcs "$(CJSON_D)/lib/libcjson.a" "$(CJSON_D)/lib/cJSON.o"; \
+ "$(RANLIB)" "$(CJSON_D)/lib/libcjson.a"; \
+ cp "$(VEXT)/cJSON-1.7.19/cJSON.h" "$(CJSON_D)/include/cjson/"; \
+ fi; \
+ if [ ! -f "$(CURL_D)/lib/libcurl.a" ]; then \
+ cd "$(VEXT)/curl-8.10.1"; \
+ CC="$(CC)" CFLAGS="-Os -ffunction-sections -fdata-sections" \
+ LDFLAGS="-Wl,--gc-sections" \
+ ./configure --host=x86_64-linux --prefix="$(CURL_D)" \
+ --disable-shared --enable-static \
+ --with-ssl="$(OPENSSL_D)" --with-zlib="$(ZLIB_D)" \
+ --disable-ftp --disable-rtsp --disable-dict --disable-telnet \
+ --disable-imap --disable-pop3 --disable-smtp --disable-ldap \
+ --without-proxy --without-metalink --without-libpsl \
+ --without-fish --without-gssapi --without-brotli --without-zstd \
+ --without-libssh2 --without-nghttp2 --without-ngtcp2 \
+ --without-libidn2 --without-libgsasl >/dev/null; \
+ make -j4 >/dev/null; make install >/dev/null; \
+ fi
+
+# Fedora does not install the static system C/C++ runtimes by default. Fetch
+# the matching distro packages into the ignored build tree (no system install
+# or sudo required) and link against those package archives.
+static-runtime:
+ @set -e; \
+ mkdir -p "$(SYSRT)"; \
+ if [ ! -f "$(SYSRT)/usr/lib64/libc.a" ] || \
+ [ ! -f "$(SYS_GCC_LIBDIR)/libstdc++.a" ]; then \
+ glibc_ver=$$(rpm -q --qf '%{VERSION}-%{RELEASE}.%{ARCH}' glibc); \
+ gcc_ver=$$(rpm -q --qf '%{VERSION}-%{RELEASE}.%{ARCH}' libstdc++); \
+ dnf download --destdir="$(SYSRT)" \
+ "glibc-static-$$glibc_ver" "libstdc++-static-$$gcc_ver" >/dev/null; \
+ for r in "$(SYSRT)"/*.rpm; do \
+ rpm2cpio "$$r" | (cd "$(SYSRT)" && cpio -idm --quiet 2>/dev/null); \
+ done; \
+ fi; \
+ mkdir -p "$(SYSRT_LINKS)"; \
+ sed "s#/usr/lib64/#$(SYSRT)/usr/lib64/#g" \
+ "$(SYSRT)/usr/lib64/libm.a" > "$(SYSRT_LINKS)/libm.a"; \
+ test -f "$(SYSRT)/usr/lib64/libc.a"; \
+ test -f "$(SYS_GCC_LIBDIR)/libstdc++.a"
+
+vendor: vendor-deps
+
+# ------------------------------------------------------------------- app
+$(BIN): $(MAIN_OBJ) $(LIB_OBJ_NO_MAIN) | vendor-deps $(RUNTIME_TARGET)
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LINK_MODE) $(STATIC_LIB_DIRS) -o $@ $^ \
+ $(VLIBS) $(LIBS) -Wl,--gc-sections -s -Wl,--build-id=none
+
+src/%.o: src/%.cpp $(HEADERS) | vendor-deps
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@
+
+src/%.o: src/%.c $(HEADERS) | vendor-deps
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
+
+tests/%.o: tests/%.cpp $(HEADERS) | vendor-deps
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@
+
+tests/%.o: tests/%.c $(HEADERS) | vendor-deps
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
+
+# Header-only portability tests.
+tests/test_frontmatter: tests/test_frontmatter.o | vendor-deps $(RUNTIME_TARGET)
+ $(CC) $(CFLAGS) -o $@ $< $(STATIC_LIB_DIRS) $(LINK_MODE)
+
+tests/test_dotagents: tests/test_dotagents.o | vendor-deps $(RUNTIME_TARGET)
+ $(CC) $(CFLAGS) -o $@ $< $(STATIC_LIB_DIRS) $(LINK_MODE)
+
+tests/test_clio: tests/test_clio.o | vendor-deps $(RUNTIME_TARGET)
+ $(CC) $(CFLAGS) -o $@ $< $(STATIC_LIB_DIRS) $(LINK_MODE)
+
+tests/test_quine: tests/test_quine.c | vendor-deps $(RUNTIME_TARGET)
+ $(CC) $(CFLAGS) -o $@ $< $(STATIC_LIB_DIRS) $(LINK_MODE)
+
+$(LINKED_TESTS): %: %.o $(LIB_OBJ_NO_MAIN) | vendor-deps $(RUNTIME_TARGET)
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LINK_MODE) $(STATIC_LIB_DIRS) -o $@ \
+ $< $(LIB_OBJ_NO_MAIN) $(VLIBS) $(LIBS) -Wl,--gc-sections -s
+
+test: all
+ @set -e; for t in $(TEST_BIN); do echo "== $$t"; ./$$t; done; \
+ echo "ALL TESTS PASSED"
+
+valgrind: all
+ @set -e; \
+ for t in $(TEST_BIN); do \
+ echo "== $$t"; \
+ valgrind --quiet --leak-check=full --error-exitcode=9 \
+ --suppressions=valgrind.supp ./$$t >/dev/null; \
+ done; \
+ echo "VALGRIND CLEAN"
+
+# The sanitizer executable uses dynamic host runtimes by design; application
+# and dependency code are instrumented independently in a scratch directory.
+asan: vendor-deps
+ @rm -rf $(ASANDIR); mkdir -p $(ASANDIR)
+ @set -e; \
+ libs=""; \
+ for f in $(LIB_SRC_NO_MAIN); do \
+ b=$$(basename $$f); b=$${b%.c}; b=$${b%.cpp}; \
+ case $$f in \
+ *.cpp) $(ASAN_CXX) $(CXXFLAGS) $(ASAN_FLAGS) $(CPPFLAGS) -c $$f -o $(ASANDIR)/$$b.o ;; \
+ *) $(ASAN_CC) $(CFLAGS) $(ASAN_FLAGS) $(CPPFLAGS) -c $$f -o $(ASANDIR)/$$b.o ;; \
+ esac; \
+ libs="$$libs $(ASANDIR)/$$b.o"; \
+ done; \
+ for f in $(TEST_SRC); do \
+ b=$$(basename $$f); b=$${b%.c}; b=$${b%.cpp}; \
+ case $$f in \
+ *.cpp) $(ASAN_CXX) $(CXXFLAGS) $(ASAN_FLAGS) $(CPPFLAGS) -c $$f -o $(ASANDIR)/$$b.test.o ;; \
+ *) $(ASAN_CC) $(CFLAGS) $(ASAN_FLAGS) $(CPPFLAGS) -c $$f -o $(ASANDIR)/$$b.test.o ;; \
+ esac; \
+ case $$b in \
+ test_clio) $(ASAN_CXX) $(ASAN_FLAGS) -o $(ASANDIR)/$$b.test $(ASANDIR)/$$b.test.o $(LIBS) ;; \
+ *) $(ASAN_CXX) $(ASAN_FLAGS) -o $(ASANDIR)/$$b.test $(ASANDIR)/$$b.test.o $$libs $(VLIBS) $(LIBS) ;; \
+ esac; \
+ echo "== $$b"; \
+ ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 \
+ UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
+ $(ASANDIR)/$$b.test >/dev/null; \
+ done; \
+ echo "ASAN CLEAN"
+
+clean:
+ rm -f $(LIB_OBJ) $(TEST_BIN) tests/*.o $(BIN) vg.log
+ rm -rf $(ASANDIR)
+
+vendor-clean:
+ rm -rf $(BUILD) $(VEXT)