CMake如何使用?

122

对于初学者来说,很难获得有用的CMake信息。到目前为止,我看过一些关于如何设置一些非常基本的项目的教程。然而,这些教程没有解释其中的任何内容背后的原因,总是留下许多未填补的空白。

在CMakeLists上调用CMake是什么意思?它是否应该在每个构建树中被调用一次?如果它们都使用来自相同源的相同CMakeLists.txt文件,我该如何为每个构建使用不同的设置? 为什么每个子目录需要自己的CMakeLists文件?在项目根目录以外的CMakeLists.txt上使用CMake是否有意义?如果有,是在哪些情况下呢? 在各自的子目录中指定如何从CMakeLists文件中构建可执行文件或库与在所有源代码的根目录的CMakeLists文件中达到效果有什么区别? 我能否创建一个针对Eclipse的项目和另一个针对Visual Studio的项目,只需在调用CMake时更改-G选项?那样做是正确的吗?

到目前为止,我看到的所有教程、文档页面或问题/答案都没有给出有用的洞察力,让人理解如何使用CMake。这些示例不够详细。无论我看哪个教程,都感觉自己缺少重要的信息。

许多像我一样的CMake新手提出了问题,虽然他们没有明确问到如何使用CMake,但显然表明我们作为新手不知道如何处理CMake或如何理解它。


2
我很想关闭投票“过于宽泛”...这感觉更像是一篇关于CMake的个人论文,而不是stackoverflow的合适内容。为什么你没有把所有这些问题都放在不同的问题中呢?你的问题+回答与许多其他问题有何不同,例如http://stackoverflow.com/q/20884380/417197、http://stackoverflow.com/q/4745996/417197、https://dev59.com/aW855IYBdhLWcg3wJg1y? - André
18
@Andre 为什么提出这个问题:我花了一周的时间来理解CMake,但我找到的任何教程都不太完整。你指出的那些链接很好,但对于那些因为有一堆CMakeLists而被迫学习CMake的人来说,并没有很好地解释CMake是如何工作的。老实说,我试图写出一个答案,让我在开始学习CMake时就能回到过去给自己看。子问题旨在表明我正在尝试询问应该知道什么,以免产生这些疑问。提出正确的问题很难。 - leinaD_natipaC
2
不是答案,但我邀请您查看jaws.rootdirectory.de。虽然CMake文档(以及被接受的答案...)向您展示了许多您可以做的小片段,但我编写了一个(绝非“完整”或“完美”)基本设置,其中包含注释,我认为这展示了CMake(和CTest和CPack和CDash...)可以为您做什么。它可以直接构建,并且您可以轻松地根据项目的需求进行调整/扩展。 - DevSolar
18
现在关闭这样的问题真是太可惜了。我认为广泛的问题,需要像教程一样的答案,但是关于文档不完整或模糊的主题(另一个例子是Torch7 C api)和研究级别的问题应该保持开放。这些问题比典型的“嘿,我在做一些玩具项目时出现了段错误”更有趣,而这种问题在该网站上很常见。 - Ash
6
刚发现这个。我作为一名程序员已经有20多年的经验,从未遇到过像CMAKE这样文档质量如此糟糕、入门难度如此之高且非常糟糕的东西。它是一个可怕的怪物,即使是最基本的事情也被埋没在大量复杂的“过程”之下。真是太可怕了。 - RichieHH
显示剩余4条评论
1个回答

183

CMake是什么?

根据维基百科:

CMake是一种软件,用于使用独立于编译器的方法管理软件的构建过程。它旨在支持目录层次结构和依赖于多个库的应用程序。它与本地构建环境(如make、苹果的Xcode和Microsoft Visual Studio)一起使用。

使用CMake,您不再需要维护特定于编译器/构建环境的单独设置。您只需拥有一个配置,即可适用于多个环境。

CMake可以从相同的文件生成Microsoft Visual Studio解决方案、Eclipse项目或Makefile迷宫,而无需更改它们中的任何内容。

给定一堆包含代码的目录,CMake管理所有依赖项、构建顺序和其他任务,以便在编译之前完成您的项目所需的工作。它实际上不会编译任何东西。要使用CMake,您必须告诉它(使用称为CMakeLists.txt的配置文件)需要编译哪些可执行文件,它们链接到哪些库,您的项目中有哪些目录以及其中的内容,以及任何您需要的详细信息,如标志或其他内容(CMake非常强大)。

如果正确设置,您可以使用CMake创建所有所选的“本机构建环境”所需的文件。在Linux中,默认情况下,这意味着Makefiles。因此,一旦运行CMake,它将为自己使用创建一堆文件以及一些Makefile。之后,您只需要在每次编辑代码完成后从根文件夹中的控制台键入“make”,就会生成已编译和链接的可执行文件。
CMake是如何工作的?它做了什么?
以下是我将在整个过程中使用的示例项目设置:
simple/
  CMakeLists.txt
  src/
    tutorial.cxx
    CMakeLists.txt
  lib/
    TestLib.cxx
    TestLib.h
    CMakeLists.txt
  build/

每个文件的内容稍后会显示并讨论。
CMake根据项目的CMakeLists.txt设置您的项目,并在您在控制台中执行cmake的任何目录中进行设置。从不是项目根目录的文件夹中执行此操作会产生所谓的源外构建,这意味着在编译过程中创建的文件(obj文件,lib文件,可执行文件等)将被放置在该文件夹中,与实际代码分开。它有助于减少混乱,并且出于其他原因也更受欢迎,我将不再讨论。
我不知道如果您在根CMakeLists.txt之外的任何位置执行cmake会发生什么。
在此示例中,由于我希望所有内容都放在build/文件夹中,因此首先我必须导航到该文件夹,然后传递包含根CMakeLists.txt的目录给CMake。
cd build
cmake ..

默认情况下,这将使用我所说的Makefiles来设置所有内容。现在,构建文件夹应该是这个样子:

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

这些文件都是什么? 你唯一需要担心的是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
    (...)

该项目已构建完成,可执行文件已经准备好运行。如果想要将可执行文件放到特定文件夹中,该怎么办呢? 设置适当的CMake变量,或更改特定目标的属性。有关CMake变量的更多信息,请参考后续内容。

如何告诉CMake如何构建我的项目?

下面是源目录中每个文件的内容说明:

simple/CMakeLists.txt:

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对库更加智能,因此这可能是您遇到这种问题的唯一时候。

simple/lib/CMakeLists.txt:

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将找到它。 是否有更好的方法来列出依赖项?绝对有。请查看下面获取更多信息。

simple/lib/TestLib.cxx:

#include <stdio.h>

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

simple/lib/TestLib.h:

#ifndef TestLib
#define TestLib

void test();

#endif

simple/src/CMakeLists.txt:

# 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 指出这一点,如下所示。

同样地,任何在 add_executable() 中被源文件 #include 的 .h 文件如果不在同一目录下,都必须以某种方式添加。如果没有 target_include_directories() 命令,编译 Tutorial 时就找不到 lib/TestLib.h,因此整个 lib/ 文件夹被添加到搜索 #includes 的包含目录中。你可能还会看到 include_directories() 命令,它的作用类似,只是它不需要你指定一个目标,因为它直接为所有可执行文件全局设置。我稍后会解释 CMAKE_SOURCE_DIR。

simple/src/tutorial.cxx:

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

请注意 "TestLib.h" 文件的包含方式。无需包含完整路径:由于 target_include_directories() 的存在,CMake 在幕后处理所有内容。
从技术上讲,在这样一个简单的源代码树中,您可以不使用 lib/src/ 下的 CMakeLists.txt 文件,只需在 simple/CMakeLists.txt 中添加类似 add_executable(Tutorial src/tutorial.cxx) 的内容即可。这取决于您和您的项目的需求。
我还需要了解哪些内容才能正确使用 CMake?
(也就是与您理解相关的主题) 查找和使用软件包这个问题的答案 比我更好地解释了它。
声明变量和函数,使用控制流等:请查看此教程,它解释了CMake提供的基础知识,并且是一个很好的入门介绍。
CMake变量:有很多种,因此下面是一个快速入门以帮助您上手。CMake维基是获取更深入信息的好地方,也包括其他内容。
您可能想要编辑一些变量而不重新构建构建树。为此,请使用ccmake(它会编辑CMakeCache.txt文件)。完成更改后,请记得进行configure,然后使用更新的配置generate makefiles。
阅读先前提到的教程以了解如何使用变量,简而言之: set(<variable name> value)用于更改或创建变量。 ${<variable name>}用于使用它。
  • CMAKE_SOURCE_DIR: 源代码的根目录。在前面的例子中,它始终等于/simple
  • CMAKE_BINARY_DIR: 构建的根目录。在前面的例子中,它等于simple/build/,但如果您从像foo/bar/etc/这样的文件夹运行cmake simple/,则该构建树中所有对CMAKE_BINARY_DIR的引用都将变为/foo/bar/etc
  • CMAKE_CURRENT_SOURCE_DIR: 当前CMakeLists.txt所在的目录。这意味着它会随着位置的不同而改变:从simple/CMakeLists.txt打印出来得到的是/simple,而从simple/src/CMakeLists.txt打印出来得到的是/simple/src
  • CMAKE_CURRENT_BINARY_DIR: 你懂的。这个路径不仅取决于构建所在的文件夹,还取决于当前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_BUILD_TYPE为DEBUG,则将使用CMAKE_CXX_FLAGS_DEBUG代替。还有更多类似的内容,请查看CMake Wiki
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY:告诉CMake在构建时将所有可执行文件放置在哪里。这是一个全局设置。例如,您可以将其设置为bin/,并将所有内容整齐地放置在那里。EXECUTABLE_OUTPUT_PATH类似,但已弃用,以防您偶然遇到它。
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY:同样是一个全局设置,用于告诉CMake在哪里放置所有库文件。

目标属性:您可以设置仅影响一个目标(无论是可执行文件还是库(或存档...您明白了))的属性。这里有一个很好的例子,展示如何使用它(使用set_target_properties())。

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

可以指定不同的构建类型吗?是的,虽然我不确定它是如何工作或其限制。它可能需要一些if/then操作,但CMake提供了一些基本支持,例如默认值CMAKE_CXX_FLAGS_DEBUG。您可以通过在CMakeLists.txt文件中设置set(CMAKE_BUILD_TYPE <type>)或通过使用适当的标志从控制台调用CMake来设置构建类型,例如cmake -DCMAKE_BUILD_TYPE=Debug
有哪些使用CMake的好项目示例?维基百科列出了使用CMake的开源项目列表,如果您想了解更多信息,可以查看。然而,在这方面,在线教程对我来说一直是一个失望,但此Stack Overflow问题有一个非常酷且易于理解的CMake设置。值得一看。

在代码中使用CMake的变量:这里有一个快速而简单的例子(改编自某个教程):

simple/CMakeLists.txt:

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"
)

simple/TutorialConfig.h.in:

// 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

通过巧妙地运用这些技巧,您可以做一些很酷的事情,比如关闭一个库等。我建议您查看该教程,因为其中还有一些稍微高级的东西,在大型项目中迟早会非常有用。

对于其他所有内容,Stack Overflow都充满了具体问题和简洁的答案,这对于除了未经训练的人以外的所有人来说都很好。


3
我接受这个答案,但如果其他人写了一个更好、更简短的答案,我会选择那个。我本来想自己写的,但是我已经为 CMake 烦恼了很久。 - leinaD_natipaC
8
感谢这份出色的工作。希望它能获得更多的赞! :) - Eddie
4
这段话中较为隐晦的观点是:使用CMake后,您无需维护特定于编译器/构建环境的单独设置。您只需要一个配置即可在多个环境中使用,如Visual Studio、Code Blocks、纯Unix Makefiles等。这就是我一开始考虑使用CMake的原因:保持Autoconf、MSVC 2005 / 32bit和MSVC 2010 / 64bit配置同步变得非常困难。一旦我学会了,我还添加了许多其他功能,如文档生成、测试、打包等,所有这些功能都采用跨平台方式实现。 - DevSolar
1
@DevSolar 对的。我更喜欢这个句子,所以如果你不介意,我会把它加到答案里。但我不会在那方面加入关于如何正确使用CMake的内容。这更多是关于能够理解发生了什么。 - leinaD_natipaC
1
@RichieHH 根据示例,您首先配置CMake使用Makefiles,然后使用CMake生成项目构建所需的文件,最后停止担心CMake,因为它在这里的工作已经完成。因此,您需要“担心”Makefile,从现在开始您需要在bash中使用make来构建您的项目。 - leinaD_natipaC
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接