[CMake] CMake学习笔记

自己的学习和使用总结,还不完善,不定时更新...

红色斜体是我还不理解的部分...

一. 简介

  • cmake是一款高级编译配置工具

  • 所有操作都是通过 CMakeLists.txt 来完成编译的;

  • CMake官方全部推荐使用大写指令

  • 学习目的:为将来处理大型的C/C++、Java项目做准备;

环境

  • Ubuntu:20.04
  • cmake:3.16.3

简单尝试

  1. C++写一个 “Hello World” 程序:

    #include <iostream>
    using namespace std;
    int main() {
        cout << "Hello World!" << endl;
        return 0;
    }
    
  2. 编写 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})
    
    
  3. 使用cmake,生成 Makefile 文件;

  4. 当前目录下执行 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文件) 都存在的情况)

  1. 新建 build 目录(固定名称,不可指定);
  2. 转到 build 目录;
  3. 执行 cmake .. ,生成 Makefile 文件;
  4. 执行 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. 安装过程

  1. 转到 build 目录;
  2. 执行 cmake .. ,生成 Makefile 文件;
  3. 执行 make 命令;
  4. 执行 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_PATHCMAKE_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

posted @ 2023-05-28 16:43  小贼的自由  阅读(34)  评论(0编辑  收藏  举报