summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2024-04-16 18:43:49 -0400
committerTavian Barnes <tavianator@tavianator.com>2024-04-16 18:58:51 -0400
commitc4c063e9844f2bd2271b2e3391f59f872c66f69a (patch)
treed09c0852ae24c78e723e425306ae006fae16e076
parent98c539eeda8f9adfd22a3b2b6ece4fe1ca06b3b4 (diff)
downloadbfs-c4c063e9844f2bd2271b2e3391f59f872c66f69a.tar.xz
build: Refactor configuration
We now use a recursive make invocation to do the work of `make config`. The new implementation is also compatible with GNU make 3.81 found on macOS.
-rw-r--r--.github/workflows/ci.yml5
-rw-r--r--Makefile369
-rwxr-xr-xconfig/cc.sh2
-rw-r--r--config/config.mk77
-rw-r--r--config/deps.mk15
-rw-r--r--config/exports.mk19
-rw-r--r--config/flags.mk119
-rw-r--r--config/pkg.mk17
-rwxr-xr-xconfig/pkgconf.sh12
-rw-r--r--config/pkgs.mk17
-rw-r--r--config/prelude.mk164
-rw-r--r--config/vars.mk21
-rwxr-xr-xconfig/vars.sh81
13 files changed, 454 insertions, 464 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 78aa196..3ad924f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -53,13 +53,12 @@ jobs:
run: |
brew install \
bash \
- expect \
- make
+ expect
- name: Run tests
run: |
jobs=$(sysctl -n hw.ncpu)
- gmake -j$jobs distcheck
+ make -j$jobs distcheck
freebsd:
name: FreeBSD
diff --git a/Makefile b/Makefile
index 7cddc51..e9efef3 100644
--- a/Makefile
+++ b/Makefile
@@ -2,285 +2,27 @@
# 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
+# portable to both GNU make and most BSD make implementations. To build bfs,
+# run
#
# $ make config
# $ make
+# Utilities and GNU/BSD portability
+include config/prelude.mk
+
# 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 ?= $^
-
-# GNU and BSD make have incompatible syntax for conditionals, but we can do a
-# lot with just recursive variable expansion. Inspired by
-# https://github.com/wahern/autoguess
-TRUTHY,y := y
-TRUTHY,1 := y
-
-# Normalize ${V} to either "y" or ""
-IS_V := ${TRUTHY,${V}}
-
-# Suppress output unless V=1
-Q, := @
-Q := ${Q,${IS_V}}
-
-# Show full commands with `make V=1`, otherwise short summaries
-MSG = @msg() { \
- MSG="$$1"; \
- shift; \
- test "${IS_V}" || printf '%s\n' "$$MSG"; \
- test "$${1:-}" || return 0; \
- test "${IS_V}" && printf '%s\n' "$$*"; \
- "$$@"; \
- }; \
- msg
-
-# 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
-
-export 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
-
-# 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}
-
-# 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}
-
-# The configuration file generated by `make config`
-CONFIG := ${GEN}/config.mk
+# Include the generated build config, if it exists
-include ${CONFIG}
## Configuration phase (`make config`)
-# Makefile fragments generated by `make config`
-MKS := \
- ${GEN}/vars.mk \
- ${GEN}/deps.mk \
- ${GEN}/objs.mk \
- ${GEN}/pkgs.mk
-
-# cat a file if V=1
-VCAT,y := @cat
-VCAT, := @:
-VCAT := ${VCAT,${IS_V}}
-
# The configuration goal itself
-config: ${MKS}
- ${MSG} "[ GEN] ${CONFIG}"
- @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >${CONFIG}
- ${VCAT} ${CONFIG}
-.PHONY: config
-
-# Saves the configurable variables
-${GEN}/vars.mk:
- @${XMKDIR} ${@D}
- ${MSG} "[ GEN] $@"
- @config/vars.sh >$@
- ${VCAT} $@
-.PHONY: ${GEN}/vars.mk
-
-# Check for dependency generation support
-${GEN}/deps.mk: ${GEN}/vars.mk
- ${MSG} "[ GEN] $@"
- @+${MAKE} -rs -f config/deps.mk TARGET=$@
- ${VCAT} $@
- @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@
-.PHONY: ${GEN}/deps.mk
-
-# Lists file.o: file.c dependencies
-${GEN}/objs.mk:
- @${XMKDIR} ${@D}
- ${MSG} "[ GEN] $@"
- @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >$@
-.PHONY: ${GEN}/objs.mk
-
-# External dependencies
-PKG_MKS := \
- ${GEN}/libacl.mk \
- ${GEN}/libcap.mk \
- ${GEN}/libselinux.mk \
- ${GEN}/liburing.mk \
- ${GEN}/oniguruma.mk
-
-# Auto-detect dependencies and their build flags
-${GEN}/pkgs.mk: ${PKG_MKS}
- ${MSG} "[ GEN] $@"
- @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >$@
- @+${MAKE} -rs -f config/pkgs.mk TARGET=$@
- ${VCAT} $@
-.PHONY: ${GEN}/pkgs.mk
-
-# Auto-detect dependencies
-${PKG_MKS}: ${GEN}/vars.mk
- @+${MAKE} -rs -f config/pkg.mk TARGET=$@
- @if [ "${IS_V}" ]; then \
- cat $@; \
- elif grep -q PKGS $@; then \
- printf '[ GEN] %-18s [y]\n' $@; \
- else \
- printf '[ GEN] %-18s [n]\n' $@; \
- fi
-.PHONY: ${PKG_MKS}
+config::
+ @+${MAKE} -sf config/config.mk
# bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.).
# Direct users to the new configuration system.
@@ -313,36 +55,6 @@ BINS := \
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}
@@ -355,22 +67,6 @@ ${BINS}:
+${MSG} "[ LD ] $@" ${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}
-
# Get the .c file for a .o file
CSRC = ${@:${OBJ}/%.o=%.c}
@@ -383,14 +79,20 @@ ${OBJS}: ${CONFIG}
# Save the version number to this file, but only update VERSION if it changes
${GEN}/NEWVERSION::
@${MKDIR} ${@D}
- @printf '%s\n' '${VERSION}' >$@
+ @if [ "$$VERSION" ]; then \
+ printf '%s\n' "$$VERSION"; \
+ elif test -d .git && command -v git >/dev/null 2>&1; then \
+ git describe --always --dirty; \
+ else \
+ echo "3.1.3"; \
+ fi >$@
${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}"'
+${OBJ}/src/version.o: CPPFLAGS := ${CPPFLAGS} -DBFS_VERSION='"$$(cat ${GEN}/VERSION)"'
## Test phase (`make check`)
@@ -418,14 +120,7 @@ unit-tests: ${UTEST_BINS}
.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 \
+ ${UNIT_OBJS} \
${LIBBFS}
${BIN}/tests/xspawnee: \
@@ -463,20 +158,20 @@ ${BIN}/tests/xtouch: \
${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}}
+DISTCHECKS := \
+ distcheck-asan \
+ distcheck-msan \
+ distcheck-tsan \
+ distcheck-m32 \
+ distcheck-release
# Test multiple configurations
-distcheck: ${DISTCHECKS}
+distcheck:
+ @+${MAKE} -s distcheck-asan
+ @+test "$$(uname)" = Darwin || ${MAKE} -s distcheck-msan
+ @+${MAKE} -s distcheck-tsan
+ @+test "$$(uname)-$$(uname -m)" != Linux-x86_64 || ${MAKE} -s distcheck-m32
+ @+${MAKE} -s distcheck-release
.PHONY: distcheck
# Per-distcheck configuration
@@ -530,9 +225,11 @@ check-install::
# Clean all build products
clean::
- ${RM} -r ${BIN} ${OBJ}
+ ${MSG} "[ RM ] bin obj" \
+ ${RM} -r ${BIN} ${OBJ}
# Clean everything, including generated files
distclean: clean
- ${RM} -r ${GEN} ${DISTCHECKS}
+ ${MSG} "[ RM ] gen" \
+ ${RM} -r ${GEN} ${DISTCHECKS}
.PHONY: distclean
diff --git a/config/cc.sh b/config/cc.sh
index 2b340c0..abce508 100755
--- a/config/cc.sh
+++ b/config/cc.sh
@@ -7,4 +7,4 @@
set -eux
-$CC $CPPFLAGS $CFLAGS $LDFLAGS "$@" $LDLIBS -o /dev/null
+$XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $XLDLIBS -o /dev/null
diff --git a/config/config.mk b/config/config.mk
new file mode 100644
index 0000000..c8d7f19
--- /dev/null
+++ b/config/config.mk
@@ -0,0 +1,77 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Makefile fragment that implements `make config`
+
+include config/prelude.mk
+include config/exports.mk
+
+# Makefile fragments generated by `make config`
+MKS := \
+ ${GEN}/vars.mk \
+ ${GEN}/flags.mk \
+ ${GEN}/deps.mk \
+ ${GEN}/objs.mk \
+ ${GEN}/pkgs.mk
+
+# The main configuration file, which includes the others
+${CONFIG}: ${MKS}
+ ${MSG} "[ GEN] $@"
+ @printf '# %s\n' "$@" >$@
+ @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@
+ ${VCAT} ${CONFIG}
+.PHONY: ${CONFIG}
+
+# Saves the configurable variables
+${GEN}/vars.mk::
+ @${MKDIR} ${@D}
+ ${MSG} "[ GEN] $@"
+ @printf '# %s\n' "$@" >$@
+ @printf 'PREFIX := %s\n' "$$XPREFIX" >>$@
+ @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@
+ @printf 'OS := %s\n' "$${OS:-$$(uname)}" >>$@
+ @printf 'ARCH := %s\n' "$${ARCH:-$$(uname -m)}" >>$@
+ @printf 'CC := %s\n' "$$XCC" >>$@
+ @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@
+ @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@
+ @printf 'RM := %s\n' "$$XRM" >>$@
+ @printf 'PKGS :=\n' >>$@
+ ${VCAT} $@
+
+# Sets the build flags. This depends on vars.mk and uses a recursive make so
+# that the default flags can depend on variables like ${OS}.
+${GEN}/flags.mk: ${GEN}/vars.mk
+ @+${MAKE} -sf config/flags.mk
+.PHONY: ${GEN}/flags.mk
+
+# Check for dependency generation support
+${GEN}/deps.mk: ${GEN}/flags.mk
+ @+${MAKE} -sf config/deps.mk
+.PHONY: ${GEN}/deps.mk
+
+# Lists file.o: file.c dependencies
+${GEN}/objs.mk::
+ @${MKDIR} ${@D}
+ ${MSG} "[ GEN] $@"
+ @printf '# %s\n' "$@" >$@
+ @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >>$@
+
+# External dependencies
+PKG_MKS := \
+ ${GEN}/libacl.mk \
+ ${GEN}/libcap.mk \
+ ${GEN}/libselinux.mk \
+ ${GEN}/liburing.mk \
+ ${GEN}/oniguruma.mk
+
+# Auto-detect dependencies and their build flags
+${GEN}/pkgs.mk: ${PKG_MKS}
+ @printf '# %s\n' "$@" >$@
+ @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@
+ @+${MAKE} -sf config/pkgs.mk
+.PHONY: ${GEN}/pkgs.mk
+
+# Auto-detect dependencies
+${PKG_MKS}: ${GEN}/flags.mk
+ @+${MAKE} -sf config/pkg.mk TARGET=$@
+.PHONY: ${PKG_MKS}
diff --git a/config/deps.mk b/config/deps.mk
index 7d991ab..52ee0e1 100644
--- a/config/deps.mk
+++ b/config/deps.mk
@@ -3,11 +3,16 @@
# Makefile that generates gen/deps.mk
-.OBJDIR: .
+include config/prelude.mk
+include ${GEN}/vars.mk
+include ${GEN}/flags.mk
+include config/exports.mk
-include config/vars.mk
-
-default::
+${GEN}/deps.mk::
+ ${MSG} "[ GEN] $@"
+ printf '# %s\n' "$@" >$@
if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \
printf 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}\n'; \
- fi >${TARGET} 2>${TARGET}.log
+ fi >>$@ 2>$@.log
+ ${VCAT} $@
+ @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@
diff --git a/config/exports.mk b/config/exports.mk
new file mode 100644
index 0000000..9128568
--- /dev/null
+++ b/config/exports.mk
@@ -0,0 +1,19 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Makefile fragment that exports variables used by configuration scripts
+
+export XPREFIX=${PREFIX}
+export XMANDIR=${MANDIR}
+
+export XCC=${CC}
+export XINSTALL=${INSTALL}
+export XMKDIR=${MKDIR}
+export XRM=${RM}
+
+export XCPPFLAGS=${CPPFLAGS}
+export XCFLAGS=${CFLAGS}
+export XLDFLAGS=${LDFLAGS}
+export XLDLIBS=${LDLIBS}
+
+export XNOLIBS=${NOLIBS}
diff --git a/config/flags.mk b/config/flags.mk
new file mode 100644
index 0000000..e62e26e
--- /dev/null
+++ b/config/flags.mk
@@ -0,0 +1,119 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Makefile that generates gen/flags.mk
+
+include config/prelude.mk
+include ${GEN}/vars.mk
+
+# 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
+export BFS_LDLIBS=${LDLIBS,${OS}}
+
+# Make sure we pick up any default flags from e.g. sys.mk
+export XCPPFLAGS=${CPPFLAGS}
+export XCFLAGS=${CFLAGS}
+export XLDFLAGS=${LDFLAGS}
+export XLDLIBS=${LDLIBS}
+
+# Build profiles
+_ASAN := ${TRUTHY,${ASAN}}
+_LSAN := ${TRUTHY,${LSAN}}
+_MSAN := ${TRUTHY,${MSAN}}
+_TSAN := ${TRUTHY,${TSAN}}
+_UBSAN := ${TRUTHY,${UBSAN}}
+_GCOV := ${TRUTHY,${GCOV}}
+_LINT := ${TRUTHY,${LINT}}
+_RELEASE := ${TRUTHY,${RELEASE}}
+
+# https://github.com/google/sanitizers/issues/342
+TSAN_CPPFLAGS,y := -DBFS_USE_TARGET_CLONES=0
+export TSAN_CPPFLAGS=${TSAN_CPPFLAGS,${_TSAN}}
+
+ASAN_CFLAGS,y := -fsanitize=address
+LSAN_CFLAGS,y := -fsanitize=leak
+MSAN_CFLAGS,y := -fsanitize=memory -fsanitize-memory-track-origins
+TSAN_CFLAGS,y := -fsanitize=thread
+UBSAN_CFLAGS.y := -fsanitize=undefined
+
+export ASAN_CFLAGS=${ASAN_CFLAGS,${_ASAN}}
+export LSAN_CFLAGS=${LSAN_CFLAGS,${_LSAN}}
+export MSAN_CFLAGS=${MSAN_CFLAGS,${_MSAN}}
+export TSAN_CFLAGS=${TSAN_CFLAGS,${_TSAN}}
+export UBSAN_CFLAGS=${UBSAN_CFLAGS,${_UBSAN}}
+
+SAN_CFLAGS,y := -fno-sanitize-recover=all
+SAN := ${NOT,${NOR,${_ASAN},${_LSAN},${_MSAN},${_TSAN},${_UBSAN}}}
+export SAN_CFLAGS=${SAN_CFLAGS,${SAN}}
+
+# MSAN and TSAN both need all code to be instrumented
+NOLIBS ?= ${NOT,${NOR,${_MSAN},${_TSAN}}}
+export XNOLIBS=${NOLIBS}
+
+# gcov only intercepts fork()/exec() with -std=gnu*
+GCOV_CFLAGS,y := -std=gnu17 --coverage
+export GCOV_CFLAGS=${GCOV_CFLAGS,${_GCOV}}
+
+LINT_CPPFLAGS,y := -D_FORTIFY_SOURCE=3 -DBFS_LINT
+LINT_CFLAGS,y := -Werror -O2
+
+export LINT_CPPFLAGS=${LINT_CPPFLAGS,${_LINT}}
+export LINT_CFLAGS=${LINT_CFLAGS,${_LINT}}
+
+RELEASE_CPPFLAGS,y := -DNDEBUG
+RELEASE_CFLAGS,y := -O3 -flto=auto
+
+export RELEASE_CPPFLAGS=${RELEASE_CPPFLAGS,${_RELEASE}}
+export RELEASE_CFLAGS=${RELEASE_CFLAGS,${_RELEASE}}
+
+# Set a variable
+SETVAR = printf '%s := %s\n' >>$@
+
+# Append to a variable, if non-empty
+APPEND = append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append
+
+${GEN}/flags.mk::
+ ${MSG} "[ GEN] $@"
+ printf '# %s\n' "$@" >$@
+ ${SETVAR} CPPFLAGS "$$BFS_CPPFLAGS"
+ ${APPEND} CPPFLAGS "$$TSAN_CPPFLAGS"
+ ${APPEND} CPPFLAGS "$$LINT_CPPFLAGS"
+ ${APPEND} CPPFLAGS "$$RELEASE_CPPFLAGS"
+ ${APPEND} CPPFLAGS "$$XCPPFLAGS"
+ ${APPEND} CPPFLAGS "$$EXTRA_CPPFLAGS"
+ ${SETVAR} CFLAGS "$$BFS_CFLAGS"
+ ${APPEND} CFLAGS "$$ASAN_CFLAGS"
+ ${APPEND} CFLAGS "$$LSAN_CFLAGS"
+ ${APPEND} CFLAGS "$$MSAN_CFLAGS"
+ ${APPEND} CFLAGS "$$TSAN_CFLAGS"
+ ${APPEND} CFLAGS "$$UBSAN_CFLAGS"
+ ${APPEND} CFLAGS "$$SAN_CFLAGS"
+ ${APPEND} CFLAGS "$$GCOV_CFLAGS"
+ ${APPEND} CFLAGS "$$LINT_CFLAGS"
+ ${APPEND} CFLAGS "$$RELEASE_CFLAGS"
+ ${APPEND} CFLAGS "$$XCFLAGS"
+ ${APPEND} CFLAGS "$$EXTRA_CFLAGS"
+ ${SETVAR} LDFLAGS "$$XLDFLAGS"
+ ${SETVAR} LDLIBS "$$XLDLIBS"
+ ${APPEND} LDLIBS "$$EXTRA_LDLIBS"
+ ${APPEND} LDLIBS "$$BFS_LDLIBS"
+ ${SETVAR} NOLIBS "$$XNOLIBS"
+ test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@
+ ${VCAT} $@
diff --git a/config/pkg.mk b/config/pkg.mk
index 9b32b42..2086555 100644
--- a/config/pkg.mk
+++ b/config/pkg.mk
@@ -3,9 +3,18 @@
# Makefile that generates gen/lib*.mk
-.OBJDIR: .
-
-include config/vars.mk
+include config/prelude.mk
+include ${GEN}/vars.mk
+include ${GEN}/flags.mk
+include config/exports.mk
default::
- config/pkg.sh ${TARGET:${GEN}/%.mk=%} >${TARGET} 2>${TARGET}.log
+ @printf '# %s\n' "${TARGET}" >${TARGET}
+ config/pkg.sh ${TARGET:${GEN}/%.mk=%} >>${TARGET} 2>${TARGET}.log
+ @if [ "${IS_V}" ]; then \
+ cat ${TARGET}; \
+ elif grep -q PKGS ${TARGET}; then \
+ printf '[ GEN] %-18s [y]\n' ${TARGET}; \
+ else \
+ printf '[ GEN] %-18s [n]\n' ${TARGET}; \
+ fi
diff --git a/config/pkgconf.sh b/config/pkgconf.sh
index a13b30f..80dcbee 100755
--- a/config/pkgconf.sh
+++ b/config/pkgconf.sh
@@ -17,29 +17,29 @@ if (($# < 1)); then
exit
fi
-if [[ "$NOLIBS" == *y* ]]; then
+if [[ "$XNOLIBS" == *y* ]]; then
exit 1
fi
-if command -v "${PKG_CONFIG:-}" &>/dev/null; then
+if command -v "${XPKG_CONFIG:-}" &>/dev/null; then
case "$MODE" in
"")
- "$PKG_CONFIG" "$@"
+ "$XPKG_CONFIG" "$@"
;;
--cflags)
- OUT=$("$PKG_CONFIG" --cflags "$@")
+ OUT=$("$XPKG_CONFIG" --cflags "$@")
if [ "$OUT" ]; then
printf 'CFLAGS += %s\n' "$OUT"
fi
;;
--ldflags)
- OUT=$("$PKG_CONFIG" --libs-only-L --libs-only-other "$@")
+ OUT=$("$XPKG_CONFIG" --libs-only-L --libs-only-other "$@")
if [ "$OUT" ]; then
printf 'LDFLAGS += %s\n' "$OUT"
fi
;;
--ldlibs)
- OUT=$("$PKG_CONFIG" --libs-only-l "$@")
+ OUT=$("$XPKG_CONFIG" --libs-only-l "$@")
if [ "$OUT" ]; then
printf 'LDLIBS := %s ${LDLIBS}\n' "$OUT"
fi
diff --git a/config/pkgs.mk b/config/pkgs.mk
index 54024b2..5ebbaec 100644
--- a/config/pkgs.mk
+++ b/config/pkgs.mk
@@ -3,12 +3,15 @@
# Makefile that generates gen/pkgs.mk
-.OBJDIR: .
-
-include config/vars.mk
+include config/prelude.mk
+include ${GEN}/vars.mk
+include ${GEN}/flags.mk
include ${GEN}/pkgs.mk
+include config/exports.mk
-default::
- config/pkgconf.sh --cflags ${PKGS} >>${TARGET} 2>>${TARGET}.log
- config/pkgconf.sh --ldflags ${PKGS} >>${TARGET} 2>>${TARGET}.log
- config/pkgconf.sh --ldlibs ${PKGS} >>${TARGET} 2>>${TARGET}.log
+${GEN}/pkgs.mk::
+ ${MSG} "[ GEN] $@"
+ config/pkgconf.sh --cflags ${PKGS} >>$@ 2>>$@.log
+ config/pkgconf.sh --ldflags ${PKGS} >>$@ 2>>$@.log
+ config/pkgconf.sh --ldlibs ${PKGS} >>$@ 2>>$@.log
+ ${VCAT} $@
diff --git a/config/prelude.mk b/config/prelude.mk
new file mode 100644
index 0000000..1d5fb83
--- /dev/null
+++ b/config/prelude.mk
@@ -0,0 +1,164 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Common makefile utilities. Compatible with both GNU make and most BSD makes.
+
+# 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 ?= $^
+
+# 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:./%=%}
+
+# The configuration file generated by `make config`
+CONFIG := ${GEN}/config.mk
+
+# Installation paths
+DESTDIR ?=
+PREFIX ?= /usr
+MANDIR ?= ${PREFIX}/share/man
+
+# 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}
+export BUILDDIR=${_BUILDDIR}
+
+# Configurable executables; can be overridden with
+#
+# $ make config CC=clang
+CC ?= cc
+INSTALL ?= install
+MKDIR ?= mkdir -p
+PKG_CONFIG ?= pkg-config
+RM ?= rm -f
+
+# GNU and BSD make have incompatible syntax for conditionals, but we can do a
+# lot with just nested variable expansion. We use "y" as the canonical
+# truthy value, and "" (the empty string) as the canonical falsey value.
+#
+# To normalize a boolean, use ${TRUTHY,${VAR}}, which expands like this:
+#
+# VAR=y ${TRUTHY,${VAR}} => ${TRUTHY,y} => y
+# VAR=1 ${TRUTHY,${VAR}} => ${TRUTHY,1} => y
+# VAR=n ${TRUTHY,${VAR}} => ${TRUTHY,n} => [empty]
+# VAR=other ${TRUTHY,${VAR}} => ${TRUTHY,other} => [empty]
+# VAR= ${TRUTHY,${VAR}} => ${TRUTHY,} => [emtpy]
+#
+# Inspired by https://github.com/wahern/autoguess
+TRUTHY,y := y
+TRUTHY,1 := y
+
+# Boolean operators are also implemented with nested expansion
+NOT,y :=
+NOT, := y
+
+# Support up to 5 arguments
+AND,y := y
+AND,y,y := y
+AND,y,y,y := y
+AND,y,y,y,y := y
+AND,y,y,y,y,y := y
+
+# NOR can be defined without combinatorial explosion.
+# OR is just ${NOT,${NOR,...}}
+NOR, := y
+NOR,, := y
+NOR,,, := y
+NOR,,,, := y
+NOR,,,,, := y
+
+# Normalize ${V} to either "y" or ""
+IS_V := ${TRUTHY,${V}}
+
+# Suppress output unless V=1
+Q, := @
+Q := ${Q,${IS_V}}
+
+# Show full commands with `make V=1`, otherwise short summaries
+MSG = @msg() { \
+ MSG="$$1"; \
+ shift; \
+ test "${IS_V}" || printf '%s\n' "$$MSG"; \
+ test "$${1:-}" || return 0; \
+ test "${IS_V}" && printf '%s\n' "$$*"; \
+ "$$@"; \
+ }; \
+ msg
+
+# cat a file if V=1
+VCAT,y := @cat
+VCAT, := @:
+VCAT := ${VCAT,${IS_V}}
+
+# List all object files here, as they're needed by both `make config` and `make`
+
+# 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
+
+# Unit test objects
+UNIT_OBJS := \
+ ${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
+
+# All object files
+OBJS := \
+ ${OBJ}/src/main.o \
+ ${OBJ}/tests/mksock.o \
+ ${OBJ}/tests/xspawnee.o \
+ ${OBJ}/tests/xtouch.o \
+ ${LIBBFS} \
+ ${UNIT_OBJS}
diff --git a/config/vars.mk b/config/vars.mk
deleted file mode 100644
index a8fae9d..0000000
--- a/config/vars.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright © Tavian Barnes <tavianator@tavianator.com>
-# SPDX-License-Identifier: 0BSD
-
-# Makefile fragment loads and exports variables for config steps
-
-GEN := ${BUILDDIR}/gen
-GEN := ${GEN:./%=%}
-
-include ${GEN}/vars.mk
-
-_CC := ${CC}
-_CPPFLAGS := ${CPPFLAGS}
-_CFLAGS := ${CFLAGS}
-_LDFLAGS := ${LDFLAGS}
-_LDLIBS := ${LDLIBS}
-
-export CC=${_CC}
-export CPPFLAGS=${_CPPFLAGS}
-export CFLAGS=${_CFLAGS}
-export LDFLAGS=${_LDFLAGS}
-export LDLIBS=${_LDLIBS}
diff --git a/config/vars.sh b/config/vars.sh
deleted file mode 100755
index 8a781bb..0000000
--- a/config/vars.sh
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env bash
-
-# Copyright © Tavian Barnes <tavianator@tavianator.com>
-# SPDX-License-Identifier: 0BSD
-
-# Writes the saved variables to gen/vars.mk
-
-set -eu
-
-print() {
- NAME="$1"
- OP="${2:-:=}"
-
- if (($# >= 3)); then
- printf '# %s\n' "${3#X}"
- declare -n VAR="$3"
- VALUE="${VAR:-}"
- else
- # Try X$NAME, $NAME, ""
- local -n XVAR="X$NAME"
- local -n VAR="$NAME"
- VALUE="${XVAR:-${VAR:-}}"
- fi
-
- printf '%s %s %s\n' "$NAME" "$OP" "$VALUE"
-}
-
-cond_flags() {
- local -n COND="$1"
-
- if [[ "${COND:-}" == *y* ]]; then
- print "$2" += "${1}_${2}"
- fi
-}
-
-print PREFIX
-print MANDIR
-
-print OS
-print ARCH
-
-print CC
-print INSTALL
-print MKDIR
-print RM
-
-print CPPFLAGS := BFS_CPPFLAGS
-cond_flags TSAN CPPFLAGS
-cond_flags LINT CPPFLAGS
-cond_flags RELEASE CPPFLAGS
-print CPPFLAGS += XCPPFLAGS
-print CPPFLAGS += EXTRA_CPPFLAGS
-
-print CFLAGS := BFS_CFLAGS
-cond_flags ASAN CFLAGS
-cond_flags LSAN CFLAGS
-cond_flags MSAN CFLAGS
-cond_flags TSAN CFLAGS
-cond_flags UBSAN CFLAGS
-cond_flags SAN CFLAGS
-cond_flags GCOV CFLAGS
-cond_flags LINT CFLAGS
-cond_flags RELEASE CFLAGS
-print CFLAGS += XCFLAGS
-print CFLAGS += EXTRA_CFLAGS
-
-print LDFLAGS := XLDFLAGS
-print LDFLAGS += EXTRA_LDFLAGS
-
-print LDLIBS := XLDLIBS
-print LDLIBS += EXTRA_LDLIBS
-print LDLIBS += BFS_LDLIBS
-
-print PKGS
-
-# Disable ASLR on FreeBSD when sanitizers are enabled
-case "$XOS-$SAN" in
- FreeBSD-*y*)
- printf 'POSTLINK = elfctl -e +noaslr $@\n'
- ;;
-esac