如何使用 CMake?

众所周知,作为一个初学者很难获得任何有关 CMake 的有用信息。到目前为止,我已经看到了一些关于如何设置一些非常基本的项目或其他项目的教程。然而,这些都无法解释 anything背后的原因,因为它们总是留下许多空洞需要填补。

在 CMakeList 刻薄上调用 CMake 是什么?它应该在每个构建树中被调用一次吗?如果每个构建都使用来自同一源的相同 CMakeLists.txt 文件,我如何为它们使用不同的设置? 为什么每个子目录都需要自己的 CMakeList 文件?在 CMakeLists.txt 上使用 CMake 而不是在项目的根目录中使用 CMake 是否有意义?如果是,在什么情况下? 指定如何在自己的子目录中从 CMakeList 文件构建可执行文件或库与在所有源的根目录中从 CMakeList 文件构建可执行文件或库有什么区别? 我是否可以为 Eclipse 创建一个项目,为 VisualStudio 创建另一个项目,只是在调用 CMake 时更改 -G选项?真的是这么用的吗?

到目前为止,我所看到的教程、文档页面或问题/答案都没有提供任何有用的见解来帮助理解如何使用 CMake。这些例子并不全面。不管我读了什么教程,我总觉得我错过了一些重要的东西。

There are many questions asked by CMake newbies like me that don't ask this explicitly, but that make obvious the fact that, as newbs, we have no idea how to deal with CMake or what to make of it.

31749 次浏览

CMake 是用来做什么的?

根据维基百科:

CMake is [...] software for managing the build process of software 使用独立于编译器的方法 目录层次结构和依赖于多个 它与本机构建环境结合使用 such as make, Apple's Xcode, and Microsoft Visual Studio.

使用 CMake,您不再需要维护特定于编译器/构建环境的单独设置。您具有 配置,这适用于 many环境。

CMake 可以从 一样文件生成 Microsoft Visual Studio 解决方案、 Eclipse 项目或 Makefile 迷宫,而不需要更改其中的任何内容。

给定一组目录,其中包含代码,CMake 管理项目在编译之前需要完成的所有依赖项、生成订单和其他任务。它实际上没有编译任何东西。要使用 CMake,您必须告诉它(使用名为 CMakeLists.txt 的配置文件)您需要编译哪些可执行文件,它们链接到哪些库,项目中有哪些目录,这些目录中有哪些内容,以及您需要的任何细节(CMake 非常强大)。

如果设置正确,那么您可以使用 CMake 创建您选择的“本机构建环境”完成其工作所需的所有文件。在 Linux 中,默认情况下,这意味着 Makefile。因此,一旦您运行 CMake,它将创建一堆文件供自己使用,外加一些 Makefile。之后,您所需要做的就是在每次编辑完代码后,在控制台的根文件夹中键入“ make”,然后就会生成一个已编译并链接的可执行文件。

CMake 是如何工作的? 它是做什么的?

下面是我将贯穿始终使用的一个示例项目设置:

simple/
CMakeLists.txt
src/
tutorial.cxx
CMakeLists.txt
lib/
TestLib.cxx
TestLib.h
CMakeLists.txt
build/

每个文件的内容将在后面显示和讨论。

CMake 根据项目的 CMakeLists.txt设置项目,并在控制台中执行 cmake的任何目录中进行设置。在不是项目根目录的文件夹中执行这个操作会产生所谓的 out-of-source构建,这意味着在编译过程中创建的文件(obj 文件、 lib 文件、可执行文件,你知道的)将被放置在这个文件夹中,与实际代码分开。它有助于减少杂乱,并且因为其他原因而受到青睐,这一点我将不予讨论。

我不知道如果在根 CMakeLists.txt之外的任何其他位置执行 cmake会发生什么。

在这个例子中,因为我希望它们都放在 build/文件夹中,所以首先我必须在那里导航,然后传递给 CMake 根 CMakeLists.txt所在的目录。

cd build
cmake ..

默认情况下,这将使用 Makefile 设置所有内容,正如我所说的。下面是构建文件夹现在的样子:

simple/build/
CMakeCache.txt
cmake_install.cmake
Makefile
CMakeFiles/
(...)
src/
CMakeFiles/
(...)
cmake_install.cmake
Makefile
lib/
CMakeFiles/
(...)
cmake_install.cmake
Makefile

What are all of these files? 你唯一需要担心的是 Makefile 和项目文件夹.

注意 src/lib/文件夹。之所以创建它们,是因为 simple/CMakeLists.txt使用命令 add_subdirectory(<folder>)指向它们。这个命令告诉 CMake 在所述文件夹中查找另一个 CMakeLists.txt文件并执行 那个脚本,因此以这种方式添加的每个子目录中都有一个 CMakeLists.txt文件。在这个项目中,simple/src/CMakeLists.txt描述了如何构建实际的可执行文件,simple/lib/CMakeLists.txt描述了如何构建库。缺省情况下,CMakeLists.txt描述的每个目标都将放置在其构建树中的子目录中。所以,在一个快速

make

在从 build/完成的控制台中,添加了一些文件:

simple/build/
(...)
lib/
libTestLib.a
(...)
src/
Tutorial
(...)

项目已经生成,可执行文件已经准备好执行。

How do I tell CMake how to build my project?

下面是源目录中每个文件的内容,解释如下:

返回文章页面

cmake_minimum_required(VERSION 2.6)


project(Tutorial)


# Add all subdirectories in this project
add_subdirectory(lib)
add_subdirectory(src)

最低要求的版本应该总是设置,根据警告 CMake 抛出时,您没有。使用您的 CMake 版本。

项目的名称可以稍后使用,并提示您可以从相同的 CMake 文件管理多个项目。不过我就不深究了。

如前所述,add_subdirectory()向项目中添加一个文件夹,这意味着 CMake 希望其中包含一个 CMakeLists.txt,然后在继续之前运行该文件夹。顺便说一下,如果您碰巧定义了一个 CMake 函数,您可以从子目录中的其他 CMakeLists.txt使用它,但是您必须在使用 add_subdirectory()之前定义它,否则它将找不到它。不过,CMake 在库方面更聪明,所以这可能是您唯一一次遇到这种问题。

返回文章页面

add_library(TestLib TestLib.cxx)

要创建您自己的库,您需要给它一个名称,然后列出它所构建的所有文件。直截了当。如果需要编译另一个文件 foo.cxx,您可以改为编写 add_library(TestLib TestLib.cxx foo.cxx)。这也适用于其他目录中的文件,例如 add_library(TestLib TestLib.cxx ${CMAKE_SOURCE_DIR}/foo.cxx)。稍后将详细介绍 CMAKE _ SOURCE _ DIR 变量。

另一件可以做的事情是指定您想要一个共享库。例子: add_library(TestLib SHARED TestLib.cxx)。不要害怕,这是 CMake 开始让你的生活变得更容易的地方。无论它是否共享,现在使用以这种方式创建的库所需处理的全部内容就是您在这里给它起的名称。这个库的名称现在是 TestLib,您可以从项目中的 任何地方引用它。CMake 会找到的。

有没有更好的方法来列出依赖项? 当然

返回文章页面

#include <stdio.h>


void test() {
printf("testing...\n");
}

返回文章页面

#ifndef TestLib
#define TestLib


void test();


#endif

返回文章页面

# Name the executable and all resources it depends on directly
add_executable(Tutorial tutorial.cxx)


# Link to needed libraries
target_link_libraries(Tutorial TestLib)


# Tell CMake where to look for the .h files
target_include_directories(Tutorial PUBLIC ${CMAKE_SOURCE_DIR}/lib)

命令 add_executable()的工作原理与 add_library()完全相同,当然,它将生成一个可执行文件。这个可执行文件现在可以作为像 target_link_libraries()这样的东西的目标被引用。由于 Tutorial.cxx 使用 TestLib 库中的代码,因此您可以向 CMake 指出这一点,如下所示。

Similarly, any .h files #included by any sources in add_executable() that are 没有 in the same directory as the source have to be added somehow. If not for the target_include_directories() command, lib/TestLib.h would not be found when compiling Tutorial, so the entire lib/ folder is added to the include directories to be searched for #includes. You might also see the command include_directories() which acts in a similar fashion, except that it does not need you to specify a target since it outright sets it globally, for all executables. Once again, I'll explain CMAKE_SOURCE_DIR later.

返回文章页面

#include <stdio.h>
#include "TestLib.h"
int main (int argc, char *argv[])
{
test();
fprintf(stdout, "Main\n");
return 0;
}

注意如何包含“ TestLib.h”文件。不需要包括完整的路径: CMake 照顾所有的幕后感谢 target_include_directories()

从技术上讲,在这样一个简单的源代码树中,您可以不使用 lib/src/下的 CMakeLists.txt,而只是将类似 add_executable(Tutorial src/tutorial.cxx)的代码添加到 simple/CMakeLists.txt。这取决于你和你的项目的需要。

我还应该知道什么才能正确使用 CMake?

(AKA topics relevant to your understanding)

查找和使用软件包 : 这个问题的答案比我更好地解释了它。

声明变量和函数,使用控制流,等等 : 查看 本教程,它解释了 CMake 必须提供的基础知识,同时也是一个很好的总体介绍。

让变量 : 有很多变量,所以接下来是一个让你回到正确轨道的速成课程。CMake wiki 是一个获取有关变量的更深入信息的好地方和表面上的其他东西。

您可能希望编辑某些变量而不重新生成树。为此使用 ccmake (它编辑 CMakeCache.txt文件)。完成更改后,请记住使用 configure,然后使用更新后的配置生成 makefile。

阅读 前面提到的教程了解如何使用变量,长话短说: set(<variable name> value)更改或创建变量。 ${<variable name>}使用它。

  • CMAKE_SOURCE_DIR: 源代码的根目录。在前面的例子中,它总是等于 /simple
  • CMAKE_BINARY_DIR: The root directory of the build. In the previous example, this is equals to simple/build/, but if you ran cmake simple/ from a folder such as foo/bar/etc/, then all references to CMAKE_BINARY_DIR in that build tree would become /foo/bar/etc.
  • CMAKE_CURRENT_SOURCE_DIR: The directory in which the current CMakeLists.txt is in. This means it changes throughout: printing this from simple/CMakeLists.txt yields /simple, and printing it from simple/src/CMakeLists.txt yields /simple/src.
  • 你懂的。此路径不仅取决于构建所在的文件夹,还取决于当前 CMakeLists.txt脚本的位置。

为什么这些很重要?源文件显然不在构建树中。如果在前面的示例中尝试使用类似于 target_include_directories(Tutorial PUBLIC ../lib)的代码,那么路径将相对于构建树,也就是说,它将类似于编写 ${CMAKE_BINARY_DIR}/lib,后者将查看 simple/build/lib/内部。没有。里面有 h 文件,最多能找到 libTestLib.a。你想要 ${CMAKE_SOURCE_DIR}/lib代替。

  • CMAKE_CXX_FLAGS: 传递给编译器的标志,在本例中是 C + + 编译器。同样值得注意的是 CMAKE_CXX_FLAGS_DEBUG,如果 CMAKE_BUILD_TYPE设置为 DEBUG,将使用它。还有更多类似的; 请查看 CMake 维基
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY: 告诉 CMake 在构建时把所有可执行文件放在哪里。这是一个全局设置。例如,您可以将其设置为 bin/,并将所有东西整齐地放置在那里。EXECUTABLE_OUTPUT_PATH类似,但不推荐,以防您偶然发现它。
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY: Likewise, a global setting to tell CMake where to put all library files.

目标属性 : 您可以设置只影响一个目标的属性,无论是可执行文件还是库(或者归档文件... ... 您明白了)。如何使用它(与 set_target_properties()

是否有一种简单的方法可以自动将源添加到目标? 使用 GLOB,在同一个变量下列出给定目录中的所有内容。示例语法是 FILE(GLOB <variable name> <directory>/*.cxx)

可以指定不同的生成类型吗?是的,尽管我不确定这是如何工作的,也不知道它的局限性。它可能需要一些 if/then‘ ning,但是 CMake 确实提供了一些基本的支持,而没有配置任何东西,比如 CMAKE_CXX_FLAGS_DEBUG的默认值。 您可以通过 set(CMAKE_BUILD_TYPE <type>)CMakeLists.txt文件中设置构建类型,也可以通过使用适当的标志(例如 cmake -DCMAKE_BUILD_TYPE=Debug)从控制台调用 CMake 来设置构建类型。

有使用 CMake 的项目的好例子吗?Wikipedia 有一个使用 CMake 的开源项目列表,如果你想了解的话。到目前为止,在线教程对我来说只是一个失望,但是 这个堆栈溢出问题有一个非常酷和容易理解的 CMake 设置。值得一看。

在您的代码 中使用来自 CMake 的变量: 这里有一个简单的例子(改编自 一些其他的教程) :

返回文章页面

project (Tutorial)


# Setting variables
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 1)


# Configure_file(<input> <output>)
# Copies a file <input> to file <output> and substitutes variable values referenced in the file content.
# So you can pass some CMake variables to the source code (in this case version numbers)
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_SOURCE_DIR}/src/TutorialConfig.h"
)

返回文章页面

// Configured options and settings
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

由 CMake,simple/src/TutorialConfig.h生成的结果文件:

// Configured options and settings
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 1

With clever use of these you can do cool things like turning off a library and such. I do recommend taking a look at that tutorial as there are some slightly more advanced things that are bound to be very useful on larger projects, sooner or later.

对于其他所有事情,Stack Overflow 充满了具体的问题和简洁的答案,这对于除了外行人之外的每个人都很好。