How CMake builds C and C++ projects with Ninja and Conan or vcpkg, which files it needs, how to lint, scan, test and cover, and a complete GitHub Actions pipeline.
Posted
DevOps pipeline loop motif with the title C++ Build with CMake
C and C++ have no official build tool or package manager, but CMake has become the de facto standard: it generates native build files (Ninja, Make, Visual Studio) from one portable description, and integrates with Conan or vcpkg for dependencies. Most modern C++ projects, and nearly every IDE, expect a CMakeLists.txt.
In CI, each stage of the generic build pipeline maps to a CMake command, plugin or scanner. Press play, or step through it:
Source + CMakeLists.txt: A commit triggers CI. CMakeLists.txt and CMakePresets.json define the build; conanfile.txt or vcpkg.json declare the dependencies.
Dependencies: conan install . --build=missing fetches or builds the dependencies (vcpkg manifest mode is the alternative); conan.lock pins versions.
Configure: cmake --preset release generates the Ninja build files and compile_commands.json for the analyzers.
Build: cmake --build --preset release compiles with Ninja; ccache or sccache avoid recompiling unchanged files.
Test and coverage: ctest runs the GoogleTest or Catch2 tests; a coverage preset plus gcovr --fail-under-line 80 enforces coverage.
Security scans in parallel: clang-tidy and cppcheck check our code, Trivy or Grype scan the Conan / vcpkg manifests (Black Duck adds snippet matching), and Gitleaks scans for secrets.
Quality gate: The build, tests, coverage gate, analyzers and a sanitizer build must pass before packaging.
Fail the build (fail): A failed check stops the pipeline at the gate, so nothing is packaged, signed or published.
Package (pass): cpack builds the release package (.tar.gz, .deb, .rpm or .zip) from the install rules.
SBOM and signing (pass): Syft generates the SBOM from the package, and Sigstore cosign signs it.
Store the package (pass): CI keeps the package with actions/upload-artifact; libraries can also be published to a Conan remote.
Configure reads CMakeLists.txt, detects the compiler, finds packages and writes native build files into a separate build folder (out-of-source).
Build runs Ninja, which compiles only what changed.
compile_commands.json (from CMAKE_EXPORT_COMPILE_COMMANDS) is what clang-tidy, cppcheck and IDEs use to know the exact compiler flags.
Presets (CMakePresets.json) put generator, build type, toolchain and cache variables in a file, so CI and developers run the same configuration with cmake --preset release.
Build files
File
Purpose
Commit?
CMakeLists.txt
Targets, sources, dependencies, tests (one per directory)
Build out of source (-B build) and never commit the build tree.
Use target-based CMake (target_link_libraries, target_compile_options) instead of global include_directories / add_definitions.
Put configuration in presets so CI and developers share one source of truth.
Pin dependencies: Conan lockfiles, a vcpkg builtin-baseline, or FetchContent with a fixed tag or URL hash.
Treat warnings as errors and run a separate sanitizer job (ASan/UBSan) in CI.
Cache compiler output with ccache or sccache; it is the biggest CI speed-up for C++.
Plan SCA explicitly: manifest scanners only see what Conan/vcpkg declare, so vendored code needs snippet-level scanning (Black Duck) or a maintained inventory.