summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml23
-rw-r--r--.github/workflows/codecov.yml3
-rw-r--r--.github/workflows/codeql.yml1
-rw-r--r--.gitignore2
-rw-r--r--GNUmakefile372
-rw-r--r--Makefile517
-rw-r--r--README.md4
-rwxr-xr-xconfig/cc.sh12
-rw-r--r--config/empty.c6
-rw-r--r--config/libacl.c6
-rw-r--r--config/libcap.c6
-rw-r--r--config/liburing.c6
-rw-r--r--config/oniguruma.c6
-rwxr-xr-xconfig/pkg.sh44
-rwxr-xr-xconfig/pkgconf.sh64
-rw-r--r--docs/BUILDING.md83
-rw-r--r--docs/USAGE.md2
-rw-r--r--src/config.h11
-rw-r--r--src/main.c1
-rw-r--r--src/parse.c2
-rw-r--r--src/version.c6
-rw-r--r--tests/run.sh2
-rw-r--r--tests/tests.mk10
23 files changed, 757 insertions, 432 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 51d06fb..78aa196 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -53,12 +53,13 @@ jobs:
run: |
brew install \
bash \
- expect
+ expect \
+ make
- name: Run tests
run: |
jobs=$(sysctl -n hw.ncpu)
- make -j$jobs distcheck
+ gmake -j$jobs distcheck
freebsd:
name: FreeBSD
@@ -79,8 +80,8 @@ jobs:
pkg install -y \
bash \
expect \
- gmake \
oniguruma \
+ pkgconf \
sudo \
tcl-wrapper
pw useradd -n action -m -G wheel -s /usr/local/bin/bash
@@ -89,7 +90,7 @@ jobs:
run: |
chown -R action:action .
- sudo -u action gmake -j$(nproc) distcheck
+ sudo -u action make -j$(nproc) distcheck
openbsd:
name: OpenBSD
@@ -119,6 +120,7 @@ jobs:
run: |
chown -R action:action .
jobs=$(sysctl -n hw.ncpu)
+ doas -u action gmake config
doas -u action gmake -j$jobs check TEST_FLAGS="--sudo=doas --verbose=skipped"
netbsd:
@@ -141,8 +143,8 @@ jobs:
pkg_add \
bash \
clang \
- gmake \
oniguruma \
+ pkgconf \
sudo \
tcl-expect
useradd -m -G wheel -g =uid action
@@ -152,7 +154,8 @@ jobs:
PATH="/sbin:/usr/sbin:$PATH"
chown -R action:action .
jobs=$(sysctl -n hw.ncpu)
- sudo -u action gmake -j$jobs check CC=clang LDFLAGS="-rpath /usr/pkg/lib" TEST_FLAGS="--sudo --verbose=skipped"
+ sudo -u action make config CC=clang
+ sudo -u action make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped"
dragonflybsd:
name: DragonFly BSD
@@ -173,8 +176,8 @@ jobs:
pkg install -y \
bash \
expect \
- gmake \
oniguruma \
+ pkgconf \
sudo \
tcl-wrapper
pw useradd -n action -m -G wheel -s /usr/local/bin/bash
@@ -183,7 +186,8 @@ jobs:
run: |
chown -R action:action .
jobs=$(sysctl -n hw.ncpu)
- sudo -u action gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped"
+ sudo -u action make config
+ sudo -u action make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped"
omnios:
name: OmniOS
@@ -215,4 +219,5 @@ jobs:
PATH="/usr/xpg4/bin:$PATH"
chown -R action:staff .
jobs=$(getconf NPROCESSORS_ONLN)
- sudo -u action gmake -j$jobs check LDFLAGS="-Wl,-rpath,/opt/ooce/lib/amd64" TEST_FLAGS="--sudo --verbose=skipped"
+ sudo -u action gmake config
+ sudo -u action gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped"
diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
index d1dc351..2abe531 100644
--- a/.github/workflows/codecov.yml
+++ b/.github/workflows/codecov.yml
@@ -25,7 +25,8 @@ jobs:
- name: Generate coverage
run: |
- make -j$(nproc) gcov check TEST_FLAGS="--sudo"
+ make config GCOV=y
+ make -j$(nproc) check TEST_FLAGS="--sudo"
gcov -abcfpu obj/*/*.o
- uses: codecov/codecov-action@v3
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index c50b266..a2c224a 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -47,6 +47,7 @@ jobs:
- name: Build
run: |
+ make config
make -j$(nproc) all
- name: Perform CodeQL Analysis
diff --git a/.gitignore b/.gitignore
index 4ded7c4..84e47fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
/bin/
+/gen/
/obj/
+/distcheck-*/
diff --git a/GNUmakefile b/GNUmakefile
deleted file mode 100644
index 8685ca3..0000000
--- a/GNUmakefile
+++ /dev/null
@@ -1,372 +0,0 @@
-# Copyright © Tavian Barnes <tavianator@tavianator.com>
-# SPDX-License-Identifier: 0BSD
-
-ifneq ($(wildcard .git),)
-VERSION := $(shell git describe --always 2>/dev/null)
-endif
-
-ifndef VERSION
-VERSION := 3.1.3
-endif
-
-ifndef OS
-OS := $(shell uname)
-endif
-
-ifndef ARCH
-ARCH := $(shell uname -m)
-endif
-
-CC ?= gcc
-INSTALL ?= install
-MKDIR ?= mkdir -p
-RM ?= rm -f
-
-export BUILDDIR ?= .
-DESTDIR ?=
-PREFIX ?= /usr
-MANDIR ?= $(PREFIX)/share/man
-
-BIN := $(BUILDDIR)/bin
-OBJ := $(BUILDDIR)/obj
-
-DEFAULT_CFLAGS := \
- -g \
- -Wall \
- -Wformat=2 \
- -Werror=implicit \
- -Wimplicit-fallthrough \
- -Wmissing-declarations \
- -Wshadow \
- -Wsign-compare \
- -Wstrict-prototypes
-
-CFLAGS ?= $(DEFAULT_CFLAGS)
-LDFLAGS ?=
-DEPFLAGS ?= -MD -MP -MF $(@:.o=.d)
-
-LOCAL_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 \
- -DBFS_VERSION=\"$(VERSION)\"
-
-LOCAL_CFLAGS := -std=c17 -pthread
-LOCAL_LDFLAGS :=
-LOCAL_LDLIBS :=
-
-ASAN := $(filter asan,$(MAKECMDGOALS))
-LSAN := $(filter lsan,$(MAKECMDGOALS))
-MSAN := $(filter msan,$(MAKECMDGOALS))
-TSAN := $(filter tsan,$(MAKECMDGOALS))
-UBSAN := $(filter ubsan,$(MAKECMDGOALS))
-
-ifdef ASAN
-LOCAL_CFLAGS += -fsanitize=address
-SANITIZE := y
-endif
-
-ifdef LSAN
-LOCAL_CFLAGS += -fsanitize=leak
-SANITIZE := y
-endif
-
-ifdef MSAN
-# msan needs all code instrumented
-NOLIBS := y
-LOCAL_CFLAGS += -fsanitize=memory -fsanitize-memory-track-origins
-SANITIZE := y
-endif
-
-ifdef TSAN
-# tsan needs all code instrumented
-NOLIBS := y
-# https://github.com/google/sanitizers/issues/342
-LOCAL_CPPFLAGS += -DBFS_USE_TARGET_CLONES=0
-LOCAL_CFLAGS += -fsanitize=thread
-SANITIZE := y
-endif
-
-ifdef UBSAN
-LOCAL_CFLAGS += -fsanitize=undefined
-SANITIZE := y
-endif
-
-ifdef SANITIZE
-LOCAL_CFLAGS += -fno-sanitize-recover=all
-endif
-
-ifndef NOLIBS
-USE_ONIGURUMA := y
-endif
-
-ifdef USE_ONIGURUMA
-LOCAL_CPPFLAGS += -DBFS_USE_ONIGURUMA=1
-
-ONIG_CONFIG := $(shell command -v onig-config 2>/dev/null)
-ifdef ONIG_CONFIG
-ONIG_CFLAGS := $(shell $(ONIG_CONFIG) --cflags)
-ONIG_LDLIBS := $(shell $(ONIG_CONFIG) --libs)
-else
-ONIG_LDLIBS := -lonig
-endif
-
-LOCAL_CFLAGS += $(ONIG_CFLAGS)
-LOCAL_LDLIBS += $(ONIG_LDLIBS)
-endif # USE_ONIGURUMA
-
-ifeq ($(OS),Linux)
-ifndef NOLIBS
-USE_ACL := y
-USE_LIBCAP := y
-USE_LIBURING := y
-endif
-
-ifdef USE_ACL
-LOCAL_LDLIBS += -lacl
-else
-LOCAL_CPPFLAGS += -DBFS_USE_SYS_ACL_H=0
-endif
-
-ifdef USE_LIBCAP
-LOCAL_LDLIBS += -lcap
-else
-LOCAL_CPPFLAGS += -DBFS_USE_SYS_CAPABILITY_H=0
-endif
-
-ifdef USE_LIBURING
-LOCAL_CPPFLAGS += -DBFS_USE_LIBURING=1
-LOCAL_LDLIBS += -luring
-endif
-
-LOCAL_LDFLAGS += -Wl,--as-needed
-LOCAL_LDLIBS += -lrt
-endif # Linux
-
-ifeq ($(OS),NetBSD)
-LOCAL_LDLIBS += -lutil
-endif
-
-ifeq ($(OS),DragonFly)
-LOCAL_LDLIBS += -lposix1e
-endif
-
-ifeq ($(OS),SunOS)
-LOCAL_LDLIBS += -lsocket -lnsl
-endif
-
-ifneq ($(filter gcov,$(MAKECMDGOALS)),)
-LOCAL_CFLAGS += --coverage
-# gcov only intercepts fork()/exec() with -std=gnu*
-LOCAL_CFLAGS := $(patsubst -std=c%,-std=gnu%,$(LOCAL_CFLAGS))
-endif
-
-ifneq ($(filter lint,$(MAKECMDGOALS)),)
-LOCAL_CPPFLAGS += \
- -D_FORTIFY_SOURCE=3 \
- -DBFS_LINT
-LOCAL_CFLAGS += -Werror -O2
-endif
-
-ifneq ($(filter release,$(MAKECMDGOALS)),)
-LOCAL_CPPFLAGS += -DNDEBUG
-CFLAGS := $(DEFAULT_CFLAGS) -O3 -flto=auto
-endif
-
-ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS) $(EXTRA_CPPFLAGS)
-ALL_CFLAGS = $(ALL_CPPFLAGS) $(LOCAL_CFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) $(DEPFLAGS)
-ALL_LDFLAGS = $(ALL_CFLAGS) $(LOCAL_LDFLAGS) $(LDFLAGS) $(EXTRA_LDFLAGS)
-ALL_LDLIBS = $(LOCAL_LDLIBS) $(LDLIBS) $(EXTRA_LDLIBS)
-
-# Default make target
-bfs: $(BIN)/bfs
-.PHONY: bfs
-
-# Goals that are treated like flags by this makefile
-FLAG_GOALS := asan lsan msan tsan ubsan gcov lint release
-
-# These are the remaining non-flag goals
-GOALS := $(filter-out $(FLAG_GOALS),$(MAKECMDGOALS))
-
-# Build the default goal if only flag goals are specified
-FLAG_PREREQS :=
-ifndef GOALS
-FLAG_PREREQS += bfs
-endif
-
-# Make sure that "make release" builds everything, but "make release obj/src/main.o" doesn't
-$(FLAG_GOALS): $(FLAG_PREREQS)
- @:
-.PHONY: $(FLAG_GOALS)
-
-all: bfs tests
-.PHONY: all
-
-$(BIN)/%:
- @$(MKDIR) $(@D)
- +$(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@
-ifeq ($(OS) $(SANITIZE),FreeBSD y)
- elfctl -e +noaslr $@
-endif
-
-$(OBJ)/%.o: %.c $(OBJ)/FLAGS
- @$(MKDIR) $(@D)
- $(CC) $(ALL_CFLAGS) -c $< -o $@
-
-# Save the full set of flags to rebuild everything when they change
-$(OBJ)/FLAGS.new:
- @$(MKDIR) $(@D)
- @echo $(CC) : $(ALL_CFLAGS) : $(ALL_LDFLAGS) : $(ALL_LDLIBS) >$@
-.PHONY: $(OBJ)/FLAGS.new
-
-# Only update obj/FLAGS if obj/FLAGS.new is different
-$(OBJ)/FLAGS: $(OBJ)/FLAGS.new
- @test -e $@ && cmp -s $@ $< && rm $< || mv $< $@
-
-# 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/xregex.o \
- $(OBJ)/src/xspawn.o \
- $(OBJ)/src/xtime.o
-
-# The main executable
-$(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS)
-
-# Testing utilities
-TEST_UTILS := \
- $(BIN)/tests/mksock \
- $(BIN)/tests/xspawnee \
- $(BIN)/tests/xtouch
-
-$(BIN)/tests/mksock: $(OBJ)/tests/mksock.o $(LIBBFS)
-
-$(BIN)/tests/xspawnee: $(OBJ)/tests/xspawnee.o
-
-$(BIN)/tests/xtouch: $(OBJ)/tests/xtouch.o $(LIBBFS)
-
-# All test binaries
-TESTS := $(BIN)/tests/units $(TEST_UTILS)
-
-$(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)
-
-# Build all the test binaries
-tests: $(TESTS)
-.PHONY: tests
-
-# Run the unit tests
-unit-tests: $(BIN)/tests/units $(BIN)/tests/xspawnee
- $<
-.PHONY: unit-tests
-
-# The different flag combinations we check
-INTEGRATIONS := default dfs ids eds j1 j2 j3 s
-INTEGRATION_TESTS := $(INTEGRATIONS:%=check-%)
-
-check-default: $(BIN)/bfs $(TEST_UTILS)
- +./tests/tests.sh --make="$(MAKE)" --bfs="$<" $(TEST_FLAGS)
-
-check-dfs check-ids check-eds: check-%: $(BIN)/bfs $(TEST_UTILS)
- +./tests/tests.sh --make="$(MAKE)" --bfs="$< -S $*" $(TEST_FLAGS)
-
-check-j1 check-j2 check-j3 check-s: check-%: $(BIN)/bfs $(TEST_UTILS)
- +./tests/tests.sh --make="$(MAKE)" --bfs="$< -$*" $(TEST_FLAGS)
-
-# Run the integration tests
-integration-tests: $(INTEGRATION_TESTS)
-.PHONY: integration-tests
-
-# Run all the tests
-check: unit-tests integration-tests
-.PHONY: check
-
-# Custom test flags for distcheck
-DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped"
-
-distcheck:
- +$(MAKE) -B asan ubsan check $(DISTCHECK_FLAGS)
-ifneq ($(OS),Darwin)
- +$(MAKE) -B msan ubsan check CC=clang $(DISTCHECK_FLAGS)
-endif
- +$(MAKE) -B tsan ubsan check CC=clang $(DISTCHECK_FLAGS)
-ifeq ($(OS) $(ARCH),Linux x86_64)
- +$(MAKE) -B check EXTRA_CFLAGS="-m32" ONIG_CONFIG= USE_LIBURING= $(DISTCHECK_FLAGS)
-endif
- +$(MAKE) -B release check $(DISTCHECK_FLAGS)
- +$(MAKE) -B check $(DISTCHECK_FLAGS)
- +$(MAKE) check-install $(DISTCHECK_FLAGS)
-.PHONY: distcheck
-
-clean:
- $(RM) -r $(BIN) $(OBJ)
-.PHONY: clean
-
-install:
- $(MKDIR) $(DESTDIR)$(PREFIX)/bin
- $(INSTALL) -m755 $(BIN)/bfs $(DESTDIR)$(PREFIX)/bin/bfs
- $(MKDIR) $(DESTDIR)$(MANDIR)/man1
- $(INSTALL) -m644 docs/bfs.1 $(DESTDIR)$(MANDIR)/man1/bfs.1
- $(MKDIR) $(DESTDIR)$(PREFIX)/share/bash-completion/completions
- $(INSTALL) -m644 completions/bfs.bash $(DESTDIR)$(PREFIX)/share/bash-completion/completions/bfs
- $(MKDIR) $(DESTDIR)$(PREFIX)/share/zsh/site-functions
- $(INSTALL) -m644 completions/bfs.zsh $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_bfs
- $(MKDIR) $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d
- $(INSTALL) -m644 completions/bfs.fish $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/bfs.fish
-.PHONY: install
-
-uninstall:
- $(RM) $(DESTDIR)$(PREFIX)/share/bash-completion/completions/bfs
- $(RM) $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_bfs
- $(RM) $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/bfs.fish
- $(RM) $(DESTDIR)$(MANDIR)/man1/bfs.1
- $(RM) $(DESTDIR)$(PREFIX)/bin/bfs
-.PHONY: uninstall
-
-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
-.PHONY: check-install
-
-.SUFFIXES:
-
--include $(wildcard $(OBJ)/*/*.d)
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
diff --git a/README.md b/README.md
index 17226fd..b95c16b 100644
--- a/README.md
+++ b/README.md
@@ -333,6 +333,7 @@ Once you have the dependencies, you can build <code>bfs</code>.
Download one of the [releases](https://github.com/tavianator/bfs/releases) or clone the [git repo](https://github.com/tavianator/bfs).
Then run
+ $ make config
$ make
This will build the `./bin/bfs` binary.
@@ -342,7 +343,8 @@ Run the test suite to make sure it works correctly:
If you're interested in speed, you may want to build the release version instead:
- $ make release
+ $ make config RELEASE=y
+ $ make
Finally, if you want to install it globally, run
diff --git a/config/cc.sh b/config/cc.sh
new file mode 100755
index 0000000..04b142a
--- /dev/null
+++ b/config/cc.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Run the compiler and check if it succeeded
+
+printf '$ %s' "$XCC" >&2
+printf ' %q' "$@" >&2
+printf ' -o /dev/null\n' >&2
+
+$XCC "$@" -o /dev/null
diff --git a/config/empty.c b/config/empty.c
new file mode 100644
index 0000000..4fa9a5b
--- /dev/null
+++ b/config/empty.c
@@ -0,0 +1,6 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+int main(void) {
+ return 0;
+}
diff --git a/config/libacl.c b/config/libacl.c
new file mode 100644
index 0000000..877cb69
--- /dev/null
+++ b/config/libacl.c
@@ -0,0 +1,6 @@
+#include <sys/acl.h>
+
+int main(void) {
+ acl_free(0);
+ return 0;
+}
diff --git a/config/libcap.c b/config/libcap.c
new file mode 100644
index 0000000..64188ac
--- /dev/null
+++ b/config/libcap.c
@@ -0,0 +1,6 @@
+#include <sys/capability.h>
+
+int main(void) {
+ cap_free(0);
+ return 0;
+}
diff --git a/config/liburing.c b/config/liburing.c
new file mode 100644
index 0000000..456059c
--- /dev/null
+++ b/config/liburing.c
@@ -0,0 +1,6 @@
+#include <liburing.h>
+
+int main(void) {
+ io_uring_free_probe(0);
+ return 0;
+}
diff --git a/config/oniguruma.c b/config/oniguruma.c
new file mode 100644
index 0000000..b834fac
--- /dev/null
+++ b/config/oniguruma.c
@@ -0,0 +1,6 @@
+#include <oniguruma.h>
+
+int main(void) {
+ onig_free(0);
+ return 0;
+}
diff --git a/config/pkg.sh b/config/pkg.sh
new file mode 100755
index 0000000..6335b4b
--- /dev/null
+++ b/config/pkg.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# pkg-config wrapper that outputs a makefile fragment
+
+set -eu
+
+NAME="${1^^}"
+declare -n XUSE="XUSE_$NAME"
+
+if [ "$XUSE" ]; then
+ USE="$XUSE"
+elif [[ "$NOLIBS" == *y* ]]; then
+ USE=n
+elif config/pkgconf.sh "$1"; then
+ USE=y
+else
+ USE=n
+fi
+
+printf '%s := %s\n' "USE_$NAME" "$USE"
+
+if [ "$USE" = y ]; then
+ printf 'CPPFLAGS += -DBFS_USE_%s=1\n' "$NAME"
+
+ CFLAGS=$(config/pkgconf.sh --cflags "$1")
+ if [ "$CFLAGS" ]; then
+ printf 'CFLAGS += %s\n' "$CFLAGS"
+ fi
+
+ LDFLAGS=$(config/pkgconf.sh --ldflags "$1")
+ if [ "$LDFLAGS" ]; then
+ printf 'LDFLAGS += %s\n' "$LDFLAGS"
+ fi
+
+ LDLIBS=$(config/pkgconf.sh --ldlibs "$1")
+ if [ "$LDLIBS" ]; then
+ printf 'LDLIBS += %s\n' "$LDLIBS"
+ fi
+else
+ printf 'CPPFLAGS += -DBFS_USE_%s=0\n' "$NAME"
+fi
diff --git a/config/pkgconf.sh b/config/pkgconf.sh
new file mode 100755
index 0000000..070fad6
--- /dev/null
+++ b/config/pkgconf.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# pkg-config wrapper with hardcoded fallbacks
+
+set -eu
+
+MODE=
+if [[ "$1" == --* ]]; then
+ MODE="$1"
+ shift
+fi
+
+if command -v "${PKG_CONFIG:-}" &>/dev/null; then
+ case "$MODE" in
+ --cflags)
+ "$PKG_CONFIG" --cflags "$@"
+ ;;
+ --ldflags)
+ "$PKG_CONFIG" --libs-only-L --libs-only-other "$@"
+ ;;
+ --ldlibs)
+ "$PKG_CONFIG" --libs-only-l "$@"
+ ;;
+ "")
+ "$PKG_CONFIG" "$@"
+ ;;
+ esac
+else
+ for lib; do
+ case "$lib" in
+ libacl)
+ LDLIB=-lacl
+ ;;
+ libcap)
+ LDLIB=-lcap
+ ;;
+ liburing)
+ LDLIB=-luring
+ ;;
+ oniguruma)
+ LDLIB=-lonig
+ ;;
+ *)
+ printf 'error: Unknown package %s\n' "$lib" >&2
+ exit 1
+ esac
+
+ case "$MODE" in
+ --ldlibs)
+ printf ' %s' "$LDLIB"
+ ;;
+ "")
+ config/cc.sh "config/$lib.c" "$LDLIB" || exit $?
+ ;;
+ esac
+ done
+
+ if [ "$MODE" = "--ldlibs" ]; then
+ printf '\n'
+ fi
+fi
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
index 7eb3a37..abf6185 100644
--- a/docs/BUILDING.md
+++ b/docs/BUILDING.md
@@ -7,9 +7,10 @@ Compiling
`bfs` uses [GNU Make](https://www.gnu.org/software/make/) as its build system.
A simple invocation of
+ $ make config
$ make
-should build `bfs` successfully, with no additional steps necessary.
+should build `bfs` successfully.
As usual with `make`, you can run a [parallel build](https://www.gnu.org/software/make/manual/html_node/Parallel.html) with `-j`.
For example, to use all your cores, run `make -j$(nproc)`.
@@ -17,25 +18,28 @@ For example, to use all your cores, run `make -j$(nproc)`.
| Command | Description |
|------------------|---------------------------------------------------------------|
+| `make config` | Configures the build system |
| `make` | Builds just the `bfs` binary |
| `make all` | Builds everything, including the tests (but doesn't run them) |
| `make check` | Builds everything, and runs the tests |
| `make install` | Installs `bfs` (with man page, shell completions, etc.) |
| `make uninstall` | Uninstalls `bfs` |
+| `make clean` | Delete the build products |
+| `make mrclean` | Delete all generated files, including the build configuration |
-### Flag-like targets
+### Build profiles
-The build system provides a few shorthand targets for handy configurations:
+The configuration system provides a few shorthand flags for handy configurations:
-| Command | Description |
-|----------------|-------------------------------------------------------------|
-| `make release` | Build `bfs` with optimizations, LTO, and without assertions |
-| `make asan` | Enable [AddressSanitizer] |
-| `make lsan` | Enable [LeakSanitizer] |
-| `make msan` | Enable [MemorySanitizer] |
-| `make tsan` | Enable [ThreadSanitizer] |
-| `make ubsan` | Enable [UndefinedBehaviorSanitizer] |
-| `make gcov` | Enable [code coverage] |
+| Command | Description |
+|-------------------------|-------------------------------------------------------------|
+| `make config RELEASE=y` | Build `bfs` with optimizations, LTO, and without assertions |
+| `make config ASAN=y` | Enable [AddressSanitizer] |
+| `make config LSAN=y` | Enable [LeakSanitizer] |
+| `make config MSAN=y` | Enable [MemorySanitizer] |
+| `make config TSAN=y` | Enable [ThreadSanitizer] |
+| `make config UBSAN=y` | Enable [UndefinedBehaviorSanitizer] |
+| `make config GCOV=y` | Enable [code coverage] |
[AddressSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizer
[LeakSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#stand-alone-mode
@@ -44,38 +48,38 @@ The build system provides a few shorthand targets for handy configurations:
[UndefinedBehaviorSanitizer]: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
[code coverage]: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html
-You can combine multiple flags and other targets (e.g. `make asan ubsan check`), but not all of them will work together.
+You can combine multiple profiles (e.g. `make config ASAN=y UBSAN=y`), but not all of them will work together.
### Flags
-Other flags are controlled with `make` variables and/or environment variables.
-Here are some of the common ones; check the [`GNUmakefile`](/GNUmakefile) for more.
-
-| Flag | Description |
-|----------------------------------|---------------------------------------------|
-| `CC` | The C compiler to use, e.g. `make CC=clang` |
-| `CFLAGS`<br>`EXTRA_CFLAGS` | Override/add to the default compiler flags |
-| `LDFLAGS`<br>`EXTRA_LDFLAGS` | Override/add to the linker flags |
-| `USE_ACL`<br>`USE_LIBCAP`<br>... | Enable/disable [optional dependencies] |
-| `TEST_FLAGS` | `tests.sh` flags for `make check` |
-| `BUILDDIR` | The build output directory (default: `.`) |
-| `DESTDIR` | The root directory for `make install` |
-| `PREFIX` | The installation prefix (default: `/usr`) |
-| `MANDIR` | The man page installation directory |
+Other flags can be specified on the `make config` command line or in the environment.
+Here are some of the common ones; check the [`Makefile`](/Makefile) for more.
+
+| Flag | Description |
+|----------------------------------|----------------------------------------------------|
+| `CC` | The C compiler to use, e.g. `make config CC=clang` |
+| `CFLAGS`<br>`EXTRA_CFLAGS` | Override/add to the default compiler flags |
+| `LDFLAGS`<br>`EXTRA_LDFLAGS` | Override/add to the linker flags |
+| `USE_ACL`<br>`USE_LIBCAP`<br>... | Enable/disable [optional dependencies] |
+| `TEST_FLAGS` | `tests.sh` flags for `make check` |
+| `BUILDDIR` | The build output directory (default: `.`) |
+| `DESTDIR` | The root directory for `make install` |
+| `PREFIX` | The installation prefix (default: `/usr`) |
+| `MANDIR` | The man page installation directory |
[optional dependencies]: #dependencies
### Dependencies
`bfs` depends on some system libraries for some of its features.
-These dependencies are optional, and can be turned off at build time if necessary by setting the appropriate variable to the empty string (e.g. `make USE_ONIGURUMA=`).
+These dependencies are optional, and can be turned off in `make config` if necessary by setting the appropriate variable to `n` (e.g. `make config USE_ONIGURUMA=n`).
-| Dependency | Platforms | `make` flag |
-|-------------|------------|-----------------|
-| [acl] | Linux only | `USE_ACL` |
-| [libcap] | Linux only | `USE_LIBCAP` |
-| [liburing] | Linux only | `USE_LIBURING` |
-| [Oniguruma] | All | `USE_ONIGURUMA` |
+| Dependency | Platforms | `make config` flag |
+|-------------|------------|--------------------|
+| [acl] | Linux only | `USE_ACL` |
+| [libcap] | Linux only | `USE_LIBCAP` |
+| [liburing] | Linux only | `USE_LIBURING` |
+| [Oniguruma] | All | `USE_ONIGURUMA` |
[acl]: https://savannah.nongnu.org/projects/acl
[libcap]: https://sites.google.com/site/fullycapable/
@@ -84,21 +88,22 @@ These dependencies are optional, and can be turned off at build time if necessar
### Dependency tracking
-The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the [`GNUmakefile`](/GNUmakefile)).
+The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the [`Makefile`](/Makefile)).
So if you edit a header file, `make` will rebuild the necessary object files ensuring they don't go out of sync.
+We also add a dependency on the current configuration, so you can change configurations and rebuild without having to `make clean`.
+
We go one step further than most build systems by tracking the flags that were used for the previous compilation.
That means you can change configurations without having to `make clean`.
For example,
+ $ make config
+ $ make
+ $ make config RELEASE=y
$ make
- $ make release
will build the project in debug mode and then rebuild it in release mode.
-A side effect of this may be surprising: `make check` by itself will rebuild the project in the default configuration.
-To test a different configuration, you'll have to repeat it (e.g. `make release check`).
-
Testing
-------
diff --git a/docs/USAGE.md b/docs/USAGE.md
index 071c95b..70f8475 100644
--- a/docs/USAGE.md
+++ b/docs/USAGE.md
@@ -7,8 +7,8 @@ When invoked with no arguments, `bfs` will list everything under the current dir
```console
$ bfs
.
-./GNUmakefile
./LICENSE
+./Makefile
./README.md
./completions
./docs
diff --git a/src/config.h b/src/config.h
index 506ad3e..2eff1fc 100644
--- a/src/config.h
+++ b/src/config.h
@@ -29,13 +29,14 @@
#ifndef BFS_COMMAND
# define BFS_COMMAND "bfs"
#endif
-#ifndef BFS_VERSION
-# define BFS_VERSION "3.1.3"
-#endif
#ifndef BFS_HOMEPAGE
# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html"
#endif
+// This is a symbol instead of a literal so we don't have to rebuild everything
+// when the version number changes
+extern const char bfs_version[];
+
// Check for system headers
#ifdef __has_include
@@ -97,10 +98,10 @@
# define BFS_USE_PATHS_H BFS_HAS_PATHS_H
#endif
#ifndef BFS_USE_SYS_ACL_H
-# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__)
+# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__ && (!__linux__ || BFS_USE_LIBACL))
#endif
#ifndef BFS_USE_SYS_CAPABILITY_H
-# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__)
+# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__ && (!__linux__ || BFS_USE_LIBCAP))
#endif
#ifndef BFS_USE_SYS_EXTATTR_H
# define BFS_USE_SYS_EXTATTR_H (BFS_HAS_SYS_EXTATTR_H && !__DragonFly__)
diff --git a/src/main.c b/src/main.c
index e120f03..b4d65ce 100644
--- a/src/main.c
+++ b/src/main.c
@@ -40,6 +40,7 @@
* - thread.h (multi-threading)
* - trie.[ch] (a trie set/map implementation)
* - typo.[ch] (fuzzy matching for typos)
+ * - version.c (defines the version number)
* - xregex.[ch] (regular expression support)
* - xspawn.[ch] (spawns processes)
* - xtime.[ch] (date/time handling utilities)
diff --git a/src/parse.c b/src/parse.c
index 2dfcab2..38ebf3f 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -2909,7 +2909,7 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2
* "Parse" -version.
*/
static struct bfs_expr *parse_version(struct bfs_parser *parser, int arg1, int arg2) {
- cfprintf(parser->ctx->cout, "${ex}%s${rs} ${bld}%s${rs}\n\n", BFS_COMMAND, BFS_VERSION);
+ cfprintf(parser->ctx->cout, "${ex}%s${rs} ${bld}%s${rs}\n\n", BFS_COMMAND, bfs_version);
printf("%s\n", BFS_HOMEPAGE);
diff --git a/src/version.c b/src/version.c
new file mode 100644
index 0000000..736f3d5
--- /dev/null
+++ b/src/version.c
@@ -0,0 +1,6 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include "config.h"
+
+const char bfs_version[] = BFS_VERSION;
diff --git a/tests/run.sh b/tests/run.sh
index 720515d..ab1ed6d 100644
--- a/tests/run.sh
+++ b/tests/run.sh
@@ -156,7 +156,7 @@ comake() {
-f "$TESTS/tests.mk" \
DONE=$DONE_PIPE \
READY=$READY_PIPE \
- "${TEST_CASES[@]/#/tests/}" \
+ "${!TEST_CASES[@]}" \
</dev/null >/dev/null
}
diff --git a/tests/tests.mk b/tests/tests.mk
index 5bf4f6c..035ca79 100644
--- a/tests/tests.mk
+++ b/tests/tests.mk
@@ -1,7 +1,13 @@
# Copyright © Tavian Barnes <tavianator@tavianator.com>
# SPDX-License-Identifier: 0BSD
-# GNU makefile that exposes make's job control to tests.sh
+# Makefile that exposes make's job control to tests.sh
-tests/%:
+# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to
+.OBJDIR: .
+
+# Turn off implicit rules
+.SUFFIXES:
+
+.DEFAULT::
bash -c 'printf . >&$(READY) && read -r -N1 -u$(DONE)'