cmake学习

一 安装cmake

可以从cmake官网下载

二 初试cmake——cmake的helloword

2.1 准备工作

在/home下建立一个cmake目录,在里边建立t1文件夹

mkdir -p cmake
cd cmake
mkdir t1
cd t1
touch main.c
touch CMakeLists.txt

在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})

注意:CMakeLists.txt最后一行的 ${SRC_LIST},在官网中是直接写着SRC_LIST,这样会报错找不到SRC_LIST

2.2 开始构建

然后cmake .

make

出现

   Scanning dependencies of target hello
   [100%] BuildingC object CMakeFiles/hello.dir/main.o
   Linking C executable hello
   [100%] Built target hello

文件夹中还出现了CMakeFiles, CMakeCache.txt, cmake_install.cmake 等文件,并且生成了Makefile.

接着尝试运行:./hello

得到输出:Hello World from t1 Main

2.3 简单的解释

来重新看一下 CMakeLists.txt,这个文件是 cmake 的构建定义文件,文件名是大小写相关的,如果工程存在多个目录,需要确保每个要管理的目录都存在一个
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})
指令1——PROJECT指令的语法是

PROJECT(projectname [CXX] [C] [Java])

你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。

这个指令隐式的定义了两个 cmake 变量:<projectname>_BINARY_DIR以及<projectname>_SOURCE_DIR,这里就是
HELLO_BINARY_DIRHELLO_SOURCE_DIR(所以 CMakeLists.txt 中两个 MESSAGE 指令可以直接使用了这两个变量)

指令2——SET指令的语法是

SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

比如我们用到的是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(hello ${SRC_LIST}):

定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表, 本例中你也可以直接写成 ADD_EXECUTABLE(hello main.c)

2.4 基本语法规则

最简单的语法规则是:

  1. 变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名

  2. 指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令。

  3. 指令(参数 1 参数 2…) 参数使用括弧括起,参数之间使用空格或分号分开。

    以上面的 ADD_EXECUTABLE 指令为例,如果存在另外一个 func.c 源文件,就要写成: ADD_EXECUTABLE(hello main.c func.c) 或者 ADD_EXECUTABLE(hello main.c;func.c)

2.5 关于语法的疑惑

2.6 清理工程

跟经典的 autotools 系列工具一样,运行:

make clean

即可对构建结果进行清理。

2.7 问题

“我尝试运行了 make distclean,这个指令一般用来清理构建过程中产生的中间文件的,如果要发布代码,必然要清理掉所有的中间文件,但是为什么在 cmake 工程中这个命令是无效的?

是的,cmake 并不支持 make distclean,关于这一点,官方是有明确解释的:因为 CMakeLists.txt 可以执行脚本并通过脚本生成一些临时文件,但是却没有办法来跟
踪这些临时文件到底是哪些。因此,没有办法提供一个可靠的 make distclean 方案。

2.8 内部构建和外部构建

上面的例子展示的是 “ 内部构建 ” ,相信看到生成的临时文件比您的代码文件还要多的时候,估计这辈子你都不希望再使用内部构建。

对于 cmake,内部编译上面已经演示过了,它生成了一些无法自动删除的中间文件,所以,
引出了我们对外部编译的探讨,外部编译的过程如下:

  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 仍然指代工程路径,即 /backup/cmake/t1,而 HELLO_BINARY_DIR 则指代编译路径,即 /backup/cmake/t1/build

2.9 小结

本小节描述了使用 cmake 构建 Hello World 程序的全部过程,并介绍了三个简单的指令: PROJECT/MESSAGE/ADD_EXECUTABLE 以及变量调用的方法,同时提及了两个隐式变量_SOURCE_DIR _BINARY_DIR,演示了变量调用的方法

三 更好一点的Hello World

本小节的任务是让前面的 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

3.1 准备工作

在/backup/cmake/目录下建立 t2 目录。
将 t1 工程的 main.c 和 CMakeLists.txt 拷贝到 t2 目录中。

3.2 添加子目录 src:

nkdir src
mv main.c src    //把main.c移动到src

现在的工程看起来是这个样子:一个子目录 src,一个 CMakeLists.txt。

上一节我们提到,需要为任何子目录建立一个 CMakeLists.txt,进入子目录 src,编写 CMakeLists.txt 如下:

ADD_EXECUTABLE(hello main.c)

将 t2 工程的 CMakeLists.txt 修改为:

PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)      //将 src 子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin 目录

然后建立 build 目录,进入 build 目录进行外部编译。

cmake ..
make

​ 构建完成后,你会发现生成的目标文件 hello 位于 build/bin 目录中。

​ 语法解释

指令5——ADD_SUBDIRECTORY

​ ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])   //这个是要加在主工程的CMakeLists.txt里的。

​ 这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。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 目录中。

3.3 换个地方保存目标二进制

不论是 SUBDIRS 还是 ADD_SUBDIRECTORY 指令(不论是否指定编译输出目录),我们都可以通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATHLIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)。这个是加在lib文件夹下的CMakeLists.txt里的。

指令6——SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

在第一节我们提到了<projectname>_BINARY_DIRPROJECT_BINARY_DIR 变量,他们指的编译发生的当前目录,如果是内部编译,就相当于 PROJECT_SOURCE_DIR 也就是工程代码所在目录,如果是外部编译,指的是外部编译所在目录,也就是本例中的 build目录。

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

本节我们没有提到共享库和静态库的构建,所以,你可以不考虑第二条指令。

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

3.4 如何安装

安装的需要有两种,一种是从代码编译后直接 make install 安装,一种是打包时的指定目录安装。

所以,即使最简单的手工编写的 Makefile,看起来也是这个样子的:

DESTDIR=
install:
	mkdir -p $(DESTDIR)/usr/bin
	install -m 755 hello $(DESTDIR)/usr/bin

可以通过:make install将 hello 直接安装到/usr/bin 目录,也可以通过make install DESRDIR=/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

那么我们的 HelloWorld 应该怎么进行安装呢?

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

CMAKE_INSTALL_PREFIX 变量类似于 configure 脚本的 – prefix,常见的使用方法看起来是这个样子:

cmake -DCMAKE_INSTALL_PREFIX=/usr .

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

INSTALL 指令包含了各种安装类型,我们需要一个个分开解释:

3.4.1 目标文件的安装:

指令7.1——INSTALL(TARGETS targets )

不同之处在于TARGETS

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 名称就可以了。

3.4.2 普通文件的安装

指令7.2——INSTALL(FILES files DESTINATION )

改变之处在于FILES,DESTINATION的意思是目的地

INSTALL(FILES files... DESTINATION <dir>   //INSTALL(FILES 文件名 DESTINATION 想安装到的目录位置)
	[PERMISSIONS permissions...]
	[CONFIGURATIONS [Debug|Release|...]]
	[COMPONENT <component>]
	[RENAME <name>] [OPTIONAL])

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

OWNER_WRITE, OWNER_READ, GROUP_READ,和 WORLD_READ,即 644 权限。

3.4.3 非目标文件的可执行程序安装(比如脚本之类):

指令7.3——INSTALL(PROGRAMS files DESTINATION )

改变之处在于PROGRAMS

INSTALL(PROGRAMS files... DESTINATION <dir>     //INSTALL(PROGRAMS 文件名.sh DESTINATION 想安装到的目录位置)
	[PERMISSIONS permissions...]
	[CONFIGURATIONS [Debug|Release|...]]
	[COMPONENT <component>]
	[RENAME <name>] [OPTIONAL])

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

OWNER_EXECUTE, GROUP_EXECUTE, 和 WORLD_EXECUTE,即 755 权限

3.4.4 目录的安装:

指令7.4——INSTALL(DIRECTORY dirs DESTINATION )

改变之处在于DIRECTORY

INSTALL(DIRECTORY dirs... DESTINATION <dir>     //INSTALL(DIRECTORY 目录 DESTINATION 想安装到的目录位置)
	[FILE_PERMISSIONS permissions...]
	[DIRECTORY_PERMISSIONS permissions...]
	[USE_SOURCE_PERMISSIONS]
	[CONFIGURATIONS [Debug|Release|...]]
	[COMPONENT <component>]
	[[PATTERN <pattern> | REGEX <regex>]
	[EXCLUDE] [PERMISSIONS permissions...]] [...])

这里主要介绍其中的 DIRECTORY、PATTERN 以及 PERMISSIONS 参数。

3.4.5 安装时CMAKE脚本的执行

指令7.5——INSTALL([[SCRIPT [CODE ]])

INSTALL([[SCRIPT ] [CODE ]] […])

SCRIPT 参数用于在安装时调用 cmake 脚本文件(也就是.cmake 文件)

CODE 参数用于执行 CMAKE 指令,必须以双引号括起来。比如:

INSTALL(CODE “MESSAGE(“Sample install message.”)”)

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

3.5 修改 Helloworld 支持安装

在本节开头我们定义了本节的任务如下:

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

首先我们先补上为添加的文件。

1添加doc目录及文件:

cd cmake/t2
mkdir doc
vim doc/hello.txt

随便填写一些内容并保存

2在工程目录添加 runhello.sh 脚本,内容为:

hello

3和4添加工程目录中的 COPYRIGHT 和 README

touch COPYRIGHT
touch README

下面改写各目录的 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)

3.6 尝试我们修改的结果

现在进入 build 目录进行外部编译,注意使用 CMAKE_INSTALL_PREFIX 参数,这里我们将它安装到了/tmp/t2 目录:(这一步就决定了整个文件安装的位置,前边那些是决定子文件安装的位置)PREFIX是前缀的意思

cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..

然后运行

make
make install

然后进/tmp/t2目录看一下安装结果:

./usr
./usr/share
./usr/share/doc
./usr/share/doc/cmake
./usr/share/doc/cmake/t2
./usr/share/doc/cmake/t2/hello.txt
./usr/share/doc/cmake/t2/README
./usr/share/doc/cmake/t2/COPYRIGHT
./usr/bin
./usr/bin/hello    //我没有这个
./usr/bin/runhello.sh

如果要直接安装到系统,可以使用如下指令:(上边是/tmp/t2/usr …,这里是/usr …)

cmake -DCMAKE_INSTALL_PREFIX=/usr …

3.7 一个疑问

如果我没有定义 CMAKE_INSTALL_PREFIX 会安装到什么地方?

你可以尝试以下cmake ..;make;make install,你会发现 CMAKE_INSTALL_PREFIX 的默认定义是**/usr/local**

3.8 小结

本小节主要描述了如何在工程中使用多目录、各种安装指令以及 CMAKE_INSTALL_PREFIX 变量(你真够牛的,这么点东西居然罗唆了这么多文字)

在下一小节,我们将探讨如何在 cmake 中构建动态库和静态库,以及如何使用外部头文件
和外部共享库。

4 静态库与动态库构建

本节任务:

  1. 建立一个静态库和动态库,提供 HelloFunc 函数供其他程序编程使用,HelloFunc 向终端输出 Hello World 字符串。
  2. 安装头文件与共享库

4.1 准备工作

在/home/cmake目录下建立t3目录,用于存放本节涉及到的工程

4.2 建立共享库

cd /home/cmake/t3
mkdir lib

在 t3 目录下建立 CMakeLists.txt,内容如下:

PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)

在 lib 目录下建立两个源文件 hello.c 与 hello.h

hello.c 内容如下:

#include "hello.h"
void HelloFunc()
{
    printf("Hello World\n");
}

hello.h内容如下:

#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif

在lib目录下建立CMakeLists.txt,内容如下:

SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

4.3 编译共享库

仍然采用 out-of-source 编译的方式,按照习惯,我们建立一个build目录,在build目录中

cmake ..
make

这时,你就可以在lib目录得到一个libhello.so,这就是我们期望的共享库。

如果你要指定libhello.so生成的位置,可以通过在主工程文件CMakeLists.txt中修改ADD_SUBDIRECTORY(lib)指令来指定一个编译输出位置或者在lib/CMakeLists.txt中添加SET(LIBRARY_OUTPUT_PATH <路径>)来指定一个新的位置**(这一句SET(LIBRARY_OUTPUT_PATH ~/cmake/t33/lib)要是没有的话,就没法在lib中生成libhello.so文件)**。这两者的区别我们上一节已经提到了,所以,这里不再赘述,下面,我们解释一下一个新的指令ADD_LIBRARY:

指令11——ADD_LIBRARY

ADD_LIBRARY(libname [SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL] source1 source2 … sourceN)

你不需要写全libhello.so,只需要填写hello即可,cmake系统会自动为你生成libhello.X。类型有三种:

  • SHARED,动态库(扩展名为.so)
  • STATIC,静态库(扩展名为.a)
  • MODULE,在使用dyld的系统有效,如果不支持dyld,则被当作SHARED对待。
  • EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。

4.4 添加静态库

同样使用上面的指令,我们在支持动态库的基础上再为工程添加一个静态库,按照一般的习惯,静态库名字跟动态库名字应该是一致的,只不过后缀是.a罢了。下面我们用这个指令再来添加静态库:

ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})

然后再在build目录进行外部编译,我们会发现,静态库根本没有被构建,仍然只生成了一个动态库。因为hello作为一个target是不能重名的,所以,静态库构建指令无效。

如果我们把上面的hello修改为hello_static:

ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

就可以构建一个libhello_static.a的静态库了。这种结果显示不是我们想要的,我们需要的是名字相同的静态库和动态库,因为target名称是唯一的,所以,我们肯定不能通过ADD_LIBRARY指令来实现了。这时候我们需要用到另外一个指令:

指令12——SET_TARGET_PROPERTIES

其基本语法是:
SET_TARGET_PROPERTIES(target1 target2 …PROPERTIES prop1 value1prop2 value2 …) //PROPERTIES 属性

这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和API版本。在本例中,我们需要作的是向lib/CMakeLists.txt中添加一条:

SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")

这样,我们就可以同时得到libhello.so/libhello.a两个库了。与他对应的指令是:

GET_TARGET_PROPERTY(VAR target property)

具体用法如下例,我们向lib/CMakeListst.txt中添加:

GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS  "This is the hello_static OUTPUT_NAME:" ${OUTPUT_VALUE})

如果没有这个属性定义,则返回 NOTFOUND。

让我们来检查一下最终的构建结果,我们发现,libhello.a已经构建完成,位于build/lib目录中,但是libhello.so去消失了。这个问题的原因是:cmake在构建一个新的target时,会尝试清理掉其他使用这个名字的库,因为,在构建libhello.a时,就会清理掉libhello.so.为了回避这个问题,比如再次使用SET_TARGET_PROPERTIES定义CLEAN_DIRECT_OUTPUT属性。向lib/CMakeLists.txt中添加:

SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

这时候,我们再次进行构建,会发现build/lib目录中同时生成了 libhello.so 和 libhello.a

4.5 动态库版本号

按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是

libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2

为了实现动态库版本号,我们仍然需要使用SET_TARGET_PROPERTIES指令。具体使用方法如下:

SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)

VERSION指代动态库版本,SOVERSION指代API版本。将上述指令加入lib/CMakeLists.txt中,重新构建看看结果。

在build/lib目录会生成:

libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1

4.6 安装共享库和头文件

以上面的例子,我们需要将libhello.a, libhello.so.x以及hello.h安装到系统目录,才能真正让其他人开发使用,在本例中我们将hello的共享库安装到/lib目录,将hello.h安装到/include/hello目录。

利用上一节了解到的INSTALL指令,我们向lib/CMakeLists.txt中添加如下指令:下边的指令就是安装了目标文件和共享库

INSTALL(TARGETS hello hello_static
		LIBRARY DESTINATION lib
		ARCHIVE DESTINATION lib)
		
INSTALL(FILES hello.h DESTINATION include/hello)

注意,静态库要使用ARCHIVE关键字

通过:

cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
make install

我们就可以将头文件和共享库安装到系统目录/usr/lib和/usr/include/hello中了。

4.7 小结

本小节,我们谈到了:

如何通过 ADD_LIBRARY 指令构建动态库和静态库。

如何通过 SET_TARGET_PROPERTIES 同时构建同名的动态库和静态库。

如何通过 SET_TARGET_PROPERTIES 控制动态库版本

最终使用上一节谈到的 INSTALL 指令来安装头文件和动态、静态库。

在下一节,我们需要编写另一个高级一点的Hello World来演示怎么使用我们已经构建的构建的共享库libhello和外部头文件。

5 如何使用外部共享库和头文件

上一节我们已经完成了libhello动态库的构建以及安装,本节我们的任务很简单:

编写一个程序使用我们上一节构建的共享库。

5.1 准备工作

请在/home/cmake目录建立t4目录,本节所有资源将存储在t4目录。

5.2 重复以前的步骤,建立src目录,编写源文件main.c,内容如下:

#include <hello.h>
int main()
{
    HelloFunc();
    return 0;
}

编写工程主文件CMakeLists.txt

PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)

编写src/CMakeLists.txt

ADD_EXECUTABLE(main main.c)

5.3 外部构建

按照习惯,仍然建立build目录,使用cmake …方式构建。

过程:

cmake ..
make

构建失败,如果需要查看细节,可以使用第一节提到的方法

make VERBOSE=1来构建

错误输出为是:

/home/cmake/t4/src/main.c:1:19: error: hello.h: 没有那个文件或目录

5.4 引入头文件搜索路径

hello.h位于/usr/include/hello目录中,并没有位于系统标准的头文件路径,(有人会说了,白痴啊,你就不会include <hello/hello.h>,同志,要这么干,我这一节就没什么可写了,只能选择一个glib或者libX11来写了,这些代码写出来很多同志是看不懂的)

为了让我们的工程能够找到hello.h头文件,我们需要引入一个新的指令

指令12——INCLUDE_DIRECTORIES

其完整语法为:

INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)

INCLUDE_DIRECTORIES( “…/…/xxxx” ) // 注意:括号里的相对路径是相对CMakeLists.txt的。

这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,你可以通过两种方式来进行控制搜索路径添加的方式:

  1. CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过SET这个cmake变量为on,可以将添加的头文件搜索路径放在已有路径的前面。
  2. 通过AFTER或者BEFORE参数,也可以控制是追加还是置前。现在我们在src/CMakeLists.txt中添加一个头文件搜索路径,方式很简单,加入:INCLUDE_DIRECTORIES(/usr/include/hello)

进入build目录,重新进行构建,这是找不到hello.h的错误已经消失,但是出现了一个新的错误:

main.c:(.text+0x12): undefined reference to `HelloFunc’

因为我们并没有link到共享库libhello上。

5.5 为target添加共享库

我们现在需要完成的任务是将目标文件链接到libhello,这里我们需要引入两个新的指令:

指令13——LINK_DIRECTORIES

LINK_DIRECTORIES的全部语法是:

LINK_DIRECTORIES(directory1 directory2 …)

LINK_DIRECTORIES(" …/…/xxxx ") // 括号里的相对路径是相对执行make动作的目录为基准的。

这个指令非常简单,添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径。这个例子中我们没有用到这个指令。

指令14——TARGET_LINK_LIBRARIES

TARGET_LINK_LIBRARIES的全部语法是:

TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2…)

这个指令可以用来为target添加需要链接的共享库,本例中是一个可执行文件,但是同样可以用于为自己编写的共享库添加共享库链接。为了解决我们前面遇到的HelloFunc未定义错误,我们需要作的是向src/CMakeLists.txt中添加如下指令:

TARGET_LINK_LIBRARIES(main hello)

也可以写成

TARGET_LINK_LIBRARIES(main libhello.so)

这里的hello指的是我们上一节构建的共享库libhello.

进入build目录重新进行构建。

cmake ..
make

这是我们就得到了一个连接到libhello的可执行程序main,位于build/src目录,运行main的结果是输出:

Hello World

让我们来检查一下main的链接情况:

ldd src/main
linux-gate.so.1 => (0xb7fa8000)
libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
/lib/ld-linux.so.2 (0xb7fa9000)

说明,main确实链接到了静态库libhello.a

5.6 特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH

务必注意,这两个是环境变量而不是cmake变量。使用方法是要在bash中用export或者在csh中使用set命令设置或者CMAKE_INCLUDE_PATH=/home/include cmake …等方式。

这两个变量主要是用来解决以前autotools工程中–extra-include-dir等参数的支持的。也就是,如果头文件没有存放在常规路径(/usr/include, /usr/local/include等),则可以通过这些变量就行弥补。我们以本例中的hello.h为例,它存放在/usr/include/hello目录,所以直接查找肯定是找不到的。前面我们直接使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello)告诉工程这个头文件目录。为了将程序更智能一点,我们可以使用CMAKE_INCLUDE_PATH来进行,使用bash的方法如下:

export CMAKE_INCLUDE_PATH=/usr/include/hello

然后在头文件中将INCLUDE_DIRECTORIES(/usr/include/hello)替换为:

FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)

上述的一些指令我们在后面会介绍。这里简单说明一下,FIND_PATH用来在指定路径中搜索文件名,比如:

FIND_PATH(myHeader NAMES hello.h PATHS /usr/include/usr/include/hello)

这里我们没有指定路径,但是,cmake仍然可以帮我们找到hello.h存放的路径,就是因为我们设置了环境变量CMAKE_INCLUDE_PATH。如果你不使用FIND_PATH,CMAKE_INCLUDE_PATH变量的设置是没有作用的,你不能指望它会直接为编译器命令添加参数-I<CMAKE_INCLUDE_PATH>。以此为例,CMAKE_LIBRARY_PATH可以用在FIND_LIBRARY中。同样,因为这些变量直接为FIND_指令所使用,所以所有使用FIND_指令的cmake模块都会受益。

5.7 小结

本节我们探讨了:

如何通过INCLUDE_DIRECTORIES指令加入非标准的头文件搜索路径。

如何通过LINK_DIRECTORIES指令加入非标准的库文件搜索路径。

如果通过TARGET_LINK_LIBRARIES为库或可执行二进制加入库链接。

并解释了如何链接到静态库。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值