7.1
7.2 如何查找已安装的软件包
比如查找protobuf
// message.proto
syntax = "proto3"
message Message{
int32 id = 1;
}
// main.cpp
#include "message.pb.h"
#include <fstream>
using namespace std;
int main(){
Message m;
m.set_id(123);
m.PrintDebugString();
fstream fo("./hello.data", ios::binary | ios::out);
m.SerailizeToOstream($fo);
fo.close();
return 0;
}
// CMakeLists.txt
find_package(Protobuf REQUIRED)
protobuf_generate_cpp(GENERATED_SRC GENERATED_HEADER message.proto)
add_executable(main main.cpp ${GENERATED_SRC} ${GENERATED_HEADER})
target_link_libraries(main PRIVATE ${Protobuf_LIBRARIES})
target_include_directories(main PRIVATE ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
使用find_package时,会预期设置一些变量: ·<PKG_NAME>_ FOUND ·<PKG_NAME>_ INCLUDE_DIRS 或 <PKG_NAME>_ INCLUDES ·<PKG_NAME>_ LIBRARIES 或 <PKG_NAME>_ LIBRARIES 或 <PKG_NAME>_ LIBS ·<PKG_NAME>_ DEFINITIONS ·IMPORTED由查找模块或配置文件指定的目标
如果导入的包(Protobuf)支持所谓的“modern cmake”(围绕目标构建),该包将提供那些导入的目标来替代这些变量. 例如,protobuf:
// CMakeLists.txt
find_package(Protobuf REQUIRED)
protobuf_generate_cpp(GENERATED_SRC GENERATED_HEADER message.proto)
add_executable(main main.cpp ${GENERATED_SRC} ${GENERATED_HEADER})
target_link_libraries(main PRIVATE protobuf::libprotobuf)
target_include_directories(main PRIVATE #{CMAKE_CURRENT_BINARY_dIR})
此处target_link_libraries(main PRIVATE prtobuf::libprotobuf)
隐式指定了包含目录。
接下来介绍find_package
命令的选项:
find_package(<Name> [version] [EXACT] [QUIET] [REQUIRED])
[verion]:请求包的特定版本,使用major.minor.pathc.tweak格式。或者提供一个范围1.22...1.40.1,使用三个圆点作为分隔符。 EXACT: 精确的版本 QUIET: 使所有关于已找到和未找到包的消息静默。 REQUIRED: 如果没有找到包,REQUIRED关键字将停止执行并打印诊断消息(即使启用了QUIET)
7.3使用FindPkgConfig
· 如果一个库非常流行,即cmake已经有查找该库的模块。使用cmake的find package · 如果没有该库的查找模块,并且只支持PkgConfig.pc,使用现成的就行。
CMake提供了FindPkgConfig模块,设置了一个直接指向pkg-config二进制文件的变量:PKG_CONFIG_EXECUTABLE
比如postgresql提供了pc文件,那么如何通过pkg-config来找到并配置该库呢?
// main.cpp
#include <pqxx/pqxx>
int main(){
pqxx::nullconnection connection;
}
// CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
project(FindPkgConfig CXX)
find_package(PkgConfig REQUIRED)
pkg_check_modules(PQXX REQUIRED IMPORTED_TARGET libqxx) // FindPkgConfig自定义宏
message("PQXX: ${PQXX_FOUND}")
add_executable(main main.cpp)
target_link_libraries(main PRIVATE PkgConfig::PQXX)
7.4编写自己的查找模块
项目既没有config文件也没有PkgConfig文件,CMake中也没有线程的查找模块,可以为这个库编写一个自定义查找模块。
编写一个新的FindPQXX.cmake文件,将他存储在项目源代码树的cmake/module目录中,需要确保当使用find_package()时,CMake可以找到这个查找模块,所以会在CMakeLists.txt中将该目录追加到CMAKE_MODULE_PATH变量中。
// CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
project(FindPackageCustom CXX)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/module") //追加模块查找路径
find_package(PQXX REQUIRED) // 查找模块
add_executable(main main.cpp)
target_link_libraries(main PRIVATE PQXX:PQXX)
现在开始写查找模块,如果某些特定的变量没有设置,CMake不会报错。最好是遵守CMake文档中的约定: 使用find_package模块式。CMake提供: · <PKG_NAME>_ FIND_ REQURED <= REQUIRED · <PKG_NAME>_ FIND_ QUIETLY <= QUIET · <PKG_NAME>_ FIND_ VERSION
为PQXX创建一个优雅的查找模块所需的步骤:
- 如果库和头文件的路径已经知道(用户提供或缓存),则使用这些路径并创建导入的目标。结束。
- 否则找到嵌套依赖的库和头文件-PostgreSQL
- 已知的路径中搜索二进制版本的PostgreSQL客户端库
- 搜索PostgreSQL客户端包含头文件的已知路径
- 检查是否找到库和include头文件。是的话创建一个IMPORTED目标。
IMPORTED目标的创建会发生两次——用户提供的库的路径,或者自动找到。 首先编写一个函数来处理搜索过程的结果来保持代码的DRY。
要创建IMPORTED目标,只需要一个IMPORTED关键字的库。库必须提供一个类型——将其标记为UNKNOWN
类型,表示我们不想检测所找到的库是静态的还是动态的,只是想为链接器提供一个参数。
接下来,IMPORTED_LOCATION
和INTERFACE_INCLUDE_DIRECTORIES
导入目标的必须属性设置为函数实参。
之后,把路径存储在缓存变量中,这样就不用再次搜索了。PQXX_FOUND
是在缓存中显示设置的,因此在全局作用域中是可见的。
最后将缓存标记为高级,除非启用了高级选项,否则它们在CMAKE GUI中是不可见的。
// FindPQXX.cmake
function(add_imported_library library header)
add_library(PQXX:PQXX UNKOWN IMPORTED)
set_target_properties(PQXX:PQXX PROPERTIES IMPORTED_LOCATION ${library} INTERFACE_INCLUDE_DIRECTORIES ${headers})
set(PQXX_FOUND 1 CACHE INTERNAL "PQXX found" FORCE)
set(PQXX_LIBRARIES #{library} CACHE STRING "Path to pqxx library" FORCE)
set(PQXX_INCLUDES ${header} CACHE STRING "Path to pqxx headers" FORCE)
mark_as_advanced(FORCE PQXX_LIBRARIES)
mark_as_advanced(FORCE PQXX_INCLUDES)
endfunction()
介绍第一种情况1. 如果库和头文件的路径已经知道(用户提供或缓存),则使用这些路径并创建导入的目标。
将PQXX安装在非标准位置的用户可以使用-D参数通过命令行提供必要的路径。如果是这种情况,只需要调用刚刚定义的function,并通过return进行转移来放弃搜索。 同时如果配置阶段在过去执行过,这个条件也为true,因为PQXX_LIBRARIES
和PQXX_INCLUDES
变量被缓存了。
代码如下:
if(PQXX_LIBRARIRS AND PQXX_INCLUDES)
add_imported_library(${PQXX_LIBRARIES} ${PQXX_INCLUDES})
return()
endif()
对于一些嵌套依赖项,比如使用PQXX还需要PostgreSQL,查找模块中使用另一个查找模块没问题,但应该将REQUIRED
和QUIET
标志进行转发(这样嵌套的搜索行为与外部的搜索行为一致)。
CMake有一个内置的帮助宏find_dependency
。文档中指出该宏不适合查找模块是因为,在没有找到对应模块的情况下,宏的行为区别于函数,宏会直接调用return()
,导致退出FindPQXX.cmake
文件,停止外部查找模块的执行。某些情况这种行为不可取,但是在当前例子下,我们需要这个特质——防止找不到库时cmake掉进坑里。
include(CMakeFindDependencyMacro)
find_dependency(PostgreSQL)
要查找PQXX库,将设置一个_PQXX_DIR
帮助变量,并使用find_library()扫描路径列表,在PATHS关键字后面提供路径。该指令将检查是否存在与另一个关键字NAMES后面提供的名称相匹配的库二进制文件,如果找到了,路径存储在PQXX_LIBRARY_PATH
变量中,否则设置为<var>NOTFOUND
或者
其中NO_DEFAULT_PAHT
关键字禁用默认行为,该行为将扫描CMake
为该主机环境提供的一长串默认路径。
file(TO_CMAKE_PATH "$ENV{PQXX_DIR}" _PQXX_DIR)
find_library(PQXX_LIBRARY_PATH NAMES libpqxx pqxx PATHS
${PQXX_DIR}/lib/${CMAKE_LIBRARY_ARCHITECTURE}
# {...} many other paths
/usr/lib
NO_DEFAULT_PATH
)
接下来用find_path
指令搜索已知的头文件,该指令的工作方式和find_library
非常相似。主要是区别是find_library
知道库特定于系统的扩展名,但是find_path
不知道,我们需要提供确切的名称。
find_path(PQXX_HEADER_PATH NAMES pqxx/pqxx
PATHS
${_PQXX_DIR}/include
# (...) many other paths
/usr/include
NO_DEFAULT_PATH
)
如果找到了库,将调用函数来定义导入的目标,并将路径存储在缓存中:
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
PQXX DEFAULT_MSG_LIBRARY_PATH PQXX_HEADER_PATH
)
if(PQXX_FOUND)
add_imported_library(
"${PQXX_LIBRARY_PATH};${POSTGRES_LIBRARIES}"
"${PQXX_HEADER_PATH};${POSTGRES_INCLUDE}"
)
endif()
7.5使用Git库
7.5.1 通过Git子库提供外部库
向存储库中添加一个子模块
git submodule add <repository-url>
如果提取的存储库?已经有子模块,需要初始化它们:
git submodule update --init -- <local-path-to-submodule>
一、在代码中使用git submodule add <url>
创建的子模块
github.com/jbeder/yaml… 使用来自该仓库的yaml-cpp模块
// main.cpp
#include <iostream>
#include <string>
#include "yaml-cpp/yaml.h"
int main(){
std::string = "Guest";
YAML::Node config = YAML::LoadFile("config.yaml");
if(config["name"])
name = config["name"].as<std::string>();
std::cout << "Welcom " << name << std::endl;
return 0;
}
// config.yaml
name: Rafal
// 在项目的根目录下
mkdir extern
cd extern
git submodule add https://github.com/jbeder/yaml-cpp
// 如果提示不是git仓库,就git init
//CMakeLists.txt
add_executable(GitSubMain main.cpp)
configure_file(config.yaml config.yaml COPYONLY)
add_subdirectory(extern/yaml-cpp)
target_link_libraries(GitSubMain PRIVATE yaml-cpp)
值得一提的是,这里yaml-cpp
的作者遵循了第三章的实践,将公共头文件存储在一个单独的目录中——<project-name>/include/<project-name>
,这允许库的使用者包含"yaml-cpp/yaml.h"
,这样的命名方法对于查找头文件非常有用,能够从名称中看出是哪个库的提供的头文件。
综上,这种方式需要用户自己手动克隆子模块。更糟糕的是,没有考虑到用户可能已经在系统中安装了这个库。
二、自动初始化Git子模块
由于存在用户可能已经安装了yaml-cpp
模块,要首先通过find_package
寻找该模块。
add_executable(GitSubMain main.cpp)
find_package(yaml-cpp QUIET)
if(NOT yaml-cpp_FOUND)
message("yaml-cpp is not found.")
execute_process(
COMMAND git submodule update --init -- extern/yaml-cpp
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_subdirectory(extern/yaml-cpp)
endif()
target_link_directories(GitSubMain PRIVATE yaml-cpp)
7.5.2 不使用Git的项目克隆依赖项
增加了一个找Git的判断逻辑