CMake基础使用和实战详解
- 一、CMake简介
- 1.1、cmake 的特点
- 1.2、注意
- 1.3、使用建议
- 二、安装 cmake
- 三、CMake的简单使用
- 3.1、准备工作
- 3.2、开始构建
- 3.3、解释CMakeLists.txt的内容
- 3.4、基本语法规则
- 四、更像样的CMake工程
- 4.1、准备工作
- 4.2、构建
- 4.3、语法解释
- 4.4、修改保存目标二进制的地方
- 4.5、如何安装编译的软件
- 4.6、修改CMakeLists.txt支持安装
- 总结
一、CMake简介
CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake 的组态档取名为CMakeLists.txt。
Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再以一般的建构方式使用。可以通过CMake官方网站获得更多关于cmake 的信息。
可以理解为,编写Makefile难度太大,CMake基于Makefile做了二次开发。
1.1、cmake 的特点
- 开放源代码,使用类BSD 许可发布。
- 跨平台,并可生成native 编译配置文件,在Linux/Unix 平台,生成 makefile,在苹果平台,可以生成xcode,在 Windows 平台,可以生成 MSVC 的工程文件。
- 能够管理大型项目,KDE4 就是最好的证明。
- 简化编译构建过程和编译过程。Cmake 的工具链非常简单:cmake+make。
- 高效率,CMake 构建KDE4 的 kdelibs 要比使用autotools 来构建KDE3.5.6 的 kdelibs 快40%,主要是因为 Cmake 在工具链中没有libtool。
- 可扩展,可以为cmake 编写特定功能的模块,扩充cmake 功能。
1.2、注意
- cmake 很简单,但绝对没有想象中那么简单,简单是相对Makefile而言的。
- cmake 编写的过程实际上是编程的过程,跟使用autotools 一样,不过需要编写的是CMakeLists.txt(每个目录一个),使用的是cmake 语言和语法。
- cmake 跟已有体系的配合并不是特别理想,比如pkgconfig。
1.3、使用建议
- 如果工程只有几个文件,直接编写Makefile 是最好的选择。
- 如果使用的是C/C++/Java 之外的语言,建议不要使用cmake。
- 如果使用的语言有非常完备的构建体系,比如java 的 ant,也不需要学习cmake。
- 如果项目已经采用了非常完备的工程管理工具,并且不存在维护问题,没有必要迁移到 cmake。
- 如果仅仅使用qt 编程,没有必要使用 cmake,因为qmake 管理 Qt 工程的专业性和自动化程度比cmake 要高很多。
二、安装 cmake
我当前的CMake版本:
$ cmake --version
cmake version 3.5.1
CMake suite maintained and supported by Kitware (kitware.com/cmake).
有些项目有最低版本要求,如果版本过低,可能项目会编译不了,可以升级CMake。
(1)卸载已经安装的旧版的CMake[非必需]。
sudo apt-get autoremove cmake
(2)下载CMake压缩包。
wget https://cmake.org/files/v3.21/cmake-3.21.3-linux-x86_64.tar.gz
(3)解压压缩包。
tar zxvf cmake-3.21.3-linux-x86_64.tar.gz
查看解压后目录:
cmake-3.21.3-linux-x86_64
├── bin
│ ├── ccmake
│ ├── cmake
│ ├── cmake-gui
│ ├── cpack
│ └── ctest
├── doc
│ └── cmake
├── man
│ ├── man1
│ └── man7
└── share
├── aclocal
├── applications
├── bash-completion
├── cmake-3.21
├── emacs
├── icons
├── mime
└── vim
15 directories, 5 files
bin下面有各种cmake家族的产品程序。
(4)创建软链接。
注意,文件路径是可以指定的, 一般选择在/opt 或 /usr 路径下, 这里选择/opt 。
sudo mv cmake-3.21.3-Linux-x86_64 /opt/cmake-3.21.3
sudo ln -sf /opt/cmake-3.21.3/bin/* /usr/bin/
检查版本号:
$ cmake --version
cmake version 3.21.3
CMake suite maintained and supported by Kitware (kitware.com/cmake).
三、CMake的简单使用
3.1、准备工作
(1)首先,建立一个文件夹,用来存放工程文件。
cd cmake
mkdir t1
cd t1
(2)在 t1 目录建立main.c 和 CMakeLists.txt(注意文件名大小写):
main.c 文件内容:
//main.c
#include <stdio.h>
int main()
{
printf(“Hello World from t1 Main!\n”);
return 0;
}
CMakeLists.txt 文件内容:
PROJECT(HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
ADD_EXECUTABLE(hello2 ${SRC_LIST})
关于CMakeLists.txt 文件内容的语法,后面会做详细说明。
3.2、开始构建
cmake .
注意命令后面的点号,代表本目录。
执行结果:
CMake Warning (dev) at CMakeLists.txt:4:
Syntax Warning in cmake code at column 37
Argument not separated from preceding token by whitespace.
This warning is for project developers. Use -Wno-dev to suppress it.
-- The C compiler identification is GNU 8.4.0
-- The CXX compiler identification is GNU 8.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - 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: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- This is BINARY dir /home/fly/workspace/cmakeProj/t1
-- This is SOURCE dir /home/fly/workspace/cmakeProj/t1
-- Configuring done
-- Generating done
-- Build files have been written to: /home/fly/workspace/cmakeProj/t1
系统自动生成了: CMakeFiles, CMakeCache.txt, cmake_install.cmake 等文件,并且生成了 Makefile。
不需要理会这些文件的作用,最关键的是,它自动生成了 Makefile。然后进行工程的实际构建,在这个目录输入 make 命令,大概会得到如下的输出:
[ 25%] Building C object CMakeFiles/hello2.dir/main.c.o
[ 50%] Linking C executable hello2
[ 50%] Built target hello2
[ 75%] Building C object CMakeFiles/hello.dir/main.c.o
[100%] Linking C executable hello
[100%] Built target hello
如果需要看到make 构建的详细过程,可以使用make VERBOSE=1 或者VERBOSE=1 make 命令来进行构建。
这时候,需要的目标文件hello 已经构建完成,位于当前目录,尝试运行一下:
$ ./hello
Hello World from t1 main
3.3、解释CMakeLists.txt的内容
CMakeLists.txt这个文件是 cmake 的构建定义文件,文件名是大小写相关的,如果工程存在多个目录,需要确保每个要管理的目录都存在一个CMakeLists.txt。这涉及到关于多目录构建,后面解释。
上面例子中的CMakeLists.txt 文件内容如下:
PROJECT(HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
ADD_EXECUTABLE(hello2 ${SRC_LIST})
(1)PROJECT指令。
语法是:
PROJECT(projectname [CXX] [C] [Java])
可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。这个指令隐式的定义了两个 cmake 变量: < projectname >_BINARY_DIR 以及< projectname >_SOURCE_DIR,这里就是HELLO_BINARY_DIR 和HELLO_SOURCE_DIR(所以CMakeLists.txt 中两个MESSAGE指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路/t1;注意,在外部编译中,两者所指代的内容会有所不同。
同时cmake 系统也帮助我们预定义了 PROJECT_BINARY_DIR 和PROJECT_SOURCE_DIR变量,他们的值分别跟HELLO_BINARY_DIR 与HELLO_SOURCE_DIR 一致。
为了统一起见,建议以后直接使用PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR,即使修改了工程名称,也不会影响这两个变量。如果使用了< projectname >_SOURCE_DIR,修改工程名称后,需要同时修改这些变量。
(2)SET指令。
语法是:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
SET 指令可以用来显式的定义变量即可。比如用到的是SET(SRC_LIST main.c),如果有多个源文件,也可以定义成:SET(SRC_LIST main.c t1.c t2.c)
。
(3)MESSAGE指令。
语法是:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
这个指令用于向终端输出用户定义的信息,包含了三种类型:
- SEND_ERROR,产生错误,生成过程被跳过。
- SATUS,输出前缀为–的信息。
- FATAL_ERROR,立即终止所有cmake 过程。
这里使用的是STATUS 信息输出,演示了由PROJECT 指令定义的两个隐式变量HELLO_BINARY_DIR 和HELLO_SOURCE_DIR。
(4)ADD_EXECUTABLE 指令。
ADD_EXECUTABLE(hello ${SRC_LIST})
定义了这个工程会生成一个文件名为hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表, 本例中也可以直接写成ADD_EXECUTABLE(hello main.c)。
在本例使用了$ {} 来引用变量,这是cmake 的变量应用方式,但是,有一些例外,比如在IF 控制语句,变量是直接使用变量名引用,而不需要$ {}。如果使用了$ {}去应用变量,其实IF 会去判断名为${}所代表的值的变量,那当然是不存在的了。
3.4、基本语法规则
cmake 其实要使用”cmake 语言和语法”去构建,上面的内容就是所谓的 ”cmake 语言和语法”,最简单的语法规则是:
(1)变量使用${}方式取值,但是在IF 控制语句中是直接使用变量名 。
(2)指令(参数1 参数 2…),参数使用括弧括起,参数之间使用空格或分号分开。以上面的ADD_EXECUTABLE 指令为例,如果存在另外一个 func.c 源文件,就要写成:
ADD_EXECUTABLE(hello main.c func.c)
# 或者
ADD_EXECUTABLE(hello main.c;func.c)
(3)指令是大小写无关的,参数和变量是大小写相关的。但,推荐全部使用大写指令。上面的MESSAGE 指令我们已经用到了这条规则:
MESSAGE(STATUS "This is BINARY dir" ${HELLO_BINARY_DIR})
# 也可以写成:
MESSAGE(STATUS "This is BINARY dir ${HELLO_BINARY_DIR}")
这里需要特别解释的是作为工程名的HELLO 和生成的可执行文件 hello 是没有任何关系的。
(4)工程名和执行文件。hello 定义了可执行文件的文件名,也完全可以写成:ADD_EXECUTABLE(t1 main.c)编译后会生成一个t1 可执行文件。
(5)关于语法的疑惑:
cmake 的语法还是比较灵活而且考虑到各种情况,比如:
SET(SRC_LIST main.c)
# 也可以写成
SET(SRC_LIST "main.c")
是没有区别的,但是假设一个源文件的文件名是 fu nc.c(文件名中间包含了空格)。这时候就必须使用双引号,如果写成了 SET(SRC_LIST fu nc.c),就会出现错误,提示找不到fu 文件和nc.c 文件。这种情况,就必须写成:
SET(SRC_LIST "fu nc.c")
此外,可以忽略掉source 列表中的源文件后缀,比如可以写成:
ADD_EXECUTABLE(t1 main)
cmake 会自动的在本目录查找main.c 或者main.cpp等,当然,最好不要偷这个懒,以免这个目录确实存在一个 main.c和一个main。
同时参数也可以使用分号来进行分割。下面的例子也是合法的:
ADD_EXECUTABLE(t1 main.c t1.c)
# 可以写成
ADD_EXECUTABLE(t1 main.c;t1.c).
只需要在编写CMakeLists.txt 时注意形成统一的风格即可。
(6)清理工程。跟经典的autotools 系列工具一样,运行make clean即可对构建结果进行清理。
(7)cmake 并不支持 make distclean,关于这一点,官方是有明确解释的。因为CMakeLists.txt 可以执行脚本并通过脚本生成一些临时文件,但是却没有办法来跟踪这些临时文件到底是哪些。因此,没有办法提供一个可靠的 make distclean 方案。
(8)内部构建。刚才进行的是内部构建(in-source build),而 cmake 强烈推荐的是外部构建(out-of-source build)。内部构建生成的临时文件可能比您的代码文件还要多,而且它生成了一些无法自动删除的中间文件。
(9)外部构建。外部编译的过程如下:
- 首先,请清除t1 目录中除main.c CmakeLists.txt 之外的所有中间文件,最关键的是CMakeCache.txt。
- 其次,在 t1 目录中建立build 目录,当然你也可以在任何地方建立build 目录,不一定必须在工程目录中。
- 再次,进入 build 目录,运行cmake …(注意,…代表父目录,因为父目录存在我们需要的CMakeLists.txt,如果你在其他地方建立了build 目录,需要运行cmake <工程的全路径>),查看一下build 目录,就会发现了生成了编译需要的Makefile 以及其他的中间文件。
- 最后,运行 make 构建工程,就会在当前目录(build 目录)中获得目标文件 hello。
上述过程就是所谓的out-of-source 外部编译,一个最大的好处是,对于原有的工程没有任何影响,所有动作全部发生在编译目录。通过这一点,也足以说服我们全部采用外部编译方式构建工程。
这里需要特别注意的是:通过外部编译进行工程构建,HELLO_SOURCE_DIR 仍然指代工程路径,即/t1;而 HELLO_BINARY_DIR 则指代编译路径,即cmake/t1/build。
四、更像样的CMake工程
以后所有的构建我们都将采用 out-of-source 外部构建,约定的构建目录是工程目录下的build 自录。
让前面的Hello World 更像一个工程,需要作的是:
- 为工程添加一个子目录src,用来放置工程源代码。
- 添加一个子目录doc,用来放置这个工程的文档hello.txt。
- 在工程目录添加文本文件COPYRIGHT,README。
- 在工程目录添加一个runhello.sh 脚本,用来调用hello 二进制。
- 将构建后的目标文件放入构建目录的bin子目录。
- 最终安装这些文件:将hello 二进制与runhello.sh 安装至/usr/bin,将doc 目录的内容以及COPYRIGHT/README 安装到/usr/share/doc/cmake/t2。
4.1、准备工作
(1)建立t2目录。
mkdir t2
(2)将 t1 工程的 main.c 和 CMakeLists.txt 拷贝到t2 目录中。
cp ./main.c ../t2
cp ./CMakeLists.txt ../t2
# 或者整个目录复制
# cp -r ../t1 ../t2
(3)添加子目录src:
cd t2
mkdir src
mv main.c src/
现在的工程看起来是这个样子:一个子目录src,一个 CMakeLists.txt。
(4)上面提到,需要为任何子目录建立一个 CMakeLists.txt,进入子目录src,编写 CMakeLists.txt 如下:
ADD_EXECUTABLE(hello main.c)
(5)将 t2 工程的 CMakeLists.txt 修改为:
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
4.2、构建
建立build 目录,进入build 目录进行外部编译。
mkdir build
cd build
cmake ..
make
构建完成后,生成的目标文件 hello 位于 build/bin 目录中。
4.3、语法解释
(1)ADD_SUBDIRECTORY 指令:
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除,比如,工程的 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建(当然,也可以通过定义依赖来解决此类问题)。
上面的例子定义了将src 子目录加入工程,并指定编译输出(包含编译中间结果)路径为 bin 目录。如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在 build/src 目录(这个目录跟原有的src 目录对应),指定 bin 目录后,相当于在编译时将src 重命名为bin,所有的中间结果和目标二进制都将存放在bin 目录。
这里需要提一下的是SUBDIRS 指令,使用方法是:
SUBDIRS(dir1 dir2...)
但是这个指令已经不推荐使用。它可以一次添加多个子目录,并且,即使外部编译,子目录体系仍然会被保存。
如果在上面的例子中将ADD_SUBDIRECTORY (src bin)修改为SUBDIRS(src),那么在build 目录中将出现一个src 目录,生成的目标代码 hello 将存放在src 目录中。
4.4、修改保存目标二进制的地方
不论是SUBDIRS 还是 ADD_SUBDIRECTORY 指令(不论是否指定编译输出目录),我们都可以通过SET 指令重新定义EXECUTABLE_OUTPUT_PATH 和LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的hello 或者最终的共享库,不包含编译生成的中间文件)。
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
前面提到了< projectname >_BINARY_DIR 和PROJECT_BINARY_DIR 变量,他们指的编译发生的当前目录,如果是内部编译,就相当于 PROJECT_SOURCE_DIR 也就是工程代码所在目录,如果是外部编译,指的是外部编译所在目录,也就是本例中的 build 目录。
所以,上面两个指令分别定义了:可执行二进制的输出路径为build/bin 和库的输出路径为build/lib。
这里没有提到共享库和静态库的构建,所以,暂时可以不考虑第二条指令。
问题是,应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的CMakeLists.txt,把握一个简单的原则:在哪里ADD_EXECUTABLE 或ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义。
在这个例子里,当然就是指src 下的CMakeLists.txt 了。
4.5、如何安装编译的软件
安装的需要有两种,一种是从代码编译后直接 make install 安装,一种是打包时的指定目录安装。所以,即使最简单的手工编写的Makefile,看起来也是这个样子的:
DESTDIR=
install:
mkdir -p $(DESTDIR)/usr/bin install -m 755 hello $(DESTDIR)/usr/bin
这时就可以通过 make install 将 hello 直接安装到/usr/bin 目录,也可以通过make install ESTDIR=/tmp/test 将它安装在/tmp/test/usr/bin 目录,打包时这个方式经常被使用。
稍微复杂一点的是还需要定义PREFIX,一般autotools 工程,会运行这样的指令:
./configure –prefix=/usr
# 或者
./configure --prefix=/usr/local
#来指定 PREFIX
比如上面的Makefile 就可以改写成:
DESTDIR=
PREFIX=/usr
install:
mkdir -p $(DESTDIR)/$(PREFIX)/bin install -m 755 hello $(DESTDIR)/$(PREFIX)/bin
那么我们CMake的HelloWorld 应该怎么进行安装呢?
这里需要引入一个新的cmake 指令 INSTALL 和一个非常有用的变量:CMAKE_INSTALL_PREFIX。
CMAKE_INSTALL_PREFIX 变量类似于configure 脚本的 –prefix,常见的使用方法是这个样子:
cmake -DCMAKE_INSTALL_PREFIX=/usr .
INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
INSTALL 指令包含了各种安装类型,需要一个个分开解释:
(1)目标文件的安装:
INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...])
参数中的TARGETS 后面跟的就是我们通过ADD_EXECUTABLE 或者ADD_LIBRARY 定义的目标文件,可能是可执行二进制、动态库、静态库。
目标类型也就相对应的有三种:ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME特指可执行目标二进制。
DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候 CMAKE_INSTALL_PREFIX 其实就无效了。如果希望使用CMAKE_INSTALL_PREFIX 来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是 ${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
举个简单的例子:
INSTALL(TARGETS myrun mylib mystaticlib RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
上面的例子会将:
- 可执行二进制myrun 安装到${CMAKE_INSTALL_PREFIX}/bin 。
- 目录动态库libmylib 安装到${CMAKE_INSTALL_PREFIX}/lib 目录。
- 静态库libmystaticlib 安装到${CMAKE_INSTALL_PREFIX}/libstatic 目录。
- 特别注意的是不需要关心TARGETS 具体生成的路径,只需要写上TARGETS 名称就可以了。
(2)普通文件的安装:
INSTALL(FILES files... DESTINATION <dir> [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果默认不定义权限PERMISSIONS,安装后的权限为:
OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ,即 644 权限。
(3)非目标文件的可执行程序安装(比如脚本之类):
INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [RENAME <name>] [OPTIONAL])
跟上面的FILES 指令使用方法一样,唯一的不同是安装后权限为:
OWNER_EXECUTE, GROUP_EXECUTE, 和WORLD_EXECUTE,即755 权限目录的安装。
(4)目录安装:
INSTALL(DIRECTORY dirs... DESTINATION <dir> [FILE_PERMISSIONS permissions...] [DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
这里主要介绍其中的DIRECTORY、PATTERN 以及 PERMISSIONS 参数。
DIRECTORY 后面连接的是所在Source 目录的相对路径,但务必注意:abc 和 abc/有很大的区别。如果目录名不以/结尾,那么这个目录将被安装为目标路径下的abc,如果目录名以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。
PATTERN 用于使用正则表达式进行过滤,PERMISSIONS 用于指定PATTERN 过滤后的文件权限。
看一个例子:
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_READ)
这条指令的执行结果是:将 icons 目录安装到 < prefix >/share/myproj,将 scripts/中的内容安装到 < prefix >/share/myproj不包含目录名为 CVS 的目录,对于scripts/*文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ。
(5)安装时CMAKE 脚本的执行:
INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
# SCRIPT #参数用于在安装时调用cmake 脚本文件(也就是<abc>.cmake 文件)
# CODE 参数用于执行CMAKE 指令,必须以双引号括起来。比如:
INSTALL(CODE "MESSAGE(\"Sample install message.\")")
安装还有几个被标记为过时的指令,比如 INSTALL_FILES 等,这些指令已经不再推荐使用。
下面,就来改写我们的工程文件,让他来支持各种文件的安装,并且,我们要使用 CMAKE_INSTALL_PREFIX 指令。
4.6、修改CMakeLists.txt支持安装
(1)首先先补上为添加的文件。添加doc 目录及文件:
cd t2
mkdir doc
vi doc/hello.txt
随便填写一些内容并保存。
(2)在工程目录添加runhello.sh 脚本,内容为:
hello
(3)添加工程目录中的COPYRIGHT 和 README:
touch COPYRIGHT
touch README
(4)下面改写各目录的CMakeLists.txt 文件。
1)安装 COPYRIGHT和README,直接修改主工程文件CMakelists.txt,加入以下指令:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
2)安装 runhello.sh,直接修改主工程文件CMakeLists.txt,加入如下指令:
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
3)安装 doc 中的hello.txt,这里有两种方式:一是通过在doc 目录建立 CMakeLists.txt 并将 doc 目录通过ADD_SUBDIRECTORY 加入工程来完成。另一种方法是直接在工程目录通过INSTALL(DIRECTORY 来完成),前者比较简单,可以根据兴趣自己完成,我们来尝试后者,顺便演示以下DIRECTORY 的安装。
因为hello.txt 要安装到//share/doc/cmake/t2,所以不能直接安装整个doc 目录,这里采用的方式是安装 doc 目录中的内容,也就是使用”doc/”。在工程文件中添加:
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)
(5)现在进入build 目录进行外部编译,注意使用 CMAKE_INSTALL_PREFIX 参数,这里我们将它安装到了/tmp/t2 目录:
cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..
(6)然后运行:
make
sudo make install
(7)进入/tmp/t2 目录看一下安装结果:
$ tree -L 5 /tmp/t2/usr/
/tmp/t2/usr/
├── bin
│ └── runhello.sh
└── share
└── doc
└── cmakeProj
└── t2
├── COPYRIGHT
├── hello.txt
└── README
5 directories, 4 files
(8)如果要直接安装到系统,可以使用如下指令:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
注意,CMAKE_INSTALL_PREFIX 的默认定义是/usr/local,如果没有定义CMAKE_INSTALL_PREFIX 会安装到这个地方。
总结
- 使用cmake 构建Hello World 程序的全部过程,并介绍三个简单的指令:PROJECT/MESSAGE/ADD_EXECUTABLE 以及变量调用的方法,同时提及了两个隐式变量 < projectname >_SOURCE_DIR 及< projectname >_BINARY_DIR,演示了变量调用的方法,从这个过程来看,可能觉得比直接写 Makefile 要复杂多了,甚至都可以不编写Makefile,直接使用gcc -o hello -c main.c 即可生成需要的目标文件。这是因为工程只有几个文件,还是直接编写 Makefile 最简单。但是,如果工程文件非常多,那么CMake就比Makefile简单了。
- 掌握如何在工程中使用多目录、各种安装指令以及CMAKE_INSTALL_PREFIX 变量。