summaryrefslogtreecommitdiffstats
path: root/Makefile
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2024-04-08 11:27:11 -0400
committerTavian Barnes <tavianator@tavianator.com>2024-04-09 17:15:23 -0400
commitc31577d102d87455f3f12086be4c0e2159fa5d35 (patch)
tree864c7c199e5b846dcf497de8b667d6c6f8c550b9 /Makefile
parent5e0b721d0d929223e4308406480a1f1ca9e3f4dc (diff)
downloadbfs-c31577d102d87455f3f12086be4c0e2159fa5d35.tar.xz
build: Add a separate configuration step
Diffstat (limited to 'Makefile')
-rw-r--r--Makefile517
1 files changed, 517 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..326ee87
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,517 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# This Makefile implements the configuration and build steps for bfs. It is
+# portable to both GNU make and the BSD make implementations (how that works
+# is documented below). To build bfs, run
+#
+# $ make config
+# $ make
+
+# The default build target
+default: bfs
+.PHONY: default
+
+# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to
+.OBJDIR: .
+
+# We don't use any suffix rules
+.SUFFIXES:
+
+# GNU make has $^ for the full list of targets, while BSD make has $> and the
+# long-form ${.ALLSRC}. We could write $^ $> to get them both, but that would
+# break if one of them implemented support for the other. So instead, bring
+# BSD's ${.ALLSRC} to GNU.
+.ALLSRC ?= $^
+
+# Platform detection
+OS != uname
+ARCH != uname -m
+
+# For out-of-tree builds, e.g.
+#
+# $ make config BUILDDIR=/path/to/build/dir
+# $ make BUILDDIR=/path/to/build/dir
+BUILDDIR ?= .
+
+# Shorthand for build subdirectories
+BIN := ${BUILDDIR}/bin
+GEN := ${BUILDDIR}/gen
+OBJ := ${BUILDDIR}/obj
+
+# GNU make strips a leading ./ from target names, so do the same for BSD make
+BIN := ${BIN:./%=%}
+GEN := ${GEN:./%=%}
+OBJ := ${OBJ:./%=%}
+
+# Installation paths
+DESTDIR ?=
+PREFIX ?= /usr
+MANDIR ?= ${PREFIX}/share/man
+
+# Configurable executables; can be overridden with
+#
+# $ make config CC=clang
+CC ?= cc
+INSTALL ?= install
+MKDIR ?= mkdir -p
+PKG_CONFIG ?= pkg-config
+RM ?= rm -f
+
+# Configurable flags
+
+CPPFLAGS ?=
+CFLAGS ?= \
+ -g \
+ -Wall \
+ -Wformat=2 \
+ -Werror=implicit \
+ -Wimplicit-fallthrough \
+ -Wmissing-declarations \
+ -Wshadow \
+ -Wsign-compare \
+ -Wstrict-prototypes
+LDFLAGS ?=
+LDLIBS ?=
+
+EXTRA_CPPFLAGS ?=
+EXTRA_CFLAGS ?=
+EXTRA_LDFLAGS ?=
+EXTRA_LDLIBS ?=
+
+GIT_VERSION != test -d .git && command -v git >/dev/null 2>&1 && git describe --always --dirty || echo 3.1.3
+VERSION ?= ${GIT_VERSION}
+
+# Immutable flags
+export BFS_CPPFLAGS= \
+ -D__EXTENSIONS__ \
+ -D_ATFILE_SOURCE \
+ -D_BSD_SOURCE \
+ -D_DARWIN_C_SOURCE \
+ -D_DEFAULT_SOURCE \
+ -D_GNU_SOURCE \
+ -D_LARGEFILE64_SOURCE \
+ -D_POSIX_PTHREAD_SEMANTICS \
+ -D_FILE_OFFSET_BITS=64 \
+ -D_TIME_BITS=64
+export BFS_CFLAGS= -std=c17 -pthread
+
+# Platform-specific system libraries
+LDLIBS,DragonFly := -lposix1e
+LDLIBS,Linux := -lrt
+LDLIBS,NetBSD := -lutil
+LDLIBS,SunOS := -lsocket -lnsl
+_BFS_LDLIBS := ${LDLIBS,${OS}}
+export BFS_LDLIBS=${_BFS_LDLIBS}
+
+# Build profiles
+ASAN ?= n
+LSAN ?= n
+MSAN ?= n
+TSAN ?= n
+UBSAN ?= n
+GCOV ?= n
+LINT ?= n
+RELEASE ?= n
+
+export ASAN_CFLAGS= -fsanitize=address
+export LSAN_CFLAGS= -fsanitize=leak
+export MSAN_CFLAGS= -fsanitize=memory -fsanitize-memory-track-origins
+export UBSAN_CFLAGS= -fsanitize=undefined
+
+# https://github.com/google/sanitizers/issues/342
+export TSAN_CPPFLAGS= -DBFS_USE_TARGET_CLONES=0
+export TSAN_CFLAGS= -fsanitize=thread
+
+SAN := ${ASAN}${LSAN}${MSAN}${TSAN}${UBSAN}
+export SAN_CFLAGS= -fno-sanitize-recover=all
+
+# MSAN and TSAN both need all code to be instrumented
+export NOLIBS= ${MSAN}${TSAN}
+
+# gcov only intercepts fork()/exec() with -std=gnu*
+export GCOV_CFLAGS= --coverage -std=gnu17
+
+export LINT_CPPFLAGS= -D_FORTIFY_SOURCE=3 -DBFS_LINT
+export LINT_CFLAGS= -Werror -O2
+
+export RELEASE_CPPFLAGS= -DNDEBUG
+export RELEASE_CFLAGS= -O3 -flto=auto
+
+# Auto-detected library dependencies. Can be set manually with
+#
+# $ make config USE_LIBURING=n USE_ONIGURUMA=y
+USE_LIBACL ?=
+USE_LIBCAP ?=
+USE_LIBURING ?=
+USE_ONIGURUMA ?=
+
+# Save the new value of these variables, before they potentially get overridden
+# by `-include ${CONFIG}` below
+
+_XPREFIX := ${PREFIX}
+_XMANDIR := ${MANDIR}
+
+_XOS := ${OS}
+_XARCH := ${ARCH}
+
+_XCC := ${CC}
+_XINSTALL := ${INSTALL}
+_XMKDIR := ${MKDIR}
+_XRM := ${RM}
+
+_XCPPFLAGS := ${CPPFLAGS}
+_XCFLAGS := ${CFLAGS}
+_XLDFLAGS := ${LDFLAGS}
+_XLDLIBS := ${LDLIBS}
+
+_XUSE_LIBACL := ${USE_LIBACL}
+_XUSE_LIBCAP := ${USE_LIBCAP}
+_XUSE_LIBURING := ${USE_LIBURING}
+_XUSE_ONIGURUMA := ${USE_ONIGURUMA}
+
+# GNU make supports `export VAR`, but BSD make requires `export VAR=value`.
+# Sadly, GNU make gives a recursion error on `export VAR=${VAR}`.
+
+_BUILDDIR := ${BUILDDIR}
+_PKG_CONFIG := ${PKG_CONFIG}
+
+export BUILDDIR=${_BUILDDIR}
+export PKG_CONFIG=${_PKG_CONFIG}
+
+export XPREFIX=${_XPREFIX}
+export XMANDIR=${_XMANDIR}
+
+export XOS=${_XOS}
+export XARCH=${_XARCH}
+
+export XCC=${_XCC}
+export XINSTALL=${_XINSTALL}
+export XMKDIR=${_XMKDIR}
+export XRM=${_XRM}
+
+export XCPPFLAGS=${_XCPPFLAGS}
+export XCFLAGS=${_XCFLAGS}
+export XLDFLAGS=${_XLDFLAGS}
+export XLDLIBS=${_XLDLIBS}
+
+export XUSE_LIBACL=${_XUSE_LIBACL}
+export XUSE_LIBCAP=${_XUSE_LIBCAP}
+export XUSE_LIBURING=${_XUSE_LIBURING}
+export XUSE_ONIGURUMA=${_XUSE_ONIGURUMA}
+
+# The configuration file generated by `make config`
+CONFIG := ${GEN}/config.mk
+-include ${CONFIG}
+
+## Configuration phase (`make config`)
+
+# External dependencies
+PKGS := \
+ ${GEN}/libacl.mk \
+ ${GEN}/libcap.mk \
+ ${GEN}/liburing.mk \
+ ${GEN}/oniguruma.mk
+
+# Makefile fragments generated by `make config`
+MKS := \
+ ${GEN}/vars.mk \
+ ${GEN}/deps.mk \
+ ${GEN}/objs.mk \
+ ${PKGS}
+
+# The configuration goal itself
+config: ${MKS}
+ @printf 'include $${GEN}/%s\n' ${MKS:${GEN}/%=%} >${CONFIG}
+.PHONY: config
+
+# Saves the configurable variables
+${GEN}/vars.mk::
+ @${XMKDIR} ${@D}
+ @printf 'PREFIX := %s\n' "$$XPREFIX" >$@
+ @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@
+ @printf 'OS := %s\n' "$$XOS" >>$@
+ @printf 'ARCH := %s\n' "$$XARCH" >>$@
+ @printf 'CC := %s\n' "$$XCC" >>$@
+ @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@
+ @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@
+ @printf 'RM := %s\n' "$$XRM" >>$@
+ @printf 'CPPFLAGS := %s\n' "$$BFS_CPPFLAGS" >>$@
+ @test "${TSAN}" != y || printf 'CPPFLAGS += %s\n' "$$TSAN_CPPFLAGS" >>$@
+ @test "${LINT}" != y || printf 'CPPFLAGS += %s\n' "$$LINT_CPPFLAGS" >>$@
+ @test "${RELEASE}" != y || printf 'CPPFLAGS += %s\n' "$$RELEASE_CPPFLAGS" >>$@
+ @test -z "$$XCPPFLAGS" || printf 'CPPFLAGS += %s\n' "$$XCPPFLAGS" >>$@
+ @test -z "$$EXTRA_CPPFLAGS" || printf 'CPPFLAGS += %s\n' "$$EXTRA_CPPFLAGS" >>$@
+ @printf 'CFLAGS := %s\n' "$$BFS_CFLAGS" >>$@
+ @test "${ASAN}" != y || printf 'CFLAGS += %s\n' "$$ASAN_CFLAGS" >>$@
+ @test "${LSAN}" != y || printf 'CFLAGS += %s\n' "$$LSAN_CFLAGS" >>$@
+ @test "${MSAN}" != y || printf 'CFLAGS += %s\n' "$$MSAN_CFLAGS" >>$@
+ @test "${TSAN}" != y || printf 'CFLAGS += %s\n' "$$TSAN_CFLAGS" >>$@
+ @test "${UBSAN}" != y || printf 'CFLAGS += %s\n' "$$UBSAN_CFLAGS" >>$@
+ @case "${SAN}" in *y*) printf 'CFLAGS += %s\n' "$$SAN_CFLAGS" >>$@ ;; esac
+ @test "${GCOV}" != y || printf 'CFLAGS += %s\n' "$$GCOV_CFLAGS" >>$@
+ @test "${LINT}" != y || printf 'CFLAGS += %s\n' "$$LINT_CFLAGS" >>$@
+ @test "${RELEASE}" != y || printf 'CFLAGS += %s\n' "$$RELEASE_CFLAGS" >>$@
+ @test -z "$$XCFLAGS" || printf 'CFLAGS += %s\n' "$$XCFLAGS" >>$@
+ @test -z "$$EXTRA_CFLAGS" || printf 'CFLAGS += %s\n' "$$EXTRA_CFLAGS" >>$@
+ @printf 'LDFLAGS := %s\n' "$$XLDFLAGS" >>$@
+ @test -z "$$EXTRA_LDFLAGS" || printf 'LDFLAGS += %s\n' "$$EXTRA_LDFLAGS" >>$@
+ @printf 'LDLIBS := %s\n' "$$XLDLIBS" >>$@
+ @test -z "$$EXTRA_LDLIBS" || printf 'LDLIBS += %s\n' "$$EXTRA_LDLIBS" >>$@
+ @test -z "$$BFS_LDLIBS" || printf 'LDLIBS += %s\n' "$$BFS_LDLIBS" >>$@
+ @case "${OS}-${SAN}" in FreeBSD-*y*) printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ ;; esac
+ @cat $@
+
+# Check for dependency generation support
+${GEN}/deps.mk::
+ @${MKDIR} ${@D}
+ @if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \
+ echo 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}'; \
+ fi 2>$@.log | tee $@
+ @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@
+
+# Lists file.o: file.c dependencies
+${GEN}/objs.mk::
+ @${MKDIR} ${@D}
+ @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >$@
+
+# Auto-detect dependencies and their build flags
+${PKGS}::
+ @${MKDIR} ${@D}
+ @config/pkg.sh ${@:${GEN}/%.mk=%} >$@ 2>$@.log
+ @cat $@
+
+# bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.).
+# Direct users to the new configuration system.
+asan lsan msan tsan ubsan gcov lint release::
+ @printf 'error: `make %s` is no longer supported. ' $@ >&2
+ @printf 'Use `make config %s=y` instead.\n' $$(echo $@ | tr '[a-z]' '[A-Z]') >&2
+ @false
+
+## Build phase (`make`)
+
+# The main binary
+bfs: ${BIN}/bfs
+.PHONY: bfs
+
+# All binaries
+BINS := \
+ ${BIN}/bfs \
+ ${BIN}/tests/mksock \
+ ${BIN}/tests/units \
+ ${BIN}/tests/xspawnee \
+ ${BIN}/tests/xtouch
+
+all: ${BINS}
+.PHONY: all
+
+# All object files except the entry point
+LIBBFS := \
+ ${OBJ}/src/alloc.o \
+ ${OBJ}/src/bar.o \
+ ${OBJ}/src/bfstd.o \
+ ${OBJ}/src/bftw.o \
+ ${OBJ}/src/color.o \
+ ${OBJ}/src/ctx.o \
+ ${OBJ}/src/diag.o \
+ ${OBJ}/src/dir.o \
+ ${OBJ}/src/dstring.o \
+ ${OBJ}/src/eval.o \
+ ${OBJ}/src/exec.o \
+ ${OBJ}/src/expr.o \
+ ${OBJ}/src/fsade.o \
+ ${OBJ}/src/ioq.o \
+ ${OBJ}/src/mtab.o \
+ ${OBJ}/src/opt.o \
+ ${OBJ}/src/parse.o \
+ ${OBJ}/src/printf.o \
+ ${OBJ}/src/pwcache.o \
+ ${OBJ}/src/stat.o \
+ ${OBJ}/src/thread.o \
+ ${OBJ}/src/trie.o \
+ ${OBJ}/src/typo.o \
+ ${OBJ}/src/version.o \
+ ${OBJ}/src/xregex.o \
+ ${OBJ}/src/xspawn.o \
+ ${OBJ}/src/xtime.o
+
+# Group relevant flags together
+ALL_CFLAGS = ${CPPFLAGS} ${CFLAGS} ${DEPFLAGS}
+ALL_LDFLAGS = ${CFLAGS} ${LDFLAGS}
+
+# The main binary
+${BIN}/bfs: ${LIBBFS} ${OBJ}/src/main.o
+
+${BINS}:
+ @${MKDIR} ${@D}
+ +${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@
+ ${POSTLINK}
+
+# All object files
+OBJS := \
+ ${OBJ}/src/main.o \
+ ${OBJ}/tests/alloc.o \
+ ${OBJ}/tests/bfstd.o \
+ ${OBJ}/tests/bit.o \
+ ${OBJ}/tests/ioq.o \
+ ${OBJ}/tests/main.o \
+ ${OBJ}/tests/mksock.o \
+ ${OBJ}/tests/trie.o \
+ ${OBJ}/tests/xspawn.o \
+ ${OBJ}/tests/xspawnee.o \
+ ${OBJ}/tests/xtime.o \
+ ${OBJ}/tests/xtouch.o \
+ ${LIBBFS}
+
+# Depend on ${CONFIG} to make sure `make config` runs first, and to rebuild when
+# the configuration changes
+${OBJS}: ${CONFIG}
+ @${MKDIR} ${@D}
+ ${CC} ${ALL_CFLAGS} -c ${@:${OBJ}/%.o=%.c} -o $@
+
+# Save the version number to this file, but only update VERSION if it changes
+${GEN}/NEWVERSION::
+ @${MKDIR} ${@D}
+ @printf '%s\n' '${VERSION}' >$@
+
+${GEN}/VERSION: ${GEN}/NEWVERSION
+ @test -e $@ && cmp -s $@ ${.ALLSRC} && rm ${.ALLSRC} || mv ${.ALLSRC} $@
+
+# Rebuild version.c whenever the version number changes
+${OBJ}/src/version.o: ${GEN}/VERSION
+${OBJ}/src/version.o: CPPFLAGS := ${CPPFLAGS} -DBFS_VERSION='"${VERSION}"'
+
+# Clean all build products
+clean::
+ ${RM} -r ${BIN} ${OBJ}
+
+# Clean everything, including generated files
+distclean: clean
+ ${RM} -r ${GEN}
+.PHONY: distclean
+
+## Test phase (`make check`)
+
+# Unit test binaries
+UTEST_BINS := \
+ ${BIN}/tests/units \
+ ${BIN}/tests/xspawnee
+
+# Integration test binaries
+ITEST_BINS := \
+ ${BIN}/tests/mksock \
+ ${BIN}/tests/xtouch
+
+# Build (but don't run) test binaries
+tests: ${UTEST_BINS} ${ITEST_BINS}
+.PHONY: tests
+
+# Run all the tests
+check: unit-tests integration-tests
+.PHONY: check
+
+# Run the unit tests
+unit-tests: ${UTEST_BINS}
+ ${BIN}/tests/units
+.PHONY: unit-tests
+
+${BIN}/tests/units: \
+ ${OBJ}/tests/alloc.o \
+ ${OBJ}/tests/bfstd.o \
+ ${OBJ}/tests/bit.o \
+ ${OBJ}/tests/ioq.o \
+ ${OBJ}/tests/main.o \
+ ${OBJ}/tests/trie.o \
+ ${OBJ}/tests/xspawn.o \
+ ${OBJ}/tests/xtime.o \
+ ${LIBBFS}
+
+${BIN}/tests/xspawnee: \
+ ${OBJ}/tests/xspawnee.o
+
+# The different flag combinations we check
+INTEGRATIONS := default dfs ids eds j1 j2 j3 s
+INTEGRATION_TESTS := ${INTEGRATIONS:%=check-%}
+
+# Check just `bfs`
+check-default: ${BIN}/bfs ${ITEST_BINS}
+ +./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs" ${TEST_FLAGS}
+
+# Check the different search strategies
+check-dfs check-ids check-eds: ${BIN}/bfs ${ITEST_BINS}
+ +./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -S ${@:check-%=%}" ${TEST_FLAGS}
+
+# Check various flags
+check-j1 check-j2 check-j3 check-s: ${BIN}/bfs ${ITEST_BINS}
+ +./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -${@:check-%=%}" ${TEST_FLAGS}
+
+# Run the integration tests
+integration-tests: ${INTEGRATION_TESTS}
+.PHONY: integration-tests
+
+${BIN}/tests/mksock: \
+ ${OBJ}/tests/mksock.o \
+ ${LIBBFS}
+
+${BIN}/tests/xtouch: \
+ ${OBJ}/tests/xtouch.o \
+ ${LIBBFS}
+
+# `make distcheck` configurations
+DISTCHECKS := distcheck-asan distcheck-tsan distcheck-release
+
+# Don't use msan on macOS
+IS_DARWIN,Darwin := y
+IS_DARWIN := ${IS_DARWIN,${OS}}
+DISTCHECK_MSAN, := distcheck-msan
+DISTCHECKS += ${DISTCHECK_MSAN,${IS_DARWIN}}
+
+# Only add a 32-bit build on 64-bit Linux
+DISTCHECK_M32,Linux,x86_64 := distcheck-m32
+DISTCHECKS += ${DISTCHECK_M32,${OS},${ARCH}}
+
+# Test multiple configurations
+distcheck: ${DISTCHECKS}
+.PHONY: distcheck
+
+# Per-distcheck configuration
+DISTCHECK_CONFIG_asan := ASAN=y UBSAN=y
+DISTCHECK_CONFIG_msan := MSAN=y UBSAN=y CC=clang
+DISTCHECK_CONFIG_tsan := TSAN=y UBSAN=y CC=clang
+DISTCHECK_CONFIG_m32 := EXTRA_CFLAGS="-m32" PKG_CONFIG_PATH=/usr/lib32/pkgconfig USE_LIBURING=n
+DISTCHECK_CONFIG_release := RELEASE=y
+
+${DISTCHECKS}::
+ +${MAKE} -rs BUILDDIR=${BUILDDIR}/$@ config ${DISTCHECK_CONFIG_${@:distcheck-%=%}}
+ +${MAKE} -s BUILDDIR=${BUILDDIR}/$@ check TEST_FLAGS="--sudo --verbose=skipped"
+
+## Packaging (`make install`)
+
+DEST_PREFIX := ${DESTDIR}${PREFIX}
+DEST_MANDIR := ${DESTDIR}${MANDIR}
+
+install::
+ ${MKDIR} ${DEST_PREFIX}/bin
+ ${INSTALL} -m755 ${BIN}/bfs ${DEST_PREFIX}/bin/bfs
+ ${MKDIR} ${DEST_MANDIR}/man1
+ ${INSTALL} -m644 docs/bfs.1 ${DEST_MANDIR}/man1/bfs.1
+ ${MKDIR} ${DEST_PREFIX}/share/bash-completion/completions
+ ${INSTALL} -m644 completions/bfs.bash ${DEST_PREFIX}/share/bash-completion/completions/bfs
+ ${MKDIR} ${DEST_PREFIX}/share/zsh/site-functions
+ ${INSTALL} -m644 completions/bfs.zsh ${DEST_PREFIX}/share/zsh/site-functions/_bfs
+ ${MKDIR} ${DEST_PREFIX}/share/fish/vendor_completions.d
+ ${INSTALL} -m644 completions/bfs.fish ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish
+
+uninstall::
+ ${RM} ${DEST_PREFIX}/share/bash-completion/completions/bfs
+ ${RM} ${DEST_PREFIX}/share/zsh/site-functions/_bfs
+ ${RM} ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish
+ ${RM} ${DEST_MANDIR}/man1/bfs.1
+ ${RM} ${DEST_PREFIX}/bin/bfs
+
+# Check that `make install` works and `make uninstall` removes everything
+check-install::
+ +${MAKE} install DESTDIR=${BUILDDIR}/pkg
+ +${MAKE} uninstall DESTDIR=${BUILDDIR}/pkg
+ ${BIN}/bfs ${BUILDDIR}/pkg -not -type d -print -exit 1
+ ${RM} -r ${BUILDDIR}/pkg