cmake食用指北

关于cmake的入门常识,不深究原理,能用就行.

参考

https://blog.csdn.net/github_18974657/category_11182071.html

https://cmake.org/cmake/help/latest/guide/tutorial/index.html

https://blog.csdn.net/qq_38410730/article/details/102477162

常识

由于C++项目中依赖关系众多(详见csapp链接一章),我们不可能每次都输入那么一大串编译命令来编译程序,而一般的脚本也无法很好地处理复杂的依赖关系,故使用makefile来代替脚本,而cmake便是生成makefile文件的工具. cmake则通过Cmakelist.txt文件来生成makefile文件.

而我们通常将cmake生成makefile文件或对应工程文件的这个过程,称为程序的构建. cmake的两大功能,就是程序的构建和管理. 其实程序管理就是如何通过Cmakelist.txt这个文件来控制程序构建的过程.

关于makefile的介绍:https://seisman.github.io/how-to-write-makefile/introduction.html


概念

Cmakelist.txt文件中的几个概念:

  1. command:即命令,如set(),add_executable()等
  2. variable:即变量,有内部变量即Cmake自身定义的变量,如PROJECT_SOURCE_DIR(Cmakelist.txt所在文件夹路径),PROJECT_NAME(Cmakelist.txt管理的项目名称),也有用户自定义的变量(可通过set命令定义)
  3. package:即包,第三方库提供,可帮助用户快速指定依赖库

基本命令

1.cmake_minimum_required(VERSION [x.xx]):设置cmake的最小版本号,可以避免cmake版本不兼容等问题

2.project([project_name] [VERSION x.xx] [LANGUAGES CXX]):设置项目名称,项目版本号,以及该项目所用语言,CXX表示C++

3.find_package([package_name] COMPONENTS [component1_name] [component2_name]…[REQUIRED]):寻找第三方库提供的组件,并将查找结果存储在[package_name]变量中(注意,包确定唯一的package_name,不可更改,即你只能用Qt5这个变量来存储Qt5提供的Widgets组件).可以一次寻找多个组件,用空格换行分割.最后的REQUIRED可加可不加,添加表示若找不到该库则编译失败.

4.set([variable-name] [variable-value]):将变量值赋给变量,其中变量可以是自己设的变量,也可以是系统自带的变量(一般有CMAKE_前缀,如CMAKE_CXX_STANDARD表示设定的C++标准,CMAKE_CXX_STANDARD_REQUIRED表示是否强制要求C++标准),系统自带变量值为ON时表示开启

5.add_executable([output_name] [file1] [file2]…):添加一个名称为[output_name]的可执行文件,该可执行文件的源码由[file1],[file2]…提供

6.target_link_libraries([output_name] [component]):说明该可执行文件需要的第三方库模块

example:

1
2
3
4
5
6
7
8
9
10
11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt5 COMPONENTS Widgets REQUIRED)
set(SOURCE_FILES
main.cpp
test.cpp
myhead.h
)
#可以用set将变量设置为多个文件
add_executable(test ${SOURCE_FILES})
target_link_libraries(test Qt5::Widgets)

7.add_library([lib_name] [SHARED/STATIC] [file1] [file2]):添加一个名称为[lib_name]的库文件,添加SHARED参数表示该库是个动态库,STATIC表示该库是个静态库,其他与add_executable类似

8.target_compile_definitions([target] [define]):在编译[target]的时候,添加一个名叫[define]的宏定义.

example: QT在生成库工程时,会额外生成一个头文件,该头文件控制:若当前工程定义了ADD_LIBRARY,则头文件会通过宏定义告诉编译器需要导出的类和函数,否则头文件会告诉编译器需要导入的类和函数(仅适用于windows平台).我们为了工程能在Windows平台正常编译,可以通过该命令添加ADD_LIBRARY宏定义.

9.include_directories([path_name]),link_directories([path_name]):在使用第三方库时,我们可以通过find_package定位我们需要的模块.但如果我们想使用自己的库,就需要使用这两个命令.其中,include_directories表示库的头文件路径,link_directories表示库文件路径,之后就可以正常链接

1
2
3
4
5
6
7
8
set(SOURCE_FILES
main.cpp
myhead.h)
add_executable(test ${SOURCE_FILES})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
link_directories(${CMAKE_CURRENT_SOURCE_DIR})
#CMAKE_CURRENT_SOURCE_DIR为Cmakelist.txt所在路径
target_link_libraries(test libadd.so)

10.add_compile_options([options]):很显然,就是在编译时,添加-O2,-Wall等参数

example:写code时若需要输出中文,仅仅是指明输出utf-8编码可能还不够,如果使用gcc编译,gcc默认情况下无法处理GBK编码下的中文,故需要添加参数-finput-charset=GBK表示源码为GBK编码


树型结构

项目文件一般是呈树形结构,如(库文件,源文件等等),我们可以对其各个模块分别使用Cmakelist管理,但模块之间实际上是被分割成了不同的项目,模块之间的依赖关系不被保留(如源文件依赖库文件,所以库文件应该先被编译)

故我们使用add_subdirectory命令.在保留每个模块Cmakelist.txt的基础上,我们在其父目录再新建一个Cmakelist.txt,使用add_subdirectory([subdirectory_name]).add_subdirectory会执行子目录中Cmakelist的指定操作,先添加库文件对应子目录,再添加源文件对应子目录即可.

tips:

1.采用该种方法,target_link_libraries([output_name] [lib_name])中lib_name不用写libxxx.so,直接用add_library中指定的output_name即可

1
2
3
4
add_library(mylib SHARED libtest.cpp)
#会生成libmylib.so库文件
target_link_libraries(test mylib)
#同时也不需要include_directories和link_directories

2.添加子目录时,变量之间是彼此共享的,多个子目录都使用的相同变量,只用在父目录的list中定义一次.在子目录中再次定义该变量,原变量会被覆盖(需要注意的是target开头的命令只针对当前模块,不对其他模块起效,如前文中在编译时添加宏定义的target_compile_definitions)


在管理项目时,我们往往希望源文件都统一放在某个目录下,库文件,可执行文件都放在某个目录下,故我们使用install命令来指定文件的路径

install(FILES/TARGETS [name1] [name2] … DESTINATION [path_name]):如果参数选择FILES,则将对应名字的文件存放于path_name所指定路径中,如果参数选择TARGETS,则将output_name所对应的executable或library生成在指定的目录中,若缺省DESTINATION和path_name,则会生成在CMAKE_INSTALL_PREFIX的bin/lib…目录中

tips:

1.还可以根据文件的不同类型,来指定其生成路径,文件大致有三种类型:(1)RUNTIME:可执行文件和dll文件(windows环境下)(默认放在bin目录),即运行时需要的文件

(2)LIBRARY:动态库文件(linux的.so)和动态库导入文件(windows下dll对应的lib文件)

(3)ARCHIVE:静态库文件,即lib文件(LIBRARY和ARCHIVE默认放在lib目录,都是链接时需要的库文件)

1
2
3
4
install(TARGETS test
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)

2.当我们将动态库库文件和可执行文件放在不同目录时需注意,程序可能无法正常运行,因为可执行程序无法找到动态库,我们在add_executable命令之前使用set命令设置CMAKE_INSTALL_RPATH为库文件对应目录,以供程序额外搜索

1
2
3
4
set(CMAKE_INSTALL_RPTAH "../lib")
#指定其到运行时目录的父目录下的lib目录中寻找
set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")
#注意两种写法差别

当我们使用相对路径(即第一种写法)时,是以运行时路径为基准,不一定是可执行程序所在目录,而第二种写法是以可执行程序所在目录作为基准,一般采用第二种

该方案不适用于Windows系统,在Windows系统下可以将动态库文件和可执行程序放在同一目录下