什么是 CMake?
根据维基百科:
CMake 是 [...] 用于管理软件构建过程的软件
使用与编译器无关的方法。它旨在支持
依赖于多个目录层次结构和应用程序
图书馆。它与本机构建环境结合使用
例如 make、Apple 的 Xcode 和 Microsoft Visual Studio。
使用 CMake,您不再需要维护特定于编译器/构建环境的单独设置。您有 一个 配置,并且适用于 许多 环境。
CMake 可以从 same 文件中生成 Microsoft Visual Studio 解决方案、Eclipse 项目或 Makefile 迷宫,而无需更改其中的任何内容。
给定一堆目录,其中包含代码,CMake 管理您的项目在编译之前需要完成的所有依赖项、构建顺序和其他任务。它实际上并没有编译任何东西。要使用 CMake,您必须告诉它(使用名为 CMakeLists.txt 的配置文件)您需要编译哪些可执行文件、它们链接到哪些库、项目中有哪些目录以及其中的内容,以及标志等任何详细信息或您需要的任何其他东西(CMake 非常强大)。
如果设置正确,然后您可以使用 CMake 创建您选择的“本机构建环境”完成其工作所需的所有文件。在 Linux 中,默认情况下,这意味着 Makefiles。所以一旦你运行 CMake,它会创建一堆文件供自己使用,外加一些 Makefiles。此后,您只需在每次编辑完代码时从根文件夹中的控制台中键入“make”,然后生成编译和链接的可执行文件。
CMake 是如何工作的?它有什么作用?
这是我将在整个过程中使用的示例项目设置:
simple/
CMakeLists.txt
src/
tutorial.cxx
CMakeLists.txt
lib/
TestLib.cxx
TestLib.h
CMakeLists.txt
build/
每个文件的内容都会在后面展示和讨论。
CMake 根据您项目的 root CMakeLists.txt 设置您的项目,并在您在控制台中执行 cmake 的任何目录中进行设置。从不是项目根目录的文件夹中执行此操作会产生所谓的 out-of-source 构建,这意味着在编译期间创建的文件(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
这些文件是什么? The only thing you have to worry about is the Makefile and the project folders。
注意src/ 和lib/ 文件夹。这些已创建,因为simple/CMakeLists.txt 使用命令add_subdirectory(<folder>) 指向它们。此命令告诉 CMake 在所述文件夹中查找另一个 CMakeLists.txt 文件并执行 that 脚本,因此以这种方式添加的每个子目录必须在其中包含一个 CMakeLists.txt 文件。在这个项目中,simple/src/CMakeLists.txt 描述了如何构建实际的可执行文件,simple/lib/CMakeLists.txt 描述了如何构建库。 CMakeLists.txt 描述的每个目标将默认放置在构建树的子目录中。所以,经过快速
make
在从build/ 完成的控制台中,添加了一些文件:
simple/build/
(...)
lib/
libTestLib.a
(...)
src/
Tutorial
(...)
项目已构建,可执行文件已准备好执行。 如果您希望将可执行文件放在特定文件夹中,您会怎么做? Set the appropriate CMake variable, or change the properties of a specific target。稍后将详细介绍 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.txts 中使用它,但是你必须在使用 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,您可以从项目中的anywhere 引用它。 CMake 会找到它。
有没有更好的方法来列出依赖项? Definitely yes。请在下方查看有关此内容的更多信息。
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() 中的任何源所包含的任何 .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.txts,只需将 add_executable(Tutorial src/tutorial.cxx) 之类的内容添加到 simple/CMakeLists.txt。这取决于您和您项目的需求。
我还应该知道什么才能正确使用 CMake?
(与您的理解相关的又名主题)
查找和使用包:The answer to this question 解释得比以往任何时候都好。
声明变量和函数,使用控制流等:查看 this tutorial,它解释了 CMake 必须提供的基础知识,并且是一个很好的一般介绍。 p>
CMake 变量:有很多,所以接下来是一个速成课程,可以让你走上正轨。 The CMake wiki is a good place to get more in-depth information on variables 以及其他表面上的东西。
您可能希望在不重新构建构建树的情况下编辑一些变量。为此使用 ccmake(它编辑 CMakeCache.txt 文件)。完成更改后记得configure,然后generate 使用更新的配置生成makefile。
阅读previously referenced tutorial 以了解如何使用变量,但长话短说:
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_CXX_FLAGS_DEBUG,如果CMAKE_BUILD_TYPE 设置为DEBUG,则将使用它。还有更多这样的;查看the CMake wiki。
-
CMAKE_RUNTIME_OUTPUT_DIRECTORY:告诉 CMake 在构建时将所有可执行文件放在哪里。这是一个全局设置。例如,您可以将其设置为 bin/ 并将所有内容整齐地放置在那里。 EXECUTABLE_OUTPUT_PATH 类似,但已弃用,以防您偶然发现它。
-
CMAKE_LIBRARY_OUTPUT_DIRECTORY:同样,一个全局设置告诉 CMake 将所有库文件放在哪里。
目标属性:您可以设置只影响一个目标的属性,无论是可执行文件还是库(或存档......你明白了)。 Here is a good example 如何使用它(与set_target_properties().
有没有一种简单的方法可以自动将源添加到目标?Use GLOB 可以在同一变量下列出给定目录中的所有内容。示例语法为FILE(GLOB <variable name> <directory>/*.cxx)。
你能指定不同的构建类型吗?可以,但我不确定它是如何工作的或它的局限性。它可能需要一些 if/then'ning,但 CMake 确实提供了一些基本支持而无需配置任何东西,例如 CMAKE_CXX_FLAGS_DEBUG 的默认值。
您可以通过set(CMAKE_BUILD_TYPE <type>) 在CMakeLists.txt 文件中设置构建类型,也可以使用适当的标志从控制台调用CMake,例如cmake -DCMAKE_BUILD_TYPE=Debug。
有使用 CMake 的项目的好例子吗? 维基百科有一个使用 CMake 的开源项目列表,如果你想了解的话。到目前为止,在线教程在这方面让我失望,但是this Stack Overflow question 有一个非常酷且易于理解的 CMake 设置。值得一看。
在代码中使用 CMake 中的变量:这是一个快速而肮脏的示例(改编自 some other tutorial):
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
通过巧妙地使用这些,您可以做一些很酷的事情,例如关闭库等。我确实建议您查看that tutorial,因为有一些更高级的东西迟早会对大型项目非常有用。
对于其他一切,Stack Overflow 充满了具体的问题和简洁的答案,这对除了初学者之外的所有人来说都很棒。