本节将围绕着hello world展开介绍如何创建一个简单的项目。
一、最小项目
1.准备工作
首先,新建一个目录01用来存放当前项目,并在目录下创建main.cpp和CMakeLists.txt文件,两者文件内容如下:
#include <iostream>
using namespace std;int main(int argc, char* argv[]){ std::cout << "hello cmake" <<std::endl;
}
project(Hello)
add_executable(hello main.cpp)
2.开始构建
完成准备工作后,此时目录结构如下:
此时,在当前目录打开命令行,输入:
cmake .
结果如下:
-- Building for: Visual Studio 15 2017
-- Selecting Windows SDK version 10.0.17763.0 to target Windows 10.0.19042.
-- The C compiler identification is MSVC 19.16.27045.0
-- The CXX compiler identification is MSVC 19.16.27045.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: E:/个人/project/cmake/test01/01
再让我们看下目录内容,发现系统自动生成了很多文件(因为是windows系统,所以生成了sln文件,如果是linux则会生成makefile文件):
我们可以使用vs打开sln文件,进行编译生成产物,这里我们通过命令的方式生成,在当前目录命令行下执行:
cmake --build .
执行成功后,可以在当前目录Debug下发现hello.exe文件
通过.\Debug\hello.exe可以执行程序,打印:
PS E:\个人\project\cmake\test01\01> .\Debug\hello.exe
hello world
3.简单的解释
对于这个简单的CMakeLists.txt,只有两条命令:
project(Hello)
add_executable(hello main.cpp)
3.1 project命令
完整语法如下:
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]][DESCRIPTION <project-description-string>][HOMEPAGE_URL <url-string>][LANGUAGES <language-name>...])
此处只用到了第一个参数,用于指定项目名称。
- 注意与最终的产物名无关,仅仅代表生成的项目名(Hello.sln)
- 此命令会设置变量PROJECT_NAME的值为<PROJECT-NAME>,当前例子则为Hello,但是由于windows下不区分大小写,所以使用时可能变为hello
3.2 add_executable命令
完整语法如下:
add_executable(<name> [WIN32] [MACOSX_BUNDLE][EXCLUDE_FROM_ALL][source1] [source2 ...])
指定当前项目的最终产物名name(此处为hello),以及相关的源文件[source1] [source2 ...](此处为main.cpp)
以上介绍了project和add_executable两个命令,通过这两个命令我们就可以构建一个简单的CMakeLists.txt了。
二、构建项目
1.内部构建
上一节我们已经实现了helloworld的最小项目,但是在构建的过程中,我们发现所有的产物和中间产物都在当前目录下,当我们完成编译后,甚至找不到cmakelists文件和源文件。
上一节中
cmake .
这种在当前目录下直接生成的方式可以称为内部构建,与之对应的是外部构建。
2.外部构建
外部构建的方式则会将生成的产物内容统一到一个目录下,步骤如下(在上一节的main.cpp和CMakeLists.txt基础上):
- 新建目录,此处使用build目录
- 然后切换到build目录下执行cmake ..,生成项目
- 继续在build目录下执行cmake --build .,生成最终产物
结果就是,所有的产物内容都到了build目录下:
3.cmake构建目录规则
从上面的内部构建和外部构建我们可以看出cmake的构建规则:
3.1 构建项目信息
cmake srcPath
srcPath表示CMakeLists.txt路径,而生成的项目信息内容则存放到执行命令的当前目录下
3.2 构建产物信息
cmake --build buildPath
通过此命令执行构建,等同于vs下的生成和linux下的make命令。其中buildPath表示前面构建的项目信息目录,生成的所有产物也会存放在此目录下。
4.快捷构建项目信息
上述所说的外部构建,需要建目录,且目录,再执行cmake,步骤繁琐,实际上cmake提供了快捷命令:
cmake -S srcPath -B buildPath
srcPath和buildPath的含义和前面相同,然后再执行cmake --build buildPath执行构建即可。
以上介绍了cmake的构建基本语法内容。
三、一点点改进(set/file)
前面的例子中源码只有main.cpp一个文件,下面我们将封装一个mprint()函数,并将声明和定义放到不同的文件:
#ifndef HELLO_H
#define HELLO_Hvoid mprint();
#endif
#include "hello.h"
#include <iostream>void mprint() {std::cout << "hello world" << std::endl;
}
#include "hello.h"int main(int argc, char *argv[]) {mprint();return 0;}
project(hello)set(SOURCES src/main.cpp src/hello.cpp src/hello.h)message(STATUS "--------PROJECT_NAME:" ${PROJECT_NAME})
message(STATUS "--------SOURCES:" ${SOURCES})add_executable(${PROJECT_NAME} ${SOURCES})
前面的例子因为考虑时最小项目,所以直接在add_executable的时候手动添加了源文件,而实际使用中,文件数目必然很多,而且可能会被多次引用,那么之前的方式就会显得笨重了(每次引用都要写一长串)。
1.通过set(file)设置变量
这里通过set命令将所有依赖的源文件设置到变量SOURCES中,后续只要通过${变量名}就可以获取变量对应的值了。
当然,这里set因为不支持通配符(正则),所以一个个录入源文件依旧显得笨重,还有一种更加简单的方式是file命令(推荐此方式):
file(GLOB|GLOB_RECURSE 变量名 通配符表达式)
比如这里可以使用
file(GLOB SOURCES "src/*.*")
的方式。file和set的实际功能还有其他区别,但是用于获取源文件主要区别在此。
2.通过PROJECT_NAME设置输出名
前面提到project命令会为当前项目生成PROJECT_NAME变量,这里通过${PROJECT_NAME}为add_executable设置输出产物名,此方法的好处就是如果修改项目名,则输出产物会同步修改。
当然,如果想要项目名和输出产物名不同,则不能使用此方式。
3.通过message命令打印
message([<mode>] "message to display" ...)
message支持多种mode打印方式(类似于日志),此处使用的是STATUS模式,可用于日志打印,获取变量值等。
4.基础语法
从前面的内容我们可以总结出CMakeLists.txt中的基础语法:
- 在cmake中,变量的值是通过${},如上述的${PROJECT_NAME}就是取变量PROJECT_NAME的值(变量含义后面说明)。但是在if语句中则是直接使用变量名。
- 指令(参数1 参数2 ...)
- 指令大小写无关,变量和参数是大小写相关的。建议指令小写,变量大写。