commit ef58cbb80324246975b35ffa7190e370772ec672
parent 4a72a4be9ded108b32e513d1a109052fce424020
Author: Samdal <samdal@protonmail.com>
Date: Fri, 28 Feb 2025 22:31:05 +0100
add build sh
Diffstat:
1 file changed, 154 insertions(+), 0 deletions(-)
diff --git a/_posts/2025-02-28-build-sh.md b/_posts/2025-02-28-build-sh.md
@@ -0,0 +1,154 @@
+---
+layout: post
+title: "Compiling C on Unix: build.sh"
+description: The no bullshit way to compile C (on Unix)
+comments: true
+tags: [writing, programming, C]
+---
+
+## What do I want from a build system?
+
+I want a build system to be:
+- Easy to maintain
+- Minimal dependencies
+- Configurable
+
+And optionally:
+- multi-threaded / incremental
+
+## The unity build
+
+Unity builds are not incremental. They work by `#include`-ing all source code into one file, and then compiling a single translation unit.
+
+Unity builds should be the go-to method for shipping a program. It's easy to port the script, and Unity builds give the compiler more opportunities for optimization.
+
+```c
+// unity.c
+#include "utils.h"
+#include "utils.c"
+#include "fs.h"
+#include "fs.c"
+#include "main.c"
+```
+
+```sh
+#!/bin/bash
+
+CC="clang"
+output=(./bin/App)
+sources=(./src/unity.c)
+compiler_flags=(-Wall -pedantic -std=c99 -Isrc/)
+linker_flags=(-fuse-ld=mold)
+
+${CC} ${compiler_flags[*]} ${sources[*]} ${linker_flags[*]} -o ${output}
+```
+
+
+## The incremental build
+
+You may want to do an incremental build if your project is "too complex." Incremental builds are rarely necessary.
+
+```sh
+#!/bin/bash
+set -e
+
+CC="ccache clang"
+output=(./bin/App)
+sources=(src/main.c src/fs.c)
+compiler_flags=(-Wall -Wextra -pedantic -std=c99 -Isrc/)
+linker_flags=(-fuse-ld=mold)
+objects=()
+
+for s in "${sources[@]}"; do
+ mkdir -p $(dirname ".obj/${s}.o")
+ ${CC} -c ${compiler_flags[*]} ${s} -o .obj/${s}.o &
+ objects+=".obj/${s}.o "
+done
+
+wait $BACK_PID # wait for compile to finish
+${CC} ${linker_flags[*]} ${objects[*]} -o ${output}
+```
+
+On top of sometimes being faster due to multi-threading, incremental builds let us leverage `ccache` to prevent recompiling unchanged code. Since we still have to re-link, using `mold` as our linker will further increase speeds.
+
+
+## Adding complexity
+
+Let's build upon the incremental build by adding the following:
+- Ensure `./bin/` is created
+- Automatically find source files
+- More compiler flags
+- User-specific compiler flags
+- Metaprogram or custom pre-processor
+- Echo state of the program
+
+```sh
+#!/bin/bash
+set -e
+
+### find and filter sources ###
+
+mapfile -t sources < <(find -name *.c | sed\
+ -e "\|./game/config.*|d" \
+ -e "\|./gs/.*|d" \
+ -e "\|.*impl.*|d" \
+ -e "\|.*third_party.*|d" \
+ -e "\|./codegen/.*|d" \
+)
+
+### flags ###
+
+CC="ccache clang"
+output=(./bin/App)
+compiler_flags=(
+ -O0 -march=native -ffast-math
+ -I./game/source
+ -Wall -Wno-missing-braces -Wno-unused-function -Wno-unused-variable -Wno-unused-but-set-variable -Wno-initializer-overrides -Wfatal-errors
+ -g
+)
+linker_flags=(
+ -fuse-ld=mold -ldl -lX11 -lXi -lm -lpthread -lavformat -lavcodec -lswscale -lavutil -ludev -lomp -lz -lcurl -luv -lssl -lcrypto
+)
+common_flags=(
+ -pthread -fopenmp
+)
+if [ $(whoami) = "halvard" ]; then
+ common_flags+=(
+ -fsanitize=address,float-divide-by-zero,float-cast-overflow -fno-sanitize=null,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer
+ )
+else
+ # ...
+fi
+
+mkdir -p $(dirname "${output}")
+
+### compile and run metaprogram ###
+
+if [ ! -f "./bin/codegen" ] || [ "./codegen/codegen.c" -nt "./bin/codegen" ]; then
+ ${CC} -g ./codegen/codegen.c -fuse-ld=mold -o ./bin/codegen
+fi
+
+./bin/codegen
+
+### compile program ###
+
+objects=()
+
+for i in "${!sources[@]}"; do
+ #echo "adding source file ${sources[i]}..."
+ mkdir -p $(dirname ".obj/${sources[i]}.o")
+ ${CC} -c ${compiler_flags[*]} ${common_flags[*]} ${sources[i]} -o .obj/${sources[i]}.o &
+ objects+=".obj/${sources[i]}.o "
+done
+
+echo "Compiling..."
+wait $BACK_PID # wait for compile to finish
+
+### link program ###
+
+echo "Linking..."
+${CC} ${linker_flags[*]} ${common_flags[*]} ${objects[*]} -o ${output}
+echo "Done!"
+```
+
+