Add autotools build system (static-only, C17)

configure.ac enforces C17 and Linux/x86_64, refuses in-tree configure; Makefile.am builds libc.a plus crt0.o; the driver Makefile dispatches out-of-tree builds into bin/release (-O2) and bin/debug (-O0 -g).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <[email protected]>
This commit is contained in:
2026-08-30 04:09:45 -04:00
co-authored by Sisyphus
parent 6768ec36e7
commit 228ed097f0
4 changed files with 196 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
# Driver Makefile — nulsl-libc
#
# This is the HAND-WRITTEN top-level makefile (committed to git). It never
# runs the autotools machinery in-tree; instead it drives two out-of-tree
# builds, one per bin/ directory, exactly as the Null Linux guidelines want:
#
# make release -> ./configure CFLAGS='-O2' into bin/release/
# make debug -> ./configure CFLAGS='-O0 -g' into bin/debug/
#
# Run `./autogen.sh` once after cloning (or just run `make release` — it
# regenerates the configure script itself when needed).
#
# Never run `./configure` at the repository root: configure.ac refuses it.
.PHONY: all release debug bench check clean distclean compile_commands
all: release
# --- bootstrapping ---------------------------------------------------------
# Regenerates configure (and friends) when autogen.sh is newer.
configure: autogen.sh
./autogen.sh
# --- builds ----------------------------------------------------------------
# Release is ALWAYS -O2 (project guideline #4).
bin/release/Makefile: configure
@mkdir -p bin/release
@cd bin/release && ../../configure CFLAGS='-O2'
bin/debug/Makefile: configure
@mkdir -p bin/debug
@cd bin/debug && ../../configure CFLAGS='-O0 -g'
release: bin/release/Makefile
$(MAKE) -C bin/release
debug: bin/debug/Makefile
$(MAKE) -C bin/debug
# --- developer conveniences -------------------------------------------------
bench: release
$(MAKE) -C bin/release bench
check: release
$(MAKE) -C bin/release check
# LSP support: compile_commands.json for clangd (requires `bear`).
compile_commands: release
@command -v bear >/dev/null 2>&1 || { \
echo "error: 'bear' is not installed (sudo pacman -S bear, apt install bear, ...)"; exit 1; }
@cd bin/release && bear --output ../../compile_commands.json -- $(MAKE) clean all
clean:
@test ! -f bin/release/Makefile || $(MAKE) -C bin/release clean
@test ! -f bin/debug/Makefile || $(MAKE) -C bin/debug clean
distclean: clean
rm -rf bin/release bin/debug
+66
View File
@@ -0,0 +1,66 @@
# Makefile.am — nulsl-libc
#
# This is the AUTOTOOLS makefile. It is only ever instantiated by a configure
# run inside bin/release/ or bin/debug/ (never at the repository root).
#
# Everything is a static library. No libtool, no shared objects, no dynamic
# linker. See docs/architecture.md.
#
# Benchmarks and tests live in one Makefile on purpose: automake orders
# targets by dependency, so libc.a (and crt0.o) are guaranteed to exist
# before any -nostdlib binary links against them. With SUBDIRS, recursion
# builds the subdirectories first and linking breaks.
AUTOMAKE_OPTIONS = foreign
AM_CPPFLAGS = -I$(top_srcdir)/include
AM_CFLAGS = -std=c17 -ffreestanding -fno-builtin -fno-stack-protector \
-Wall -Wextra -Wshadow -Wpointer-arith
lib_LIBRARIES = libc.a
libc_a_SOURCES = \
src/errno.c \
src/stdio.c \
src/stdlib.c \
src/string.c \
src/syscall.c \
src/unistd.c
# The C runtime entry point is deliberately NOT a member of libc.a: nothing
# references _start, so the linker would never extract it from an archive.
# It is built as a standalone object and linked explicitly by everything
# that wants a runnable binary.
noinst_DATA = crt0.o
crt0.o: $(top_srcdir)/src/crt/crt0.S
$(CC) $(AM_CPPFLAGS) $(CFLAGS) -c -o $@ $<
CLEANFILES = crt0.o
EXTRA_DIST = autogen.sh
# --- benchmarks ------------------------------------------------------------
# Fully static, -nostdlib: the numbers reflect exactly what ships.
noinst_PROGRAMS = benchmarks/bench_strlen benchmarks/bench_syscall
benchmarks_bench_strlen_SOURCES = benchmarks/bench_strlen.c
benchmarks_bench_strlen_LDADD = crt0.o libc.a -lgcc
benchmarks_bench_strlen_LDFLAGS = -nostdlib -static -no-pie -Wno-unused-command-line-argument
benchmarks_bench_syscall_SOURCES = benchmarks/bench_syscall.c
benchmarks_bench_syscall_LDADD = crt0.o libc.a -lgcc
benchmarks_bench_syscall_LDFLAGS = -nostdlib -static -no-pie -Wno-unused-command-line-argument
.PHONY: bench
bench: all
./benchmarks/bench_strlen
./benchmarks/bench_syscall
# --- tests ------------------------------------------------------------------
check_PROGRAMS = tests/smoke
TESTS = tests/smoke
tests_smoke_SOURCES = tests/smoke.c
tests_smoke_LDADD = crt0.o libc.a -lgcc
tests_smoke_LDFLAGS = -nostdlib -static -no-pie -Wno-unused-command-line-argument
Executable
+19
View File
@@ -0,0 +1,19 @@
#!/bin/sh
# autogen.sh — regenerate all autotools machinery.
#
# Usage: ./autogen.sh (then: make release / make debug / make bench)
#
# Never run ./configure at the repository root — use the driver Makefile
# targets instead, which configure out-of-tree into bin/{release,debug}.
set -e
cd "$(dirname "$0")"
autoreconf -i -f -v
echo
echo "autotools machinery regenerated."
echo "next: make release (bin/release/, CFLAGS='-O2')"
echo " make debug (bin/debug/, CFLAGS='-O0 -g')"
echo " make bench (benchmarks against the release build)"
echo " make check (smoke tests)"
+50
View File
@@ -0,0 +1,50 @@
dnl configure.ac — nulsl-libc, a very lightweight C17 libc for Linux.
dnl
dnl Static-only on purpose: the dynamic linker is a memory tax we refuse to
dnl pay (see docs/architecture.md). libc.a is a plain archive, so no libtool.
dnl
dnl IMPORTANT: never run ./configure at the repository root. The root holds a
dnl hand-written driver Makefile that builds into bin/{release,debug}; the
dnl guard below refuses to overwrite it.
AC_PREREQ([2.71])
AC_INIT([nulsl-libc], [0.1.0], [], [nulsl-libc],
[https://git.spectoria.dev/The-Null-Linux-Project/nulsl-libc])
AC_CONFIG_SRCDIR([include/stdio.h])
AC_CONFIG_AUX_DIR([build-aux])
AC_CANONICAL_HOST
AM_INIT_AUTOMAKE([foreign subdir-objects])
dnl C17, and only C17 (project guideline #1). Modern autoconf ships no
dnl AC_PROG_CC_C17 macro, so prove it with a compile test instead.
AC_PROG_CC
saved_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS -std=c17"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([#if __STDC_VERSION__ < 201710L
#error "not C17"
#endif
], [])],
[],
[AC_MSG_ERROR([nulsl-libc requires a C17 compiler (GCC >= 8 or Clang >= 6); -std=c17 was rejected])])
CFLAGS="$saved_CFLAGS"
dnl Target validation: Linux on x86_64, for now.
AS_CASE([$host_os],
[linux*], [],
[AC_MSG_ERROR([nulsl-libc currently targets Linux only (configured for $host_os)])])
AS_CASE([$host_cpu],
[x86_64], [],
[AC_MSG_ERROR([nulsl-libc currently targets x86_64 only (configured for $host_cpu); src/arch/ is where new ports go])])
dnl Plain archives need ar/ranlib; no libtool, no shared objects.
AC_CHECK_TOOL([AR], [ar])
AC_PROG_RANLIB
dnl Refuse in-tree configuration: it would clobber the committed driver
dnl Makefile that dispatches into bin/{release,debug}.
AS_IF([test "x$srcdir" = "x." && test -f Makefile],
[AC_MSG_ERROR([in-tree builds are disabled — run 'make release' or 'make debug' from the repository root instead])])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT