Keywords: CMake | Shared Libraries | C++ Build | Library Installation | pkg-config
Abstract: This article provides a comprehensive guide on using CMake to build and install C++ shared libraries. It covers CMakeLists.txt configuration, shared library target creation, version management, header file installation, and pkg-config file generation. Through step-by-step examples and in-depth analysis, it helps developers migrate from traditional Makefiles to modern CMake build systems for standardized library distribution and dependency management.
Core Concepts of CMake Shared Library Building
In modern C++ development, CMake has become the standard choice for cross-platform build systems. Compared to traditional Makefiles, CMake provides higher-level abstractions and better maintainability. Building shared libraries is a common use case for CMake, but many developers encounter various configuration issues during implementation.
Basic CMakeLists.txt Configuration
First, specify the minimum required CMake version to ensure build script compatibility. The cmake_minimum_required command explicitly defines the minimum version requirement:
cmake_minimum_required(VERSION 3.9)
Next, declare project information. CMake requires using the project command to define the project. This command automatically creates useful variables such as PROJECT_NAME, PROJECT_VERSION, and PROJECT_DESCRIPTION:
project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")
Creating Shared Library Targets
When creating shared library targets with the add_library command, explicitly list all source files. Avoid using the file(GLOB ...) feature as it doesn't provide precise control over the compilation process:
add_library(mylib SHARED
src/animation.cpp
src/buffers.cpp
src/vertex.cpp
src/world.cpp
)
While this explicit listing approach requires more maintenance, it ensures build system reliability and predictability.
Version Management Best Practices
Setting version properties for shared libraries is good development practice. CMake allows setting both VERSION and SOVERSION properties:
set_target_properties(mylib PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
This configuration generates library files like libmylib.so.1.0.0 and creates symbolic links libmylib.so.1 pointing to the major version. This version management approach follows Linux shared library naming conventions.
Header File Management and Include Directories
Defining public API header files is crucial. It's recommended to place public header files in a separate include/ directory, while keeping private header files with source files:
set_target_properties(mylib PROPERTIES PUBLIC_HEADER include/mylib.h)
For include directory configuration, use the target_include_directories command. When dealing with subdirectories, setting the top-level directory as an include path is more convenient:
target_include_directories(mylib PRIVATE .)
# Or more precisely specify directories
target_include_directories(mylib PRIVATE include)
target_include_directories(mylib PRIVATE src)
Installation Configuration
Use the GNUInstallDirs module to obtain standard installation directory variables:
include(GNUInstallDirs)
Then define installation rules for library files and header files:
install(TARGETS mylib
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
pkg-config Support
To facilitate third-party application usage, generate pkg-config files. First, create a template file mylib.pc.in:
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: @PROJECT_NAME@
Description: @PROJECT_DESCRIPTION@
Version: @PROJECT_VERSION@
Requires:
Libs: -L${libdir} -lmylib
Cflags: -I${includedir}
Configure and install the pkg-config file in CMakeLists.txt:
configure_file(mylib.pc.in mylib.pc @ONLY)
install(FILES ${CMAKE_BINARY_DIR}/mylib.pc
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)
Complete Example and Advanced Considerations
A complete CMakeLists.txt example follows:
cmake_minimum_required(VERSION 3.9)
project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")
include(GNUInstallDirs)
add_library(mylib SHARED
src/animation.cpp
src/buffers.cpp
src/vertex.cpp
src/world.cpp
)
set_target_properties(mylib PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
PUBLIC_HEADER include/mylib.h
)
configure_file(mylib.pc.in mylib.pc @ONLY)
target_include_directories(mylib PRIVATE .)
install(TARGETS mylib
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(FILES ${CMAKE_BINARY_DIR}/mylib.pc
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig
)
In real-world projects, static library generation might also be considered. While not core to shared library requirements, providing static library versions can be beneficial in certain scenarios. Link order issues may also arise, particularly when dealing with third-party libraries. Methods like add_link_options(-Wl,--start-group) can be used to handle link dependency problems.
Migration Recommendations
When migrating from traditional Makefiles to CMake, proceed incrementally. First ensure basic shared library building works correctly, then gradually add installation support, version management, and pkg-config generation features. Test build results at each step to ensure generated library files can be properly linked and used.