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 "); 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模块都可以使用上述环境变量。

原文地址:https://www.cnblogs.com/hg-love-dfc/p/10244328.html