summaryrefslogtreecommitdiffstats
path: root/build
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2024-05-07 15:42:46 -0400
committerTavian Barnes <tavianator@tavianator.com>2024-05-07 15:42:46 -0400
commit452d6697e0f92326ab139eed4eadd9c2fd8b55ca (patch)
tree0feeb3722dcf6debb6c33c5175342bf1d70a1dba /build
parenta4299f9bc1d3e60a7e628561e8d650c2a241e1c2 (diff)
parentc5cf2cf90834f2f56b2940d2a499a1a614ebfd21 (diff)
downloadbfs-452d6697e0f92326ab139eed4eadd9c2fd8b55ca.tar.xz
Merge branch 'main' into find2fdfind2fd
Diffstat (limited to 'build')
-rwxr-xr-xbuild/cc.sh16
-rw-r--r--build/config.mk62
-rwxr-xr-xbuild/define-if.sh20
-rw-r--r--build/deps.mk18
-rw-r--r--build/empty.c6
-rw-r--r--build/exports.mk20
-rw-r--r--build/flags.mk136
-rw-r--r--build/has/acl-get-entry.c8
-rw-r--r--build/has/acl-get-file.c8
-rw-r--r--build/has/acl-get-tag-type.c10
-rw-r--r--build/has/acl-is-trivial-np.c12
-rw-r--r--build/has/acl-trivial.c8
-rw-r--r--build/has/aligned-alloc.c8
-rw-r--r--build/has/confstr.c9
-rw-r--r--build/has/extattr-get-file.c10
-rw-r--r--build/has/extattr-get-link.c10
-rw-r--r--build/has/extattr-list-file.c10
-rw-r--r--build/has/extattr-list-link.c10
-rw-r--r--build/has/fdclosedir.c8
-rw-r--r--build/has/getdents.c9
-rw-r--r--build/has/getdents64-syscall.c11
-rw-r--r--build/has/getdents64.c9
-rw-r--r--build/has/getmntent-1.c9
-rw-r--r--build/has/getmntent-2.c10
-rw-r--r--build/has/getmntinfo.c10
-rw-r--r--build/has/getprogname-gnu.c9
-rw-r--r--build/has/getprogname.c9
-rw-r--r--build/has/max-align-t.c8
-rw-r--r--build/has/pipe2.c10
-rw-r--r--build/has/posix-spawn-addfchdir-np.c11
-rw-r--r--build/has/posix-spawn-addfchdir.c11
-rw-r--r--build/has/st-acmtim.c12
-rw-r--r--build/has/st-acmtimespec.c12
-rw-r--r--build/has/st-birthtim.c9
-rw-r--r--build/has/st-birthtimespec.c9
-rw-r--r--build/has/st-flags.c9
-rw-r--r--build/has/statx-syscall.c13
-rw-r--r--build/has/statx.c11
-rw-r--r--build/has/strerror-l.c11
-rw-r--r--build/has/strerror-r-gnu.c11
-rw-r--r--build/has/strerror-r-posix.c11
-rw-r--r--build/has/string-to-flags.c9
-rw-r--r--build/has/strtofflags.c9
-rw-r--r--build/has/timegm.c9
-rw-r--r--build/has/tm-gmtoff.c9
-rw-r--r--build/has/uselocale.c9
-rw-r--r--build/header.mk72
-rwxr-xr-xbuild/msg-if.sh21
-rwxr-xr-xbuild/msg.sh62
-rwxr-xr-xbuild/pkgconf.sh100
-rw-r--r--build/pkgs.mk33
-rw-r--r--build/prelude.mk122
-rw-r--r--build/use/libacl.c9
-rw-r--r--build/use/libcap.c9
-rw-r--r--build/use/libselinux.c9
-rw-r--r--build/use/liburing.c9
-rw-r--r--build/use/oniguruma.c9
57 files changed, 1113 insertions, 0 deletions
diff --git a/build/cc.sh b/build/cc.sh
new file mode 100755
index 0000000..45d51ca
--- /dev/null
+++ b/build/cc.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Run the compiler and check if it succeeded
+
+set -eu
+
+TMP=$(mktemp)
+trap 'rm -f "$TMP"' EXIT
+
+(
+ set -x
+ $XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $XLDLIBS -o "$TMP"
+)
diff --git a/build/config.mk b/build/config.mk
new file mode 100644
index 0000000..24873ec
--- /dev/null
+++ b/build/config.mk
@@ -0,0 +1,62 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Makefile that implements `./configure`
+
+include build/prelude.mk
+include build/exports.mk
+
+# All configuration steps
+config: gen/config.mk gen/config.h
+.PHONY: config
+
+# Makefile fragments generated by `./configure`
+MKS := \
+ gen/vars.mk \
+ gen/flags.mk \
+ gen/deps.mk \
+ gen/pkgs.mk
+
+# The main configuration file, which includes the others
+gen/config.mk: ${MKS}
+ ${MSG} "[ GEN] $@"
+ @printf '# %s\n' "$@" >$@
+ @printf 'include %s\n' ${.ALLSRC} >>$@
+ ${VCAT} gen/config.mk
+.PHONY: gen/config.mk
+
+# 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 'CC := %s\n' "$$XCC" >>$@
+ @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@
+ @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@
+ @printf 'PKG_CONFIG := %s\n' "$$XPKG_CONFIG" >>$@
+ @printf 'RM := %s\n' "$$XRM" >>$@
+ ${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
+ @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/flags.mk $@
+.PHONY: gen/flags.mk
+
+# Check for dependency generation support
+gen/deps.mk: gen/flags.mk
+ @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/deps.mk $@
+.PHONY: gen/deps.mk
+
+# Auto-detect dependencies and their build flags
+gen/pkgs.mk: gen/flags.mk
+ @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/pkgs.mk $@
+.PHONY: gen/pkgs.mk
+
+# Compile-time feature detection
+gen/config.h: gen/config.mk
+ @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/header.mk $@
+.PHONY: gen/config.h
diff --git a/build/define-if.sh b/build/define-if.sh
new file mode 100755
index 0000000..295ead8
--- /dev/null
+++ b/build/define-if.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Output a C preprocessor definition based on whether a command succeeds
+
+set -eu
+
+SLUG="${1#build/}"
+SLUG="${SLUG%.c}"
+MACRO="BFS_$(printf '%s' "$SLUG" | tr '/a-z-' '_A-Z_')"
+shift
+
+if "$@"; then
+ printf '#define %s true\n' "$MACRO"
+else
+ printf '#define %s false\n' "$MACRO"
+ exit 1
+fi
diff --git a/build/deps.mk b/build/deps.mk
new file mode 100644
index 0000000..3db62b6
--- /dev/null
+++ b/build/deps.mk
@@ -0,0 +1,18 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Makefile that generates gen/deps.mk
+
+include build/prelude.mk
+include gen/vars.mk
+include gen/flags.mk
+include build/exports.mk
+
+gen/deps.mk::
+ ${MSG} "[ GEN] $@"
+ @printf '# %s\n' "$@" >$@
+ @if build/cc.sh -MD -MP -MF /dev/null build/empty.c; then \
+ printf 'CPPFLAGS += -MD -MP\n'; \
+ fi >>$@ 2>$@.log
+ ${VCAT} $@
+ @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@
diff --git a/build/empty.c b/build/empty.c
new file mode 100644
index 0000000..4fa9a5b
--- /dev/null
+++ b/build/empty.c
@@ -0,0 +1,6 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+int main(void) {
+ return 0;
+}
diff --git a/build/exports.mk b/build/exports.mk
new file mode 100644
index 0000000..ed19134
--- /dev/null
+++ b/build/exports.mk
@@ -0,0 +1,20 @@
+# 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 XPKG_CONFIG=${PKG_CONFIG}
+export XRM=${RM}
+
+export XCPPFLAGS=${CPPFLAGS}
+export XCFLAGS=${CFLAGS}
+export XLDFLAGS=${LDFLAGS}
+export XLDLIBS=${LDLIBS}
+
+export XNOLIBS=${NOLIBS}
diff --git a/build/flags.mk b/build/flags.mk
new file mode 100644
index 0000000..c911b22
--- /dev/null
+++ b/build/flags.mk
@@ -0,0 +1,136 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Makefile that generates gen/flags.mk
+
+include build/prelude.mk
+include gen/vars.mk
+
+# Configurable flags
+CPPFLAGS ?=
+CFLAGS ?= \
+ -g \
+ -Wall \
+ -Wformat=2 \
+ -Werror=implicit \
+ -Wimplicit-fallthrough \
+ -Wmissing-declarations \
+ -Wshadow \
+ -Wsign-compare \
+ -Wstrict-prototypes
+LDFLAGS ?=
+LDLIBS ?=
+
+export XCPPFLAGS=${CPPFLAGS}
+export XCFLAGS=${CFLAGS}
+export XLDFLAGS=${LDFLAGS}
+export XLDLIBS=${LDLIBS}
+
+# Immutable flags
+export BFS_CPPFLAGS= \
+ -Isrc \
+ -Igen \
+ -D__EXTENSIONS__ \
+ -D_ATFILE_SOURCE \
+ -D_BSD_SOURCE \
+ -D_DARWIN_C_SOURCE \
+ -D_DEFAULT_SOURCE \
+ -D_GNU_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 := -lsec -lsocket -lnsl
+export BFS_LDLIBS=${LDLIBS,${OS}}
+
+# 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
+INSANE := ${NOT,${_ASAN}${_LSAN}${_MSAN}${_TSAN}${_UBSAN}}
+SAN := ${NOT,${INSANE}}
+export SAN_CFLAGS=${SAN_CFLAGS,${SAN}}
+
+# MSAN and TSAN both need all code to be instrumented
+YESLIBS := ${NOT,${_MSAN}${_TSAN}}
+NOLIBS ?= ${NOT,${YESLIBS}}
+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/build/has/acl-get-entry.c b/build/has/acl-get-entry.c
new file mode 100644
index 0000000..3cce771
--- /dev/null
+++ b/build/has/acl-get-entry.c
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <sys/acl.h>
+
+int main(void) {
+ acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT);
+ acl_entry_t entry;
+ return acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
+}
diff --git a/build/has/acl-get-file.c b/build/has/acl-get-file.c
new file mode 100644
index 0000000..89fbf23
--- /dev/null
+++ b/build/has/acl-get-file.c
@@ -0,0 +1,8 @@
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/acl.h>
+
+int main(void) {
+ acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT);
+ return acl == (acl_t)NULL;
+}
diff --git a/build/has/acl-get-tag-type.c b/build/has/acl-get-tag-type.c
new file mode 100644
index 0000000..2901956
--- /dev/null
+++ b/build/has/acl-get-tag-type.c
@@ -0,0 +1,10 @@
+#include <string.h>
+#include <sys/types.h>
+#include <sys/acl.h>
+
+int main(void) {
+ acl_entry_t entry;
+ memset(&entry, 0, sizeof(entry));
+ acl_tag_t tag;
+ return acl_get_tag_type(entry, &tag);
+}
diff --git a/build/has/acl-is-trivial-np.c b/build/has/acl-is-trivial-np.c
new file mode 100644
index 0000000..9ca9fc7
--- /dev/null
+++ b/build/has/acl-is-trivial-np.c
@@ -0,0 +1,12 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <sys/types.h>
+#include <sys/acl.h>
+
+int main(void) {
+ acl_t acl = acl_get_fd(3);
+ int trivial;
+ acl_is_trivial_np(acl, &trivial);
+ return 0;
+}
diff --git a/build/has/acl-trivial.c b/build/has/acl-trivial.c
new file mode 100644
index 0000000..7efc838
--- /dev/null
+++ b/build/has/acl-trivial.c
@@ -0,0 +1,8 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <sys/acl.h>
+
+int main(void) {
+ return acl_trivial(".");
+}
diff --git a/build/has/aligned-alloc.c b/build/has/aligned-alloc.c
new file mode 100644
index 0000000..4460038
--- /dev/null
+++ b/build/has/aligned-alloc.c
@@ -0,0 +1,8 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stdlib.h>
+
+int main(void) {
+ return !aligned_alloc(_Alignof(void *), sizeof(void *));
+}
diff --git a/build/has/confstr.c b/build/has/confstr.c
new file mode 100644
index 0000000..58280b4
--- /dev/null
+++ b/build/has/confstr.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <unistd.h>
+
+int main(void) {
+ confstr(_CS_PATH, NULL, 0);
+ return 0;
+}
diff --git a/build/has/extattr-get-file.c b/build/has/extattr-get-file.c
new file mode 100644
index 0000000..ac9cf96
--- /dev/null
+++ b/build/has/extattr-get-file.c
@@ -0,0 +1,10 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/extattr.h>
+
+int main(void) {
+ return extattr_get_file("file", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0);
+}
diff --git a/build/has/extattr-get-link.c b/build/has/extattr-get-link.c
new file mode 100644
index 0000000..c35be5b
--- /dev/null
+++ b/build/has/extattr-get-link.c
@@ -0,0 +1,10 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/extattr.h>
+
+int main(void) {
+ return extattr_get_link("link", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0);
+}
diff --git a/build/has/extattr-list-file.c b/build/has/extattr-list-file.c
new file mode 100644
index 0000000..e68a8bb
--- /dev/null
+++ b/build/has/extattr-list-file.c
@@ -0,0 +1,10 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/extattr.h>
+
+int main(void) {
+ return extattr_list_file("file", EXTATTR_NAMESPACE_USER, NULL, 0);
+}
diff --git a/build/has/extattr-list-link.c b/build/has/extattr-list-link.c
new file mode 100644
index 0000000..49f0ec2
--- /dev/null
+++ b/build/has/extattr-list-link.c
@@ -0,0 +1,10 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/extattr.h>
+
+int main(void) {
+ return extattr_list_link("link", EXTATTR_NAMESPACE_USER, NULL, 0);
+}
diff --git a/build/has/fdclosedir.c b/build/has/fdclosedir.c
new file mode 100644
index 0000000..f4ad1f5
--- /dev/null
+++ b/build/has/fdclosedir.c
@@ -0,0 +1,8 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <dirent.h>
+
+int main(void) {
+ return fdclosedir(opendir("."));
+}
diff --git a/build/has/getdents.c b/build/has/getdents.c
new file mode 100644
index 0000000..579898f
--- /dev/null
+++ b/build/has/getdents.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <dirent.h>
+
+int main(void) {
+ char buf[1024];
+ return getdents(3, (void *)buf, sizeof(buf));
+}
diff --git a/build/has/getdents64-syscall.c b/build/has/getdents64-syscall.c
new file mode 100644
index 0000000..7642d93
--- /dev/null
+++ b/build/has/getdents64-syscall.c
@@ -0,0 +1,11 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <dirent.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+int main(void) {
+ char buf[1024];
+ return syscall(SYS_getdents64, 3, (void *)buf, sizeof(buf));
+}
diff --git a/build/has/getdents64.c b/build/has/getdents64.c
new file mode 100644
index 0000000..d8e8062
--- /dev/null
+++ b/build/has/getdents64.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <dirent.h>
+
+int main(void) {
+ char buf[1024];
+ return getdents64(3, (void *)buf, sizeof(buf));
+}
diff --git a/build/has/getmntent-1.c b/build/has/getmntent-1.c
new file mode 100644
index 0000000..9854dcd
--- /dev/null
+++ b/build/has/getmntent-1.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <mntent.h>
+#include <stdio.h>
+
+int main(void) {
+ return !getmntent(stdin);
+}
diff --git a/build/has/getmntent-2.c b/build/has/getmntent-2.c
new file mode 100644
index 0000000..71f0220
--- /dev/null
+++ b/build/has/getmntent-2.c
@@ -0,0 +1,10 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stdio.h>
+#include <sys/mnttab.h>
+
+int main(void) {
+ struct mnttab mnt;
+ return getmntent(stdin, &mnt);
+}
diff --git a/build/has/getmntinfo.c b/build/has/getmntinfo.c
new file mode 100644
index 0000000..90ef5fb
--- /dev/null
+++ b/build/has/getmntinfo.c
@@ -0,0 +1,10 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/mount.h>
+
+int main(void) {
+ return getmntinfo(NULL, MNT_WAIT);
+}
diff --git a/build/has/getprogname-gnu.c b/build/has/getprogname-gnu.c
new file mode 100644
index 0000000..6b97c5e
--- /dev/null
+++ b/build/has/getprogname-gnu.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <errno.h>
+
+int main(void) {
+ const char *str = program_invocation_short_name;
+ return str[0];
+}
diff --git a/build/has/getprogname.c b/build/has/getprogname.c
new file mode 100644
index 0000000..83dc8e8
--- /dev/null
+++ b/build/has/getprogname.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stdlib.h>
+
+int main(void) {
+ const char *str = getprogname();
+ return str[0];
+}
diff --git a/build/has/max-align-t.c b/build/has/max-align-t.c
new file mode 100644
index 0000000..96165ce
--- /dev/null
+++ b/build/has/max-align-t.c
@@ -0,0 +1,8 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stddef.h>
+
+int main(void) {
+ return _Alignof(max_align_t);
+}
diff --git a/build/has/pipe2.c b/build/has/pipe2.c
new file mode 100644
index 0000000..4cb43b5
--- /dev/null
+++ b/build/has/pipe2.c
@@ -0,0 +1,10 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <fcntl.h>
+#include <unistd.h>
+
+int main(void) {
+ int fds[2];
+ return pipe2(fds, O_CLOEXEC);
+}
diff --git a/build/has/posix-spawn-addfchdir-np.c b/build/has/posix-spawn-addfchdir-np.c
new file mode 100644
index 0000000..b870a53
--- /dev/null
+++ b/build/has/posix-spawn-addfchdir-np.c
@@ -0,0 +1,11 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <spawn.h>
+
+int main(void) {
+ posix_spawn_file_actions_t actions;
+ posix_spawn_file_actions_init(&actions);
+ posix_spawn_file_actions_addfchdir_np(&actions, 3);
+ return 0;
+}
diff --git a/build/has/posix-spawn-addfchdir.c b/build/has/posix-spawn-addfchdir.c
new file mode 100644
index 0000000..c52ff81
--- /dev/null
+++ b/build/has/posix-spawn-addfchdir.c
@@ -0,0 +1,11 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <spawn.h>
+
+int main(void) {
+ posix_spawn_file_actions_t actions;
+ posix_spawn_file_actions_init(&actions);
+ posix_spawn_file_actions_addfchdir(&actions, 3);
+ return 0;
+}
diff --git a/build/has/st-acmtim.c b/build/has/st-acmtim.c
new file mode 100644
index 0000000..d687ab0
--- /dev/null
+++ b/build/has/st-acmtim.c
@@ -0,0 +1,12 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <sys/stat.h>
+
+int main(void) {
+ struct stat sb = {0};
+ unsigned int a = sb.st_atim.tv_sec;
+ unsigned int c = sb.st_ctim.tv_sec;
+ unsigned int m = sb.st_mtim.tv_sec;
+ return a + c + m;
+}
diff --git a/build/has/st-acmtimespec.c b/build/has/st-acmtimespec.c
new file mode 100644
index 0000000..f747bc0
--- /dev/null
+++ b/build/has/st-acmtimespec.c
@@ -0,0 +1,12 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <sys/stat.h>
+
+int main(void) {
+ struct stat sb = {0};
+ unsigned int a = sb.st_atimespec.tv_sec;
+ unsigned int c = sb.st_ctimespec.tv_sec;
+ unsigned int m = sb.st_mtimespec.tv_sec;
+ return a + c + m;
+}
diff --git a/build/has/st-birthtim.c b/build/has/st-birthtim.c
new file mode 100644
index 0000000..4964571
--- /dev/null
+++ b/build/has/st-birthtim.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <sys/stat.h>
+
+int main(void) {
+ struct stat sb = {0};
+ return sb.st_birthtim.tv_sec;
+}
diff --git a/build/has/st-birthtimespec.c b/build/has/st-birthtimespec.c
new file mode 100644
index 0000000..91a613f
--- /dev/null
+++ b/build/has/st-birthtimespec.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <sys/stat.h>
+
+int main(void) {
+ struct stat sb = {0};
+ return sb.st_birthtimespec.tv_sec;
+}
diff --git a/build/has/st-flags.c b/build/has/st-flags.c
new file mode 100644
index 0000000..b1d0c32
--- /dev/null
+++ b/build/has/st-flags.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <sys/stat.h>
+
+int main(void) {
+ struct stat sb = {0};
+ return sb.st_flags;
+}
diff --git a/build/has/statx-syscall.c b/build/has/statx-syscall.c
new file mode 100644
index 0000000..87ec869
--- /dev/null
+++ b/build/has/statx-syscall.c
@@ -0,0 +1,13 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <fcntl.h>
+#include <linux/stat.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+int main(void) {
+ struct statx sb;
+ syscall(SYS_statx, AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb);
+ return 0;
+}
diff --git a/build/has/statx.c b/build/has/statx.c
new file mode 100644
index 0000000..65f1674
--- /dev/null
+++ b/build/has/statx.c
@@ -0,0 +1,11 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+int main(void) {
+ struct statx sb;
+ statx(AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb);
+ return 0;
+}
diff --git a/build/has/strerror-l.c b/build/has/strerror-l.c
new file mode 100644
index 0000000..3dcc4d7
--- /dev/null
+++ b/build/has/strerror-l.c
@@ -0,0 +1,11 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <errno.h>
+#include <locale.h>
+#include <string.h>
+
+int main(void) {
+ locale_t locale = duplocale(LC_GLOBAL_LOCALE);
+ return !strerror_l(ENOMEM, locale);
+}
diff --git a/build/has/strerror-r-gnu.c b/build/has/strerror-r-gnu.c
new file mode 100644
index 0000000..26ca0ee
--- /dev/null
+++ b/build/has/strerror-r-gnu.c
@@ -0,0 +1,11 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <errno.h>
+#include <string.h>
+
+int main(void) {
+ char buf[256];
+ // Check that strerror_r() returns a pointer
+ return *strerror_r(ENOMEM, buf, sizeof(buf));
+}
diff --git a/build/has/strerror-r-posix.c b/build/has/strerror-r-posix.c
new file mode 100644
index 0000000..41b2d30
--- /dev/null
+++ b/build/has/strerror-r-posix.c
@@ -0,0 +1,11 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <errno.h>
+#include <string.h>
+
+int main(void) {
+ char buf[256];
+ // Check that strerror_r() returns an integer
+ return 2 * strerror_r(ENOMEM, buf, sizeof(buf));
+}
diff --git a/build/has/string-to-flags.c b/build/has/string-to-flags.c
new file mode 100644
index 0000000..027d72c
--- /dev/null
+++ b/build/has/string-to-flags.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stddef.h>
+#include <util.h>
+
+int main(void) {
+ return string_to_flags(NULL, NULL, NULL);
+}
diff --git a/build/has/strtofflags.c b/build/has/strtofflags.c
new file mode 100644
index 0000000..73ecbcb
--- /dev/null
+++ b/build/has/strtofflags.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <stddef.h>
+#include <unistd.h>
+
+int main(void) {
+ return strtofflags(NULL, NULL, NULL);
+}
diff --git a/build/has/timegm.c b/build/has/timegm.c
new file mode 100644
index 0000000..6e2d155
--- /dev/null
+++ b/build/has/timegm.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <time.h>
+
+int main(void) {
+ struct tm tm = {0};
+ return (int)timegm(&tm);
+}
diff --git a/build/has/tm-gmtoff.c b/build/has/tm-gmtoff.c
new file mode 100644
index 0000000..543df48
--- /dev/null
+++ b/build/has/tm-gmtoff.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <time.h>
+
+int main(void) {
+ struct tm tm = {0};
+ return tm.tm_gmtoff;
+}
diff --git a/build/has/uselocale.c b/build/has/uselocale.c
new file mode 100644
index 0000000..a712ff8
--- /dev/null
+++ b/build/has/uselocale.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <locale.h>
+
+int main(void) {
+ locale_t locale = uselocale((locale_t)0);
+ return locale == LC_GLOBAL_LOCALE;
+}
diff --git a/build/header.mk b/build/header.mk
new file mode 100644
index 0000000..da6de68
--- /dev/null
+++ b/build/header.mk
@@ -0,0 +1,72 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Makefile that generates gen/config.h
+
+include build/prelude.mk
+include gen/config.mk
+include build/exports.mk
+
+# All header fragments we generate
+HEADERS := \
+ gen/has/acl-get-entry.h \
+ gen/has/acl-get-file.h \
+ gen/has/acl-get-tag-type.h \
+ gen/has/acl-is-trivial-np.h \
+ gen/has/acl-trivial.h \
+ gen/has/aligned-alloc.h \
+ gen/has/confstr.h \
+ gen/has/extattr-get-file.h \
+ gen/has/extattr-get-link.h \
+ gen/has/extattr-list-file.h \
+ gen/has/extattr-list-link.h \
+ gen/has/fdclosedir.h \
+ gen/has/getdents.h \
+ gen/has/getdents64.h \
+ gen/has/getdents64-syscall.h \
+ gen/has/getmntent-1.h \
+ gen/has/getmntent-2.h \
+ gen/has/getmntinfo.h \
+ gen/has/getprogname.h \
+ gen/has/getprogname-gnu.h \
+ gen/has/max-align-t.h \
+ gen/has/pipe2.h \
+ gen/has/posix-spawn-addfchdir.h \
+ gen/has/posix-spawn-addfchdir-np.h \
+ gen/has/st-acmtim.h \
+ gen/has/st-acmtimespec.h \
+ gen/has/st-birthtim.h \
+ gen/has/st-birthtimespec.h \
+ gen/has/st-flags.h \
+ gen/has/statx.h \
+ gen/has/statx-syscall.h \
+ gen/has/strerror-l.h \
+ gen/has/strerror-r-gnu.h \
+ gen/has/strerror-r-posix.h \
+ gen/has/string-to-flags.h \
+ gen/has/strtofflags.h \
+ gen/has/timegm.h \
+ gen/has/tm-gmtoff.h \
+ gen/has/uselocale.h
+
+# Previously generated by pkgs.mk
+PKG_HEADERS := ${ALL_PKGS:%=gen/use/%.h}
+
+gen/config.h: ${PKG_HEADERS} ${HEADERS}
+ ${MSG} "[ GEN] $@"
+ @printf '// %s\n' "$@" >$@
+ @printf '#ifndef BFS_CONFIG_H\n' >>$@
+ @printf '#define BFS_CONFIG_H\n' >>$@
+ @cat ${.ALLSRC} >>$@
+ @printf '#endif // BFS_CONFIG_H\n' >>$@
+ @cat ${.ALLSRC:%=%.log} >gen/config.log
+ ${VCAT} $@
+.PHONY: gen/config.h
+
+# The short name of the config test
+SLUG = ${@:gen/%.h=%}
+
+${HEADERS}::
+ @${MKDIR} ${@D}
+ @build/define-if.sh ${SLUG} build/cc.sh build/${SLUG}.c >$@ 2>$@.log; \
+ build/msg-if.sh "[ CC ] ${SLUG}.c" test $$? -eq 0
diff --git a/build/msg-if.sh b/build/msg-if.sh
new file mode 100755
index 0000000..8112aea
--- /dev/null
+++ b/build/msg-if.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Print a success/failure indicator from a makefile:
+#
+# $ ./configure
+# [ CC ] use/liburing.c ✘
+# [ CC ] use/oniguruma.c ✔
+
+set -eu
+
+MSG="$1"
+shift
+
+if "$@"; then
+ build/msg.sh "$(printf '%-37s ✔' "$MSG")"
+else
+ build/msg.sh "$(printf '%-37s ✘' "$MSG")"
+fi
diff --git a/build/msg.sh b/build/msg.sh
new file mode 100755
index 0000000..a7da31b
--- /dev/null
+++ b/build/msg.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Print a message from a makefile:
+#
+# $ make -s
+# $ make
+# [ CC ] src/main.c
+# $ make V=1
+# cc -Isrc -Igen -D...
+
+set -eu
+
+# Get the $MAKEFLAGS from the top-level make invocation
+MFLAGS="${XMAKEFLAGS-${MAKEFLAGS-}}"
+
+# Check if make should be quiet (make -s)
+is_quiet() {
+ # GNU make puts single-letter flags in the first word of $MAKEFLAGS,
+ # without a leading dash
+ case "${MFLAGS%% *}" in
+ -*) : ;;
+ *s*) return 0 ;;
+ esac
+
+ # BSD make puts each flag separately like -r -s -j 48
+ for flag in $MFLAGS; do
+ case "$flag" in
+ # Ignore things like --jobserver-auth
+ --*) continue ;;
+ # Skip variable assignments
+ *=*) break ;;
+ -*s*) return 0 ;;
+ esac
+ done
+
+ return 1
+}
+
+# Check if make should be loud (make V=1)
+is_loud() {
+ test "$XV"
+}
+
+MSG="$1"
+shift
+
+if ! is_quiet && ! is_loud; then
+ printf '%s\n' "$MSG"
+fi
+
+if [ $# -eq 0 ]; then
+ exit
+fi
+
+if is_loud; then
+ printf '%s\n' "$*"
+fi
+
+"$@"
diff --git a/build/pkgconf.sh b/build/pkgconf.sh
new file mode 100755
index 0000000..96e4bf1
--- /dev/null
+++ b/build/pkgconf.sh
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# pkg-config wrapper with hardcoded fallbacks
+
+set -eu
+
+MODE=
+case "${1:-}" in
+ --*)
+ MODE="$1"
+ shift
+esac
+
+if [ $# -lt 1 ]; then
+ exit
+fi
+
+case "$XNOLIBS" in
+ y|1)
+ exit 1
+esac
+
+if [ -z "$MODE" ]; then
+ # Check whether the libraries exist at all
+ for LIB; do
+ # Check ${USE_$LIB}
+ USE_LIB="USE_$(printf '%s' "$LIB" | tr 'a-z-' 'A-Z_')"
+ eval "USE=\"\${$USE_LIB:-}\""
+ case "$USE" in
+ y|1)
+ continue
+ ;;
+ n|0)
+ exit 1
+ ;;
+ esac
+
+ CFLAGS=$("$0" --cflags "$LIB") || exit 1
+ LDFLAGS=$("$0" --ldflags "$LIB") || exit 1
+ LDLIBS=$("$0" --ldlibs "$LIB") || exit 1
+ build/cc.sh $CFLAGS $LDFLAGS build/use/$LIB.c $LDLIBS || exit 1
+ done
+fi
+
+# Defer to pkg-config if possible
+if command -v "${XPKG_CONFIG:-}" >/dev/null 2>&1; then
+ case "$MODE" in
+ --cflags)
+ "$XPKG_CONFIG" --cflags "$@"
+ ;;
+ --ldflags)
+ "$XPKG_CONFIG" --libs-only-L --libs-only-other "$@"
+ ;;
+ --ldlibs)
+ "$XPKG_CONFIG" --libs-only-l "$@"
+ ;;
+ esac
+
+ exit
+fi
+
+# pkg-config unavailable, emulate it ourselves
+CFLAGS=""
+LDFLAGS=""
+LDLIBS=""
+
+for LIB; do
+ case "$LIB" in
+ libacl)
+ LDLIB=-lacl
+ ;;
+ libcap)
+ LDLIB=-lcap
+ ;;
+ libselinux)
+ LDLIB=-lselinux
+ ;;
+ liburing)
+ LDLIB=-luring
+ ;;
+ oniguruma)
+ LDLIB=-lonig
+ ;;
+ *)
+ printf 'error: Unknown package %s\n' "$LIB" >&2
+ exit 1
+ ;;
+ esac
+
+ LDLIBS="$LDLIBS$LDLIB "
+done
+
+case "$MODE" in
+ --ldlibs)
+ printf '%s\n' "$LDLIBS"
+ ;;
+esac
diff --git a/build/pkgs.mk b/build/pkgs.mk
new file mode 100644
index 0000000..39b550d
--- /dev/null
+++ b/build/pkgs.mk
@@ -0,0 +1,33 @@
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Makefile that generates gen/pkgs.mk
+
+include build/prelude.mk
+include gen/vars.mk
+include gen/flags.mk
+include build/exports.mk
+
+HEADERS := ${ALL_PKGS:%=gen/use/%.h}
+
+gen/pkgs.mk: ${HEADERS}
+ ${MSG} "[ GEN] $@"
+ @printf '# %s\n' "$@" >$@
+ @gen() { \
+ printf 'PKGS := %s\n' "$$*"; \
+ printf 'CFLAGS += %s\n' "$$(build/pkgconf.sh --cflags "$$@")"; \
+ printf 'LDFLAGS += %s\n' "$$(build/pkgconf.sh --ldflags "$$@")"; \
+ printf 'LDLIBS := %s $${LDLIBS}\n' "$$(build/pkgconf.sh --ldlibs "$$@")"; \
+ }; \
+ gen $$(grep -l ' true$$' ${.ALLSRC} | sed 's|.*/\(.*\)\.h|\1|') >>$@
+ ${VCAT} $@
+
+.PHONY: gen/pkgs.mk
+
+# Convert gen/use/foo.h to foo
+PKG = ${@:gen/use/%.h=%}
+
+${HEADERS}::
+ @${MKDIR} ${@D}
+ @build/define-if.sh use/${PKG} build/pkgconf.sh ${PKG} >$@ 2>$@.log; \
+ build/msg-if.sh "[ CC ] use/${PKG}.c" test $$? -eq 0;
diff --git a/build/prelude.mk b/build/prelude.mk
new file mode 100644
index 0000000..5be26cb
--- /dev/null
+++ b/build/prelude.mk
@@ -0,0 +1,122 @@
+# 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 ?= $^
+
+# Installation paths
+DESTDIR ?=
+PREFIX ?= /usr
+MANDIR ?= ${PREFIX}/share/man
+
+# Configurable executables
+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
+
+# Normalize ${V} to either "y" or ""
+export XV=${TRUTHY,${V}}
+
+# Suppress output unless V=1
+Q, := @
+Q := ${Q,${XV}}
+
+# Show full commands with `make V=1`, otherwise short summaries
+MSG = @build/msg.sh
+
+# cat a file if V=1
+VCAT,y := @cat
+VCAT, := @:
+VCAT := ${VCAT,${XV}}
+
+# All external dependencies
+ALL_PKGS := \
+ libacl \
+ libcap \
+ libselinux \
+ liburing \
+ oniguruma
+
+# List all object files here, as they're needed by both `./configure` 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/xregex.o \
+ obj/src/xspawn.o \
+ obj/src/xtime.o \
+ obj/gen/version.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/build/use/libacl.c b/build/use/libacl.c
new file mode 100644
index 0000000..de1fe50
--- /dev/null
+++ b/build/use/libacl.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <sys/acl.h>
+
+int main(void) {
+ acl_free(0);
+ return 0;
+}
diff --git a/build/use/libcap.c b/build/use/libcap.c
new file mode 100644
index 0000000..58e832c
--- /dev/null
+++ b/build/use/libcap.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <sys/capability.h>
+
+int main(void) {
+ cap_free(0);
+ return 0;
+}
diff --git a/build/use/libselinux.c b/build/use/libselinux.c
new file mode 100644
index 0000000..bca409d
--- /dev/null
+++ b/build/use/libselinux.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <selinux/selinux.h>
+
+int main(void) {
+ freecon(0);
+ return 0;
+}
diff --git a/build/use/liburing.c b/build/use/liburing.c
new file mode 100644
index 0000000..bea499a
--- /dev/null
+++ b/build/use/liburing.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <liburing.h>
+
+int main(void) {
+ io_uring_free_probe(0);
+ return 0;
+}
diff --git a/build/use/oniguruma.c b/build/use/oniguruma.c
new file mode 100644
index 0000000..cb17596
--- /dev/null
+++ b/build/use/oniguruma.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <oniguruma.h>
+
+int main(void) {
+ onig_free(0);
+ return 0;
+}