Complete Guide to Building Shared Libraries with CMake

Nov 21, 2025 · Programming · 25 views · 7.8

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.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.