# Copyright © Tavian Barnes # 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_LIBSELINUX ?= 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_LIBSELINUX := ${USE_LIBSELINUX} _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_LIBSELINUX=${_XUSE_LIBSELINUX} 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}/libselinux.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_LIBDIR=/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