cmake 使用
cmake 最新版的安装方法:
1,下载最新版的cmake,如 cmake-3.24.0-rc1-linux-x86_64.sh
2,mkdir /home/hzh/soft/softy/cmake-3.24.0-rc1
3,sh some_path/cmake-3.24.0-rc1-linux-x86_64.sh --prefix=/home/hzh/soft/softy/cmake-3.24.0-rc1 --skip-license
4,sudo ln -s /home/hzh/soft/cmake/bin/cmake /usr/local/bin/cmake
5,cmake --version
1、cmake 显示编译命令:
在顶层CMakeLists.txt
里设置 set(CMAKE_VERBOSE_MAKEFILE ON)
或者 cmake . 再 make VERBOSE=1
或 cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON . 再 make
最好: cmake -DCMAKE_RULE_MESSAGES:BOOL=OFF -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON . 再 make --no-print-directory
最后这种方式可以减少很多无用的信息,如:make[1]: Entering directory
and make[1]: Leaving directory
注意,你可以使用 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 选项来查看最干净的编译选项,它会生成一个 compile_commands.json 文件。
2、cmake 将库加入链接选项:
这样加: target_link_libraries(target_name m), 不过应该在targe申明语句之后加,如:
add_executable(
target_name
main
.c)
3、cmake的执行方式:
可以 cmake . # 其中的 . 表示home directory 为当前目录, 而 binary directory 为执行cmake命令所在的目录(这里也是当前目录)。如果执行 cmake .. 这个命令,则 home directory 为上级目录,binary directory为当前目录。
或者 cmake -H. -Bbuild #其中 -H 是 SetHomeDirectory 的意思,即sets the source directory for the project, -B 表示sets the binary directory 为 build 目录(若目录不存在,会自动创建)
4、cmake项目的编译方式:
可以自己到build目录(也即顶层Makefile所在目录)去执行 make
也可以采用cmake执行编译: cmake --build build -- -j3 # --build告诉cmake执行编译过程,build 告诉cmake编译所需的cmake file在哪个目录
5、在cmake命令行中对CMakeLists.txt定义的 option 进行定义: (这个定义的option可以在configure_file中使用,参见 https://cmake.org/cmake-tutorial/ 的Step2)
若要进行定义某个宏:最标准的方法:cmake -DUSE_MYMATH=ON 不标准的方法: cmake -DUSE_MYMATH=\"\" .
若要取消对某个宏的定义: 最标准的方法:cmake -DUSE_MYMATH=OFF 不标准的方法: cmake -DUSE_MYMATH= 或者 cmake -DUSE_MYMATH=""
6、cmake中设置编译选项:
在cmake脚本中,设置编译选项可以通过add_compile_options
命令,也可以通过set命令修改CMAKE_CXX_FLAGS
或CMAKE_C_FLAGS
。
使用这两种方式在有的情况下效果是一样的,但请注意它们还是有区别的:
add_compile_options
命令添加的编译选项是针对所有编译器的(包括c和c++编译器),而set命令设置CMAKE_C_FLAGS
或CMAKE_CXX_FLAGS
变量则是分别只针对c和c++编译器的。
7、cmake获得某目录所有子目录:
MACRO(SUBDIRLIST result curdir)
FILE(GLOB children RELATIVE ${curdir} ${curdir}/*)
SET(dirlist "")
FOREACH(child ${children})
IF(IS_DIRECTORY ${curdir}/${child})
LIST(APPEND dirlist ${child})
ENDIF()
ENDFOREACH()
SET(${result} ${dirlist})
ENDMACRO()
Example:
SUBDIRLIST(SUBDIRS ${MY_CURRENT_DIR})
then,use foreach to add each subdir
:
FOREACH(subdir ${SUBDIRS})
ADD_SUBDIRECTORY(${subdir})
ENDFOREACH()
8、cmake的library搜索path:
使用 NO_DEFAULT_PATH 和 NO_CMAKE_SYSTEM_PATH 和可以禁止cmake在系统默认路径去搜索library。
9、cmake不能(不推荐)做的事情,及推荐的习惯:
https://cliutils.gitlab.io/modern-cmake/chapters/intro/dodonot.html
do not to do:
- Do not use global functions: This includes
link_directories
,include_libraries
, and similar. - Don't add unneeded PUBLIC requirements: You should avoid forcing something on users that is not required (
-Wall
). Make these PRIVATE instead. - Don't GLOB files: Make or another tool will not know if you add files without rerunning CMake. Note that CMake 3.12 adds a
CONFIGURE_DEPENDS
flag that makes this far better if you need to use it. - Link to built files directly: Always link to targets if available.
- Never skip PUBLIC/PRIVATE when linking: This causes all future linking to be keyword-less.
- 不要使用 file(GLOB headers *.h) 或 file(GLOB sources *.cc) 来获取源代码文件的列表; 见 https://stackoverflow.com/questions/1027247/is-it-better-to-specify-source-files-with-glob-or-each-file-individually-in-cmak/55290546
recommend to do:
- Treat CMake as code: It is code. It should be as clean and readable as all other code.
- Think in targets: Your targets should represent concepts. Make an (IMPORTED) INTERFACE target for anything that should stay together and link to that.
- Export your interface: You should be able to run from build or install.
- Write a Config.cmake file: This is what a library author should do to support clients.
- Make ALIAS targets to keep usage consistent: Using
add_subdirectory
andfind_package
should provide the same targets and namespaces. - Combine common functionality into clearly documented functions or macros: Functions are better usually.
- Use lowercase function names: CMake functions and macros can be called lower or upper case. Always use lower case. Upper case is for variables.
- Use
cmake_policy
and/or range of versions: Policies change for a reason. Only piecemeal set OLD policies if you have to. - 建议直接将每个源代码文件列出来,而不是使用 file(GLOB headers *.h) 来批量查找文件;
10、cmake的调试输出:
$ cmake .. --trace-source=CMakeLists.txt
If you add --trace-expand
, the variables will be expanded into their values.
11、cmake输出所有选项:
mkdir build cd build cmake .. cmake -L #列出所有选项,不包括advance选项 cmake -LA #列出所有选项,包括advance选项 cmake -LAH #列出所有选项,包括advance选项,包括选项的注释/解释 cmake -LH #列出所有选项,不包括advance选项,包括选项的注释/解释
cmake中的option 和 configure_file:
option 选项,让你可以根据选项值进行条件编译。 configure_file 配置文件,让你可以在代码文件中使用CMake中定义的的变量 option
Provides an option that the user can optionally select. option 提供一个用户可以任选的选项。语法如下
option(<option_variable> "help string describing option" [initial value])
Provide an option for the user to select as ON or OFF. If no initial value is provided, OFF is used. option 提供选项让用户选择是 ON 或者 OFF ,如果没有提供初始化值,使用OFF。 也就是说默认的值是OFF。 但是请注意:这个option命令和你本地是否存在编译缓存的关系很大。 所以,如果你有关于 option 的改变,那么请你务必清理 CMakeCache.txt 和 CMakeFiles 文件夹。 还有,请使用标准的 [initial value] 值 ON 或者 OFF。 可以在命令行通过以下的方式设置选项 比如想打开 FOO_ENABLE 选项 -DFOO_ENABLE=ON configure_file
configure_file 的作用是让普通文件也能使用CMake中的变量。 也就是说代码文件中可以使用CMake中的变量。 语法如下:
configure_file(<input> <output> [COPYONLY] [ESCAPE_QUOTES] [@ONLY] [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
Copies an <input> file to an <output> file and substitutes variable values referenced as @VAR@ or ${VAR} in the input file content. Each variable reference will be replaced with the current value of the variable, or the empty string if the variable is not defined. Furthermore, input lines of the form: 拷贝一个 <input>(输入文件) 文件到 <output> (输出文件),并且替换输入文件中被 @VAR@ 或者 ${VAR} 引用的变量值。每一个变量将被替换成当前的变量值(注:CMake中的变量值)或者空串当变量未定义。 输入文件中的
#cmakedefine VAR ...
将被替换为
#define VAR ...
或者
/* #undef VAR */
下面看看各选项的意义 COPYONLY Copy the file without replacing any variable references or other content. This option may not be used with NEWLINE_STYLE. 只拷贝文件,不进行任何的变量替换。这个选项在指定了 NEWLINE_STYLE 选项时不能使用(无效)。 ESCAPE_QUOTES Escape any substituted quotes with backslashes (C-style). 躲过任何的反斜杠(C风格)转义。 注:躲避转义,比如你有个变量在CMake中是这样的 set(FOO_STRING "\"foo\"") 那么在没有 ESCAPE_QUOTES 选项的状态下,通过变量替换将变为 ""foo"",如果指定了 ESCAPE_QUOTES 选项,变量将不变。 @ONLY Restrict variable replacement to references of the form @VAR@. This is useful for configuring scripts that use ${VAR} syntax. 限制变量替换,让其只替换被 @VAR@ 引用的变量(那么 ${VAR} 格式的变量将不会被替换)。这在配置 ${VAR} 语法的脚本时是非常有用的。 NEWLINE_STYLE <style> Specify the newline style for the output file. Specify UNIX or LF for \n newlines, or specify DOS, WIN32, or CRLF for \r\n newlines. This option may not be used with COPYONLY. 指定输出文件中的新行格式。UNIX 和 LF 的新行是 \n ,DOS 和 WIN32 和 CRLF 的新行格式是 \r\n 。 这个选项在指定了 COPYONLY 选项时不能使用(无效)。 Example Consider a source tree containing a foo.h.in file:
#cmakedefine FOO_ENABLE #cmakedefine FOO_STRING "@FOO_STRING@"
An adjacent CMakeLists.txt may use configure_file to configure the header:
option(FOO_ENABLE "Enable Foo" ON) if(FOO_ENABLE) set(FOO_STRING "foo") endif() configure_file(foo.h.in foo.h @ONLY)
This creates a foo.h in the build directory corresponding to this source directory. If the FOO_ENABLE option is on, the configured file will contain:
#define FOO_ENABLE #define FOO_STRING "foo"
Otherwise it will contain:
/* #undef FOO_ENABLE */ /* #undef FOO_STRING */
One may then use the include_directories() command to specify the output directory as an include directory:
include_directories(${CMAKE_CURRENT_BINARY_DIR})
so that sources may include the header as #include <foo.h>.
cmake 的一些example:
https://cmake.org/cmake-tutorial/
https://cliutils.gitlab.io/modern-cmake/chapters/intro/dodonot.html
cmake的一些好资料:
Easily supporting CMake install and find_package(): http://foonathan.net/blog/2016/03/03/cmake-install.html
CMake Useful Variables: https://cmake.org/Wiki/CMake_Useful_Variables
CMake: The Cross Platform Build System: http://clubjuggler.livejournal.com/138364.html
CMake rpath handling : https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling
CMake Cross Compiling: https://cmake.org/Wiki/CMake_Cross_Compiling
CMake FAQ: https://cmake.org/Wiki/CMake_FAQ
CMake 2.8.12 Documentation: https://cmake.org/cmake/help/v2.8.12/cmake.html
find_package与CMake如何查找链接库详解
如果编译软件使用了外部库,事先并不知道它的头文件和链接库的位置。得在编译命令中加上包含它们的查找路径。CMake使用 find_package 命令来解决这个问题。本文讨论了如何在CMake项目中使用外部库,以及如何给没有查找模块的库写一个。
1 FIND_PACKAGE
FIND_PACKAGE( <name> [version] [EXACT] [QUIET] [NO_MODULE] [ [ REQUIRED | COMPONENTS ] [ componets... ] ] )
用来调用预定义在 CMAKE_MODULE_PATH 下的 Find<name>.cmake 模块。
也可以自己定义 Find<name>模块,将其放入工程的某个目录中,通过 SET(CMAKE_MODULE_PATH dir)设置查找路径,供工程FIND_PACKAGE使用。
CMake
会到变量 CMAKE_MODULE_PATH 指示的目录中查找文件 Findname.cmake 并执行。这条命令执行后,
version参数
需要一个版本号,它是正在查找的包应该兼容的版本号(格式是major[.minor[.patch[.tweak]]])。
EXACT选项
要求版本号必须精确匹配。如果在find-module内部对该命令的递归调用没有给定[version]参数,那么[version]和EXACT选项会自动地从外部调用前向继承。对版本的支持目前只存在于包和包之间(详见下文)。
QUIET 参数:
会禁掉包没有被发现时的警告信息。对应于Find<name>.cmake模块中的 NAME_FIND_QUIETLY。
REQUIRED 参数
其含义是指是否是工程必须的,表示如果报没有找到的话,cmake的过程会终止,并输出警告信息。对应于Find<name>.cmake模块中的
NAME_FIND_REQUIRED 变量。
COMPONENTS参数
在REQUIRED选项之后,或者如果没有指定REQUIRED选项但是指定了COMPONENTS选项,在它们的后面可以列出一些与包相关(依赖)的部件清单(components
list)
示例:
FIND_PACKAGE( libdb_cxx REQUIRED)
CMake
会到变量 CMAKE_MODULE_PATH 指示的目录中查找文件 Findlibdb_cxx.cmake 并执行。这条命令执行后,
find_package() 命令会在模块路径中寻找 Find<name>.cmake ,这是查找库的一个典型方式。首先CMake查看${CMAKE_MODULE_PATH} 中的所有目录,然后再查看它自己的模块目录 <CMAKE_ROOT>/share/cmake-x.y/Modules/ 。
find_package命令到底找什么:如果没找到Find<name>.cmake这样的文件,会继续寻找 <Name>Config.cmake 或者 <lower-case-name>-config.cmake ,它们是假定库会安装的文件(但是目前还没有多少库会安装它们)。不做检查,直接包含安装的库的固定值。
前面的称为模块模式,后面的称为配置模式。配置模式的文件的编写见 这里的文档 。可能还会用到 importing and exporting targets 这篇文档。
模块系统好像还没有文档,所以本文主要讨论这方面的内容。
不管使用哪一种模式,只要找到包,就会定义下面这些变量:
<NAME>_FOUND <NAME>_INCLUDE_DIRS or <NAME>_INCLUDES <NAME>_LIBRARIES or <NAME>_LIBRARIES or <NAME>_LIBS <NAME>_DEFINITIONS
这些都在 Find<name>.cmake 文件中。
现在,在你的代码(要使用库 <name> 的代码)的顶层目录中的 CMakeLists.txt 文件中,我们检查变量<NAME>_FOUND 来确定包是否被找到。大部分包的这些变量中的包名是全大写的,如 LIBFOO_FOUND ,有些包则使用包的实际大小写,如 LibFoo_FOUND 。如果找到这个包,我们用 <NAME>_INCLUDE_DIRS 调用 include_directories() 命令,用 <NAME>_LIBRARIES 调用 target_link_libraries() 命令。
这些约定的文档在CMake模块目录中的 readme.txt 文件中。
REQUIRED 和其他可选的 find_package 的参数被 find_package 传给模块,模块由此确定操作。
用户代码总体上应该使用上述的简单调用格式查询需要的包。本命令文档的剩余部分则详述了find_package的完整命令格式以及具体的查询过程。期望通过该命令查找并提供包的项目维护人员,我们鼓励你能继续读下去。
该命令在搜索包时有两种模式:“模块”模式和“配置”模式。当该命令是通过上述的精简格式调用的时候,用的就是模块模式。在该模式下,CMake搜索所有名为Find<package>.cmake的文件,这些文件的路径由安装CMake时指定的CMAKE_MODULE_PATH变量指定。如果查找到了该文件,它会被CMake读取并被处理。该模式对查找包,检查版本以及生成任何别的必须信息负责。许多查找模块(find-module)仅仅提供了有限的,甚至根本就没有对版本化的支持;具体信息查看该模块的文档。如果没有找到任何模块,该命令会进入配置模式继续执行。
完整的配置模式下的命令格式是:
find_package(<package> [version] [EXACT] [QUIET]
[[REQUIRED|COMPONENTS] [components...]] [NO_MODULE]
[NO_POLICY_SCOPE]
[NAMES name1 [name2 ...]]
[CONFIGS config1 [config2 ...]]
[HINTS path1 [path2 ... ]]
[PATHS path1 [path2 ... ]]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[NO_DEFAULT_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_CMAKE_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_PACKAGE_REGISTRY]
[NO_CMAKE_BUILDS_PATH]
[NO_CMAKE_SYSTEM_PATH]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH])
NO_MODULE可以用来明确地跳过模块模式。它也隐含指定了不使用在精简格式中使用的那些选项。
配置模式试图查找一个由待查找的包提供的配置文件的位置。包含该文件的路径会被存储在一个名为<package>_DIR的cache条目里。默认情况下,该命令搜索名为<package>的包。如果指定了NAMES选项,那么其后的names参数会取代<package>的角色。该命令会为每个在names中的name搜索名为<name>Config.cmake或者<name全小写>-config.cmake的文件。
通过使用CONFIGS选项可以改变可能的配置文件的名字。以下描述搜索的过程。如果找到了配置文件,它将会被CMake读取并处理。由于该文件是由包自身提供的,它已经知道包中内容的位置。配置文件的完整地址存储在cmake的变量<package>_CONFIG中。
所有CMake要处理的配置文件将会搜索该包的安装信息,并且将该安装匹配的适当版本号(appropriate version)存储在cmake变量<package>_CONSIDERED_CONFIGS中,与之相关的版本号(associated version)将被存储在<package>_CONSIDERED_VERSIONS中。
如果没有找到包配置文件,CMake将会生成一个错误描述文件,用来描述该问题——除非指定了QUIET选项。如果指定了REQUIRED选项,并且没有找到该包,将会报致命错误,然后配置步骤终止执行。如果设置了<package>_DIR变量被设置了,但是它没有包含配置文件信息,那么CMake将会直接无视它,然后重新开始查找。
如果给定了[version]参数,那么配置模式仅仅会查找那些在命令中请求的版本(格式是major[.minor[.patch[.tweak]]])与包请求的版本互相兼容的那些版本的包。如果指定了EXACT选项,一个包只有在它请求的版本与[version]提供的版本精确匹配时才能被找到。CMake不会对版本数的含义做任何的转换。包版本号由包自带的版本文件来检查。对于一个备选的包配置文件<config-file>.cmake,对应的版本文件的位置紧挨着它,并且名字或者是<config-file>-version.cmake或者是<config-file>Version.cmake。如果没有这个版本文件,那么配置文件就会认为不兼容任何请求的版本。当找到一个版本文件之后,它会被加载然后用来检查(find_package)请求的版本号。版本文件在一个下述变量被定义的嵌套域中被加载:
PACKAGE_FIND_NAME = <package>名字。
PACKAGE_FIND_VERSION = 请求的完整版本字符串
PACKAGE_FIND_VERSION_MAJOR = 如果被请求了,那么它是major版本号,否则是0。
PACKAGE_FIND_VERSION_MINOR = 如果被请求了,那么它是minor版本号,否则是0。
PACKAGE_FIND_VERSION_PATCH = 如果被请求了,那么它是patch版本号,否则是0。
PACKAGE_FIND_VERSION_TWEAK = 如果被请求了,那么它是tweak版本号,否则是0。
PACKAGE_FIND_VERSION_COUNT = 版本号包含几部分,0到4。
版本文件会检查自身是否满足请求的版本号,然后设置了下面这些变量:
PACKAGE_VERSION = 提供的完整的版本字符串。
PACKAGE_VERSION_EXACT = 如果版本号精确匹配,返回true。
PACKAGE_VERSION_COMPATIBLE = 如果版本号相兼容,返回true。
PACKAGE_VERSION_UNSUITABLE = 如果不适合任何版本,返回true。
下面这些变量将会被find_package命令检查,用以确定配置文件是否提供了可接受的版本。在find_package命令返回后,这些变量就不可用了。如果版本可接受,下述的变量会被设置:
<package>_VERSION = 提供的完整的版本字符串。
<package>_VERSION_MAJOR = 如果被请求了,那么它是major版本号,否则是0。
<package>_VERSION_MINOR = 如果被请求了,那么它是minor版本号,否则是0。
<package>_VERSION_PATCH = 如果被请求了,那么它是patch版本号,否则是0。
<package>_VERSION_TWEAK = 如果被请求了,那么它是tweak版本号,否则是0。
<package>_VERSION_COUNT = 版本号包含几部分,0到4。
然后,对应的包配置文件才会被加载。当多个包配置文件都可用时,并且这些包的版本文件都与请求的版本兼容,选择哪个包将会是不确定的。不应该假设cmake会选择最高版本或者是最低版本。(以上的若干段是对find_package中版本匹配步骤的描述,并不需要用户干预——译注。)
配置模式提供了一种高级接口和搜索步骤的接口。这些被提供的接口的大部分是为了完整性的要求,以及在模块模式下,包被find-module加载时供内部使用。大多数用户仅仅应该调用:
find_package(<package> [major[.minor]] [EXACT] [REQUIRED|QUIET])
来查找包。鼓励那些需要提供CMake包配置文件的包维护人员应该命名这些文件并安装它们,这样下述的整个过程将会找到它们而不需要使用附加的选项。
find_package命令在什么目录结构里找它需要的文件:
CMake为查找包描述文件构造了一组可能的安装前缀,即会在这些目录下去寻找包描述文件。在每个前缀下,若干个目录会被搜索,用来查找配置文件。下述的表格展示了待搜索的路径。每个条目都是专门为Windows(W),UNIX(U)或者Apple(A)约定的安装树指定的。
<prefix>/ (W) <prefix>/(cmake|CMake)/ (W) <prefix>/<name>*/ (W) <prefix>/<name>*/(cmake|CMake)/ (W) <prefix>/(share|lib)/cmake/<name>*/ (U) <prefix>/(share|lib)/<name>*/ (U) <prefix>/(share|lib)/<name>*/(cmake|CMake)/ (U)
其中prefix除了默认值之外,还可以通过 CMAKE_PREFIX_PATH 等等设置 prefix,见下面的介绍。
在支持OS X平台和Application Bundles的系统上,包含配置文件的框架或者bundles会在下述的路径中被搜索:
<prefix>/<name>.framework/Resources/ (A)
<prefix>/<name>.framework/Resources/CMake/ (A)
<prefix>/<name>.framework/Versions/*/Resources/ (A)
<prefix>/<name>.framework/Versions/*/Resources/CMake/ (A)
<prefix>/<name>.app/Contents/Resources/ (A)
<prefix>/<name>.app/Contents/Resources/CMake/ (A)
在所有上述情况下,<name>是区分大小写的,并且对应于在<package>或者由NAMES给定的任何一个名字。
这些路径集用来与那些在各自的安装树上提供了配置文件的工程协作。上述路径中被标记为(W)的是专门为Windows上的安装设置的,其中的<prefix>部分可能是一个应用程序的顶层安装路径。那些被标记为(U)的是专门为UNIX平台上的安装设置的,其中的<prefix>被多个包共用。这仅仅是个约定,因此,所有(W)和(U)路径在所有平台上都仍然会被搜索。那些被标记为(A)的路径是专门为Apple平台上的安装设置的。CMake变量CMAKE_FIND_FRAMEWORK和CMAKE_FIND_APPBUNDLE确定了偏好的顺序,如下所示:
安装前缀是通过以下步骤被构建出来的。如果指定了NO_DEFAULT_PATH选项,所有NO_*选项都会被激活。
find_package 设置在哪个目录里去找,即设置prefix 目录路径:
1、搜索在cmake特有的cache变量中指定的搜索路径。这些变量是为了在命令行中用-DVAR=value选项指定而设计的。通过指定NO_CMAKE_PATH选项可以跳过该搜索路径。搜索路径还包括:
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
2、搜索cmake特有的环境变量。这些变量是为了在用户的shell配置中进行配置而设计的。通过指定NO_CMAKE_ENVIRONMENT_PATH选项可以跳过该路径。搜索的路径包括:
<package>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
3、搜索HINTS选项指定的路径。这些路径应该是由操作系统内省时计算产生的,比如由其它已经找到的项的位置而提供的线索。硬编码的参考路径应该在PATHS选项中指定。
4、搜索标准的系统环境变量。如果指定了NO_SYSTEM_ENVIRONMENT_PATH选项,这些路径会被跳过。以"/bin"或"/sbin"结尾的路径条目会被自动转换为它们的父路径。搜索的路径包括:
PATH
5、搜索在CMake GUI中最新配置过的工程的构建树。可以通过设置NO_CMAKE_BUILDS_PATH选项来跳过这些路径。这是为了在用户正在依次构建多个相互依赖的工程时而准备的。
6、搜索存储在CMake用户包注册表中的路径。通过设置NO_CMAKE_PACKAGE_REGISTRY选项可以跳过这些路径。当CMake嗲用export(PACKAGE<name>)配置一个工程时,这些路径会被存储在注册表中。参见export(PACKAGE)命令的文档阅读更多细节。
7、搜索在当前系统的平台文件中定义的cmake变量。可以用NO_CMAKE_SYSTEM_PATH选项跳过这些路径。
CMAKE_SYSTEM_PREFIX_PATH
CMAKE_SYSTEM_FRAMEWORK_PATH
CMAKE_SYSTEM_APPBUNDLE_PATH
8、搜索由PATHS选项指定的路径。这些路径一般是硬编码的参考路径。
在Darwin或者支持OS X 框架的系统上,cmake变量CMAKE_FIND_FRAMEWORK可以用来设置为空,或者下述值之一:
"FIRST" - 在标准库或头文件之前查找框架。在Darwin系统上这是默认选项。
"LAST" - 在标准库或头文件之后查找框架。
"ONLY" - 仅仅查找框架。
"NEVER" - 从不查找框架。
在Darwin或者支持OS X Application Bundles的系统,cmake变量CMAKE_FIND_APPBUNDLE可以被设置为空或者下面这些值中的一个:
"FIRST" - 在标准库或头文件之前查找application bundles。在Darwin系统上这是默认选项。
"LAST" - 在标准库或头文件之后查找application bundles。
"ONLY" - 仅仅查找application bundles。
"NEVER" - 从不查找application bundles。
CMake变量CMAKE_FIND_ROOT_PATH指定了一个或者多个优先于其他搜索路径的搜索路径。该变量能够有效地重新定位在给定位置下进行搜索的根路径。该变量默认为空。当使用交叉编译时,该变量十分有用:用该变量指向目标环境的根目录,然后CMake将会在那里查找。默认情况下,在CMAKE_FIND_ROOT_PATH中列出的路径会首先被搜索,然后是“非根”路径。该默认规则可以通过设置CMAKE_FIND_ROOT_PATH_MODE_LIBRARY做出调整。在每次调用该命令之前,都可以通过设置这个变量来手动覆盖默认行为。如果使用了NO_CMAKE_FIND_ROOT_PATH变量,那么只有重定位的路径会被搜索。
默认的搜索顺序的设计逻辑是按照使用时从最具体到最不具体。通过多次调用find_library命令以及NO_*选项,可以覆盖工程的这个默认顺序:
find_library(<VAR> NAMES name PATHS paths... NO_DEFAULT_PATH)
find_library(<VAR> NAMES name)
只要这些调用中的一个成功返回,结果变量就会被设置并且被存储到cache中;这样随后的调用都不会再行搜索。如果那找到的库是一个框架,VAR将会被设置为指向框架“<完整路径>/A.framework” 的完整路径。当一个指向框架的完整路径被用作一个库文件,CMake将使用-framework A,以及-F<完整路径>这两个选项将框架连接到目标上。
参见cmake_policy()命令的文档中关于NO_POLICY_SCOPE选项讨论。
find_package 找到后我们会得到什么:
if the package has been found, a set of variables will be defined:
- _FOUND
- _INCLUDE_DIRS or _INCLUDES
- _LIBRARIES or _LIBRARIES or _LIBS
- _DEFINITIONS
All this takes place in the Find.cmake file.
为了能支持各种常见的库和包,CMake自带了很多模块。可以通过命令 cmake --help-module-list (输入cmake --help,然后双击Tab会有命令提示)得到你的CMake支持的模块的列表:
cmake version 2.8.12.2
AddFileDependencies
BundleUtilities
CMakeAddFortranSubdirectory
CMakeBackwardCompatibilityCXX
CMakeDependentOption
CMakeDetermineVSServicePack
CMakeExpandImportedTargets
CMakeFindFrameworks
CMakeFindPackageMode
CMakeForceCompiler
CMakeGraphVizOptions
CMakePackageConfigHelpers
CMakeParseArguments
CMakePrintHelpers
CMakePrintSystemInformation
CMakePushCheckState
CMakeVerifyManifest
CPack
CPackBundle
CPackComponent
CPackCygwin
CPackDMG
CPackDeb
CPackNSIS
CPackPackageMaker
CPackRPM
CPackWIX
CTest
CTestScriptMode
CTestUseLaunchers
CheckCCompilerFlag
CheckCSourceCompiles
CheckCSourceRuns
CheckCXXCompilerFlag
CheckCXXSourceCompiles
CheckCXXSourceRuns
CheckCXXSymbolExists
CheckFortranFunctionExists
CheckFunctionExists
CheckIncludeFile
CheckIncludeFileCXX
CheckIncludeFiles
CheckLanguage
CheckLibraryExists
CheckPrototypeDefinition
CheckStructHasMember
CheckSymbolExists
CheckTypeSize
CheckVariableExists
Dart
DeployQt4
Documentation
ExternalData
ExternalProject
FeatureSummary
FindALSA
FindASPELL
FindAVIFile
FindArmadillo
FindBISON
FindBLAS
FindBZip2
...
...
或者直接查看模块路径。比如Ubuntu Linux上,模块的路径是 ls /usr/share/cmake/Modules/:
AddFileDependencies.cmake CPackDeb.cmake FindOpenThreads.cmake
AutomocInfo.cmake.in CPackNSIS.cmake FindPHP4.cmake
BasicConfigVersion-AnyNewerVersion.cmake.in CPackPackageMaker.cmake FindPNG.cmake
BasicConfigVersion-ExactVersion.cmake.in
CPackRPM.cmake FindPackageHandleStandardArgs.cmake
BasicConfigVersion-SameMajorVersion.cmake.in CPackWIX.cmake FindPackageMessage.cmake
BundleUtilities.cmake CPackZIP.cmake FindPerl.cmake
CMake.cmake CTest.cmake FindPerlLibs.cmake
CMakeASM-ATTInformation.cmake CTestScriptMode.cmake FindPhysFS.cmake
CMakeASMCompiler.cmake.in CTestTargets.cmake FindPike.cmake
CMakeASMInformation.cmake CTestUseLaunchers.cmake FindPkgConfig.cmake
CMakeASM_MASMInformation.cmake CheckCCompilerFlag.cmake FindPostgreSQL.cmake
CMakeASM_NASMInformation.cmake CheckCSourceCompiles.cmake FindProducer.cmake
CMakeAddFortranSubdirectory CheckCSourceRuns.cmake FindProtobuf.cmake
FindBZip2.cmake
...
...
让我们以bzip2库为例。CMake中有个 FindBZip2.cmake 模块。只要使用 find_package(BZip2) 调用这个模块,cmake会自动给一些变量赋值,然后就可以在CMake脚本中使用它们了。变量的列表可以查看cmake模块文件,或者使用命令 cmake --help-module FindBZip2 :
cmake version 2.8.12.2
FindBZip2
Try to find BZip2
Once done this will define
BZIP2_FOUND - system has BZip2
BZIP2_INCLUDE_DIR - the BZip2 include directory
BZIP2_LIBRARIES - Link these to use BZip2
BZIP2_NEED_PREFIX - this is set if the functions are prefixed with BZ2_
BZIP2_VERSION_STRING - the version of BZip2 found (since CMake 2.8.8)
Defined in: /usr/share/cmake-2.8/Modules/FindBZip2.cmake
比如一个使用bzip2的简单程序,编译器需要知道 bzlib.h 的位置,链接器需要找到bzip2库(动态链接的话,Unix上是 libbz2.so 类似的文件,Windows上是 libbz2.dll )。
可以用 cmake 和 make VERBOSE=1 来验证传给编译器和链接器的flag是否正确。也可以用ldd或者dependency walker之类的工具在编译后验证 helloworld 链接的文件。
假设你想要使用LibXML++库。在写本文时,CMake还没有一个libXML++的查找模块。但是可以在网上搜索到一个( FindLibXML++.cmake )。在 CMakeLists.txt 中写:
如果包是可选的,可以忽略 REQUIRED 关键字,通过 LibXML++_FOUND 布尔变量来判断是否找到。检测完所有的库后,对于链接目标有:
为了能正常的工作,需要把 FindLibXML++.cmake 文件放到CMake的模块路径(/usr/share/cmake/Modules/)。因为CMake还不包含它,需要在项目中指定。在自己的项目根目录下创建一个 cmake/Modules/ 文件夹,并且在主 CMakeLists.txt 中包含下面的代码:
把刚才的需要用到的CMake模块放到这个文件夹下。
一般来说就是这样。有些库可能还需要些其他的什么,所以要再看一下 FindSomething.cmake 文件的文档。
有些库不是一个整体,还包含一些依赖的库或者组件。一个典型的例子是Qt库,它其中包含QtOpenGL和QtXml组件。使用下面的 find_package 命令来使用这些组件:
如果包是可选的,这里同样可以忽略 REQUIRED 关键字。这时可以使用 <PACKAGE>_<COMPONENT>_FOUND 变量(如Qt_QtXml_FOUND )来检查组件是否被找到。下面的 find_package 命令是等价的:
如果包中的组件有些是必需的,有些不是,可以调用 find_package 两次:
或者也可以不加 REQUIRED 关键字用 find_package 同时查找全部组件,然后再显式地检查必需的组件:
pkg-config是个用来帮助构建的工具,它基于记录库文件和头文件位置的 .pc 文件。主要用在类Unix系统上。可以在pkg-config的网站 找到更多的信息。CMake可以利用pkg-config,可以在CMake的模块目录下的 FindPkgConfig.cmake 文件中找到相关的文档。这在当你处理一个没有cmake脚本的库的时候,或者遇到CMake的查找脚本失效的情况,非常有帮助。
但是,直接使用pkg-config的结果需要非常小心。一个主要原因是对于ccmake手动定义的库路径,可能覆盖到或者发生冲突。此外,也有可能pkg-config提供了错误的信息(错误的编辑器等)。对于这些情况,让CMake不依赖pkg-config做检测,而只用pkg-config作为查找路径的提示。
首先,注意传给 find_package 的名字或者前缀,是用于全部变量的部分文件名和前缀。这很重要,名字必须完全匹配。不幸的是很多情况下,即使是CMake自带的模块,也有不匹配的名字,导致各种问题。
模块的基本操作应该大体按下面的顺序:
- 使用 find_package 检测库依赖的其他的库
- 需要转发 QUIETLY 和 REQUIRED 参数(比如,如果当前的包是 REQUIRED 的,它的依赖也应该是)
- 可选地使用pkg-config来检测 include/library 的路径(如果pkg-config可用的话)
- 分别使用 find_path 和 find_library 寻找头文件和库文件
- pkg-config提供的路径只用来作为查找位置的提示
- CMake也有很多其他查找路径是写死的
- 结果应该保存在 <name>_INCLUDE_DIR 和 <name>_LIBRARY 变量中(注意不是复数形式)
- 设置 <name>_INCLUDE_DIRS 到 <name>_INCLUDE_DIR <dependency1>_INCLUDE_DIRS ...
- 设置 <name>_LIBRARIES 到 <name>_LIBRARY <dependency1>_LIBRARIES ...
- 依赖使用复数形式,包自身使用 find_path 和 find_library 定义的单数形式
- 调用 find_package_handle_standard_args() 宏来设置 <name>_FOUND 变量,并打印一条成功或者失败的消息
# - Try to find LibXml2
# Once done this will define
# LIBXML2_FOUND - System has LibXml2
# LIBXML2_INCLUDE_DIRS - The LibXml2 include directories
# LIBXML2_LIBRARIES - The libraries needed to use LibXml2
# LIBXML2_DEFINITIONS - Compiler switches required for using LibXml2
find_package(PkgConfig)
pkg_check_modules(PC_LIBXML QUIET libxml-2.0)
set(LIBXML2_DEFINITIONS ${PC_LIBXML_CFLAGS_OTHER})
find_path(LIBXML2_INCLUDE_DIR libxml/xpath.h
HINTS ${PC_LIBXML_INCLUDEDIR} ${PC_LIBXML_INCLUDE_DIRS}
PATH_SUFFIXES libxml2 )
find_library(LIBXML2_LIBRARY NAMES xml2 libxml2
HINTS ${PC_LIBXML_LIBDIR} ${PC_LIBXML_LIBRARY_DIRS} )
set(LIBXML2_LIBRARIES ${LIBXML2_LIBRARY} )
set(LIBXML2_INCLUDE_DIRS ${LIBXML2_INCLUDE_DIR} )
include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LIBXML2_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(LibXml2 DEFAULT_MSG
LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR)
mark_as_advanced(LIBXML2_INCLUDE_DIR LIBXML2_LIBRARY )
(这一段看不懂,应该是排版错误,是5.2的。
第一行包含了LibFindMacros。因为当前CMake中并没有,所以要想生效,就必须把 LibFindMacros.cmake 文件放到模块路径下。)
然后是实际的检测。给 find_path 和 find_library 提供一个变量名作为第一个参数。如果你需要多个 include 路径,用不同的变量名多次调用 find_path , find_library 类似。
NAMES 指定目标的一个或多个名字,只要匹配上一个,就会选中它。在 find_path 中应该使用主头文件或者C/C++代码导入的文件。也有可能会包含目录,比如 alsa/asound.h,它会使用 asound.h 所在文件夹的父目录作为结果。
PATHS 用来给CMake提供额外的查找路径,他不应该用于定义pkg-config以外的东西(CMake有自己的内置默认值,如果需要可以通过各种配置变量添加更多)。如果你不使用它,忽略这部分内容。
PATH_SUFFIXES 对于某些系统上的库很有用,这类库把它们的文件放在类似 /usr/include/ExampleLibrary-1.23/ExampleLibrary/main.h 这样的路径。这种情况你可以使用 NAMES ExampleLibrary/main.h PATH_SUFFIXESExampleLibrary-1.23 。可以指定多个后缀,CMake会在所有包含的目录和主目录逐一尝试,也包括没有后缀的情况。
库名不包括UNIX系统上使用的前缀,也不包括任何文件扩展名或编译器标准之类的,CMake会不依赖平台地检测它们。如果库文件名中有库的版本号,那么它仍然需要。
5.2 使用LibFindMacros
有一个 LibFindMacros.cmake 文件,用来便于写查找模块。它包含对于每个库都相同的各种 libfind 宏。使用它的脚本看起来像这样:
# - Try to find ImageMagick++
# Once done, this will define
#
# Magick++_FOUND - system has Magick++
# Magick++_INCLUDE_DIRS - the Magick++ include directories
# Magick++_LIBRARIES - link these to use Magick++
include(LibFindMacros)
# Dependencies
libfind_package(Magick++ Magick)
# Use pkg-config to get hints about paths
libfind_pkg_check_modules(Magick++_PKGCONF ImageMagick++)
# Include dir
find_path(Magick++_INCLUDE_DIR
NAMES Magick++.h
PATHS ${Magick++_PKGCONF_INCLUDE_DIRS}
)
# Finally the library itself
find_library(Magick++_LIBRARY
NAMES Magick++
PATHS ${Magick++_PKGCONF_LIBRARY_DIRS}
)
# Set the include dir variables and the libraries and let libfind_process do the rest.
# NOTE: Singular variables for this library, plural for libraries this this lib depends on.
set(Magick++_PROCESS_INCLUDES Magick++_INCLUDE_DIR Magick_INCLUDE_DIRS)
set(Magick++_PROCESS_LIBS Magick++_LIBRARY Magick_LIBRARIES)
libfind_process(Magick++)
第一行包含了LibFindMacros。因为当前CMake中并没有,所以要想生效,就必须把 LibFindMacros.cmake 文件放到模块路径下。
libfind_pkg_check_modules 是CMake自己的pkg-config模块的一个用来简化的封装。你不用再检查CMake的版本,加载合适的模块,检查是否被加载,等等。参数和传给 pkg_check_modules 的一样:先是待返回变量的前缀,然后是包名(pkg-config的)。这样就定义了 <prefix>_INCLUDE_DIRS 和其他的这种变量。
CMake的变量系统要比初看起来的要复杂得多。有些变量做了缓存。做了缓存的变量有内部的(不能用ccmake编辑)和外部的(可以被ccmake修改)。另外,外部变量只能在ccmake的高级模式可见。
默认情况下,所有变量都是不缓存的。
为了避免每次执行时都重复检测全部的库,更为了允许用户在ccmake中设置include目录和库,需要支持缓存。幸运的是,这已经被 find_path 和 find_library 支持,它们可以缓存它们的变量。如果变量已经设置为有效值(比如不是 -NOTFOUND 或者未定义),这些函数将什么也不做,保持旧值。类似地, pkg_check_modules 支持结果的内部缓存,因此不需要每次都再调用pkg-config。
另一方面,查找模块的输出值( <name>_FOUND, <name>_INCLUDE_DIRS 和 <name>_LIBRARIES )不应该被缓存,否则修改其他缓存的变量就不能改变输出,这显然是不期望的。
<name>_FIND_REQUIRED 或 <name>_FIND_QUIETLY ,因此 find_package 的参数 QUIET 和 REQUIRED 没有效果
- 没有设置 <name>_INCLUDE_DIRS 和 <name>_LIBRARIES ,只有单数形式的可用
8 链接
我自己的一个演示项目:
(附注:对于如何组织工程目录,更好的示例请阅读 https://cliutils.gitlab.io/modern-cmake/chapters/basics/structure.html )
./
├── build/
├── CMakeLists.txt
├── hzh/
│ ├── CMakeLists.txt
│ └── main.c
└── src/
├── CMakeLists.txt
└── hzh.c
主目录下的 ./CMakeList.txt :
cmake_minimum_required(VERSION 3.3)
PROJECT(HELLO) ADD_SUBDIRECTORY(hzh) ADD_SUBDIRECTORY(src) #这行是因为 main 入口函数是在 hzh/main.c 下定义的,而 ADD_EXECUTABLE 有必须要一个source file,所以我们在binary目录创建了一个欺骗它的file,让它编译通过,内容可以随便写 file(WRITE ${PROJECT_BINARY_DIR}/main.c "int main() { return 0; }") ADD_EXECUTABLE(main ${PROJECT_BINARY_DIR}/main.c) target_link_libraries(main t1 hzh)
./hzh/main.c :
void f();
void a();
void a() { f(); }
void main() { a(); }
./hzh/CMakeLists.txt :
ADD_LIBRARY(t1 main.c)
./src/hzh.c :
#include <stdio.h>
void f();
void f() {printf("%s:%s:%d\n", __FILE__, __func__, __LINE__);}
src/CMakeLists.txt :
add_library(hzh hzh.c)
另外,你可以将主目录下的 CMakeLists.txt 改成下面这样来完成一个完整的可执行文件的编译:
cmake_minimum_required(VERSION 3.3) PROJECT(HELLO) ADD_SUBDIRECTORY(hzh) ADD_SUBDIRECTORY(src) ADD_EXECUTABLE(main hzh/main.c) target_link_libraries(main hzh)
cmake的示例与问题还可以见:
https://stackoverflow.com/questions/54473851/cmake-from-scratch-for-a-multi-directory-project
大致是:
1、你不必在每个目录里都创建一个CMakeLists.txt,那些不是模块的目录,不需要CMakeLists.txt,及子目录可能只是某模块一部分代码的独立存放空间。
2、不要使用 file(GLOB 来查找文件。
3、不需要为目标TARGET指定其需要的.h文件列表,因为源文件里有,但还是建议指定头文件,这样的话对ide方便集成。
支付宝扫一扫捐赠
![支付宝扫一扫捐赠](https://images.cnblogs.com/cnblogs_com/welhzh/2159800/o_220512091701_hzh_alipay.jpg)
微信公众号: 共鸣圈
欢迎讨论,邮件: 924948$qq.com 请把$改成@
QQ群:263132197
QQ: 924948