diff options
author | Tavian Barnes <tavianator@tavianator.com> | 2024-04-16 18:43:49 -0400 |
---|---|---|
committer | Tavian Barnes <tavianator@tavianator.com> | 2024-04-16 18:58:51 -0400 |
commit | c4c063e9844f2bd2271b2e3391f59f872c66f69a (patch) | |
tree | d09c0852ae24c78e723e425306ae006fae16e076 | |
parent | 98c539eeda8f9adfd22a3b2b6ece4fe1ca06b3b4 (diff) | |
download | bfs-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.yml | 5 | ||||
-rw-r--r-- | Makefile | 369 | ||||
-rwxr-xr-x | config/cc.sh | 2 | ||||
-rw-r--r-- | config/config.mk | 77 | ||||
-rw-r--r-- | config/deps.mk | 15 | ||||
-rw-r--r-- | config/exports.mk | 19 | ||||
-rw-r--r-- | config/flags.mk | 119 | ||||
-rw-r--r-- | config/pkg.mk | 17 | ||||
-rwxr-xr-x | config/pkgconf.sh | 12 | ||||
-rw-r--r-- | config/pkgs.mk | 17 | ||||
-rw-r--r-- | config/prelude.mk | 164 | ||||
-rw-r--r-- | config/vars.mk | 21 | ||||
-rwxr-xr-x | config/vars.sh | 81 |
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 @@ -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 |