
引言
本文讨论了在大量C / C ++项目中使用的CMake构建系统的用法。 强烈建议您阅读本手册的第一部分,以免误解了CMake语言的语法,该语法在整篇文章中都明确提到。
CMake发布
以下是使用应练习的CMake语言的示例。 通过修改现有命令并添加新命令来试验源代码。 要运行这些示例,请从官方网站安装CMake。
工作原理
CMake构建系统是其他依赖于平台的实用程序(例如Ninja或Make )的包装。 因此,在组装过程本身中,无论听起来多么矛盾,它都不会直接参与。
CMake构建系统接受CMakeLists.txt
文件并以正式的CMake语言描述构建规则,然后在平台上接受的同一目录中生成中间构建文件和本机构建文件。
生成的文件将包含系统实用程序,目录和编译器的特定名称,而CMake命令仅使用编译器的抽象概念,并不依赖于平台相关的工具,这些工具在不同的操作系统上有很大的不同。
检查CMake版本
cmake_minimum_required
命令检查CMake的运行版本:如果它小于指定的最小值,则CMake终止并出现致命错误。 一个示例,该示例演示在任何CMake文件的开始处此命令的典型用法:
如注释中所述, cmake_minimum_required
命令设置所有兼容性标志(请参阅cmake_policy
)。 一些开发人员有意设置较低版本的CMake,然后手动调整功能。 这使您可以同时支持较旧版本的CMake,并且在某些地方可以利用新功能。
在任何CMakeLists.txt
的开头CMakeLists.txt
应该与项目团队一起指定项目特征,以便使用集成环境和其他开发工具进行更好的设计。
值得注意的是,如果省略LANGUAGES
关键字,则默认语言为C CXX
。 您还可以通过将关键字NONE
编写为语言列表来禁用任何语言的指示,或者只留下一个空列表。
运行脚本文件
include
命令用指定文件的代码替换其调用行,其作用类似于C / C ++预处理程序include
命令。 本示例MyCMakeScript.cmake
描述MyCMakeScript.cmake
命令运行脚本文件MyCMakeScript.cmake
:
message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]")
在此示例中,第一条消息将通知尚未定义TEST_VARIABLE
变量,但是,如果MyCMakeScript.cmake
脚本MyCMakeScript.cmake
此变量,则第二条消息将已通知测试变量的新值。 因此, include
命令包含的脚本文件不会创建自己的作用域,这在上一篇文章的注释中已提到 。
可执行文件的编译
add_executable
命令从源列表中使用给定名称编译可执行文件。 重要的是要注意,最终文件名取决于目标平台(例如, <ExecutableName>.exe
或仅<ExecutableName>
)。 调用此命令的典型示例:
图书馆汇编
add_library
命令使用源中指定的视图和名称来编译库。 重要的是要注意,最终的库名称取决于目标平台(例如, lib<LibraryName>.a
或<LibraryName>.lib
)。 调用此命令的典型示例:
- 静态库由
STATIC
关键字定义为第二个参数,并且是在编译时与可执行文件和其他库关联的目标文件的存档; - 动态库由
SHARED
关键字指定为第二个参数,并且是操作系统在程序执行期间加载的二进制库。 - 模块化库由
MODULE
关键字定义为第二个参数,并且是由可执行文件本身使用执行技术加载的二进制库。 - 对象库由
OBJECT
关键字定义为第二个参数,并且是在编译时与可执行文件和其他库关联的一组对象文件。
向目标添加源
在某些情况下,需要向目标多次添加源文件。 为此,提供了target_sources
命令,该命令可以将源多次添加到目标。
target_sources
命令的第一个参数是先前使用add_library
或add_executable
指定的目标的名称,后续参数是要添加的源文件的列表。
重复调用target_sources
将源文件按照被调用的顺序添加到目标中,因此底部的两个代码块在功能上是等效的:
生成的文件
仅在生成阶段才能确定由add_executable
和add_library
生成的输出文件的位置,但是,可以使用确定二进制文件最终位置的几个变量来更改此规则:
可执行文件始终被视为执行目标,静态库被视为归档目标,而模块化库则被视为库目标。 对于“非DLL”平台,动态库被视为库目标,对于“ DLL平台”,动态库被视为执行目标。 此类变量未提供给对象库,因为此类库是在CMakeFiles
目录的肠道中生成的。
重要的是要注意,所有基于Windows的平台,包括Cygwin,都被视为“ DLL平台”。
图书馆布局
target_link_libraries
命令与提供的其他库一起target_link_libraries
库或可执行文件。 该命令的第一个参数是add_executable
或add_library
生成的目标的名称,后续参数是库目标的名称或库的完整路径。 一个例子:
值得注意的是,模块化库不能与可执行文件或其他库链接,因为它们仅供执行技术加载。
与目标合作
正如评论中提到的,CMake中的目标也需要手动操作,但是非常有限。
可以控制旨在设置项目组装过程的目标的属性。 get_target_property
命令get_target_property
target属性get_target_property
值get_target_property
为提供的变量。 本示例在屏幕上显示C_STANDARD
目标的C_STANDARD
属性的值:
set_target_properties
命令将指定的目标属性设置为指定的值。 该命令接受将为其设置属性值的目标列表,然后接受关键字PROPERTIES
,后跟格式为< > < >
:
上面的示例设置了影响编译过程的MyTarget
目标属性,即:在编译MyTarget
目标时MyTarget
CMake将MyTarget
编译器使用C11标准。 此页面上列出了所有已知的目标属性命名。
也可以使用if(TARGET <TargetName>)
构造来验证先前定义的目标:
添加子项目
add_subdirectory
命令提示CMake立即处理指定的子项目文件。 下面的示例演示了所描述机制的应用:
在此示例中, add_subdirectory
命令的第一个参数是add_subdirectory
子项目,第二个参数是可选的,它通知CMake用于包含子项目的生成文件的文件夹(例如CMakeCache.txt
和cmake_install.cmake
)。
值得注意的是,父作用域中的所有变量都由添加的目录继承,并且在此目录中定义和重新定义的所有变量将仅对它可见(如果set
命令参数未指定关键字PARENT_SCOPE
)。 在上一篇文章的评论中提到了此功能。
套餐搜索
find_package
命令查找并加载外部项目的设置。 在大多数情况下,它用于后续链接外部库(例如Boost和GSL) 。 本示例调用描述的命令来搜索GSL库,然后链接:
在上面的示例中, find_package
命令接受包的名称作为find_package
第一个参数,然后接受所需的版本。 如果找不到所需的包装,则REQUIRED
选项需要打印致命错误并终止CMake。 相反的是QUIET
选项,即使未找到软件包,也要求CMake继续工作。
接下来,使用GSL::gsl
变量,使用target_link_libraries
命令将MyExecutable
链接到GSL库,该变量封装了已编译的GSL的位置。
最后,将target_include_directories
命令,将GSL库头文件的位置通知编译器。 请注意, GSL_INCLUDE_DIRS
变量用于GSL_INCLUDE_DIRS
我描述的标头GSL_INCLUDE_DIRS
位置(这是导入的程序包设置的示例)。
如果您指定了QUIET
选项,则可能要检查软件包搜索的结果。 这可以通过检查<PackageName>_FOUND
来完成,该<PackageName>_FOUND
在find_package
命令find_package
后自动确定。 例如,如果您成功将GSL设置导入到项目中,则GSL_FOUND
变量将变为true。
通常, find_package
命令具有两种启动方式:模块化和配置。 上面的示例应用了模块化形式。 这意味着调用该命令时,CMake在CMAKE_MODULE_PATH
目录中搜索形式为Find<PackageName>.cmake
的脚本文件,然后启动它并导入所有必需的设置(在这种情况下,CMake启动了标准的FindGSL.cmake
文件)。
包含标题的方式
您可以使用两个命令: include_directories
和target_include_directories
来告知编译器所包含标头的位置。 您可以决定使用哪一个,但是,值得考虑一下它们之间的一些差异(该想法已在注释中提出 )。
include_directories
命令会影响目录的范围。 这意味着此命令指定的所有标头目录将用于当前CMakeLists.txt
所有目的,以及已处理的子项目(请参见add_subdirectory
)。
target_include_directories
命令target_include_directories
影响第一个参数指定的目标,而不影响其他目标。 下面的示例演示了这两个命令之间的区别:
add_executable(RequestGenerator RequestGenerator.c) add_executable(ResponseGenerator ResponseGenerator.c)
在注释中提到 ,在现代项目中,不希望使用include_directories
和link_libraries
。 另一种选择是target_include_directories
和target_link_libraries
,它们仅对特定目标起作用,而不对整个当前范围起作用。
项目安装
install
命令为您的项目生成安装规则。 此命令能够处理目标,文件,文件夹等。 首先,考虑设定目标。
要设置目标,必须将TARGETS
关键字作为所描述函数的第一个参数传递,然后传递要设置的目标的列表,然后传递DESTINATION
关键字以及将在其中设置这些目标的目录的位置。 此示例演示了一个典型的目标设置:
描述文件安装的过程类似,只是必须指定FILES
而不是TARGETS
关键字。 演示文件安装的示例:
描述文件夹安装的过程类似,除了必须指定DIRECTORY
而不是FILES
关键字。 重要的是要注意,在安装过程中将复制文件夹的全部内容,而不仅仅是其名称。 安装文件夹的示例如下:
完成所有文件的CMake处理后,您可以使用sudo checkinstall
安装所有描述的对象(如果CMake生成Makefile
),或者在支持CMake的集成开发环境中执行此操作。
可视项目示例
如果没有演示使用CMake构建系统的真实示例,本指南将是不完整的。 考虑一个使用CMake作为唯一构建系统的简单项目图:
+ MyProject - CMakeLists.txt - Defines.h - StartProgram.c + core - CMakeLists.txt - Core.h - ProcessInvoker.c - SystemManager.c
主体程序文件CMakeLists.txt
描述了整个程序的编译:首先, add_executable
命令编译可执行文件,然后add_subdirectory
命令,这刺激了子项目的处理,最后,可执行文件链接到已编译的库:
core/CMakeLists.txt
文件由主程序集文件调用,并编译MyProgramCore
静态库,用于与可执行文件链接:
经过一系列的cmake . && make && sudo checkinstall
命令cmake . && make && sudo checkinstall
cmake . && make && sudo checkinstall
CMake构建系统成功完成。 第一个命令开始处理项目根目录中的CMakeLists.txt
文件,第二个命令最终编译所需的二进制文件,第三个命令将编译后的MyProgram
安装到系统中。
结论
现在,您可以编写自己的文件并了解其他人的CMake文件,并且可以在官方网站上详细了解其他机制。
本指南的下一篇文章将重点介绍如何使用CMake测试和创建软件包,并将在一周内发布。 待会见!