掌握CMake基础:从零开始实战编译工程

一、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 的特点

  1. 开放源代码,使用类BSD 许可发布。
  2. 跨平台,并可生成native 编译配置文件,在Linux/Unix 平台,生成 makefile,在苹果平台,可以生成xcode,在 Windows 平台,可以生成 MSVC 的工程文件。
  3. 能够管理大型项目,KDE4 就是最好的证明。
  4. 简化编译构建过程和编译过程。Cmake 的工具链非常简单:cmake+make。
  5. 高效率,CMake 构建KDE4 的 kdelibs 要比使用autotools 来构建KDE3.5.6 的 kdelibs 快40%,主要是因为 Cmake 在工具链中没有libtool。
  6. 可扩展,可以为cmake 编写特定功能的模块,扩充cmake 功能。

1.2、注意

  1. cmake 很简单,但绝对没有想象中那么简单,简单是相对Makefile而言的。
  2. cmake 编写的过程实际上是编程的过程,跟使用autotools 一样,不过需要编写的是CMakeLists.txt(每个目录一个),使用的是cmake 语言和语法。
  3. cmake 跟已有体系的配合并不是特别理想,比如pkgconfig。

1.3、使用建议

  1. 如果工程只有几个文件,直接编写Makefile 是最好的选择。
  2. 如果使用的是C/C++/Java 之外的语言,建议不要使用cmake。
  3. 如果使用的语言有非常完备的构建体系,比如java 的 ant,也不需要学习cmake。
  4. 如果项目已经采用了非常完备的工程管理工具,并且不存在维护问题,没有必要迁移到 cmake。
  5. 如果仅仅使用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指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路经。注意,在外部编译中,两者所指代的内容会有所不同。

同时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)外部构建。外部编译的过程如下:

  1. 首先,请清除t1 目录中除main.c CmakeLists.txt 之外的所有中间文件,最关键的是CMakeCache.txt。
  2. 其次,在 t1 目录中建立build 目录,当然你也可以在任何地方建立build 目录,不一定必须在工程目录中。
  3. 再次,进入 build 目录,运行cmake ..(注意,..代表父目录,因为父目录存在我们需要的CMakeLists.txt,如果你在其他地方建立了build 目录,需要运行cmake <工程的全路径>),查看一下build 目录,就会发现了生成了编译需要的Makefile 以及其他的中间文件。
  4. 最后,运行 make 构建工程,就会在当前目录(build 目录)中获得目标文件 hello。
    上述过程就是所谓的out-of-source 外部编译,一个最大的好处是,对于原有的工程没有任何影响,所有动作全部发生在编译目录。通过这一点,也足以说服我们全部采用外部编译方式构建工程。

这里需要特别注意的是:通过外部编译进行工程构建,HELLO_SOURCE_DIR 仍然指代工程路径,即/t1;而 HELLO_BINARY_DIR 则指代编译路径,即cmake/t1/build

四、更像样的CMake工程

以后所有的构建我们都将采用 out-of-source 外部构建,约定的构建目录是工程目录下的build 自录。

让前面的Hello World 更像一个工程,需要作的是:

  1. 为工程添加一个子目录src,用来放置工程源代码。
  2. 添加一个子目录doc,用来放置这个工程的文档hello.txt。
  3. 在工程目录添加文本文件COPYRIGHT,README。
  4. 在工程目录添加一个runhello.sh 脚本,用来调用hello 二进制。
  5. 将构建后的目标文件放入构建目录的bin子目录。
  6. 最终安装这些文件:将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 目录的相对路径,但务必注意:abcabc/有很大的区别。如果目录名不以/结尾,那么这个目录将被安装为目标路径下的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 要安装到/<prefix>/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 会安装到这个地方。

总结

  1. 使用cmake 构建Hello World 程序的全部过程,并介绍三个简单的指令:PROJECT/MESSAGE/ADD_EXECUTABLE 以及变量调用的方法,同时提及了两个隐式变量 < projectname >_SOURCE_DIR< projectname >_BINARY_DIR,演示了变量调用的方法,从这个过程来看,可能觉得比直接写 Makefile 要复杂多了,甚至都可以不编写Makefile,直接使用gcc -o hello -c main.c 即可生成需要的目标文件。这是因为工程只有几个文件,还是直接编写 Makefile 最简单。但是,如果工程文件非常多,那么CMake就比Makefile简单了。
  2. 掌握如何在工程中使用多目录、各种安装指令以及CMAKE_INSTALL_PREFIX 变量。
    在这里插入图片描述
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
"CMake实战"是一本针对CMake工具的实用指南,旨在帮助开发人员更有效地使用CMake构建和管理项目。此书详细介绍了CMake的基本概念、安装和配置,以及使用CMake构建C/C++项目的步骤和工具链集成。 "CMake实战"首先介绍了CMake的基本概念,如CMakeLists.txt文件的结构和语法。接着,书中介绍了CMake的模块化设计和使用方式,以及如何编写自定义模块。此外,书中还介绍了常用的CMake命令和变量,以及如何使用它们来定义项目的构建流程和设置编译选项。 书籍还提供了大量的示例和实践项目,涵盖了从简单的项目到复杂的项目的构建过程。这些示例帮助读者深入理解CMake使用方法,并学习如何解决实际项目中常见的构建问题。书中还介绍了CMake和其他构建工具(如make和Ninja)的集成,以及如何使用CTest和CPack进行测试和软件打包。 "CMake实战"书籍的重点在于实践,每个章节都有练习和挑战问题,读者可以通过完成这些练习来巩固所学的知识。此外,书中还提供了一些高级主题,如创建自定义构建目标和安装规则,以及使用CMake构建多平台和多配置的项目。 总之,"CMake实战"是一本全面的CMake指南,旨在帮助开发人员更好地理解和使用CMake工具。通过阅读和实践,读者将能够提高项目的构建效率和质量,同时减少构建和调试过程中的错误和烦恼。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值