

Introduction:
In the previous notes (CMake: Detailed Explanation of Static and Dynamic Libraries (Linux/Windows)), we demonstrated how to output dynamic and static libraries. However, there were some issues, such as only outputting header files, symbol tables, and library files. What we actually want is for others to easily find our library when they compile and install it, whether by configuring the environment variables or specifying the library path. This note will show how CMake allows us to export targets so that other CMake projects can easily access them.

✦
Project Structure
✦
.
├── cmake
│ └── messageConfig.cmake.in
├── CMakeLists.txt
├── src
│ ├── CMakeLists.txt
│ ├── hello_world.cpp
│ ├── message.cpp
│ └── message.hpp
└── test
└── CMakeLists.txt
Project Address
https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter9/03
✦
Related Source Code
✦
CMakeLists.txt
# CMake 3.6 needed for IMPORTED_TARGET option
# to pkg_search_module
cmake_minimum_required(VERSION 3.6 FATAL_ERROR)
project(example
LANGUAGES CXX
VERSION 1.0.0
)
# <<< General set up >>>
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Check if the installation prefix has been set
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
# Set the installation directory to the output folder under the project source directory
set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/output/" CACHE PATH "..." FORCE)
endif()
message(STATUS "Project will be installed to ${CMAKE_INSTALL_PREFIX}")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
message(STATUS "Build type set to ${CMAKE_BUILD_TYPE}")
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDDIR})
# Offer the user the choice of overriding the installation directories
set(INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Installation directory for libraries")
set(INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Installation directory for executables")
set(INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "Installation directory for header files")
if(WIN32 AND NOT CYGWIN)
set(DEF_INSTALL_CMAKEDIR CMake)
else()
set(DEF_INSTALL_CMAKEDIR share/cmake/${PROJECT_NAME})
endif()
set(INSTALL_CMAKEDIR ${DEF_INSTALL_CMAKEDIR} CACHE PATH "Installation directory for CMake files")
# Report to user
foreach(p LIB BIN INCLUDE CMAKE)
file(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/${INSTALL_${p}DIR} _path )
message(STATUS "Installing ${p} components to ${_path}")
unset(_path)
endforeach()
add_subdirectory(src)
enable_testing()
add_subdirectory(test)
The above CMake has been analyzed in detail in the previous CMake: Exporting Header Files article. Please refer to the previous article. cmake/messageConfig.cmake.in
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/messageTargets.cmake")
check_required_components(
"message-shared"
"message-static"
"message-hello-world_wDSO"
"message-hello-world_wAR"
)
# Remove dependency on UUID if on Windows
if(NOT WIN32)
if(NOT TARGET PkgConfig::UUID)
find_package(PkgConfig REQUIRED QUIET)
pkg_search_module(UUID REQUIRED uuid IMPORTED_TARGET)
endif()
endif()
@PACKAGE_INIT@
Placeholders will be replaced using the configure_package_config_file command. If the project builds successfully, the replacements will occur in the messageConfig.cmake file:
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
macro(set_and_check _var _file)
set(${_var} "${_file}")
if(NOT EXISTS "${_file}")
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
endif()
endmacro()
macro(check_required_components _NAME)
foreach(comp ${${_NAME}_FIND_COMPONENTS})
if(NOT ${_NAME}_${comp}_FOUND)
if(${_NAME}_FIND_REQUIRED_${comp})
set(${_NAME}_FOUND FALSE)
endif()
endif()
endforeach()
endmacro()
include("${CMAKE_CURRENT_LIST_DIR}/messageTargets.cmake")
Includes the automatically generated export file for targets.
check_required_components(
"message-shared"
"message-static"
"message-hello-world_wDSO"
"message-hello-world_wAR"
)
Checks for the static and dynamic libraries, as well as the two Hello, World executables to see if they contain the check_required_components function provided by CMake.
if(NOT WIN32)
if(NOT TARGET PkgConfig::UUID)
find_package(PkgConfig REQUIRED QUIET)
pkg_search_module(UUID REQUIRED uuid IMPORTED_TARGET)
endif()
endif()
Checks whether the target PkgConfig::UUID exists. If not, it searches for the UUID library again (only valid on non-Windows operating systems). src/CMakeLists.txt
# Search for pkg-config and UUID
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_search_module(UUID uuid IMPORTED_TARGET)
if(TARGET PkgConfig::UUID)
message(STATUS "Found libuuid")
set(UUID_FOUND TRUE)
endif()
endif()
# <<< Build targets >>>
# SHARED library
add_library(message-shared SHARED "")
include(GenerateExportHeader)
generate_export_header(message-shared
BASE_NAME "message"
EXPORT_MACRO_NAME "MESSAGE_LIB_API"
EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h"
STATIC_DEFINE "MESSAGE_STATIC_DEFINE"
DEFINE_NO_DEPRECATED
)
target_sources(message-shared
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/message.cpp
)
target_compile_definitions(message-shared
PUBLIC
$<$<BOOL:${UUID_FOUND}>:HAVE_UUID>
INTERFACE
$<INSTALL_INTERFACE:USING_MESSAGE>
)
target_include_directories(message-shared
PUBLIC
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}>
$<INSTALL_INTERFACE:${INSTALL_INCLUDEDIR}>
)
target_link_libraries(message-shared
PUBLIC
$<$<BOOL:${UUID_FOUND}>:PkgConfig::UUID>
)
set_target_properties(message-shared
PROPERTIES
POSITION_INDEPENDENT_CODE 1
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN 1
SOVERSION ${PROJECT_VERSION_MAJOR}
OUTPUT_NAME "message"
DEBUG_POSTFIX "_d"
PUBLIC_HEADER "message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h"
MACOSX_RPATH ON
)
# STATIC library
add_library(message-static STATIC "")
target_sources(message-static
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/message.cpp
)
target_compile_definitions(message-static
PUBLIC
message_STATIC_DEFINE
$<$<BOOL:${UUID_FOUND}>:HAVE_UUID>
INTERFACE
$<INSTALL_INTERFACE:USING_MESSAGE>
)
target_include_directories(message-static
PUBLIC
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}>
$<INSTALL_INTERFACE:${INSTALL_INCLUDEDIR}>
)
target_link_libraries(message-static
PUBLIC
$<$<BOOL:${UUID_FOUND}>:PkgConfig::UUID>
)
set_target_properties(message-static
PROPERTIES
POSITION_INDEPENDENT_CODE 1
ARCHIVE_OUTPUT_NAME "message"
DEBUG_POSTFIX "_sd"
RELEASE_POSTFIX "_s"
PUBLIC_HEADER "message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h"
)
# EXECUTABLES
add_executable(hello-world_wDSO hello_world.cpp)
target_link_libraries(hello-world_wDSO
PUBLIC
message-shared
)
add_executable(hello-world_wAR hello_world.cpp)
target_link_libraries(hello-world_wAR
PUBLIC
message-static
)
# <<< Install and export targets >>>
install(
TARGETS
message-shared
message-static
hello-world_wDSO
hello-world_wAR
EXPORT
messageTargets
ARCHIVE
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
RUNTIME
DESTINATION ${INSTALL_BINDIR}
COMPONENT bin
LIBRARY
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
PUBLIC_HEADER
DESTINATION ${INSTALL_INCLUDEDIR}/message
COMPONENT dev
)
install(
EXPORT
messageTargets
NAMESPACE
"message::"
DESTINATION
${INSTALL_CMAKEDIR}
COMPONENT
dev
)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
configure_package_config_file(
${PROJECT_SOURCE_DIR}/cmake/messageConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake
INSTALL_DESTINATION ${INSTALL_CMAKEDIR}
)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmake
DESTINATION
${INSTALL_CMAKEDIR}
)
install(
EXPORT
messageTargets
NAMESPACE
"message::"
DESTINATION
${INSTALL_CMAKEDIR}
COMPONENT
dev
)
-
EXPORT messageTargets: -
This specifies the name of the export set to be installed.
messageTargetsis a previously defined export set that contains some targets in the project (such as libraries or executables). -
These targets are defined and built during the CMake build process, and the
EXPORTkeyword indicates that these targets should be exported as a set during installation. This article packages the dynamic library, static library, and corresponding header files into a set (messageTargets). -
NAMESPACE "message::": -
This command sets a namespace prefix for the exported targets. In this case, any exported target will be prefixed with
message::. -
For example, if there is a library named
message-shared, it can be referenced in other projects usingfind_packageasmessage::message-shared. -
DESTINATION ${INSTALL_CMAKEDIR}: -
This specifies the target folder where the exported targets (
messageTargets) should be installed. -
${INSTALL_CMAKEDIR}is a variable defined in the mainCMakeLists.txt. -
COMPONENT dev: -
The
COMPONENTkeyword specifies which component this part of the installation belongs to. Here, the component is nameddev. -
In CMake, installations can be divided into multiple components, such as library files, header files, documentation, etc., allowing selective installation of specific components. The
devcomponent may contain development-related files, such as CMake configurations and library files.
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
configure_package_config_file(
${PROJECT_SOURCE_DIR}/cmake/messageConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake
INSTALL_DESTINATION ${INSTALL_CMAKEDIR}
)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmake
DESTINATION
${INSTALL_CMAKEDIR}
)
-
include(CMakePackageConfigHelpers): -
This command includes the CMake module
CMakePackageConfigHelpers, which provides some helper macros and functions for generating and configuring package configuration files. -
These helpers simplify the process of creating configuration files compatible with different projects.
-
write_basic_package_version_file: -
Generates a package version file containing information about the package version and compatibility.
-
${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmakespecifies the path and name of the generated file. -
VERSION ${PROJECT_VERSION}specifies the current version of the package, where${PROJECT_VERSION}is a variable defined in CMake for the project version. -
COMPATIBILITY SameMajorVersionspecifies the version compatibility rules. In this case, it indicates that the package is compatible with other versions having the same major version number. -
configure_package_config_file: -
This command generates a package configuration file from a
.intemplate file. -
${PROJECT_SOURCE_DIR}/cmake/messageConfig.cmake.inis the path to the template file, which typically contains configuration information such as library locations, dependencies, etc. -
${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmakeis the path and name of the output file. -
INSTALL_DESTINATION ${INSTALL_CMAKEDIR}specifies the target directory for the configuration file upon installation. -
The
installcommand: -
This
installcommand is used to install the previously generated configuration files. -
The files to be installed are listed after the
FILESkeyword: the generatedmessageConfig.cmakeandmessageConfigVersion.cmakefiles. -
DESTINATION ${INSTALL_CMAKEDIR}specifies the installation directory for these files.
src/message.hpp
#pragma once
#include <iosfwd>
#include <string>
#include "message_export.h"
class MESSAGE_LIB_API Message {
public:
Message(const std::string &m) : message_(m) {}
friend std::ostream &operator<<(std::ostream &os, Message &obj) {
return obj.PrintObject(os);
}
private:
std::string message_;
std::ostream &PrintObject(std::ostream &os);
};
std::string GetUUID();
src/message.cpp
#include "message.hpp"
#include <iostream>
#include <string>
#ifdef HAVE_UUID
#include <uuid/uuid.h>
#endif
std::ostream &Message::PrintObject(std::ostream &os) {
os << "This is my very nice message: " << std::endl;
os << message_ << std::endl;
os << "...and here is its UUID: " << GetUUID();
return os;
}
#ifdef HAVE_UUID
std::string GetUUID() {
uuid_t uuid;
uuid_generate(uuid);
char uuid_str[37];
uuid_unparse_lower(uuid, uuid_str);
uuid_clear(uuid);
std::string uuid_cxx(uuid_str);
return uuid_cxx;
}
#else
std::string GetUUID() { return "Ooooops, no UUID for you!"; }
#endif
src/hello_world.cpp
#include <cstdlib>
#include <iostream>
#include "message.hpp"
int main() {
Message say_hello("Hello, CMake World!");
std::cout << say_hello << std::endl;
Message say_goodbye("Goodbye, CMake World");
std::cout << say_goodbye << std::endl;
return EXIT_SUCCESS;
}
test/CMakeLists.txt
add_test(
NAME test_shared
COMMAND $<TARGET_FILE:hello-world_wDSO>
)
add_test(
NAME test_static
COMMAND $<TARGET_FILE:hello-world_wAR>
)
✦
Results Display
✦
$ mkdir -p build
$ cd build
$ cmake ..
$ cmake --build . --target install
Installation Tree Structure:
output
├── bin
│ ├── hello-world_wAR
│ └── hello-world_wDSO
├── include
│ └── message
│ ├── message_export.h
│ └── message.hpp
├── lib
│ ├── libmessage_s.a
│ ├── libmessage.so -> libmessage.so.1
│ └── libmessage.so.1
└── share
└── cmake
└── example
├── messageConfig.cmake
├── messageConfigVersion.cmake
├── messageTargets.cmake
└── messageTargets-release.cmake
Finally, I wish everyone to become stronger!!! If this note has helped you, please like and share!!!🌹🌹🌹

