diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0bee5ef --- /dev/null +++ b/Makefile @@ -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 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..3b9cb6c --- /dev/null +++ b/Makefile.am @@ -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 diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..7efa2bb --- /dev/null +++ b/autogen.sh @@ -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)" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..4ca1243 --- /dev/null +++ b/configure.ac @@ -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