「CMake」是一个跨平台的C++项目管理解决方案,它可以帮助用户方便地管理大型C++项目,支持生成不同平台的Makefile。本系列文章可以帮助你快速掌握CMake,所有内容来源于Mastering Cmake

本篇文章为该系列的第四篇。我们主要介绍CMake的一些关键概念。

学习CMake的过程中,你可能已经遇到了很多不同的概念,比如目标(targets),生成器(generators)和命令(commands)。理解这些概念可以让你高效地编写CMakeLists文件。许多CMake中的对象比如目标,目录和源文件都拥有属性。属性是一个特定对象所拥有的的键值对。访问属性最通用的方法是通过set_propertyget_property命令。这些命令可以让你设置和获取一个CMake对象的属性。你可以查看cmake-properties手册,其中列出了所有支持的属性。在命令行中,你可以运行对CMake命令附加--help-property-list选项来查看所有CMake支持的属性。

目标

CMake中最重要的概念应该是「目标」。目标代表着可执行文件,库,以及由CMake构建的工具。所有的add_libraryadd_executableadd_custom_target命令都会创建一个目标。比如,下列命令会使用foo1.cfoo2.c两个源文件来创建一个名为“foo”的静态库,该静态库是一个目标。

add_library(foo STATIC foo1.c foo2.c)

接下来在项目中的任何地方都可以通过“foo”这个名称来使用该静态库了,CMake会在该库被使用时自动将其展开。库可以被声明为特别的类型比如STATICSHAREDMODULE,或者特可以不声明。STATIC表明这个库必须作为静态库构建。类似的,SHARED表明它必须作为共享库构建,MODULE表示这个库必须已经创建,这样才能动态加载到一个可执行文件中。模块库在许多平台上被实现为共享库,但不是所有平台。因此,CMake不允许其他目标链接到模块。如果没有指定这些选项,则表示库可以构建为共享或静态库。在这种情况下,CMake使用变量BUILD_SHARED_LIBS的设置来确定库应该是SHARED还是STATIC。如果未设置,则CMake默认构建静态库。

同样,可执行文件也有一些选项。默认情况下,可执行文件将是具有主入口点的传统控制台应用程序。在Windows系统上可以指定一个WIN32选项来指定WinMain函数作为入口点,而在非Windows系统上则保留main函数作为入口点。

除了存储其类型外,目标还包含一些通用属性。可以使用set_target_propertiesget_target_property命令或更通用的set_propertyget_property命令来设置和检索这些属性。一个比较有用的属性是link_flags,用于为特定目标指定其他链接标志。目标存储了它们链接到的库列表,这些库是使用target_link_libraries命令设置的。传递到此命令的名称可以是库,通往库的完整路径,也可以是从add_library命令中产生的库名称。目标还存储链接时使用的链接目录,以及在构建后执行的自定义命令。

使用依赖

CMake还会在链接库的目标中传递“使用依赖(usage requirements)”。使用依赖影响着源文件的编译。它们由链接目标中定义的属性所指定。

例如,当链接一个库时,如果你想要要指定包含目录,那么你可以使用以下命令:

add_library(foo foo.cxx)
target_include_directories(foo PUBLIC
"${CMAKE_CURRENT_BINARY_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}"
)

现在任何链接目标foo的内容都会自动包含foo的二进制和源码目录。包含目录的顺序会和target_include_directories命令调用时指定的顺序保持一致。

对于CMake创造的所有库和可执行文件,它们会追踪所有使用target_link_libraries命令

所指定的依赖库。例如:

add_library(foo foo.cxx)
target_link_libraries(foo bar)

add_executable(foobar foobar.cxx)
target_link_libraries(foobar foo)

上述命令中,尽管“foobar”只是明确指定了要链接“foo”这个库,但由于依赖的传递性,CMake最终会将“foo”和“bar”两个库链接到“foobar”中。

为目标指定优化或调试库

在Windows平台上,用户通常要求将调试库链接到调试库中,优化过的库链接到优化过的库中。CMake可以通过 target_link_libraries 命令来实现这个要求,只需传入一个可选的选项debugoptimized。如果一个库处理过程使用了调试或优化选项,那么该库将与适当的库类型进行链接。例如:

add_executable(foo foo.c)
target_link_libraries(foo debug libdebug optimized libopt)

在啊这个例子中,foo将会在调试构建中和libdebug链接,而在优化构建中将会和libopt链接。

对象库

大型项目经常将源文件分为组,可能在单独的子目录中,分别需要不同的包含目录和预处理器定义。对于这个用例,CMake开发了对象库的概念。

对象库是编译成目标文件的源文件的集合,目标文件没有链接到库文件或制作成存档。相反,由 add_libraryadd_executable创建的其他目标可以使用表达式$<TARGET objects:name>作为源引用对象,其中name是由add_library命令创建的目标。例如:

add_library(A OBJECT a.cpp)
add_library(B OBJECT b.cpp)
add_library(Combined $<TARGET_OBJECTS:A> $<TARGET_OBJECTS:B>)

将会在Combined库中包含A和B对象文件。对象库可能仅包含源文件编译成的对象文件。

源文件

源文件结构在许多方面与目标文件相似。它存储文件名、扩展名和许多与源文件相关的通用属性。与目标一样,你可以使用set_source_files_propertiesget_source_file_property命令,或更通用的命令版本来设置和获取属性。

目录,测试,和属性

除了目标和源文件之外,你可能偶尔也会使用其他对象,比如目录和测试。也可以采用setting和getting形式来获取属性(例如,set_directory_propertiesset_tests_properties)。