본문으로 건너뛰기
Previous
Next
Advanced CMake for C++: Multi-Target Projects, External

Advanced CMake for C++: Multi-Target Projects, External

Advanced CMake for C++: Multi-Target Projects, External

이 글의 핵심

Advanced CMake: multi-target layouts, target_link_libraries, FetchContent, find_package, generator expressions, install() and Config.cmake, cross-platform patterns, and production checklists for large C++ codebases.

Introduction: builds get complex as projects grow

“Every time I add a library, the build breaks”

Projects with several libraries and executables often suffer tangled build settings when new code is added.
Requirements: CMake 3.10+, g++/Clang or MSVC. If you know the basics from #4 CMake intro, this post extends the same toolchain with multi-target workflows, options, and package integration. Use add_library for shared code and target_link_libraries for dependencies. Splitting by target clarifies build order, include paths, and platform options—and later integrates cleanly with vcpkg or Conan.

Pain scenarios from real CMake usage

Scenario 1: “Cannot find header”

fatal error: mylib.h: No such file or directory

Cause: target_include_directories only PRIVATE, or wrong link order—library A uses B but B’s include path does not propagate to A.

Scenario 2: “undefined reference”

Cause: Missing target_link_libraries, STATIC/SHARED confusion, or add_subdirectory order so a target is used before it exists.

Scenario 3: Windows-only failures

Cause: Missing platform libs (ws2_32, …), path separators, or DLL export macros.

Scenario 4: External library version skew

Cause: System find_package picking mixed Boost versions—pin versions with FetchContent.

Scenario 5: Slow builds

Cause: Duplicate compilation, no PCH, no parallel -j.

Scenario 6: Generated code not built

Cause: .proto or codegen not wired with add_custom_command—missing .pb.cc at link time.

Scenario 7: find_package after install

Cause: Missing Config.cmake / Version.cmake or CMAKE_PREFIX_PATH.

Scenario 8: CI fetch timeouts

Cause: FetchContent cloning full history—use GIT_SHALLOW TRUE.

Problem CMakeLists (anti-pattern)

Listing the same util.cpp, db.cpp in every executable duplicates work and rebuilds everything on any change.

# Bad: everything in one place
// 실행 예제
add_executable(app1 main1.cpp util.cpp db.cpp)
add_executable(app2 main2.cpp util.cpp db.cpp)
add_executable(app3 main3.cpp util.cpp db.cpp)

Better: add_library(util STATIC util.cpp), add_library(db STATIC db.cpp), then each app links util and db once.

add_library(util STATIC util.cpp)
add_library(db STATIC db.cpp)
add_executable(app1 main1.cpp)
target_link_libraries(app1 PRIVATE util db)

Project dependency architecture

flowchart TB
    subgraph apps[Executables]
        app1[app1]
        app2[app2]
        app3[app3]
    end
    subgraph libs[Libraries]
        util[util]
        db[db]
    end
    subgraph external[External]
        boost[Boost]
        json[nlohmann_json]
    end
    app1 --> util
    app1 --> db
    app2 --> util
    app2 --> db
    app3 --> util
    app3 --> db
    db --> boost
    app1 --> json

Table of contents

  1. Project structure
  2. Targets and libraries
  3. Target properties
  4. External libraries
  5. Build options
  6. Common errors
  7. Practical patterns
  8. Best practices Advanced topics: FetchContent, ExternalProject, add_custom_command, generator expressions, install + Config.cmake, protobuf integration.

1. Project structure

Use a root CMakeLists.txt for global settings (C++ standard, build type) and add_subdirectory for src, lib, tests, external.

myproject/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   └── ...
├── lib/
│   └── mylib/
├── tests/
└── external/

Root CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()
add_subdirectory(lib)
add_subdirectory(src)
add_subdirectory(tests)

2. Targets and libraries

  • STATIC: .a / .lib linked into the executable at link time.
  • SHARED: .so / .dll loaded at runtime.
  • INTERFACE: header-only—expose includes with target_include_directories(mylib INTERFACE include/).

PUBLIC vs PRIVATE vs INTERFACE

PUBLIC on target_link_libraries(B PUBLIC A) means consumers of B also need A. PRIVATE keeps A internal to B.

target_link_libraries(B PUBLIC A)
target_link_libraries(C PRIVATE B)
# C gets A automatically through B’s PUBLIC edge.

3. Target properties

Use target_include_directories, target_compile_options, target_link_libraries with PUBLIC/PRIVATE/INTERFACE as appropriate.

Generator expressions

$<CONFIG:Debug>, $<BUILD_INTERFACE:...>, $<INSTALL_INTERFACE:...> apply values at generate time, not configure time.

target_compile_definitions(mylib PRIVATE
    $<$<CONFIG:Debug>:DEBUG_MODE>
)
target_include_directories(mylib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

4. External libraries

find_package

find_package(Boost REQUIRED COMPONENTS system filesystem)
target_link_libraries(myapp PRIVATE Boost::system Boost::filesystem)

FetchContent

include(FetchContent)
FetchContent_Declare(
    json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG v3.11.2
)
FetchContent_MakeAvailable(json)
target_link_libraries(myapp PRIVATE nlohmann_json::nlohmann_json)

find_package first, else FetchContent

Try system/vcpkg package; fall back to git tag for reproducible builds.

ExternalProject

Builds in the build phase (not configure)—use for non-CMake deps or install-then-find workflows.

add_custom_command / protobuf

Wire generated sources as outputs and add them to an add_library so CMake knows when to run the generator.

5. Build options

option(BUILD_TESTS "Build tests" ON)
if(BUILD_TESTS)
    add_subdirectory(tests)
endif()

CMAKE_BUILD_TYPE: Debug (-g -O0), Release (-O3 -DNDEBUG), RelWithDebInfo. Platform blocks: if(WIN32), elseif(APPLE), elseif(UNIX) for macros and extra libraries (ws2_32, pthread).

6. Common errors (summary)

IssueFix
Missing headersPUBLIC target_include_directories + correct usage
Undefined referenceList all libs in target_link_libraries; fix subdirectory order
find_package failsCMAKE_PREFIX_PATH, vcpkg toolchain, Conan
Duplicate symbolsinline / single definition in .cpp
DLL not foundAlign RUNTIME_OUTPUT_DIRECTORY with DLL location
Generator expr typoUse $<OR:$<...>,$<...>> not commas inside one $<CXX_COMPILER_ID:...> incorrectly
ExternalProject rebuildsSet BUILD_BYPRODUCTS
Custom command not runAttach OUTPUT to a target’s sources or a custom target dependency

7. Practical patterns

  • BUILD_INTERFACE / INSTALL_INTERFACE for reusable libraries.
  • CTest + FetchContent googletest for tests.
  • install(TARGETS …EXPORT) + install(EXPORT) for find_package consumers.
  • write_basic_package_version_file + configure_package_config_file for Config.cmake.
  • target_precompile_headers (CMake 3.16+) to speed builds.

8. Best practices

  • Modularize with libraries; minimize duplicate sources.
  • Mark PUBLIC vs PRIVATE deliberately.
  • Pin dependency versions (FetchContent GIT_TAG).
  • Platform-specific code behind if(WIN32) etc.
  • Options for tests, examples, sanitizers.
  • Define install rules for redistribution.

Build flow (sequence)

sequenceDiagram
    participant Dev as Developer
    participant CMake as CMake configure
    participant Fetch as FetchContent
    participant Build as Build system
    Dev->>CMake: cmake -B build
    CMake->>Fetch: Download external deps
    Fetch->>CMake: Targets created
    CMake->>CMake: Dependency graph
    CMake->>Build: Generate Ninja/Makefile
    Dev->>Build: cmake --build build
    Build->>Build: lib (mylib)
    Build->>Build: src (myapp)
    Build->>Build: tests (optional)

  • Package managers #17-2
  • CMake intro #4
  • Environment setup #1

Summary

TaskCommand
Libraryadd_library
Executableadd_executable
Linktarget_link_libraries
Includestarget_include_directories
Externalfind_package, FetchContent
Optionsoption
Testsenable_testing, add_test
Next: Package managers (#17-2)
Previous: Logging and assertions (#16-3)

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Advanced CMake: multi-target layouts, target_link_libraries, FetchContent, find_package, generator expressions, install(… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • VS Code C++ 설정 | IntelliSense·빌드·디버깅
  • CMake 입문 | 수십 개 파일 컴파일할 때 필요한 빌드 자동화 (CMakeLists.txt 기초)
  • C++ 패키지 매니저 | vcpkg·Conan으로 ‘라이브러리 설치 지옥’ 탈출하기

이 글에서 다루는 키워드 (관련 검색어)

C++, CMake, Build system, Project layout, Cross-platform, FetchContent 등으로 검색하시면 이 글이 도움이 됩니다.