Files

169 lines
6.7 KiB
Makefile
Raw Permalink Normal View History

2026-10-03 13:12:54 +03:00
# Makefile for Solo-C.
#
# Written for the POSIX make utility (IEEE Std 1003.1, XCU make) and a POSIX
# shell (/bin/sh), so it works with any conforming make and no GNU-only
# features. Deliberately avoided:
#
# $(shell ...) not POSIX - pkg-config is run by the recipe shell
# ?= not POSIX - defaults are plain "=" and are overridden on
# the command line, e.g. make CC=clang CFLAGS=-O1
# .PHONY not POSIX - see the FORCE target at the bottom
# target-specific variables, ifeq, wildcard, -include, ... all non-POSIX
2026-10-03 13:37:26 +03:00
# $(patsubst ...) not POSIX - so the install directory is resolved by the
# recipe shell, see INSTALL_DIR below
2026-10-03 13:12:54 +03:00
#
# Rule syntax used here: recipe lines start with a TAB, macros are written
# $(NAME) (POSIX also accepts ${NAME}), and the only automatic macro used is
# $@, the name of the target. Prerequisite names are written out: $< is POSIX
# for inference rules only, and "$< is the first prerequisite" in a plain rule
# is a GNU extension that even BusyBox make lacks.
SHELL = /bin/sh
# Standard macros. POSIX defaults CC to c99; Solo-C uses the system compiler
# driver "cc" instead, which is the compiler a platform actually ships
# (gcc, clang, musl-gcc, ...). Every macro here can be overridden from the
# command line: make CC=clang, make CFLAGS=-O1, make LDLIBS=-lcurl.
CC = cc
CFLAGS = -Wall -Wextra -O2 -std=c99 -march=native
LDFLAGS =
LDLIBS =
LINT = lint
LINTFLAGS =
LINTLIBS = -lc
# Link-time discovery of the shared dependencies. POSIX make cannot run
# commands while reading a makefile, so the flags are collected by the shell
# that runs the recipe instead of by $(shell ...) while parsing.
PKG_CONFIG = pkg-config
PKGS = libcurl json-c readline
# $$(...) survives expansion of the macro and reaches the shell as $(...).
PKG_CFLAGS = $$($(PKG_CONFIG) --cflags $(PKGS))
PKG_LIBS = $$($(PKG_CONFIG) --libs $(PKGS))
# Fully static binary.
#
# The distro libcurl.a cannot be linked statically on Alpine: it pulls in
# libpsl, libbrotli, libzstd and libnghttp2, whose static archives hold LTO
# bytecode from GCC 14 that the installed GCC 15 cannot read ("bytecode
# stream ... generated with LTO version 14.0 instead of the expected 15.1"),
# so psl_*/Brotli*/ZSTD* never resolve. -fno-lto does not help; the objects
# are bytecode, not C.
#
# static-deps/libcurl.a is curl built without those four libraries, leaving
# only LTO-free static deps. Regenerate with: make static-deps
#
# c-ares is built from source by the same script, for two reasons: a minimal
# system often has no libcares.a at all (Void's c-ares-devel ships the shared
# library only), and where an archive does exist it can carry the same LTO
# bytecode as the distro libcurl.a. Building it also pins the version.
#
# Archive order matters: libcurl.a pulls in ares_* symbols, so libcares.a has to
# come after it on the link line.
2026-10-03 13:12:54 +03:00
CURL_STATIC = static-deps/libcurl.a
CARES_STATIC = static-deps/libcares.a
STATIC_LIBS = $(CURL_STATIC) $(CARES_STATIC) -lssl -lcrypto -lz -lidn2 -lunistring \
-lreadline -lncursesw -ljson-c -lm -ldl -pthread
solo: main.c
$(CC) $(CFLAGS) $(PKG_CFLAGS) $(LDFLAGS) -o $@.new main.c $(PKG_LIBS) $(LDLIBS)
strip $@.new
mv -f $@.new $@
2026-10-03 13:12:54 +03:00
# Writes solo-static, not a file called "static", so $@ is not used here.
static: main.c
@for f in $(CURL_STATIC) $(CARES_STATIC); do \
test -f $$f || { echo "missing $$f - run: make static-deps"; exit 1; }; \
done
$(CC) $(CFLAGS) $(LDFLAGS) -fno-lto -static -o solo-static.new main.c $(STATIC_LIBS) $(LDLIBS)
strip solo-static.new
mv -f solo-static.new solo-static
2026-10-03 13:12:54 +03:00
static-deps: FORCE
sh ./mkstaticdeps.sh
# Rebuild both archives from scratch, ignoring what is already there.
2026-10-03 13:12:54 +03:00
static-deps-force: FORCE
FORCE=1 sh ./mkstaticdeps.sh
2026-10-03 13:37:26 +03:00
# Install paths. Every one of these can be set on the command line, so the
# install target takes any location you name:
#
# make install # $(DESTDIR)$(BINDIR) = /usr/local/bin
# make install PREFIX=$HOME/.local # ~/.local/bin
# make install BINDIR=/opt/solo/bin # an explicit directory
# make install DIR=/opt/solo/bin # the same, spelled in one macro
# make install DIR=~/.local/bin # a leading ~ is expanded, see below
# make install DIR=/usr/bin INSTALL="install -C"
# make install DESTDIR=/tmp/pkg DIR=/usr/bin # staged, for packaging
#
# DIR wins over BINDIR when both are given; DESTDIR is prepended either way, so
# "make install DESTDIR=/tmp/pkg DIR=/usr/bin" stages into /tmp/pkg/usr/bin.
DESTDIR =
2026-10-03 13:12:54 +03:00
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
2026-10-03 13:37:26 +03:00
DIR =
2026-10-03 13:12:54 +03:00
INSTALL = install
2026-10-03 13:37:26 +03:00
# The installation directory, as a shell snippet that prints it on stdout.
#
# POSIX make has no string functions, so "DIR if set, else BINDIR" and the
# leading ~ are handled by the recipe shell instead of by the makefile. Both
# install and uninstall use this one snippet, so they always agree on the path.
# $$(...) survives expansion of the macro and reaches the shell as $(...).
INSTALL_DIR = d="$(DIR)"; \
test -n "$$d" || d="$(BINDIR)"; \
case "$$d" in \
'~') d="$$HOME";; \
'~/'*) d="$$HOME/$$(printf '%s' "$$d" | cut -c3-)";; \
esac; \
printf '%s%s\n' "$(DESTDIR)" "$$d"
2026-10-03 13:37:26 +03:00
# Install solo, and solo-static too when it has been built. Prints the location
# it used, so a script can see where the binaries went.
install: solo FORCE
@dir=$$($(INSTALL_DIR)); \
echo "installing into $$dir"; \
$(INSTALL) -d "$$dir"; \
$(INSTALL) -m 755 solo "$$dir/solo"; \
if test -f solo-static; then \
$(INSTALL) -m 755 solo-static "$$dir/solo-static"; \
fi
# Install the static binary only; fails if it has not been built yet.
install-static: static FORCE
@dir=$$($(INSTALL_DIR)); \
echo "installing solo-static into $$dir"; \
$(INSTALL) -d "$$dir"; \
$(INSTALL) -m 755 solo-static "$$dir/solo-static"
# Prints the installation directory that install would use, and nothing else.
installdir: FORCE
@dir=$$($(INSTALL_DIR)); echo "$$dir"
# Removes the binaries from the location resolved the same way, so
# "make install DIR=~/.local/bin" is undone by
# "make uninstall DIR=~/.local/bin".
2026-10-03 13:12:54 +03:00
uninstall: FORCE
2026-10-03 13:37:26 +03:00
@dir=$$($(INSTALL_DIR)); \
echo "removing from $$dir"; \
rm -f "$$dir/solo" "$$dir/solo-static"
2026-10-03 13:12:54 +03:00
clean: FORCE
rm -f solo solo-static solo.new solo-static.new
# Offline regression tests. No API key, no network: every line sent to the agent
# starts with / or !, so nothing reaches the model. Pass another binary as $1.
test: solo FORCE
sh ./test.sh ./solo
2026-10-03 13:12:54 +03:00
# POSIX lint target: runs the lint tool named by the LINT macro.
lint: main.c FORCE
$(LINT) $(LINTFLAGS) $(LINTLIBS) main.c
# POSIX make has no .PHONY special target. Any target that depends on FORCE is
# always out of date, because FORCE never creates a file, so its recipe always
# runs. Keep this target last: the first target in the file is the default goal.
FORCE:
@: