41. Cross-compilation and build systems at scale (CMake/Meson)
41.1 Cross-compiling — building for a target that isn't your machine
# Building for a Raspberry Pi (ARM) from an x86_64 or Apple Silicon dev machine:
aarch64-linux-gnu-gcc -std=c17 -o app app.c
file app # ELF 64-bit LSB executable, ARM aarch64 — even though you're not on ARM
This works because a "compiler" is usefully thought of as three separable pieces — a front end (parses C), a code generator (targets a specific instruction set), and system headers/libraries for the target OS — and a cross-compiler points the last two at the target instead of the host. This is the basic shape behind embedded-Linux, Android NDK, and firmware builds: developers often build on powerful workstations and produce binaries for ARM/RISC-V devices that may be too small or inconvenient to run the compiler itself.
41.2 CMake — the de facto standard for C/C++ projects beyond one Makefile
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(myapp C)
set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_compile_options(-Wall -Wextra -Wpedantic -Werror)
add_executable(myapp src/main.c src/util.c)
target_include_directories(myapp PRIVATE include)
enable_testing()
add_executable(myapp_tests tests/test_util.c src/util.c)
add_test(NAME UtilTests COMMAND myapp_tests)
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build
CMake's job is to generate the actual build files (Makefiles, Ninja
files, Xcode/Visual Studio projects) from one portable description.
Many large C projects use CMake, Autotools, Meson, or similar build
systems instead of one hand-written Makefile, because cross-platform
builds quickly need compiler, OS, dependency, and feature-detection logic.