Chapter 14: Installing and Packaging《Modern CMake for C++》_Notes

发布于:2025-04-12 ⋅ 阅读:(38) ⋅ 点赞:(0)

Chapter 14: Installing and Packaging

1. Exporting Targets Without Installation

Key Concept: Allow other CMake projects to use your library without installing it system-wide.
Mechanism: Use export() to generate a <Project>Targets.cmake file for dependency resolution.

Code Example:

add_library(MyLib STATIC mylib.cpp)
target_include_directories(MyLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

# Export targets to a file
export(
  TARGETS MyLib
  FILE "${CMAKE_CURRENT_BINARY_DIR}/MyLibTargets.cmake"
  NAMESPACE MyNamespace::
)

This generates MyLibTargets.cmake, allowing other projects to include it via include() and link with MyNamespace::MyLib.


2. Installing Projects System-Wide

Key Concept: Install built artifacts (executables, libraries, headers) to standard system paths.
Critical Commands:

  • install(TARGETS): Install build targets.
  • install(FILES|DIRECTORY): Install headers/docs.

Code Example:

install(
  TARGETS MyLib
  EXPORT MyLibTargets
  ARCHIVE DESTINATION lib        # Static libs (.a/.lib)
  LIBRARY DESTINATION lib        # Shared libs (.so/.dll)
  RUNTIME DESTINATION bin        # Executables/DLLs on Windows
  INCLUDES DESTINATION include
)

install(
  DIRECTORY include/ DESTINATION include
  FILES LICENSE DESTINATION doc
)

# Install exported targets for downstream projects
install(
  EXPORT MyLibTargets
  FILE MyLibConfig.cmake
  DESTINATION lib/cmake/MyLib
)

Platform-Specific Paths:

  • Unix: /usr/local/ (default CMAKE_INSTALL_PREFIX)
  • Windows: C:/Program Files/

3. Creating Reusable Packages

Goal: Generate <Package>Config.cmake for find_package() compatibility.
Tools:

  • configure_package_config_file(): Generates config file with correct paths.
  • write_basic_package_version_file(): Handles version compatibility.

Code Example:

include(CMakePackageConfigHelpers)

configure_package_config_file(
  MyLibConfig.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
  INSTALL_DESTINATION lib/cmake/MyLib
)

write_basic_package_version_file(
  ${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
  VERSION 1.2.3
  COMPATIBILITY SameMajorVersion  # Accepts 1.x.x
)

install(FILES
  ${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
  ${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
  DESTINATION lib/cmake/MyLib
)

Template File MyLibConfig.cmake.in:

@PACKAGE_INIT@  # CMake-provided initialization macro

include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")
check_required_components(MyLib)

4. Component-Based Installation

Use Case: Split installation into logical components (e.g., runtime, development, docs).
Implementation: Add COMPONENT to install() commands.

Code Example:

install(TARGETS MyLib
  EXPORT MyLibTargets
  ARCHIVE DESTINATION lib COMPONENT development
  LIBRARY DESTINATION lib COMPONENT runtime
  INCLUDES DESTINATION include COMPONENT development
)

install(DIRECTORY include/ 
  DESTINATION include 
  COMPONENT development
)

install(FILES LICENSE
  DESTINATION doc 
  COMPONENT docs
)

Component-Specific Packaging (with CPack):

cpack_add_component(runtime DISPLAY_NAME "Runtime")
cpack_add_component(development DISPLAY_NAME "Development")
cpack_add_component(docs DISPLAY_NAME "Documentation")

5. Symbolic Links for Shared Libraries

Unix-Specific: Handle library versioning via symlinks (e.g., libfoo.so.1 → libfoo.so.1.2.3).
CMake Support: Use SOVERSION and VERSION target properties.

Code Example:

set_target_properties(MyLib PROPERTIES
  VERSION 1.2.3     # Full version
  SOVERSION 1       # API compatibility version
)

This generates:

  • libMyLib.so.1.2.3 (real binary)
  • libMyLib.so.1libMyLib.so.1.2.3
  • libMyLib.solibMyLib.so.1

6. Packaging with CPack

Goal: Generate platform-specific packages (DEB, RPM, ZIP, etc.).
Configuration:

set(CPACK_PACKAGE_NAME "MyLib")
set(CPACK_PACKAGE_VERSION "1.2.3")
set(CPACK_PACKAGE_DESCRIPTION "A sample library")

# Component-aware packaging
set(CPACK_DEB_COMPONENT_INSTALL YES)  # For Debian
include(CPack)

Generate Packages:

cmake --build build_dir --target install  # Install artifacts first
cpack -G DEB  # Generate Debian package

Component-Specific Variables:

cpack_add_component(runtime
  DISPLAY_NAME "Runtime"
  DESCRIPTION "Runtime components"
  REQUIRED
)
cpack_add_component(development
  DISPLAY_NAME "Development"
  DEPENDS runtime
)

Critical Takeaways

  1. Exporting enables dependency resolution between projects without system-wide installation.
  2. Installation requires specifying destinations for different target types (executables, libs, headers).
  3. Config Files (<Package>Config.cmake) make your library discoverable via find_package().
  4. Components allow granular control over installation/packaging (e.g., separate dev and runtime files).
  5. CPack automates cross-platform package generation but requires careful configuration for components.

Common Pitfalls

  • Missing Namespaces: Always use namespaces (MyNamespace::) in exported targets to avoid conflicts.
  • Path Hardcoding: Use CMAKE_INSTALL_PREFIX, not absolute paths, for portability.
  • Version Mismatch: Ensure VERSION properties match between targets and ConfigVersion.cmake.
  • Symlink Handling: On Windows, shared libraries don’t use symlinks; focus on VERSION/SOVERSION for Unix.

Multiple Choice Questions


1. Which statements about exporting CMake targets without installation are correct?
A. export(TARGETS ...) writes target properties to a file for external projects.
B. install(TARGETS ... EXPORT) generates a <PackageName>Targets.cmake file.
C. Exported targets automatically resolve transitive dependencies.
D. The EXPORT keyword in install() requires a separate install(EXPORT ...) call.

2. Which variables define default installation paths for public headers on Unix-like systems?
A. CMAKE_INSTALL_INCLUDEDIR
B. CMAKE_INSTALL_PREFIX
C. CMAKE_INSTALL_BINDIR
D. CMAKE_INSTALL_LIBDIR

3. What are valid methods for installing public headers?
A. Use target_include_directories() with the PUBLIC keyword.
B. Use install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}).
C. Use set_target_properties(... PUBLIC_HEADER ...).
D. Use file(COPY include/ DESTINATION ...) followed by install(FILES ...).

4. Which are required steps to create a relocatable package with CMake?
A. Use absolute paths in target_include_directories().
B. Use configure_package_config_file() with @PACKAGE_INIT@.
C. Avoid CMAKE_INSTALL_PREFIX in find_dependency() calls.
D. Generate a <PackageName>ConfigVersion.cmake file.

5. What correctly describes component-based packaging in CMake?
A. Components are defined using cpack_add_component().
B. install(TARGETS ... COMPONENT runtime) assigns targets to a component.
C. find_package(... COMPONENTS runtime) filters installed components.
D. CPACK_DEB_COMPONENT_INSTALL enables component-aware DEB packages.

6. Which CPack generators support component-based packaging?
A. ZIP
B. DEB
C. NSIS
D. RPM

7. How to manage versioned shared library symbolic links during installation?
A. Use install(CODE "execute_process(...)") to create symlinks manually.
B. Use set_target_properties(... VERSION ... SOVERSION ...).
C. Use install(TARGETS ... LIBRARY DESTINATION ... NAMELINK_SKIP).
D. Use CMAKE_INSTALL_SO_NO_EXE to disable symlink creation.

8. Which statements about runtime dependencies installation are correct?
A. install(TARGETS ... RUNTIME_DEPENDENCIES) bundles all DLLs on Windows.
B. CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS specifies additional runtime libraries.
C. BundleUtilities is required for macOS .app bundles.
D. WIX CPack generator automatically resolves runtime dependencies.

9. Which are valid use cases for write_basic_package_version_file()?
A. Enforce package version compatibility using ExactVersion.
B. Generate a <PackageName>ConfigVersion.cmake file.
C. Compare versions using COMPATIBLE_VERSION_INTERNAL.
D. Define custom version-check logic with macros.

10. What ensures correct behavior when using find_package() for a custom package?
A. The package’s Config.cmake file must be in CMAKE_PREFIX_PATH.
B. find_dependency() must be used for transitive dependencies.
C. The VERSION field in project() defines package compatibility.
D. CMAKE_MODULE_PATH must include the package’s CMake/ directory.


Answers & Explanations

  1. A, B, D

    • export(TARGETS) writes targets to a file (A). install(TARGETS ... EXPORT) triggers export file generation (B). The EXPORT keyword requires install(EXPORT) (D). Exported targets do not auto-resolve dependencies (C is false).
  2. A

    • CMAKE_INSTALL_INCLUDEDIR defaults to include/ (A). CMAKE_INSTALL_PREFIX is the root (B). BINDIR and LIBDIR are for binaries/libraries (C/D incorrect).
  3. B, C

    • install(DIRECTORY ...) copies headers (B). PUBLIC_HEADER property installs headers via install(TARGETS) ©. target_include_directories() doesn’t install files (A). file(COPY) lacks integration with install() (D is manual, not idiomatic).
  4. B, C, D

    • configure_package_config_file() handles relative paths (B). Avoid absolute paths ©. Version files ensure compatibility (D). Absolute paths break relocatability (A is wrong).
  5. B, D

    • COMPONENT in install() assigns targets (B). CPACK_DEB_COMPONENT_INSTALL enables DEB components (D). Components are defined via install(... COMPONENT) (A is false). find_package(... COMPONENTS) checks availability (C is correct but not a step in creation).
  6. B, D

    • DEB (B) and RPM (D) support components. ZIP/NSIS (A/C) have limited/no component support.
  7. B, C

    • VERSION/SOVERSION controls symlink names (B). NAMELINK_SKIP skips symlinks ©. Manual symlinks (A) are error-prone. CMAKE_INSTALL_SO_NO_EXE is unrelated (D).
  8. A, C

    • RUNTIME_DEPENDENCIES bundles dependencies (A). BundleUtilities is for macOS ©. CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS is not a standard variable (B). WIX doesn’t auto-resolve (D).
  9. B, D

    • write_basic_package_version_file() generates version files (B). Custom logic requires macros (D). ExactVersion is not a valid argument (A). COMPATIBLE_VERSION_INTERNAL is internal ©.
  10. A, B

    • Config.cmake must be in CMAKE_PREFIX_PATH (A). find_dependency() handles transitive deps (B). VERSION in project() doesn’t enforce compatibility ©. CMAKE_MODULE_PATH is for Find*.cmake, not Config.cmake (D).

Build Challenges

Medium Question 1: Basic Installation and Exporting Targets

Scenario:
You’re developing a C++ library MathLib with the following structure:

MathLib/
├── include/MathLib.h
├── src/Calculator.cpp
└── apps/Main.cpp

Create CMake installation rules to:

  1. Install the shared library to standard system locations
  2. Install public headers to include/MathLib
  3. Install the executable to bin
  4. Export targets for find_package() usage
  5. Generate both MathLibConfig.cmake and MathLibConfigVersion.cmake

Answer:

include(GNUInstallDirs)

# Install targets
install(TARGETS MathLib
  ARCHIVE  DESTINATION ${CMAKE_INSTALL_LIBDIR}
  LIBRARY  DESTINATION ${CMAKE_INSTALL_LIBDIR}
  RUNTIME  DESTINATION ${CMAKE_INSTALL_BINDIR}
  INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/MathLib
)

install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/MathLib)
install(TARGETS MathApp DESTINATION ${CMAKE_INSTALL_BINDIR})

# Export targets
install(EXPORT MathLibTargets
  FILE MathLibTargets.cmake
  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathLib
  NAMESPACE MathLib::
)

# Config files
include(CMakePackageConfigHelpers)
configure_package_config_file(
  MathLibConfig.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/MathLibConfig.cmake
  INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathLib
)

write_basic_package_version_file(
  MathLibConfigVersion.cmake
  VERSION ${PROJECT_VERSION}
  COMPATIBILITY SameMajorVersion
)

install(FILES
  ${CMAKE_CURRENT_BINARY_DIR}/MathLibConfig.cmake
  ${CMAKE_CURRENT_BINARY_DIR}/MathLibConfigVersion.cmake
  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathLib
)

Explanation:

  1. GNUInstallDirs ensures cross-platform compatible install paths
  2. ARCHIVE/LIBRARY/RUNTIME destinations handle different target types
  3. INCLUDES propagates include directories to dependent targets
  4. install(EXPORT) creates target export files with proper namespacing
  5. configure_package_config_file generates config file with relocatable paths
  6. write_basic_package_version_file handles version compatibility checks
  7. Final install rules place all config files in standard CMake package location

Medium Question 2: CPack Configuration for DEB Packages

Scenario:
Configure CPack to generate DEB packages with:

  1. Package name mathlib-<version>.deb
  2. Maintainer info “Dev Team dev@mathlib.org
  3. Dependency on libboost-all-dev (>= 1.65)
  4. Systemd service file installation
  5. Post-install script that runs ldconfig

Answer:

set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Dev Team <dev@mathlib.org>")
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-all-dev (>= 1.65)")

install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mathlib.service
  DESTINATION /lib/systemd/system
)

set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
  "${CMAKE_CURRENT_BINARY_DIR}/postinst"
)

# Create postinst script
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/postinst CONTENT "#!/bin/sh\nldconfig\n")

include(CPack)

Explanation:

  1. CPACK_GENERATOR selects DEB package format
  2. CPACK_DEBIAN_FILE_NAME uses default naming convention
  3. CPACK_DEBIAN_PACKAGE_DEPENDS specifies runtime dependencies
  4. Systemd service file installed to standard location
  5. CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA adds post-install script
  6. file(GENERATE) creates required ldconfig script
  7. Final include(CPack) activates packaging support

Hard Question: Component-Based Installation with Versioned Libraries

Scenario:
Create an installation with:

  1. Separate runtime and development components
  2. Versioned shared library with symlinks:
    • libmath.so.1.2.3 (real)
    • libmath.so.1 (major version)
    • libmath.so (latest)
  3. Generate MathLibConfigVersion.cmake that:
    • Rejects versions < 2.0.0
    • Compatible with same major version
  4. Component requirements:
    • development requires runtime
    • docs optional component

Answer:

# Versioned library setup
set_target_properties(MathLib PROPERTIES
  VERSION 1.2.3
  SOVERSION 1
  OUTPUT_NAME "math"
)

# Component definitions
cpack_add_component(runtime DISPLAY_NAME "Runtime")
cpack_add_component(development
  DISPLAY_NAME "Development"
  DEPENDS runtime
)
cpack_add_component(docs
  DISPLAY_NAME "Documentation"
  DISABLED
)

# Installation rules
install(TARGETS MathLib
  EXPORT MathLibTargets
  RUNTIME DESTINATION bin COMPONENT runtime
  LIBRARY DESTINATION lib COMPONENT runtime
  ARCHIVE DESTINATION lib COMPONENT development
  INCLUDES DESTINATION include/MathLib
)

install(EXPORT MathLibTargets
  FILE MathLibTargets.cmake
  DESTINATION lib/cmake/MathLib
  COMPONENT development
  NAMESPACE MathLib::
)

# Version config
write_basic_package_version_file(
  MathLibConfigVersion.cmake
  VERSION 2.0.0
  COMPATIBILITY SameMajorVersion
)

# Compatibility check
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/MathLibConfig.cmake.in
  "if(NOT MathLib_FIND_VERSION VERSION_GREATER_EQUAL 2.0.0)\n"
  "  message(FATAL_ERROR \"Requires at least version 2.0.0\")\n"
  "endif()\n"
  "include(\${CMAKE_CURRENT_LIST_DIR}/MathLibTargets.cmake)"
)

configure_package_config_file(
  MathLibConfig.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/MathLibConfig.cmake
  INSTALL_DESTINATION lib/cmake/MathLib
)

include(CPack)

Explanation:

  1. VERSION/SOVERSION creates versioned library files and symlinks
  2. cpack_add_component defines component hierarchy and dependencies
  3. Split installation using COMPONENT arguments
  4. write_basic_package_version_file enforces major version compatibility
  5. Custom config file adds minimum version check
  6. Library symlinks are automatically managed via SOVERSION property
  7. Documentation component is disabled by default
  8. Development component includes archive (static lib) and CMake configs
  9. Runtime component contains shared library and runtime dependencies

Key Concepts Reinforcement:

  1. Component-Based Installation: Separates logical parts of the package using CPack components
  2. Versioned Libraries: Uses target properties to manage shared library versioning and symlinks
  3. Config Version Validation: Ensures compatibility through version checks in config files
  4. Cross-Component Dependencies: Manages inter-component requirements via DEPENDS
  5. Package Configuration: Combines target exports with custom validation logic
  6. Symlink Management: Automatic handling through CMake target properties rather than manual commands
  7. Conditional Installation: Disabled components (like docs) can be enabled during package creation

网站公告

今日签到

点亮在社区的每一天
去签到