05 — Build and Package

C’s build story is not as polished as Cargo or npm, and that is a feature, not a bug — you learn what the compiler actually does. You need three build systems in your muscle memory: Make (which you must be able to read), CMake (which the world runs on), and Meson (the modern alternative for new projects). Package managers for C mostly don’t apply; the world is apt/brew + system libraries + pkg-config.

Make — you must be able to read it

Half of every C project you’ll ever read has a hand-written Makefile. You don’t need to write elaborate Makefiles; you need to be able to read one.

Minimal working Makefile:

CC = gcc
CFLAGS = -std=c17 -Wall -Wextra -Wpedantic -O2 -g -fsanitize=address,undefined
LDFLAGS = -fsanitize=address,undefined
SRCS = $(wildcard src/*.c)
OBJS = $(SRCS:.c=.o)
TARGET = prog

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

clean:
	rm -f $(TARGET) $(OBJS)

.PHONY: all clean

Special variables you’ll see everywhere:

Variable

Meaning

$@

Target of the rule (left of :)

$<

First prerequisite (first item right of :)

$^

All prerequisites, deduped, space-separated

$*

Stem of implicit rule (matches %)

$?

Prerequisites newer than target

$(wildcard pattern)

Shell glob

.PHONY:

Targets that are not files (like clean)

GNU make manual, chapter on “Automatic Variables”: https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html. Print this page.

CMake — the industry standard

CMake 3.28+ (mid-2026 stable is 3.31 or later) is what open-source C/C++ projects use. Learn modern CMake — the target-based style, not the pre-3.0 variable-based mess.

Minimal modern CMakeLists.txt:

cmake_minimum_required(VERSION 3.28)
project(myproj LANGUAGES C)

set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

add_executable(prog src/main.c src/util.c)

target_compile_options(prog PRIVATE
  -Wall -Wextra -Wpedantic -O2 -g
  $<$<CONFIG:Debug>:-fsanitize=address,undefined>
)
target_link_options(prog PRIVATE
  $<$<CONFIG:Debug>:-fsanitize=address,undefined>
)

Standard build cycle:

cmake -B build -DCMAKE_BUILD_TYPE=Debug -G Ninja
cmake --build build
./build/prog

Why Ninja over Make as the CMake generator: Ninja is 2-5x faster on large projects, and every C project you’ll join uses it.

Learning resource: https://cliutils.gitlab.io/modern-cmake/ — the community-maintained “An Introduction to Modern CMake.” Free, current, well-organized.

Meson — the lighter alternative

Meson is Python-configured, Ninja-driven. It is what glibc, systemd, GTK, Mesa, and QEMU use in 2026. If you start a new project, Meson is arguably a better default than CMake.

Minimal meson.build:

project('myproj', 'c',
  version : '0.1',
  default_options : ['warning_level=3', 'c_std=c17'])

executable('prog', 'src/main.c', 'src/util.c',
  install : true)

Build:

meson setup build
meson compile -C build
./build/prog

When to pick Meson: new personal projects, especially if you know your target audience uses Linux. CMake is still the default for cross-platform open-source distribution.

pkg-config — the dependency glue

pkg-config is the “how do I link against libfoo?” answer for Linux/Mac C. Every C library ships a .pc file describing its include paths and link flags.

Basic usage:

$ pkg-config --cflags openssl
-I/opt/homebrew/include
$ pkg-config --libs openssl
-L/opt/homebrew/lib -lssl -lcrypto

In Makefile:

CFLAGS += $(shell pkg-config --cflags openssl)
LDLIBS += $(shell pkg-config --libs openssl)

In CMake:

find_package(PkgConfig REQUIRED)
pkg_check_modules(OPENSSL REQUIRED openssl)
target_include_directories(prog PRIVATE ${OPENSSL_INCLUDE_DIRS})
target_link_libraries(prog PRIVATE ${OPENSSL_LIBRARIES})

On macOS: brew install pkg-config. On Ubuntu: apt install pkg-config.

Package managers for C? Mostly no.

Tool

Verdict for C in 2026

Conan

Popular in C++ shops; overkill for pure C, and most C libraries don’t have Conan packages.

vcpkg

Microsoft’s C++ manager. Same story.

apt / dnf / pacman

The right answer for Linux. apt install libssl-dev libcurl4-openssl-dev libpcre2-dev and move on.

Homebrew

The right answer for macOS. Same story.

git submodules / meson subprojects / CMake FetchContent

The right answer for header-only or small deps you want to vendor.

The honest truth: for the vast majority of C projects, apt + pkg-config is the entire package management story. You don’t need Conan.

What most people get wrong about build systems

They learn CMake first, poorly, and then hate CMake. Learn make first (it’s 30 minutes to a working Makefile), then modern CMake, then Meson. Understand each tool’s mental model instead of memorizing incantations from Stack Overflow. Build systems are their own subskill; budget 8 hours across M2-M3 for it and you’ll be set for years.

Return to README.md · Next: 06_profilers_and_tracers.md