[CMake] CMake学习笔记
自己的学习和使用总结,还不完善,不定时更新...
红色斜体是我还不理解的部分...
一. 简介
-
cmake是一款高级编译配置工具;
-
所有操作都是通过 CMakeLists.txt 来完成编译的;
-
CMake官方全部推荐使用大写指令;
-
学习目的:为将来处理大型的C/C++、Java项目做准备;
环境:
- Ubuntu:20.04
- cmake:3.16.3
简单尝试:
-
用C++写一个 “Hello World” 程序:
#include <iostream> using namespace std; int main() { cout << "Hello World!" << endl; return 0; }
-
编写 CMakeLists.txt 文件(严格区分大小写):
#CMakeLists.txt PROJECT (HELLO) SET(SRC_LIST main.cpp) MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR}) MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR}) ADD_EXECUTABLE(hello ${SRC_LIST})
-
使用cmake,生成 Makefile 文件;
-
在当前目录下执行
make
命令,就会生成可执行文件。
二. 简单使用
注意事项:
-
变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名;
-
指令(参数 1 参数 2...) 参数使用括弧括起,参数之间使用空格或分号分开。 以上面的 ADD_EXECUTABLE 指令为例,如果存在另外一个 func.cpp 源文件
就要写成:
ADD_EXECUTABLE(hello main.cpp func.cpp)
或者ADD_EXECUTABLE(hello main.cpp;func.cpp)
。 -
指令是大小写无关的,参数和变量是大小写相关的。但,推荐全部使用大写指令。
-
常量参数可以加双引号也可以不加,但是如果有空格,则必须加,例如源代码文件名为“m ain.cpp”;
1. 设置工程名和支持的语言
PROJECT(<PROJECT-NAME> [<language-name>...])
-
作用:用于指定工程的名字和支持的语言,默认支持所有语言;
-
示例:
-
PROJECT (HELLO)
:指定了工程的名字为 HELLO ,并且支持所有语言,建议使用; -
PROJECT (HELLO CXX)
:指定了工程的名字,并且支持语言是C++; -
PROJECT (HELLO C CXX)
:指定了工程的名字,并且支持语言是C和C++;
-
该关键字隐式定义了两个CMAKE的变量:
<projectname>_BINARY_DIR
,本例中是 HELLO_BINARY_DIR
<projectname>_SOURCE_DIR
,本例中是 HELLO_SOURCE_DIR
MESSAGE关键字就可以直接使用者两个变量,当前都指向当前的工作目录,后面会讲外部编译
【注】如果改了工程名,这两个变量名也会改变。解决方法是又定义两个预定义变量:PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR,这两个变量和HELLO_BINARY_DIR,HELLO_SOURCE_DIR是一致的。所以改了工程名也没有关系。
2. 设置变量
SET(<variable> <value>... [PARENT_SCOPE])
- 作用:设置变量;
- 示例:
SET(SRC_LIST main.cpp)
,定义了一个变量 SRC_LIST变量,其包含了main.cpp。如果有多个值,也可以SET(SRC_LIST main.cpp t1.cpp t2.cpp)
3. 输出信息
MESSAGE([<mode>] "message text" ...)
-
作用:向终端输出用户自定义的信息;
-
mode:
-
SEND_ERROR:产生错误,生成过程被跳过。
-
SATUS:输出前缀为 "--" 的状态信息。
-
FATAL_ERROR:立即终止所有 cmake 过程。
-
-
示例:
MESSAGE(STATUS "根目录:安装的相对路径根目录是 " ${CMAKE_INSTALL_PREFIX})
4. 生成可执行文件
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
- 作用:生成可执行文件;
- 示例:
ADD_EXECUTABLE(hello ${SRC_LIST})
;
5. 关联源文件子目录
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])
- 作用:用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制的存放位置,一般用于项目根目录下的 CMakeLists 文件;
- 示例:
ADD_SUBDIRECTORY (src bin)
,源码目录是 ./src,输出的目标二进制文件存放位置是 ./build/bin(会自动新建),而不是 ./bin;
6. 生成库文件
ADD_LIBRARY(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[<source>...])
- 作用:生成库文件;
- 参数:
- name:生成的共享库的名字;
- SHARED / STATIC:固定关键字,SHARED——动态库 STATIC——静态库;
- EXCLUDE_FROM_ALL:源文件列表,或包含源文件的变量。
- 示例:
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
;
7. 设置构建目标文件的属性?
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
- 作用:可以使用该命令设置目标的编译选项、链接选项、输出名称、输出路径等属性。例如,可以使用 SET_TARGET_PROPERTIES 命令设置一个可执行文件的输出名称和输出路径;
- 示例:
set_target_properties(myapp PROPERTIES OUTPUT_NAME "myapp" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
,这将设置可执行文件 myapp 的输出名称为 myapp,输出路径为${CMAKE_BINARY_DIR}/bin
。
8. 添加头文件的搜索路径
INCLUDE_DIRECTORIES(path1 path2 ...)
- 作用:添加头文件的搜索路径;
- 示例:
INCLUDE_DIRECTORIES(/usr/include/hello)
,如果有多个头文件搜索路径,路径之间用空格分割。
9. 添加共享库的搜索路径
LINK_DIRECTORIES(path1)
- 作用:添加非标准的共享库搜索路径,但我试了没成功;
- 示例:
LINK_DIRECTORIES(/usr/lib)
;
10. 添加共享库
TARGET_LINK_LIBRARIES(<target> ... <item>... ...)
- 作用:添加需要链接的共享库,只需要传入名字,不需要传入路径,但是共享库的路径需要在系统环境路径下,否则只能使用LINK_DIRECTORIES;
- 示例:
TARGET_LINK_LIBRARIES(hello libhello.so)
;
三. 外部构建
上面的简单尝试中就是使用内部构建。但是它生产的临时文件特别多,不方便清理,因此一般都是使用外部构建,该方式就会把生成的临时文件放在build目录下,不会对源文件有任何影响强烈使用外部构建方式。
1. 单CMakeLists文件
通常这种方式是只有一个 CMakeLists.txt 文件,代码源文件放在工程根目录下。通常大一点的项目都不会使用这种方式。
工程根目录结构:
.
├── build/
├── CMakeLists.txt
└── main.cpp # 源码文件可以有多个
步骤:(在 CMakeLists.txt 文件和 源码(main.cpp文件) 都存在的情况)
- 新建 build 目录(固定名称,不可指定);
- 转到 build 目录;
- 执行
cmake ..
,生成 Makefile 文件; - 执行
make
命令,就会在当前目录(build目录)生成可执行文件;
2. 多CMakeLists文件
源码通常放在一个目录下。通常用改方式构建项目。
工程根目录结构:
.
├── build
├── CMakeLists.txt # 根目录下的 CMakeLists.txt 文件
└── src
├── CMakeLists.txt # 源码目录下的 CMakeLists.txt 文件
└── main.cpp
两个 CMakeLists.txt 文件,通常在根目录存放一个,在源码中存放多个:
根目录下的 CMakeLists.txt 文件:
PROJECT (HELLO)
ADD_SUBDIRECTORY (src bin) # 指定源码文件夹和输出二进制文件夹,注意bin是在build目录下生成的
源码目录下的 CMakeLists.txt 文件:
# 生成名为“hello”的可执行文件:这里就不用变量了
ADD_EXECUTABLE (hello main.cpp)
编译步骤和上面的四点一样。
.
├── build
│ ├── bin
│ │ ├── hello # 可执行文件
│ │ └── # 其他文件
│ ├── CMakeCache.txt
│ └── # 其他文件
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp
四. 安装
安装需要使用倒cmake的一个命令:INSTALL。它可以安装包括:二进制、动态库、静态库以及文件、目录、脚本等。
通常我们会把不同的文件安装到不同的路径下:
类型 | 路径 | 参数类型 | 备注 |
---|---|---|---|
可执行文件 | /usr/bin 、/usr/local/bin |
- | |
静态库*.a | /usr/lib 、/usr/local/lib |
TARGETS | - |
动态库(*.so) | /usr/lib 、/usr/local/lib |
TARGETS | - |
头文件(*.h) | /usr/include 、/usr/local/include |
FILES | - |
脚本(*.sh) | (下面是安装在/usr/local/bin ,不确定是规范的) |
PROGRAMS | - |
工程文档 | (下面是安装在/usr/local/share/doc/程序名/ ,不确定是规范的) |
DIRECTORY | - |
1. 工程要求
- 为工程添加一个子目录 src,用来放置工程源代码;
- 添加一个子目录 doc,用来放置这个工程的文档 tutorial.txt;
- 在工程目录添加文本文件 COPYRIGHT、README;
- 在工程目录添加一个 runhello.sh 脚本,用来调用 hello 二进制;
- 将构建后的目标文件放入构建目录的 bin 子目录;
- 将 doc 目录 的内容以及 COPYRIGHT/README 安装到 /usr/share/doc/cmake/ ;
2. 项目目录结构
.
├── build
├── CMakeLists.txt
├── COPYRIGHT
├── doc
│ └── tutorial.txt
├── runhello.sh
└── src
├── CMakeLists.txt
└── main.cpp
3. 安装COPYRIGHT和README
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/)
- FILES:表示你需要安装的是文件,后面跟上具体文件名;
- DESTINATION:表示安装的目标路径,后面跟上路径名(相对路径或绝对路径);
- 相对路径:它是相对于
${CMAKE_INSTALL_PREFIX}
变量表示的路径,默认为 “/usr/local/”。
- 相对路径:它是相对于
4. 安装运行脚本
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
- PROGRAMS:代表非目标文件的可执行程序安装(比如脚本之类),后面跟上具体文件名;
- DESTINATION:表示安装的目标路径,后面跟上路径名(相对路径或绝对路径);
- 相对路径:它是相对于
${CMAKE_INSTALL_PREFIX}
变量表示的路径,默认为 “/usr/local/”。
- 相对路径:它是相对于
5. 安装工程文档
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)
DIRECTORY是用于普通文档吗?
【注】abc 和 abc/有很大的区别
-
目录名 不以/ 结尾:这个整个目录将被安装为目标路径下的(就是说将这个目录复制到目标路径);
-
目录名 以/ 结尾:将这个目录中的内容安装到目标路径;
6. CMakeLists文件
只需要修改工程根目录下的 CMakeLists 文件就行了。
PROJECT (HELLO)
ADD_SUBDIRECTORY (src bin) # 指定源码文件夹和输出二进制文件夹,注意bin是在build目录下生成的
# 以下是添加到内容
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/) # 安装COPYRIGHT和README
INSTALL(PROGRAMS runhello.sh DESTINATION bin) # 安装运行脚本
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake) # 安装工程文档
7. 安装过程
- 转到 build 目录;
- 执行
cmake ..
,生成 Makefile 文件; - 执行
make
命令; - 执行
make install
(普通用户需要加上sudo
);
snail@chasemeng:~/work/CMake/build$ sudo make install
[100%] Built target hello
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/share/doc/cmake/COPYRIGHT
-- Installing: /usr/local/share/doc/cmake/README
-- Installing: /usr/local/bin/runhello.sh
-- Installing: /usr/local/share/doc/cmake
-- Installing: /usr/local/share/doc/cmake/tutorial.txt
五. 库文件
1. 静态库和动态库
- 静态库的扩展名一般为“.a”或“.lib”,动态库的扩展名一般为“.so”或“.dll”。
- 静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行
- 动态库在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。
2. 构建静态库/动态库
2.1 项目目录结构
.
├── build
├── CMakeLists.txt
└── lib
├── CMakeLists.txt
├── hello.cpp
└── hello.h
2.2 源码文件
存放在lib目录下,包含hello.h和hello.cpp两个文件。
hello.h头文件:动态库和静态库是其实现
#ifndef HELLO_H
#define Hello_H
void HelloFunc();
#endif
hello.cpp文件:制作成动态库和静态库
#include "hello.h"
#include <iostream>
void HelloFunc(){
std::cout << "Hello World" << std::endl;
}
2.3 CMakeLists文件
根目录下的 CMakeLists.txt 文件:
PROJECT(HELLO)
ADD_SUBDIRECTORY(lib bin)
源码目录下的 CMakeLists.txt 文件:
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
生成步骤和上面编译步骤一样,这里不再赘述...
执行完之后会在 ./build/bin 文件夹中生成动态库文件 "libhello.so"。
3. 同时构建同名静态库和动态库
虽然静态库和动态库的后缀名不一样,但是只要它们的名字一样,就无法同时构建:
# 如果用这种方式,只会构建一个动态库,不会构建出静态库,虽然静态库的后缀是.a
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
# 修改静态库的名字,这样是可以的,但是我们往往希望他们的名字是相同的,只是后缀不同而已
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
使用 SET_TARGET_PROPERTIES 可以解决该问题
在第2节的基础上,修改文件 源码目录下的 CMakeLists.txt 文件:(我不是很理解,以后再补充吧)
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
# 对hello_static的重名为hello
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
# cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.so 时, 就会清理掉 libhello.a
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
输出:
snail@chasemeng:~/work/CMake/build$ make
Scanning dependencies of target hello
[ 25%] Building CXX object bin/CMakeFiles/hello.dir/hello.o
[ 50%] Linking CXX shared library libhello.so
[ 50%] Built target hello
Scanning dependencies of target hello_static
[ 75%] Building CXX object bin/CMakeFiles/hello_static.dir/hello.o
[100%] Linking CXX static library libhello.a
[100%] Built target hello_static
指定版本:(9)CMake入门笔记--同时生成动态库与静态库-蒲公英云 (dandelioncloud.cn)
4. 安装
安装之后,其他项目就可以该头文件,使用这些库文件了。但是需要在使用的项目中的 CMakeLists 文件中,使用 INCLUDE_DIRECTORY 添加这些库文件的头文件的路径;
# 安装头文件(下面不作解释)
INSTALL(FILES hello.h DESTINATION include/hello)
# 安装库文件
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
- TARGETS:安装类型关键字?二进制、动态库,静态库都用 TARGETS ;
- LIBRARY:代表动态库;
- ARCHIVE:代表静态库;
- DISTINATION:安装的目标路径,上面使用的是相对路径(相对于
${CMAKE_INSTALL_PREFIX}
变量表示的路径,默认为 “/usr/local/”);
(安装步骤,略...)
【注】安装的时候,可以指定一下相对路径: cmake -D CMAKE_INSTALL_PREFIX=/usr ..
,或者直接在项目根目录的 CMakeLists 文件中用SET指定该变量的值;
snail@chasemeng:~/work/CMake/build$ sudo make install
[ 50%] Built target hello
[100%] Built target hello_static
Install the project...
-- Install configuration: ""
-- Installing: /usr/include/hello/hello.h
-- Installing: /usr/lib/libhello.so
-- Installing: /usr/lib/libhello.a
5. CMakeList文件
根目录下的 CMakeLists.txt 文件:
PROJECT(HELLO)
SET(CMAKE_INSTALL_PREFIX /usr)
MESSAGE(STATUS "根目录:安装的相对路径根目录是 " ${CMAKE_INSTALL_PREFIX})
ADD_SUBDIRECTORY(lib bin)
源码目录下的 CMakeLists.txt 文件:
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
# 更改安装的相对路径根目录
SET(CMAKE_INSTALL_PREFIX /usr)
MESSAGE(STATUS "源码:安装的相对路径根目录是 " ${CMAKE_INSTALL_PREFIX})
# 对hello_static的重名为hello
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
# cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.so 时, 就会清理掉 libhello.a
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
# 安装头文件(下面不作解释)
INSTALL(FILES hello.h DESTINATION include/hello)
# 安装库文件
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
六. 使用外部共享库
1. 项目目录结构
.
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp
2. 源码文件
此代码想要调用第五章中的头文件和库文件。
main.cpp:
// 这个头文件是第五章安装好的
#include <hello.h>
int main() {
HelloFunc();
}
3. CMakeList文件
根目录下的 CMakeLists.txt 文件:
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
源码目录下的 CMakeLists.txt 文件:
# 添加头文件搜索路径
INCLUDE_DIRECTORIES(/usr/include/hello)
# 生成可执行文件(需要放在后面)
ADD_EXECUTABLE(hello main.cpp)
# 添加需要链接的共享库:需要放在ADD_EXECUTABLE后面
# 可执行文件hello链接动态库libhello.so
# 由于第五章中我们将库文件安装在/usr/lib,这个路径默认会被搜索到(32位机器),但是如果是64位机器,默认被搜索到路径是/usr/lib64,而不是/usr/lib,因此,如果机器是64位,就需要将库文件从/usr/lib移动到/usr/lib64,才能被正确链接,否则编译不会报错,但是执行生成的可执行文件(程序)会报错(但是我这里是没问题的,也不知道为啥)
TARGET_LINK_LIBRARIES(hello libhello.so)
# 或者也可以用此方法链接共享库(但是我这里执行不成功,不知道为啥)
# LINK_DIRECTORIES(/usr/lib)
补充
-
特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH:这两个是系统环境变量而不是 cmake 变量,可以在linux的bash中进行设置。我们上面例子中使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello) 来指明include路径的位置。我们还可以使用另外一种方式,使用环境变量
export CMAKE_INCLUDE_PATH=/usr/include/hello
。 -
生成可调试的 debug版本 的方法:
cmake .. -D CMAKE_BUILD_TYPE=debug