n-channel

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit ef58cbb80324246975b35ffa7190e370772ec672
parent 4a72a4be9ded108b32e513d1a109052fce424020
Author: Samdal <samdal@protonmail.com>
Date:   Fri, 28 Feb 2025 22:31:05 +0100

add build sh

Diffstat:
A_posts/2025-02-28-build-sh.md | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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!" +``` + +