热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

CMake基础使用和实战详解

CMake基础使用和实战详解一、CMake简介1.1、cmake的特点1.2、注意1.3、使用建议二、安装cmake三、CMake的简单使用3.1、准备工作3.2、开始构建3.3、

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
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 变量: _BINARY_DIR 以及_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,即使修改了工程名称,也不会影响这两个变量。如果使用了_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. 再次&#xff0c;进入 build 目录&#xff0c;运行cmake …(注意,…代表父目录&#xff0c;因为父目录存在我们需要的CMakeLists.txt&#xff0c;如果你在其他地方建立了build 目录&#xff0c;需要运行cmake <工程的全路径>)&#xff0c;查看一下build 目录&#xff0c;就会发现了生成了编译需要的Makefile 以及其他的中间文件。
  4. 最后&#xff0c;运行 make 构建工程&#xff0c;就会在当前目录(build 目录)中获得目标文件 hello。
    上述过程就是所谓的out-of-source 外部编译&#xff0c;一个最大的好处是&#xff0c;对于原有的工程没有任何影响&#xff0c;所有动作全部发生在编译目录。通过这一点&#xff0c;也足以说服我们全部采用外部编译方式构建工程。

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

四、更像样的CMake工程

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

让前面的Hello World 更像一个工程&#xff0c;需要作的是&#xff1a;

  1. 为工程添加一个子目录src&#xff0c;用来放置工程源代码。
  2. 添加一个子目录doc&#xff0c;用来放置这个工程的文档hello.txt。
  3. 在工程目录添加文本文件COPYRIGHT&#xff0c;README。
  4. 在工程目录添加一个runhello.sh 脚本&#xff0c;用来调用hello 二进制。
  5. 将构建后的目标文件放入构建目录的bin子目录。
  6. 最终安装这些文件&#xff1a;将hello 二进制与runhello.sh 安装至/usr/bin&#xff0c;将doc 目录的内容以及COPYRIGHT/README 安装到/usr/share/doc/cmake/t2。

4.1、准备工作

&#xff08;1&#xff09;建立t2目录。

mkdir t2

&#xff08;2&#xff09;将 t1 工程的 main.c 和 CMakeLists.txt 拷贝到t2 目录中。

cp ./main.c ../t2
cp ./CMakeLists.txt ../t2
# 或者整个目录复制
# cp -r ../t1 ../t2

&#xff08;3&#xff09;添加子目录src&#xff1a;

cd t2
mkdir src
mv main.c src/

现在的工程看起来是这个样子&#xff1a;一个子目录src&#xff0c;一个 CMakeLists.txt。

&#xff08;4&#xff09;上面提到&#xff0c;需要为任何子目录建立一个 CMakeLists.txt&#xff0c;进入子目录src&#xff0c;编写 CMakeLists.txt 如下&#xff1a;

ADD_EXECUTABLE(hello main.c)

&#xff08;5&#xff09;将 t2 工程的 CMakeLists.txt 修改为&#xff1a;

PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)

4.2、构建

建立build 目录&#xff0c;进入build 目录进行外部编译。

mkdir build
cd build
cmake ..
make

构建完成后&#xff0c;生成的目标文件 hello 位于 build/bin 目录中。

4.3、语法解释

&#xff08;1&#xff09;ADD_SUBDIRECTORY 指令&#xff1a;

ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

这个指令用于向当前工程添加存放源文件的子目录&#xff0c;并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除&#xff0c;比如&#xff0c;工程的 example&#xff0c;可能就需要工程构建完成后&#xff0c;再进入 example 目录单独进行构建(当然&#xff0c;也可以通过定义依赖来解决此类问题)。

上面的例子定义了将src 子目录加入工程&#xff0c;并指定编译输出(包含编译中间结果)路径为 bin 目录。如果不进行 bin 目录的指定&#xff0c;那么编译结果(包括中间结果)都将存放在 build/src 目录(这个目录跟原有的src 目录对应)&#xff0c;指定 bin 目录后&#xff0c;相当于在编译时将src 重命名为bin&#xff0c;所有的中间结果和目标二进制都将存放在bin 目录。

这里需要提一下的是SUBDIRS 指令&#xff0c;使用方法是&#xff1a;

SUBDIRS(dir1 dir2...)

但是这个指令已经不推荐使用。它可以一次添加多个子目录&#xff0c;并且&#xff0c;即使外部编译&#xff0c;子目录体系仍然会被保存。

如果在上面的例子中将ADD_SUBDIRECTORY (src bin)修改为SUBDIRS(src)&#xff0c;那么在build 目录中将出现一个src 目录&#xff0c;生成的目标代码 hello 将存放在src 目录中。

4.4、修改保存目标二进制的地方

不论是SUBDIRS 还是 ADD_SUBDIRECTORY 指令(不论是否指定编译输出目录)&#xff0c;我们都可以通过SET 指令重新定义EXECUTABLE_OUTPUT_PATH 和LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的hello 或者最终的共享库&#xff0c;不包含编译生成的中间文件)。

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

前面提到了_BINARY_DIR 和PROJECT_BINARY_DIR 变量&#xff0c;他们指的编译发生的当前目录&#xff0c;如果是内部编译&#xff0c;就相当于 PROJECT_SOURCE_DIR 也就是工程代码所在目录&#xff0c;如果是外部编译&#xff0c;指的是外部编译所在目录&#xff0c;也就是本例中的 build 目录。

所以&#xff0c;上面两个指令分别定义了&#xff1a;可执行二进制的输出路径为build/bin 和库的输出路径为build/lib。

这里没有提到共享库和静态库的构建&#xff0c;所以&#xff0c;暂时可以不考虑第二条指令。

问题是&#xff0c;应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的CMakeLists.txt&#xff0c;把握一个简单的原则&#xff1a;在哪里ADD_EXECUTABLE 或ADD_LIBRARY&#xff0c;如果需要改变目标存放路径&#xff0c;就在哪里加入上述的定义。

在这个例子里&#xff0c;当然就是指src 下的CMakeLists.txt 了。

4.5、如何安装编译的软件

安装的需要有两种&#xff0c;一种是从代码编译后直接 make install 安装&#xff0c;一种是打包时的指定目录安装。所以&#xff0c;即使最简单的手工编写的Makefile&#xff0c;看起来也是这个样子的&#xff1a;

DESTDIR&#61;
install:
mkdir -p $(DESTDIR)/usr/bin install -m 755 hello $(DESTDIR)/usr/bin

这时就可以通过 make install 将 hello 直接安装到/usr/bin 目录&#xff0c;也可以通过make install ESTDIR&#61;/tmp/test 将它安装在/tmp/test/usr/bin 目录&#xff0c;打包时这个方式经常被使用。

稍微复杂一点的是还需要定义PREFIX&#xff0c;一般autotools 工程&#xff0c;会运行这样的指令:

./configure –prefix&#61;/usr
# 或者
./configure --prefix&#61;/usr/local
#来指定 PREFIX

比如上面的Makefile 就可以改写成:

DESTDIR&#61;
PREFIX&#61;/usr
install:
mkdir -p $(DESTDIR)/$(PREFIX)/bin install -m 755 hello $(DESTDIR)/$(PREFIX)/bin

那么我们CMake的HelloWorld 应该怎么进行安装呢&#xff1f;

这里需要引入一个新的cmake 指令 INSTALL 和一个非常有用的变量&#xff1a;CMAKE_INSTALL_PREFIX。

CMAKE_INSTALL_PREFIX 变量类似于configure 脚本的 –prefix&#xff0c;常见的使用方法是这个样子&#xff1a;

cmake -DCMAKE_INSTALL_PREFIX&#61;/usr .

INSTALL 指令用于定义安装规则&#xff0c;安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。

INSTALL 指令包含了各种安装类型&#xff0c;需要一个个分开解释&#xff1a;

&#xff08;1&#xff09;目标文件的安装&#xff1a;

INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...])

参数中的TARGETS 后面跟的就是我们通过ADD_EXECUTABLE 或者ADD_LIBRARY 定义的目标文件&#xff0c;可能是可执行二进制、动态库、静态库。

目标类型也就相对应的有三种&#xff1a;ARCHIVE 特指静态库&#xff0c;LIBRARY 特指动态库&#xff0c;RUNTIME特指可执行目标二进制。

DESTINATION 定义了安装的路径&#xff0c;如果路径以/开头&#xff0c;那么指的是绝对路径&#xff0c;这时候 CMAKE_INSTALL_PREFIX 其实就无效了。如果希望使用CMAKE_INSTALL_PREFIX 来定义安装路径&#xff0c;就要写成相对路径&#xff0c;即不要以/开头&#xff0c;那么安装后的路径就是 ${CMAKE_INSTALL_PREFIX}/

举个简单的例子&#xff1a;

INSTALL(TARGETS myrun mylib mystaticlib RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)

上面的例子会将&#xff1a;

  • 可执行二进制myrun 安装到${CMAKE_INSTALL_PREFIX}/bin 。
  • 目录动态库libmylib 安装到${CMAKE_INSTALL_PREFIX}/lib 目录。
  • 静态库libmystaticlib 安装到${CMAKE_INSTALL_PREFIX}/libstatic 目录。
  • 特别注意的是不需要关心TARGETS 具体生成的路径&#xff0c;只需要写上TARGETS 名称就可以了。

&#xff08;2&#xff09;普通文件的安装&#xff1a;

INSTALL(FILES files... DESTINATION <dir> [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>]
[RENAME <name>] [OPTIONAL])

可用于安装一般文件&#xff0c;并可以指定访问权限&#xff0c;文件名是此指令所在路径下的相对路径。如果默认不定义权限PERMISSIONS&#xff0c;安装后的权限为&#xff1a;

OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ&#xff0c;即 644 权限。

&#xff08;3&#xff09;非目标文件的可执行程序安装(比如脚本之类)&#xff1a;

INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [RENAME <name>] [OPTIONAL])

跟上面的FILES 指令使用方法一样&#xff0c;唯一的不同是安装后权限为:

OWNER_EXECUTE, GROUP_EXECUTE, 和WORLD_EXECUTE&#xff0c;即755 权限目录的安装。

&#xff08;4&#xff09;目录安装&#xff1a;

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 目录的相对路径&#xff0c;但务必注意&#xff1a;abc 和 abc/有很大的区别。如果目录名不以/结尾&#xff0c;那么这个目录将被安装为目标路径下的abc&#xff0c;如果目录名以/结尾&#xff0c;代表将这个目录中的内容安装到目标路径&#xff0c;但不包括这个目录本身。

PATTERN 用于使用正则表达式进行过滤&#xff0c;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)

这条指令的执行结果是&#xff1a;将 icons 目录安装到 /share/myproj&#xff0c;将 scripts/中的内容安装到 /share/myproj不包含目录名为 CVS 的目录&#xff0c;对于scripts/*文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ。

&#xff08;5&#xff09;安装时CMAKE 脚本的执行&#xff1a;

INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
# SCRIPT #参数用于在安装时调用cmake 脚本文件&#xff08;也就是.cmake 文件&#xff09;
# CODE 参数用于执行CMAKE 指令&#xff0c;必须以双引号括起来。比如&#xff1a;
INSTALL(CODE "MESSAGE(\"Sample install message.\")")

安装还有几个被标记为过时的指令&#xff0c;比如 INSTALL_FILES 等&#xff0c;这些指令已经不再推荐使用。

下面&#xff0c;就来改写我们的工程文件&#xff0c;让他来支持各种文件的安装&#xff0c;并且&#xff0c;我们要使用 CMAKE_INSTALL_PREFIX 指令。

4.6、修改CMakeLists.txt支持安装

&#xff08;1&#xff09;首先先补上为添加的文件。添加doc 目录及文件:

cd t2
mkdir doc
vi doc/hello.txt

随便填写一些内容并保存。

&#xff08;2&#xff09;在工程目录添加runhello.sh 脚本&#xff0c;内容为&#xff1a;

hello

&#xff08;3&#xff09;添加工程目录中的COPYRIGHT 和 README&#xff1a;

touch COPYRIGHT
touch README

&#xff08;4&#xff09;下面改写各目录的CMakeLists.txt 文件。

1&#xff09;安装 COPYRIGHT和README&#xff0c;直接修改主工程文件CMakelists.txt&#xff0c;加入以下指令&#xff1a;

INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)

2&#xff09;安装 runhello.sh&#xff0c;直接修改主工程文件CMakeLists.txt&#xff0c;加入如下指令&#xff1a;

INSTALL(PROGRAMS runhello.sh DESTINATION bin)

3&#xff09;安装 doc 中的hello.txt&#xff0c;这里有两种方式&#xff1a;一是通过在doc 目录建立 CMakeLists.txt 并将 doc 目录通过ADD_SUBDIRECTORY 加入工程来完成。另一种方法是直接在工程目录通过INSTALL(DIRECTORY 来完成)&#xff0c;前者比较简单&#xff0c;可以根据兴趣自己完成&#xff0c;我们来尝试后者&#xff0c;顺便演示以下DIRECTORY 的安装。
因为hello.txt 要安装到//share/doc/cmake/t2&#xff0c;所以不能直接安装整个doc 目录&#xff0c;这里采用的方式是安装 doc 目录中的内容&#xff0c;也就是使用”doc/”。在工程文件中添加&#xff1a;

INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)

&#xff08;5&#xff09;现在进入build 目录进行外部编译&#xff0c;注意使用 CMAKE_INSTALL_PREFIX 参数&#xff0c;这里我们将它安装到了/tmp/t2 目录&#xff1a;

cmake -DCMAKE_INSTALL_PREFIX&#61;/tmp/t2/usr ..

&#xff08;6&#xff09;然后运行&#xff1a;

make
sudo make install

&#xff08;7&#xff09;进入/tmp/t2 目录看一下安装结果&#xff1a;

$ tree -L 5 /tmp/t2/usr/
/tmp/t2/usr/
├── bin
│ └── runhello.sh
└── share
└── doc
└── cmakeProj
└── t2
├── COPYRIGHT
├── hello.txt
└── README
5 directories, 4 files

&#xff08;8&#xff09;如果要直接安装到系统&#xff0c;可以使用如下指令&#xff1a;

cmake -DCMAKE_INSTALL_PREFIX&#61;/usr ..

注意&#xff0c;CMAKE_INSTALL_PREFIX 的默认定义是/usr/local&#xff0c;如果没有定义CMAKE_INSTALL_PREFIX 会安装到这个地方。

总结
  1. 使用cmake 构建Hello World 程序的全部过程&#xff0c;并介绍三个简单的指令&#xff1a;PROJECT/MESSAGE/ADD_EXECUTABLE 以及变量调用的方法&#xff0c;同时提及了两个隐式变量 _SOURCE_DIR 及_BINARY_DIR&#xff0c;演示了变量调用的方法&#xff0c;从这个过程来看&#xff0c;可能觉得比直接写 Makefile 要复杂多了&#xff0c;甚至都可以不编写Makefile&#xff0c;直接使用gcc -o hello -c main.c 即可生成需要的目标文件。这是因为工程只有几个文件&#xff0c;还是直接编写 Makefile 最简单。但是&#xff0c;如果工程文件非常多&#xff0c;那么CMake就比Makefile简单了。
  2. 掌握如何在工程中使用多目录、各种安装指令以及CMAKE_INSTALL_PREFIX 变量。
    在这里插入图片描述






推荐阅读
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 【重识云原生】第四章云网络4.8.3.2节——Open vSwitch工作原理详解
    2OpenvSwitch架构2.1OVS整体架构ovs-vswitchd:守护程序,实现交换功能,和Linux内核兼容模块一起,实现基于流的交换flow-basedswitchin ... [详细]
author-avatar
一剑吹雪_811
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有