一 安装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_DIR
和 HELLO_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 基本语法规则
最简单的语法规则是:
-
变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名
-
指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令。
-
指令(参数 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,内部编译上面已经演示过了,它生成了一些无法自动删除的中间文件,所以,
引出了我们对外部编译的探讨,外部编译的过程如下:
- 首先,请清除 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 仍然指代工程路径,即 /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 更像一个工程,我们需要做的是:
-
为工程添加一个子目录 src,用来放置工程源代码;
-
添加一个子目录 doc,用来放置这个工程的文档 hello.txt
-
在工程目录添加文本文件 COPYRIGHT, README;
-
在工程目录添加一个 runhello.sh 脚本,用来调用 hello 二进制
-
将构建后的目标文件放入构建目录的 bin 子目录;
-
最终安装这些文件:将 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_PATH 和LIBRARY_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_DIR
和 PROJECT_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 支持安装
在本节开头我们定义了本节的任务如下:
- 为工程添加一个子目录 src,用来存储源代码;
- 添加一个子目录 doc,用来存储这个工程的文档 hello.txt;
- 在工程目录添加文本文件 COPYRIGHT, README;
- 在工程目录添加一个 runhello.sh 脚本,用来调用 hello 二进制;
- 将构建后的目标文件放入构建目录的 bin 子目录;
- 最终安装这些文件:将 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 文件。
-
安装 COPYRIGHT/README,直接修改主工程文件 CMakelists.txt,加入以下指令:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
-
安装 runhello.sh,直接修改主工程文件 CMakeLists.txt,加入如下指令:
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
-
安装 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 静态库与动态库构建
本节任务:
- 建立一个静态库和动态库,提供 HelloFunc 函数供其他程序编程使用,HelloFunc 向终端输出 Hello World 字符串。
- 安装头文件与共享库
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的。
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,你可以通过两种方式来进行控制搜索路径添加的方式:
- CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过SET这个cmake变量为on,可以将添加的头文件搜索路径放在已有路径的前面。
- 通过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为库或可执行二进制加入库链接。
并解释了如何链接到静态库。