Skip to content

C++ Build Systems

Overview

Build systems are essential tools in C++ development for automating compilation, linking, testing, and deployment processes. This chapter introduces mainstream C++ build systems, including CMake, Make, Ninja, and modern build configurations and best practices.

🔨 CMake Basics

Basic CMakeLists.txt

cmake
# Minimum CMake version requirement
cmake_minimum_required(VERSION 3.16)

# Project name and version
project(MyProject VERSION 1.0.0 LANGUAGES CXX)

# C++ standard settings
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Compilation options
if(MSVC)
    add_compile_options(/W4)
else()
    add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# Create executable
add_executable(${PROJECT_NAME} 
    src/main.cpp
    src/utils.cpp
)

# Include directories
target_include_directories(${PROJECT_NAME} PRIVATE 
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

# Installation rules
install(TARGETS ${PROJECT_NAME}
    DESTINATION bin
)

Library Creation and Usage

cmake
# Create static library
add_library(MyLibrary STATIC
    src/library.cpp
    src/helper.cpp
)

# Library include directories
target_include_directories(MyLibrary PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

# Create shared library
add_library(MySharedLib SHARED
    src/shared.cpp
)

# Set library version
set_target_properties(MySharedLib PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION ${PROJECT_VERSION_MAJOR}
)

# Executable links library
add_executable(MyApp src/main.cpp)
target_link_libraries(MyApp PRIVATE MyLibrary)

# Conditional compilation
option(BUILD_TESTS "Build test programs" ON)
option(BUILD_EXAMPLES "Build example programs" OFF)

if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

if(BUILD_EXAMPLES)
    add_subdirectory(examples)
endif()

Finding and Using Third-Party Libraries

cmake
# Find system packages
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)

# Modern CMake target linking
target_link_libraries(MyApp PRIVATE 
    Threads::Threads
    OpenSSL::SSL
    OpenSSL::Crypto
)

# pkg-config support
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)

target_link_libraries(MyApp PRIVATE ${GTK3_LIBRARIES})
target_include_directories(MyApp PRIVATE ${GTK3_INCLUDE_DIRS})
target_compile_options(MyApp PRIVATE ${GTK3_CFLAGS_OTHER})

# Custom find modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(MyCustomLib REQUIRED)

# FetchContent to download dependencies
include(FetchContent)

FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG release-1.12.1
)

FetchContent_MakeAvailable(googletest)

# Use downloaded library
target_link_libraries(MyTests PRIVATE gtest_main)

🔧 Advanced CMake Techniques

Target Properties and Generator Expressions

cmake
# Set target properties
set_target_properties(MyLibrary PROPERTIES
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON
    POSITION_INDEPENDENT_CODE ON
    OUTPUT_NAME "mylib"
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)

# Generator expressions
target_compile_definitions(MyApp PRIVATE
    $<$<CONFIG:Debug>:DEBUG_BUILD>
    $<$<CONFIG:Release>:NDEBUG>
    $<$<PLATFORM_ID:Windows>:WINDOWS_BUILD>
    $<$<PLATFORM_ID:Linux>:LINUX_BUILD>
)

# Conditional compilation options
target_compile_options(MyApp PRIVATE
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
    $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
    $<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra>
)

# Interface property propagation
target_compile_features(MyLibrary PUBLIC cxx_std_17)
target_compile_definitions(MyLibrary PUBLIC 
    MYLIB_VERSION="${PROJECT_VERSION}"
)

# Installation and export
install(TARGETS MyLibrary
    EXPORT MyLibraryTargets
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
    INCLUDES DESTINATION include
)

install(DIRECTORY include/ DESTINATION include)

install(EXPORT MyLibraryTargets
    FILE MyLibraryTargets.cmake
    NAMESPACE MyLibrary::
    DESTINATION lib/cmake/MyLibrary
)

Configuration File Generation

cmake
# Generate configuration header file
configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/config.h.in"
    "${CMAKE_CURRENT_BINARY_DIR}/config.h"
)

# config.h.in content:
/*
#ifndef CONFIG_H
#define CONFIG_H

#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define PROJECT_VERSION "@PROJECT_VERSION@"

#cmakedefine HAVE_FEATURE_X
#cmakedefine01 ENABLE_LOGGING

#endif // CONFIG_H
*/

# Package configuration files
include(CMakePackageConfigHelpers)

configure_package_config_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/MyLibraryConfig.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
    INSTALL_DESTINATION lib/cmake/MyLibrary
)

write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
)

install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
    "${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
    DESTINATION lib/cmake/MyLibrary
)

🚀 Make System

Makefile Basics

makefile
# Variable definitions
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -O2
LDFLAGS = -pthread
TARGET = myapp
SRCDIR = src
OBJDIR = obj
SOURCES = $(wildcard $(SRCDIR)/*.cpp)
OBJECTS = $(SOURCES:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o)

# Default target
all: $(TARGET)

# Link target
$(TARGET): $(OBJECTS)
	$(CXX) $(OBJECTS) -o $@ $(LDFLAGS)

# Compilation rule
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp | $(OBJDIR)
	$(CXX) $(CXXFLAGS) -c $< -o $@

# Create directory
$(OBJDIR):
	mkdir -p $(OBJDIR)

# Clean
clean:
	rm -rf $(OBJDIR) $(TARGET)

# Install
install: $(TARGET)
	install -d $(DESTDIR)/usr/local/bin
	install -m 755 $(TARGET) $(DESTDIR)/usr/local/bin

# Phony targets
.PHONY: all clean install

# Dependencies
$(OBJDIR)/main.o: $(SRCDIR)/main.cpp $(SRCDIR)/utils.h
$(OBJDIR)/utils.o: $(SRCDIR)/utils.cpp $(SRCDIR)/utils.h

Advanced Makefile Techniques

makefile
# Automatic dependency generation
DEPDIR = .deps
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp $(DEPDIR)/%.d | $(DEPDIR) $(OBJDIR)
	$(CXX) $(DEPFLAGS) $(CXXFLAGS) -c $< -o $@

$(DEPDIR): ; @mkdir -p $@

DEPFILES := $(SOURCES:$(SRCDIR)/%.cpp=$(DEPDIR)/%.d)
$(DEPFILES):

include $(wildcard $(DEPFILES))

# Multi-configuration support
BUILD_TYPE ?= Release

ifeq ($(BUILD_TYPE),Debug)
    CXXFLAGS += -g -DDEBUG
else ifeq ($(BUILD_TYPE),Release)
    CXXFLAGS += -O3 -DNDEBUG
endif

# Platform detection
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
    LDFLAGS += -ldl
endif
ifeq ($(UNAME_S),Darwin)
    LDFLAGS += -framework CoreFoundation
endif

# Colored output
RED = \033[0;31m
GREEN = \033[0;32m
YELLOW = \033[0;33m
NC = \033[0m # No Color

$(TARGET): $(OBJECTS)
	@echo "$(GREEN)Linking $(TARGET)...$(NC)"
	$(CXX) $(OBJECTS) -o $@ $(LDFLAGS)
	@echo "$(GREEN)Build successful!$(NC)"

⚡ Ninja Build System

Ninja Configuration

ninja
# build.ninja
# Variable definitions
cxx = g++
cxxflags = -std=c++17 -Wall -Wextra -O2
ldflags = -pthread

# Rule definitions
rule cxx
  command = $cxx $cxxflags -MMD -MF $out.d -c $in -o $out
  description = Compiling $out
  depfile = $out.d
  deps = gcc

rule link
  command = $cxx $in -o $out $ldflags
  description = Linking $out

# Build targets
build obj/main.o: cxx src/main.cpp
build obj/utils.o: cxx src/utils.cpp

build myapp: link obj/main.o obj/utils.o

# Default target
default myapp

# Clean
rule clean
  command = rm -rf obj myapp
  description = Cleaning

build clean: clean

Integration with CMake

cmake
# Using Ninja generator
# cmake -G Ninja ..
# ninja

# Or in CMakeLists.txt
if(CMAKE_GENERATOR STREQUAL "Ninja")
    # Ninja-specific configuration
    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
endif()

🔧 Build System Integration

Continuous Integration Configuration

yaml
# .github/workflows/build.yml
name: Build

on: [push, pull_request]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        build_type: [Debug, Release]
        compiler: [gcc, clang]
        exclude:
          - os: windows-latest
            compiler: gcc

    steps:
    - uses: actions/checkout@v3

    - name: Install dependencies (Ubuntu)
      if: matrix.os == 'ubuntu-latest'
      run: |
        sudo apt-get update
        sudo apt-get install -y cmake ninja-build

    - name: Install dependencies (macOS)
      if: matrix.os == 'macos-latest'
      run: |
        brew install cmake ninja

    - name: Configure CMake
      run: |
        cmake -B build -G Ninja \
          -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
          -DCMAKE_CXX_COMPILER=${{ matrix.compiler == 'clang' && 'clang++' || 'g++' }}

    - name: Build
      run: cmake --build build --config ${{ matrix.build_type }}

    - name: Test
      working-directory: build
      run: ctest --output-on-failure

Docker Build Environment

dockerfile
# Dockerfile.build
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    cmake \
    ninja-build \
    g++ \
    clang \
    git \
    pkg-config \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /workspace
COPY . .

RUN cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
RUN cmake --build build

CMD ["./build/myapp"]

📦 Package Management Integration

vcpkg Integration

cmake
# Using vcpkg
find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)

target_link_libraries(MyApp PRIVATE 
    fmt::fmt
    spdlog::spdlog
    nlohmann_json::nlohmann_json
)

Conan Integration

cmake
# conanfile.txt
[requires]
boost/1.82.0
openssl/1.1.1
gtest/1.14.0

[generators]
CMakeDeps
CMakeToolchain

# CMakeLists.txt
find_package(Boost REQUIRED COMPONENTS system filesystem)
find_package(OpenSSL REQUIRED)
find_package(GTest REQUIRED)

target_link_libraries(MyApp PRIVATE 
    Boost::system 
    Boost::filesystem
    OpenSSL::SSL
    GTest::gtest_main
)

🛠️ Build Optimization

Compilation Cache

cmake
# ccache support
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
    set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}")
endif()

# Precompiled headers
target_precompile_headers(MyApp PRIVATE
    <iostream>
    <vector>
    <string>
    <memory>
)

# Unity build
set_property(GLOBAL PROPERTY CMAKE_UNITY_BUILD ON)
set_property(TARGET MyApp PROPERTY UNITY_BUILD ON)

Parallel Build

bash
# Make parallel build
make -j$(nproc)

# CMake parallel build
cmake --build build --parallel $(nproc)

# Ninja auto parallel
ninja -C build

Summary

Build System Comparison

FeatureCMakeMakeNinja
Learning CurveMediumSteepSimple
Cross-PlatformExcellentLimitedGood
PerformanceGoodMediumExcellent
EcosystemRichTraditionalEmerging

Best Practices

  • Modern CMake: Use targets and properties, avoid global variables
  • Version Control: Set minimum version requirements
  • Modularization: Use subdirectories and modules to organize code
  • Dependency Management: Prefer package managers
  • CI/CD Integration: Automate builds and tests

Selection Recommendations

  • New Projects: Recommend CMake + Ninja
  • Legacy Projects: Can keep Make, migrate gradually
  • Large Projects: CMake + Package Manager
  • Cross-Platform: CMake is mandatory

Toolchain

  • Build Generation: CMake, GN, Bazel
  • Build Execution: Make, Ninja, MSBuild
  • Package Management: vcpkg, Conan, Hunter
  • Cache Acceleration: ccache, sccache

Build systems are key infrastructure for C++ project success. Choosing the right tools and configuring them properly can significantly improve development efficiency.

Content is for learning and research only.