0. 引言
在为我们的库编写CMake时,我们确保它能够正确编译。然而,库的用户需要编写一些CMake代码来查找包含的头文件、共享/静态库和可执行文件。这对于库的用户来说可能是一项麻烦的任务。作为库的开发者,我们可以在CMake脚本中添加几行模板代码,使用户只需使用一行find_package()
命令即可轻松导入所有内容。
在这篇文章中,我将通过一个示例展示如何为库创建版本文件和配置文件,以便能方便地导入到其他项目中。
1. 示例
示例代码可以在GitHub上找到。它是一个共享库,文件结构如下:
--- geometry
|
---- shape
| |
| --- shape.h, shape.cpp
| |
| --- CMakeLists.txt
|
---- square
| |
| --- square.h, square.cpp, info.h
| |
| --- CMakeLists.txt
|
---- example
| |
| --- app.cpp
|
---- CMakeLists.txt
CMakeLists.txt
文件内容如下:
cmake_minimum_required(VERSION 3.23)
project(geometry LANGUAGES CXX VERSION 5.4.3)
if (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
add_library(geo SHARED)
target_include_directories(geo PRIVATE "${PROJECT_SOURCE_DIR}")
add_subdirectory("shape")
add_subdirectory("square")
install(TARGETS geo
EXPORT geoTargets
FILE_SET HEADERS
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include)
install(EXPORT geoTargets
FILE geoTargets.cmake
NAMESPACE geo::
DESTINATION lib/cmake/geo)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"geoConfigVersion.cmake"
VERSION ${geo_VERSION}
COMPATIBILITY AnyNewerVersion)
install(FILES "geoConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/geoConfigVersion.cmake"
DESTINATION lib/cmake/geo)
在这个示例中,目标和依赖项的解释在之前的文章中已有说明,因此我将继续讲解。
3. 关键命令解析
-
install(TARGETS geo EXPORT geoTargets ...)
:这里的EXPORT定义了geoTargets
变量。它表示如果你需要导出geo
的目标,请使用geoTargets
。 -
install(EXPORT geoTargets)
:这是前一个安装命令的配对,我们定义了要导出目标文件的名称、命名空间以及安装位置。命名空间是加载到其他项目时添加到包名称的前缀。因此,geo
目标在其他项目中将被视为geo::geo
。 -
include(CMakePackageConfigHelpers)
:这是CMake加载的一个模块,用于创建配置文件。 -
write_basic_package_version_file()
:该函数创建geoConfigVersion.cmake
文件,存储包的版本信息及其兼容性。AnyNewerVersion
表示如果包的版本是请求版本的同一版本或更高版本,CMake将导入该包。有关更多详细信息,请参见CMake手册。 -
install(FILES...)
:将配置文件和版本文件复制到安装目录。版本文件由此脚本创建,而geoConfig.cmake
则是我们自己编写的。
geometry/geoConfig.cmake
文件如下所示:
include(CMakeFindDependencyMacro)
# find_dependency(xxx 2.0)
include(${CMAKE_CURRENT_LIST_DIR}/geoTargets.cmake)
我注释掉了find_dependency()
行,因为这个示例没有其他依赖项。如果你的包有其他依赖项,需要在这里提及。
4. 构建和安装
现在我们可以编译和安装库。在源目录中打开终端,运行以下命令:
mkdir build
cd build
对于单配置生成器(如make或Ninja):
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build . # CMake提供的一个命令,用于调用生成的构建系统(如Makefile、Ninja等)来编译项目
cmake --install . --prefix ../../geo-install # 安装构建好的项目,--prefix ../../geo-install指定了安装的目标目录
对于多配置生成器(如VS C++或Xcode):
cmake ..
cmake --build . --config release
cmake --install . --prefix ../../geo-install --config release
对应的geoTargets.cmake文件,的主要作用是定义和管理一个名为 geo::geo 的导入目标。这个文件通常是在构建和安装库时自动生成的,目的是在其他项目中使用这个库时能够方便地导入和链接。
# Generated by CMake
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
message(FATAL_ERROR "CMake >= 2.8.0 required")
endif()
if(CMAKE_VERSION VERSION_LESS "2.8.3")
message(FATAL_ERROR "CMake >= 2.8.3 required")
endif()
cmake_policy(PUSH)
cmake_policy(VERSION 2.8.3...3.25)
#----------------------------------------------------------------
# Generated CMake target import file.
#----------------------------------------------------------------
# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)
# Protect against multiple inclusion, which would fail when already imported targets are added once more.
set(_cmake_targets_defined "")
set(_cmake_targets_not_defined "")
set(_cmake_expected_targets "")
foreach(_cmake_expected_target IN ITEMS geo::geo)
list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
if(TARGET "${_cmake_expected_target}")
list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
else()
list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
endif()
endforeach()
unset(_cmake_expected_target)
if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)
unset(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)
return()
endif()
if(NOT _cmake_targets_defined STREQUAL "")
string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n")
endif()
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)
# Compute the installation prefix relative to this file.
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
if(_IMPORT_PREFIX STREQUAL "/")
set(_IMPORT_PREFIX "")
endif()
# Create imported target geo::geo
add_library(geo::geo SHARED IMPORTED)
set_target_properties(geo::geo PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
)
if(NOT CMAKE_VERSION VERSION_LESS "3.23.0")
target_sources(geo::geo
INTERFACE
FILE_SET "HEADERS"
TYPE "HEADERS"
BASE_DIRS "${_IMPORT_PREFIX}/include"
FILES "${_IMPORT_PREFIX}/include/shape/shape.h" "${_IMPORT_PREFIX}/include/square/square.h"
)
else()
set_property(TARGET geo::geo
APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES
"${_IMPORT_PREFIX}/include"
)
endif()
# Load information for each installed configuration.
file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/geoTargets-*.cmake")
foreach(_cmake_config_file IN LISTS _cmake_config_files)
include("${_cmake_config_file}")
endforeach()
unset(_cmake_config_file)
unset(_cmake_config_files)
# Cleanup temporary variables.
set(_IMPORT_PREFIX)
# Loop over all imported files and verify that they actually exist
foreach(_cmake_target IN LISTS _cmake_import_check_targets)
foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}")
if(NOT EXISTS "${_cmake_file}")
message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file
\"${_cmake_file}\"
but this file does not exist. Possible reasons include:
* The file was deleted, renamed, or moved to another location.
* An install or uninstall procedure did not complete successfully.
* The installation package was faulty and contained
\"${CMAKE_CURRENT_LIST_FILE}\"
but not all the files it references.
")
endif()
endforeach()
unset(_cmake_file)
unset("_cmake_import_check_files_for_${_cmake_target}")
endforeach()
unset(_cmake_target)
unset(_cmake_import_check_targets)
# This file does not depend on other imported targets which have
# been exported from the same project but in a separate export set.
# Commands beyond this point should not need to know the version.
set(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)