CMake简述
CMake旨在成为一个跨平台的构建流程管理器,因此它定义了它自己的脚本语言,具有一定的语法和内置功能。 CMake本身是一个软件程序,因此应该使用脚本文件调用它来解释和生成实际的构建文件。
开发人员可以使用CMake语言为项目编写简单或复杂的构建脚本。
使用CMake语言构建逻辑和定义可以在CMakeLists.txt中编写,也可以使用
- CMakeLists.txt文件位于要构建的项目的源文件。
- CMakeLists.txt一般放置在整个工程项目文件夹的根目录下。
- 如果有多个模块,并且每个模块可以单独编译和构建,则可以将CMakeLists.txt插入到子文件夹中(本文仅提及单个CMakeLists.txt的示例)。
- cmake文件可以用作脚本,它运行cmake命令来准备环境预处理或拆分任务,这些任务可以在CMakeLists.txt之外编写
- cmake文件还可以为项目定义模块 ,这些项目可以和库程序文件分离构建,也可以是复杂的多模块项目的额外方法。
编写Makefile可能比编写CMake脚本更难。 通过语法和逻辑的CMake脚本与高级语言具有相似性,因此开发人员可以更轻松地创建他们的cmake脚本,而不会在Makefile中迷失。
常用的命令:
下文的示例会尽量用到这些命令
- message:打印给定消息
- cmake_minimum_required:设置要使用的最低的cmake版本
- add_executable:添加具有给定名称的可执行目标
- add_library:从列出的源文件中添加要构建的库目标
- add_subdirectory:添加一个子目录来构建
还有一些命令可以让开发人员编写条件语句,循环,迭代列表,分配:
- if, endif
- elif, endif
- while, endwhile
- foreach, endforeach
- list
- return
- set_property(分配值给变量)
缩写不是强制性的,但在编写CMake脚本时建议使用。 CMake不使用';'来理解陈述的结尾。所有条件语句都应该以相应的结束命令结束(endif,endwhile,endforeach)CMake的所有这些属性可帮助开发人员编写复杂的构建过程,包括多个模块,库和平台。
CMake环境变量
环境变量用于配置编译器标志,链接器标志,常规构建过程的测试配置。 必须引导编译器搜索库的给定目录。
从以下URL可以看到环境变量的详细列表,可以参见一下链接:
https://cmake.org/cmake/help/latest/manual/cmake-env-variables.7.html
某些环境变量被预定义的CMake变量覆盖。 例如 定义CMAKE_CXX_FLAGS时,CXXFLAGS将被覆盖。
下面是一个示例用例,当您想在编译过程中启用所有警告时,您可以编写-Wall to build命令。 如果使用CMake构建代码,可以使用set命令添加-Wall标志。
set(CMAKE_CXX_FLAGS,"-Wall")
defined flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
CMake变量
CMake包括预定义变量,这些变量默认设置为源树和系统组件的位置。
变量区分大小写,而不是命令。 在变量的定义中,您只能使用字母数字字符和下划线,短划线(_, - )。
您可以在以下URL中找到有关CMake变量的更多详细信息
https://cmake.org/cmake/help/v3.5/manual/cmake-language.7.html#variables
一些变量是根据根文件夹预定义的:
- CMAKE_BINARY_DIR:构建树和二进制输出文件夹顶级的完整路径,默认情况下,它定义为构建树的顶级。
- CMAKE_HOME_DIRECTORY:HOME目录顶部的路径
- CMAKE_SOURCE_DIR:源文件目录顶级的完整路径。
- CMAKE_INCLUDE_PATH:用于查找文件,路径的路径
可以使用$ {
message("CXX Standard: ${CMAKE_CXX_STANDARD}")
set(CMAKE_CXX_STANDARD 14)
就像上面的变量一样,您可以定义自己的变量。 您可以调用set命令将值设置为新变量或更改现有变量的值,如下所示:
set("APP_VERSION","1.0.1")
message("${APP_VERSION}")
CMake 列表
CMake中的所有值都存储为字符串,但在某些上下文中可以将字符串视为列表。
通过连接由半列';'分隔的元素表示为字符串的元素列表
set(files a.txt b.txt c.txt)
# sets files to "a.txt;b.txt;c.txt"
要访问值列表,您可以使用CMake的foreach命令,如下所示:
foreach(file ${files})
message("Filename: ${file}")
endforeach()
完整的表达式列表可以看这里
https://cmake.org/cmake/help/v3.8/manual/cmake-generator-expressions.7.html
使用CMake构建C ++项目
在前面的部分中,我们介绍了编写CMake脚本的核心原则。 现在,我们可以构建一个基本的链表为示例。
如图所示:
很明显,对于这样一个小项目来说,使用CMake是多余的,但是当事情变得复杂时,它会有很大帮助。
为了构建main.cpp,在项目的根目录下创建一个CMakeLists.txt,并且写入如下代,然后执行cmake命令
/**/
cmake_minimum_required(VERSION 3.9.1)
/*设定项目的名称*/
project(cmake_tor)
/*我们编译的目标文件*/
add_executable(cmake_tor main.cpp)
然后在vscode终端中使用运行cmake命令,CMake会生成如下文件和目录
- Makefile脚本,这个是我们执行对编译后的程序,执行tarball安装方式的 时候需要用到make脚本。
- CMakeCache.txt:这个是CMake会根据你的开发环境自动生成一些脚本的预设配置。如果当前的预设配置不符合你的要求,从该文件拷贝相关的配置条目到CMakeLists.txt,修改配置选项后,重新执行一次cmake即可生成符合你需求的配置。
-
CMakeFiles目录:里面包含日志文件和一些临时的二进制文件,一般我们是不用理会的.
问题导入
我们的目的是生成一个二进制文件,但我们这里首先看一下刚才配置有哪些不足之处?
- 首先,我们希望cmake生成的文件放到一个指定的目录里面,这样我们的项目不至于杂乱无章。
- 其次,我希望能够使用自己指定的C/C++编译器,而不是cmake预定义的操作系统默认的编译器。
- 最后,希望执行make命令的时候,编译后的可执行程序和库文件存放到一个有别于其他cmake文件夹的特殊目录。
下面先看看我们之后改进的项目目录结构,如下图,这是更符合我们实际开发工作中的一个目录结构。
那么,我们带着这三个问题定制自己的CMake配置方案吧!
首先,我们在项目的根目录先手动创建一个build的文件夹,在VSCode的终端重定向到build目录之下.
$ mkdir build
$ cd build
然后,在CMakeLists.txt使用以下cmake脚本,每个脚本的语句,代码中有注解
cmake_minimum_required(VERSION 3.1.0)
#设定c编译器和c++编译器,必须放在整个脚本的开头
set(CMAKE_C_COMPILER /usr/local/bin/gcc)
set(CMAKE_CXX_COMPILER /usr/local/bin/g++)
#工程项目的名称
project(cmake_tor)
#包含headers和source目录
include_directories(
"${PROJECT_SOURCE_DIR}/headers"
"${PROJECT_SOURCE_DIR}/source"
)
# 修正在macOS下CMAKE_CXX_STANDARD判断C++编译器标准的
#出错的bug,如果你用到是Windows/Linux系统,可以忽略这条语句
if (POLICY CMP0025)
cmake_policy(SET CMP0025 NEW)
endif ()
#让CMake自动检测gcc的支持的c++标准,
#根据CMake的版本号判断它所支持c++标准
if(CMAKE_VERSION VERSION_LESS "3.8")
set(CMAKE_CXX_STANDARD 14)
message("The compile ${CMAKE_CXX_COMPILER} use C++14 standard!!")
elseif(CMAKE_VERSION VERSION_LESS "3.11")
set(CMAKE_CXX_STANDARD 17)
message("The compile ${CMAKE_CXX_COMPILER} use C++17 standard!!")
else()
set(CMAKE_CXX_STANDARD 20)
message("The compile ${CMAKE_CXX_COMPILER} use C++20 standard!!")
endif()
set(CMAKE_CXX_EXTENSIONS OFF)
#构建自己的软件系统
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/app)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs)
add_executable(main "${PROJECT_SOURCE_DIR}/source/main.cpp")
add_library(shape SHARED "${PROJECT_SOURCE_DIR}/source/shape.c")
最后执行cmake和make,我们会发现如下图,正如我们所愿在指定的目录下生成了一个可执行文件和共享对象文件
$ cmake
$ make
结束语:
第一次接触CMake的同学,可能觉得无从入手,单独花上一段时间去完全掌握它得不偿失,本文是对以前遇到的一些典型问题做成一个易懂的教程。
带着问题去stackflow寻找一些相关问题的答案是很好的学习方法之一,至少你在碰壁的时候,不会为了某个方面迷失了自我,然后自己去根据前人提供的建议去实践并归纳到一个文件里,这样就变成你自己已经掌握的技能了。
技术文~~分享至此,希望你喜欢。