Cmake实践(Cmake Practice)第二部分
参考资料地址:https://github.com/Akagi201/learning-cmake/blob/master/docs/cmake-practice.pdf
一、静态库与动态库构建
本小节目标如下:
- 建立一个静态库和动态库,提供HelloFunc函数供其他程序编程使用,HelloFunc向终端输出Hello World字符串
- 安装头文件与共享库
1. 建立工作目录t3
mkdir -p /backup/cmake/t3
2. 建立共享库目录
cd /backup/cmake/t3
mkdir lib
在t3工程目录下建立CMakeLists.txt:
// /backup/cmake/t3/CMakeLists.txt
1 PROJECT(HELLOLIB) 2 ADD_SUBDIRECTORY(lib)
在lib目录下建立源文件hello.c与hello.h:
//hello.c
1 #include "hello.h" 2 void HelloFunc() 3 { 4 printf("Hello World\n"); 5 }
//hello.h
1 #ifndef HELLO_H 2 #define HELLO_H 3 #include <stdio.h> 4 void HelloFunc(); 5 #endif
在lib子目录下建立CMakeLists.txt:
// /backup/cmake/t3/CMakeLists.txt
1 SET(LIBHELLO_SRC hello.c) 2 ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
3. 编译共享库
mkdir build + cd build
cmake .. + make //在lib目录生成共享库libhello.so
//可修改工程目录t3中的CMakeLists.txt指定生成位置:ADD_SUBDIRECTORY(lib <目录>)指令来指定一个编译输出位置
//或者在lib/CMakeLists.txt中添加SET(LIBRARY_OUTPUT_PATH <路径>)来指定一个新的位置
ADD_LIBRARY指令的语法:
ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
注:
(1)无需写全libhello.so,只需要将libname设置为hello即可,cmake系统会自动生成libhello.X
(2)库类型有三种:
- SHARED:动态库
- STATIC: 静态库
- MODULE:在使用dyld的系统中有效,如果不支持dyld则被视为SHARED
(3)EXCLUDE_FROM_ALL参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建
4. 添加静态库(lib/CMakeLists.txt)
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) //静态库和动态库名字一致,生成后缀为.a
添加上述命令并重新进行外部编译后,仍然仅生成动态库,并没有构建静态库。原因:hello作为一个target是不能重名的,所以,静态库构建指令无效。
(1)解决方案一:ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
构建了libhello_static.a的静态库;不足:静态库和动态库名字不同
(2)解决方案二:采用SET_TARGET_PROPERTIES指令,基本语法如下
SET_TARGET_PROPERTIES(target1 target2 ... PROPERTIES prop1 value1 prop2 value2 ...)
设置输出的名称,对于动态库,还可以用来指定动态库版本和API版本
在lib/CMakeLists.txt继续添加:SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
至此,可以同时得到libhello.so和libhello.a两个库。
补充:对应的GET_TARGET_PROPERTY指令
GET_TARGET_PROPERTY(VAR target property)
具体用法:向lib/CMakeLists.txt添加
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS “This is the hello_static OUTPUT_NAME: ” ${OUTPUT_VALUE}) //如果未定义该属性,则返回NOTFOUND
(3)问题:检查最终的构建结果,会发现build/lib目录中存在libhello.a,但是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库
5. 动态库版本号:使用SET_TARGET_PROPERTIES指令
在lib/CMakeLists.txt中加入:SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
构建结果会在build/lib目录生成:
libhello.so.1.2
libhello.so.1 -> libhello.so.1.2
libhello.so -> libhello.so.1
6. 安装共享库和头文件(将libhello.a, libhello.so.x以及hello.h安装到系统目录,以供其他人开发使用)
在lib/CMakeLists.txt中添加如下指令:
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
注:静态库要使用ARCHIVE关键字
执行cd build + cmake -DCMAKE_INSTALL_PREFIX=/usr .. + make + make install 将头文件和共享库安装到系统目录/usr/lib和/usr/include/hello目录中
二、使用外部共享库和头文件
1. 创建工作目录
mkdir -p /bakcup/cmake/t4
2. 建立src子目录 mkdir src,编写源文件main.c:
//main.c
1 #include<hello.h> 2 int main() 3 { 4 HelloFunc(); 5 return 0; 6 }
编写工程目录CMakeLists.txt:
// /backup/cmake/t4/CMakeLists
1 PROJECT(NEWHELLO) 2 ADD_SUBDIRECTORY(src)
编写src/CMakeLists.txt:
// /backup/cmake/t4/src/CMakeLists
1 ADD_EXECUTABLE(main main.c)
3. 外部构建
mkdir build + cd build + cmake .. + make VERBOSE=1
构建失败,错误输出为:/backup/cmake/t4/src/main.c:1:19: error: hello.h: 没有那个文件或目录
原因:找不到头文件
4. 引入头文件搜索路径(上一节的hello.h位于/usr/include/hello目录中,并没有位于系统标准的头文件路径)
解决方法:采用INCLUDE_DIRECTORIES指令
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,可以通过两种方式来进行控制搜索路径添加的方式:
(1)通过SET指令设置cmake变量CMAKE_INCLUDE_DIRECTORIES_BEFORE为on,将添加的头文件搜索路径放在已有路径的前面
(2)通过上述指令的AFTER或者BEFORE参数,控制是追加还是置前
因此,在src/CMakeLists.txt中添加头文件搜索路径:INCLUDE_DIRECTORIES(/usr/include/hello)
重新进入build构建,仍然会失败,新的错误为:main.c:(.text+0x12): undefined reference to `HelloFunc'
原因:目标文件没有link到共享库libhello上
5. 为target添加共享库
解决方法:LINK_DIRECTORIES和TARGET_LINK_LIBRARIES指令
LINK_DIRECTORIES(directory1 directory2 ...) //添加非标准的共享库搜索路径
TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...) //添加链接,为target添加需要链接的共享库或可执行二进制文件,本例中是一个可执行文件,也可以用于为自己编写的共享库添加共享库链接
因此,在src/CMakeLists.txt中添加如下指令:TARGET_LINK_LIBRARIES(main hello) 或者 TARGET_LINK_LIBRARIES(main libhello.so)
重新进入build构建,得到了一个连接到libhello的可执行程序main,位于build/src目录
查看main的链接情况:ldd src/main //ldd:list dynamic dependencies,列出动态库依赖关系
linux-gate.so.1 => (0xb7ee7000)
libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000) //链接动态库libhello.so.1
libc.so.6 => /lib/libc.so.6 (0xb7d77000)
/lib/ld-linux.so.2 (0xb7ee8000)
如何链接到静态库?修改链接指令为:TARGET_LINK_LIBRARIES(main libhello.a)
ldd src/main:
linux-gate.so.1 => (0xb7fa8000)
libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
/lib/ld-linux.so.2 (0xb7fa9000)
结果无动态库,表明确实链接到静态库
6. 特殊的环境变量CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH(非cmake变量)
用于在bash中用export或者在csh中使用set命令设置或者CMAKE_INCLUDE_PATH=/home/include cmake ..等方式;主要用于弥补头文件没有存放在常规路径的搜索情况
例如前面我们直接使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello)告诉工程这个头文件目录。为了将程序更智能一点,我们可以使用CMAKE_INCLUDE_PATH来进行,使用bash的方法如下:
export CMAKE_INCLUDE_PATH=/usr/include/hello
然后在src/CMakeLists.txt中将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_INCLUDE_PATH;相应的FIND_LIBRARY可以使用CMAKE_LIBRARY_PATH变量,所有使用FIND_指令的cmake模块都可以使用上述环境变量。