Building C and C++ Projects with CMake
Building C and C++ Projects with CMake
CMake projects should not grow a hand-written cmake -B build in buildPhase. The boring default is: cmake in nativeBuildInputs, optional ninja, flags in cmakeFlags, and stdenv’s configure/build/install hooks.
Mental model
Putting cmake on nativeBuildInputs enables the CMake setup hook:
cmake -B build -DCMAKE_INSTALL_PREFIX=$out [+ cmakeFlags]
cmake --build build
cmake --install build
nativeBuildInputs = tools that run on the build machine (cmake, ninja, pkg-config). buildInputs = libraries linked into the binary (zlib, openssl). Mixing them up is how cross breaks.
stdenv.mkDerivation {
nativeBuildInputs = [ cmake ninja ];
buildInputs = [ zlib ];
cmakeFlags = [ "-DENABLE_DESK=ON" ];
}
Worked examples
Case 1: Tiny CMake project
Save as cmake_project.nix:
# cmake_project.nix
{ lib, stdenv, cmake }:
stdenv.mkDerivation {
pname = "desk-math-engine";
version = "1.0.0";
src = ./.;
nativeBuildInputs = [ cmake ];
preConfigure = ''
cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.20)
project(DeskMath C)
add_executable(desk-calc main.c)
install(TARGETS desk-calc DESTINATION bin)
EOF
cat > main.c << 'EOF'
#include <stdio.h>
int main(void) {
puts("Desk Math Engine: 42");
return 0;
}
EOF
'';
meta = {
description = "Desk calculation engine";
license = lib.licenses.mit;
};
}# default.nix
{ pkgs ? import <nixpkgs> { } }:
pkgs.callPackage ./cmake_project.nix { }nix-build default.nix
./result/bin/desk-calcOutput:
Desk Math Engine: 42
Move CMakeLists.txt / main.c into git and delete preConfigure for a real tree.
Case 2: cmakeFlags
cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Release"
"-DBUILD_TESTING=OFF"
];Do not append these onto a custom buildPhase. The hook reads cmakeFlags.
Case 3: Ninja
nativeBuildInputs = [ cmake ninja ];The hook prefers Ninja when ninja is on nativeBuildInputs. Faster incremental compiles in the sandbox still start from zero each derivation — Ninja mainly helps huge trees and nicer logs.
Case 4: Link zlib
# cmake_zlib.nix
{ stdenv, cmake, zlib }:
stdenv.mkDerivation {
pname = "desk-z";
version = "1.0";
src = ./.;
nativeBuildInputs = [ cmake ];
buildInputs = [ zlib ];
}CMake’s find_package(ZLIB) sees pkg-config / cmake files from the zlib derivation. You did not set ZLIB_ROOT by hand.
Add pkg-config when the project uses it:
nativeBuildInputs = [ cmake pkg-config ];
buildInputs = [ zlib openssl ];CMAKE_PREFIX_PATH is already set by the setup hook. strictDeps = true; so a buildInputs = [ cmake ]; mistake fails instead of linking host cmake into a cross build.
Case 5: Tests
doCheck = true;
# CMake: enable tests in cmakeFlags if needed
# cmakeFlags = [ "-DBUILD_TESTING=ON" ];Hermetic tests only. A test that curls the network fails the sandbox. doCheck = false; with a comment when upstream tests are broken on Nix — then a passthru.tests later.
The trap
The trap is buildPhase = "cmake -B build && cmake --build build";. You skip CMAKE_INSTALL_PREFIX=$out and the stdenv flags. Use cmakeFlags and the hook.
The other trap is buildInputs = [ cmake ];. cmake then looks like a runtime library. nativeBuildInputs.
The boring rule
cmake(andninja) innativeBuildInputs.- Linked libs in
buildInputs.pkg-configis native.strictDepson new CMake packages. - Options via
cmakeFlags, not a custom cmake command. - Real
CMakeLists.txtin git;preConfigureonly for the listing in this book. doCheckwhen tests do not need the network.
Try this
- Case 2: add
-DCMAKE_BUILD_TYPE=Release;nix logand grepCMAKE_BUILD_TYPE. - Move cmake to
buildInputs;nix-build; note warnings or cross pain; put it back. - Add
zlibandfind_package(ZLIB)in a labCMakeLists.txt. nix path-info -Shr result— cmake should not appear in the runtime closure.