参考链接:
bottle/cpp_with_cmake/cmake_static_library
CMake官方文档:CMakePackageConfigHelpers
CMake要生成一个C++静态库,其实非常简单,只需要如下即可
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16.6)
project(lib)
##################################################################
# 目标配置
##################################################################
set(TARGET_NAME lib)
##################################################################
# 源文件
##################################################################
set(TARGET_SOURCES
lib.cpp
)
set(TARGET_PUBLIC_HEADERS
lib.h
)
##################################################################
# 生成库属性
##################################################################
add_library(${TARGET_NAME} STATIC ${TARGET_PUBLIC_HEADERS} ${TARGET_SOURCES})
我们的源文件内容如下
// lib.cpp
#include "lib.h"
int add(int a, int b) {
return a + b;
}
// lib.h
#pragma once
int add(int a, int b);
通过以下命令就可以完成构建
构建结果如下
但是我们需要通过 install 命令安装,需要增加 isntall 到 CMakeLists.txt
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16.6)
project(lib)
##################################################################
# 目标配置
##################################################################
set(TARGET_NAME lib)
set(TARGET_NAMESPACE Library)
set(TARGET_RUNTIME_INSTALL_DIR bin)
set(TARGET_ARCHIVE_INSTALL_DIR lib)
set(TARGET_LIBRARY_INSTALL_DIR lib)
set(TARGET_PUBLIC_HEADER_INSTALL_DIR include)
##################################################################
# 源文件
##################################################################
set(TARGET_SOURCES
lib.cpp
)
set(TARGET_PUBLIC_HEADERS
lib.h
)
##################################################################
# 生成库属性
##################################################################
add_library(${TARGET_NAME} STATIC ${TARGET_PUBLIC_HEADERS} ${TARGET_SOURCES})
# 配置头文件,install命令执行时将复制这些配置的头文件
set_target_properties(${TARGET_NAME} PROPERTIES
PUBLIC_HEADER "${TARGET_PUBLIC_HEADERS}"
)
# 将头文件包含目录配置到 INTERFACE,便于生成的库在被引用时可以自动添加包含目录
string(REPLACE ";" "$<SEMICOLON>" _include_dirs "${TARGET_INCLUDE_DIRS}")
target_include_directories(${TARGET_NAME} SYSTEM INTERFACE
"$<BUILD_INTERFACE:${_include_dirs}>"
"$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${TARGET_PUBLIC_HEADER_INSTALL_DIR}>"
)
##################################################################
# 安装目标
##################################################################
# 配置安装路径
install(
TARGETS ${TARGET_NAME}
EXPORT ${TARGET_NAME}
RUNTIME DESTINATION ${TARGET_RUNTIME_INSTALL_DIR}
ARCHIVE DESTINATION ${TARGET_ARCHIVE_INSTALL_DIR}
LIBRARY DESTINATION ${TARGET_LIBRARY_INSTALL_DIR}
PUBLIC_HEADER DESTINATION ${TARGET_PUBLIC_HEADER_INSTALL_DIR}
PRIVATE_HEADER DESTINATION ${TARGET_PRIVATE_HEADER_INSTALL_DIR}
)
# 生成 <TARGET>Targets*.cmake 文件
install(
EXPORT ${TARGET_NAME}
FILE ${TARGET_NAME}Targets.cmake
NAMESPACE ${TARGET_NAMESPACE}:: # Defines namespace in XXXTargets.cmake
DESTINATION lib/cmake/${TARGET_NAME}
)
那么我们重新进行生成,指定 CMAKE_INSTALL_PREFIX,安装到指定临时文件夹
生成的结果为
、
将被安装到
我们看看通过 install 命令生成的 <TARGET>Targets*.cmake 内容
# libTargets.cmake
# 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.22)
#----------------------------------------------------------------
# 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 Library::lib)
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 Library::lib
add_library(Library::lib STATIC IMPORTED)
set_target_properties(Library::lib PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
)
# Load information for each installed configuration.
file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/libTargets-*.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)
# libTargets-debug.cmake
#----------------------------------------------------------------
# Generated CMake target import file for configuration "Debug".
#----------------------------------------------------------------
# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)
# Import target "Library::lib" for configuration "Debug"
set_property(TARGET Library::lib APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(Library::lib PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/lib.lib"
)
list(APPEND _cmake_import_check_targets Library::lib )
list(APPEND _cmake_import_check_files_for_Library::lib "${_IMPORT_PREFIX}/lib/lib.lib" )
# Commands beyond this point should not need to know the version.
set(CMAKE_IMPORT_FILE_VERSION)
第一个文件 libTargets.cmake,主要就是配置了版本号,并通过 add_library 添加了一个 IMPORTED 类型的静态库,并添加了包含路径
# CMakeLists.txt
string(REPLACE ";" "$<SEMICOLON>" _include_dirs "${TARGET_INCLUDE_DIRS}")
target_include_directories(${TARGET_NAME} SYSTEM INTERFACE
"$<BUILD_INTERFACE:${_include_dirs}>"
"$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${TARGET_PUBLIC_HEADER_INSTALL_DIR}>"
)
# libTargets.cmake 中这一段指定的 INCLUDE 路径,就是通过上面 CMakeLists.txt 的 target_include_directories (INTERFACE) 指定的
set_target_properties(Library::lib PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
)
这里如果我们想通过 find_package 去引用此库,会发生以下现象
虽然库是有的,但是 find-package 并不能找到。
那么需要通过 CMakePackageConfigHelpers 来完成配置。在 CMakeLists.txt 末尾增加如下内容
# CMakeLists.txt
##################################################################
# <TARGET>Config.cmake and <TARGET>ConfigVersion.cmake
##################################################################
# CMakePackageConfigHelpers provides
# write_basic_package_version_file
# configure_package_config_file
include(CMakePackageConfigHelpers)
# 生成 <TARGET>ConfigVersion.cmake 文件
write_basic_package_version_file(
${TARGET_NAME}ConfigVersion.cmake
VERSION 1.0.0
COMPATIBILITY SameMajorVersion # AnyNewerVersion|SameMajorVersion|SameMinorVersion|ExactVersion
)
# 生成 <TARGET>Config.cmake 文件 -- 非常重要,find_package 主要就是搜索该文件
configure_package_config_file(
PackageConfig.cmake.in ${TARGET_NAME}Config.cmake
INSTALL_DESTINATION lib/cmake/${TARGET_NAME}
)
# 安装生成的 <TARGET>Config.cmake 和 <TARGET>ConfigVersion.cmake 文件
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake
${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}ConfigVersion.cmake
DESTINATION lib/cmake/${TARGET_NAME}
)
这里用到了一个输入文件 PackageConfig.cmake.in,其内容如下,主要就是自动生成 libConfig.cmake 的模板,CMake会将 @PACKAGE_INIT@ 部分用默认的模板填充,后面的部分是我们根据自己库的需要指定的。这里我们只增加了一个组件校验,用作示例(其实这个是可以不需要的)。
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
include("${CMAKE_CURRENT_LIST_DIR}/@TARGET_NAME@Targets.cmake")
check_required_components("@TARGET_NAME@")
好了,那么接下来我们再次生成,得到了如下结果。
这个时候,再用 find_package 来搜索,就可以搜索到 lib 库了。
引用库的 CMakeLists.txt 也非常简单,这里简单贴一下
# 引用库的 CMakeLists.txt
cmake_minimum_required(VERSION 3.16.6)
project(lib_ref_demo)
# 只需要再 CMAKE_PREFIX_PATH 中添加了安装目录即可
# CMake 会按照 .install/lib/cmake/<TargetName>/<TargetName>Config.cmake 这个路径去搜索目标
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/library/.install)
# 通过 find_package 命令搜索我们构建的库
find_package(lib CONFIG REQUIRED)
# 添加 exe 示例程序
add_executable(lib_ref_demo
ref.cpp
)
# 链接刚刚搜索到的 lib 库
target_link_libraries(lib_ref_demo PUBLIC Library::lib)
具体的工程内容详见 bottle,当然bottle里面还有动态库的生成。补充说明一下的是,bottle 库中的示例要比这里复杂一丢丢,主要验证了库的后缀指定,include多种结构,public/private 头文件等等细节问题。