CMake基础使用和实战详解

news2024/11/13 12:00:33

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

  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指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路/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)外部构建。外部编译的过程如下:

  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 目录的相对路径,但务必注意: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 会安装到这个地方。

总结

  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 变量。
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/358102.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

水文监测场景的数据通信规约解析和落地实践

[小 迪 导 读]&#xff1a;江苏云上需要通过云平台接入水文设备来实现水文数据的采集、存储、显示、控制、报警及传输等综合功能。企业介绍江苏云上智联物联科技有限公司是专业从事物联网相关产品与解决方案服务的高科技公司&#xff0c;总部位于美丽的江苏无锡。公司遵循“智联…

linux高级命令之死锁

死锁学习目标能够知道产生死锁的原因1. 死锁的概念死锁: 一直等待对方释放锁的情景就是死锁为了更好的理解死锁&#xff0c;来看一个现实生活的效果图:说明:现实社会中&#xff0c;男女双方一直等待对方先道歉的这种行为就好比是死锁。死锁的结果会造成应用程序的停止响应&…

面试官:你是怎样理解Fiber的

hello&#xff0c;这里是潇晨&#xff0c;今天我们来聊一聊Fiber。不知道大家面试的时候有没有遇到过和react Fiber相关的问题呢&#xff0c;这一类问题比较开放&#xff0c;但也是考察对react源码理解深度的问题&#xff0c;如果面试高级前端岗&#xff0c;恰巧你平时用的是re…

细谈JavaWeb中的Request和Response

文章目录1&#xff0c;Request和Response的概述2&#xff0c;Request对象2.1 Request继承体系2.2 Request获取请求数据2.2.1 获取请求行数据2.2.2 获取请求头数据2.2.3 获取请求体数据2.2.4 获取请求参数的通用方式2.4 请求参数中文乱码问题2.4.1 POST请求解决方案2.4.2 GET请求…

MySQL使用C语言连接

文章目录MySQL使用C语言连接引入库下载库文件在项目中使用库使用库连接数据库下发SQL请求获取查询结果MySQL使用C语言连接 引入库 要使用C语言连接MySQL&#xff0c;需要使用MySQL官网提供的库。 下载库文件 下载库文件 首先&#xff0c;进入MySQL官网&#xff0c;选择DEVEL…

Java代码使用最小二乘法实现线性回归预测

最小二乘法简介最小二乘法是一种在误差估计、不确定度、系统辨识及预测、预报等数据处理诸多学科领域得到广泛应用的数学工具。它通过最小化误差&#xff08;真实目标对象与拟合目标对象的差&#xff09;的平方和寻找数据的最佳函数匹配。利用最小二乘法可以简便地求得未知的数…

如何写一个 things3 client

Things3[1] 是一款苹果生态内的任务管理软件&#xff0c;是一家德国公司做的&#xff0c;非常好用。我前后尝试了众多任务管理软件&#xff0c;最终选定 things3&#xff0c;以后有机会会写文章介绍我是如何用 things3 来管理我的日常任务。本文主要介绍欧神写的 tli[2] 工具来…

3D沉浸式体验开发技巧【Three.js】

在本文中&#xff0c;我们将看看如何使用 Three.js 创建一个充满后期效果和微交互的迷你城市。 推荐&#xff1a;将 NSDT场景编辑器 加入你的3D开发工具链。 1、背景介绍 我是一个游戏爱好者。 我一直梦想创建一个交互式迷你城市&#xff0c;使用饱和的颜色&#xff0c;类似于…

Android自动化测试(UiAutomator)——UiObject

本文主要讲解使用UiAutomator的一些技巧&#xff0c;希望对于初学者有一定的帮助 UiObject 1、首先要声明对象 UiObject XXX new UiObject(new Selector) ; 2、其次对对象进行操作 操作类型包括&#xff1a; 执行类&#xff1a;文本输入与清除、点击/长按、拖动/滑动、 …

JAVA JDK 常用工具类和工具方法

目录 Pair与Triple Lists.partition-将一个大集合分成若干 List集合操作的轮子 对象工具Objects 与ObjectUtils 字符串工具 MapUtils Assert断言 switch语句 三目表达式 IOUtils MultiValueMap MultiMap JAVA各个时间类型的转换&#xff08;LocalDate与Date类型&a…

开源软件AirByte:入湖入仓,数据集成管道

从ETL到ELT就传统的 ETL而言&#xff0c;当我们开始构建数据仓库时&#xff0c;都要先去了解业务流程&#xff0c;明晰业务是如何运转的&#xff0c;数据是如何留痕的。通过收集用户的相关需求&#xff0c;从而去规划设计报表。企业需要进行数仓分域、分层、逻辑建模等一系列操…

Linux下程序调试的方法【GDB】GDB相关命令和基础操作(命令收藏)

目录 1、编译 2、启动gdb调试 2.1 直接运行 2.2 运行gdb后使用run命令 2.3 调试已运行的程序 3、图形界面提示 4、调试命令 1、查看源码 2、运⾏程序/查看运⾏信息 3、设置断点 5、单步/跳步执⾏ 6、分割窗口 7、其他命令 8、相关参数 1、编译 在编译时要加上-g选…

stm32f407探索者开发板(十七)——串口寄存器库函数配置方法

文章目录一、STM32串口常用寄存器和库函数1.1 常用的串口寄存器1.2 串口相关的库函数1.3 状态寄存器&#xff08;USART_ SR&#xff09;1.4 数据寄存器&#xff08;USART_ DR&#xff09;1.5 波特率寄存器&#xff08;USART_BRR&#xff09;二、串口配置一般步骤一、STM32串口常…

java static关键字 万字详解

目录 一、为什么需要static关键字&#xff1a; 二、static关键字概述 : 1.作用 : 2.使用 : 三、static修饰成员变量详解 : 1.特点 : 2.细节 : ①什么时候考虑使用static关键字? ②静态变量和非静态变量的区别&#xff1f; ③关于静态变量的初始化问题 : ④关于静态变…

基于springboot+vue的器官捐献系统

基于springbootvue的器官捐献系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&…

内网渗透(四十一)之横向移动篇-PsExec工具远程命令执行横向移动

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

CCNP350-401学习笔记(201-250题)

201、An engineer attempts to configure a trunk between switch SW1 and switch SW2 using DTP, but the trunk does not form. Which command should the engineer apply to switch SW2 to resolve this issue? A. switchport mode dynamic desirable B. switchport mode a…

GNU make 中文手册 第一二章 概述与介绍

一、第一章&#xff1a;概述 准备知识 在开始我们关于 make 的讨论之前&#xff0c;首先需要明确一些基本概念&#xff1a; 编译&#xff1a;把高级语言书写的代码&#xff0c;转换为机器可识别的机器指令。编译高级语言后生成的指令虽然可被机器识别&#xff0c;但是还不能…

小程序 npm sill idealTree buildDeps 安装一直没反应

目录 一、问题 二、解决 1、删除.npmsrc 、清除缓存 2、更换镜像源 3、最终检测 一、问题 记录&#xff1a;今天npm 一直安装不成功 显示&#xff1a;sill idealTree buildDeps 我的版本&#xff1a; 我百度到换镜像源安装方法&#xff0c;但我尝试后&#xff0c;依然…

CUDA性能指南

CUDA性能指南 文章目录CUDA性能指南5.1 整体性能优化策略5.2 最大化利用率5.2.1 应用程序层次5.2.2 设备层次5.2.3 多处理器层次5.2.3.1 占用率计算5.3 最大化存储吞吐量5.3.1 设备与主机之间的数据传输5.3.2 设备内存访问5.4最大化指令吞吐量5.4.1 算数指令5.4.2 控制流指令5.…