百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

CMAKE最全实战(2) cmake -d

lipiwang 2024-10-16 13:08 10 浏览 0 评论

阅读本篇文章前,请阅读CMAKE最全实战(1),这2篇是有关联。

1.静态库与动态库构建

从本节开始,我们不再折腾Hello World 了,我们来折腾Hello World 的共享库。

本节的任务:

1.建??个静态库和动态库,提供HelloFunc 函数供其他程序编程使?,HelloFunc向终端输出Hello World 字符串。

2.安装头?件与共享库。

准备?

在cmake ?录建? t3 ?录,?于存放本节涉及到的?程

建?共享库

cd make/t3

mkdir lib

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

PROJECT(HELLOLIB)

ADD_SUBDIRECTORY(lib)

在 lib ?录下建?两个源?件hello.c 与hello.h

hello.c 内容如下:

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

hello.h 内容如下:

#ifndef HELLO_H_
#define HELLO_H_ (1)
#include <stdio.h>
void hello_func(void);
#endif

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

SET(LIBHELLO_SRC hello.c)

ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

编译共享库

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

cmake ..

make

这时,你就可以在lib ?录得到?个libhello.so,这就是我们期望的共享库。如果你要指定libhello.so ?成的位置,可以通过在主?程?件CMakeLists.txt 中修改 ADD_SUBDIRECTORY(lib)指令来指定?个编译输出位置或者在lib/CMakeLists.txt 中添加SET(LIBRARY_OUTPUT_PATH <路径>)来指定?个新的位置。

这两者的区别CMAKE最全实战(1) 已经提到了,所以,这?不再赘述,下?,我们解释?下?个新的指令

ADD_LIBRARY

ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL]

source1 source2 ... sourceN)

不需要写全libhello.so,只需要填写hello 即可,cmake 系统会?动为你?成 libhello.X。

类型有三种:

SHARED,动态库

STATIC,静态库

MODULE,在使? dyld 的系统有效,如果不?持dyld,则被当作SHARED 对待。

EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除?有其他的组件依赖或者??构建。

添加静态库

同样使?上?的指令,我们在?持动态库的基础上再为?程添加?个静态库,按照?般的习惯,静态库名字跟动态库名字应该是?致的,只不过后缀是.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 指令来实现了。这时候我们需要?到另外?个指令:

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(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

动态库版本号

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

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

安装共享库和头?件

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

利?CMAKE最全实战(1) 了解到的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 中了

如果报错

CMake Error: cmake_symlink_library: System Error: Operation not supported

CMake Error: cmake_symlink_library: System Error: Operation not supported

make[2]: *** [lib/CMakeFiles/hello_dynamic.dir/build.make:85: lib/libhello.so.1.2] Error 1

make[2]: *** Deleting file 'lib/libhello.so.1.2'

make[1]: *** [CMakeFiles/Makefile2:130: lib/CMakeFiles/hello_dynamic.dir/all] Error 2

make: *** [Makefile:130: all] Error 2

则说明你你可能是通过hgfs共享的Windows?录下进?make,?成so时需要在纯linux环境运?,?如把t3拷?到~/0/makefile/t3, 并且先把对应的build?录??的内容清空,重新cmake .. 再make

本?节,我们谈到了:如何通过ADD_LIBRARY 指令构建动态库和静态库。如何通过SET_TARGET_PROPERTIES 同时构建同名的动态库和静态库。如何通过SET_TARGET_PROPERTIES 控制动态库版本最终使?上?节谈到的INSTALL 指令来安装头?件和动态、静态库。在下?节,我们需要编写另?个?级?点的 Hello World 来演示怎么使?我们已经构建的构建的共享库libhello 和外部头?件。


如何使?外部共享库和头?件

上?节我们已经完成了libhello 动态库的构建以及安装,本节我们的任务很简单。编写?个程序使?我们上?节构建的共享库。

请在cmake ?录建? t4 ?录,本节所有资源将存储在t4 ?录。

构建?程

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

#include "hello.h"

int main(void) {

hello_func();

return 0;

}

编写?程主?件CMakeLists.txt

PROJECT(NEWHELLO)

ADD_SUBDIRECTORY(src)

编写src/CMakeLists.txt

ADD_EXECUTABLE(main main.c)

外部构建

按照习惯,仍然建?build ?录,使? cmake ..?式构建。

cmake ..

make

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

make VERBOSE=1 来构建

错误输出为是:

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

引?头?件搜索路径

假如hello.h 位于/usr/include/hello ?录中,并没有位于系统标准的头?件路径。

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

INCLUDE_DIRECTORIES,其完整语法为:

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

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

(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 上。

为 target 添加共享库

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

LINK_DIRECTORIES 和TARGET_LINK_LIBRARIES

LINK_DIRECTORIES 的全部语法是:

LINK_DIRECTORIES(directory1 directory2 ...)

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

TARGETLINKLIBRARIES 的全部语法:

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/bin?录,运? main 的结果是输出:

Hello World

检查?下main 的链接情况:

ldd bin/main


可以清楚的看到main 确实链接了共享库libhello,?且链接的是动态库 libhello.so.1。

如何链接到静态库呢?

?法很简单:

将 TARGET_LINK_LIBRRARIES 指令修改为: TARGET_LINK_LIBRARIES(main libhello.a)

重新构建后再来看?下main 的链接情况。说明,main 确实链接到了静态库libhello.a。

ldd src/main


特殊的环境变量CMAKE_INCLUDE_PATH 和CMAKE_LIBRARY_PATH。

注意,这两个是环境变量?不是cmake 变量。

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 ?来在指定路径中搜索?件名,?如:

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 模块都会受益。

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

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

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

并解释了如果链接到静态库。到这?为?,您应该基本可以使?cmake ?作了,但是还有很多?级的话题没有探讨,?如编译条件检查、编译器定义、平台判断、如何跟 pkgconfig 配合使?等等。到这?,或许你可以理解前?讲到的“cmake 的使?过程其实就是学习cmake 语?并编写 cmake 程序的过程”,既然是“cmake 语?”,?然涉及到变量、语法等.

下?节,我们将抛开程序的话题,看看常?的 CMAKE 变量以及?些基本的控制语法规则。


cmake 常?变量和常?环境变量

cmake 变量引?的?式

使?${}进?变量的引?。在IF 等语句中,是直接使?变量名?不通过${}取值。主要有隐式定义和显式定义两种,前?举了?个隐式定义的例?,就是 PROJECT 指令,他会隐式的定义<projectname>_BINARY_DIR 和<projectname>_SOURCE_DIR 两个变量。显式定义的例?我们前?也提到了,使? SET 指令,就可以构建?个?定义变量了。

?如:

SET(HELLO_SRC main.c),就PROJECT_BINARY_DIR 可以通过${HELLO_SRC}来引?这个?定义变量了。

cmake 常?变量

(1)CMAKE_BINARY_DIR

PROJECT_BINARY_DIR

<projectname>_BINARY_DIR

这三个变量指代的内容是?致的,如果是 in source 编译,指得就是?程顶层?录,如果是 out-ofsource 编译,指的是?程编译发?的?录。PROJECT_BINARY_DIR 跟其他指令稍有区别,现在,你可以理解为他们是?致的。

(2)CMAKE_SOURCE_DIR

PROJECT_SOURCE_DIR

<projectname>_SOURCE_DIR

这三个变量指代的内容是?致的,不论采?何种编译?式,都是?程顶层?录。也就是在in source 编译时,他跟 CMAKE_BINARY_DIR 等变量?致。PROJECT_SOURCE_DIR 跟其他指令稍有区别,现在,你可以理解为他们是?致的。

(3)CMAKE_CURRENT_SOURCE_DIR

指的是当前处理的CMakeLists.txt 所在的路径,?如上?我们提到的 src ??录。

(4)CMAKE_CURRRENT_BINARY_DIR

如果是in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR ?致,如果是out-of-source 编译,它指的是target 编译?录。

使?上?提到的ADD_SUBDIRECTORY(src bin)可以更改这个变量的值。使?SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量造成影响,它仅仅修改了最终?标?件存放的路径。

(5)CMAKE_CURRENT_LIST_FILE

输出调?这个变量的CMakeLists.txt 的完整路径

(6)CMAKE_CURRENT_LIST_LINE

输出这个变量所在的?

(7)CMAKE_MODULE_PATH

这个变量?来定义??的cmake 模块所在的路径。如果你的?程?较复杂,有可能会??编写?些cmake模块,这些cmake 模块是随你的?程发布的,为了让cmake 在处理 CMakeLists.txt 时找到这些模块,你需要通过SET 指令,将??的cmake 模块路径设置?下。?如

SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

这时候你就可以通过INCLUDE 指令来调???的模块了。

(8)EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH

分别?来重新定义最终结果的存放?录,前?我们已经提到了这两个变量。

(9)PROJECT_NAME

返回通过PROJECT 指令定义的项?名称。


cmake 调?环境变量的?式

使?$ENV{NAME}指令就可以调?系统的环境变量了。

?如

MESSAGE(STATUS “HOME dir: $ENV{HOME}”)

设置环境变量的?式是:

SET(ENV{变量名} 值)

(1)CMAKE_INCLUDE_CURRENT_DIR

?动添加CMAKE_CURRENT_BINARY_DIR 和CMAKE_CURRENT_SOURCE_DIR 到当前处理的 CMakeLists.txt。相当于在每个CMakeLists.txt 加?:


(2)CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE

将?程提供的头?件?录始终?于系统头?件?录的前?,当你定义的头?件确实跟系统发?冲突时可以提供?些帮助。

(3)CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH 我们在上?节已经提及。

系统信息

(1)CMAKE_MAJOR_VERSION,CMAKE 主版本号,?如2.4.6 中的2

(2)CMAKE_MINOR_VERSION,CMAKE 次版本号,?如2.4.6 中的4

(3)CMAKE_PATCH_VERSION,CMAKE 补丁等级,?如2.4.6 中的6

(4)CMAKE_SYSTEM,系统名称,?如Linux-2.6.22

(5)CMAKE_SYSTEM_NAME,不包含版本的系统名,?如Linux

(6)CMAKE_SYSTEM_VERSION,系统版本,?如 2.6.22

(7)CMAKE_SYSTEM_PROCESSOR,处理器名称,?如i686。

(8)UNIX,在所有的类 UNIX 平台为 TRUE,包括OS X 和cygwin

(9)WIN32,在所有的 win32 平台为 TRUE,包括cygwin


主要的开关选项

(1)CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS,?来控制IF ELSE 语句的书写?式,在下?节语法部分会讲到。

(2)BUILD_SHARED_LIBS。这个开关?来控制默认的库编译?式,如果不进?设置,使? ADD_LIBRARY 并没有指定库类型的情况下,默认编译?成的库都是静态库。如果SET(BUILD_SHARED_LIBS ON)后,默认?成的为动态库。

(3)CMAKE_C_FLAGS

设置C 编译选项,也可以通过指令 ADD_DEFINITIONS()添加。

(4)CMAKE_CXX_FLAGS

设置C++编译选项,也可以通过指令 ADD_DEFINITIONS()添加。

本篇就介绍到这里,欢迎关注、点赞、分享、收藏

相关推荐

Nat. Synthesis: 重大突破,电化学形成C-S键

第一作者:JunnanLi,HasanAl-Mahayni通讯作者:AliSeifitokaldani,NikolayKornienko通讯单位:蒙特利尔大学,麦吉尔大学【研究亮点】形成C-...

网络安全与应用(二)(网络安全理论与应用)

1、应用层安全协议SHTTP和HTTPS:SHTTP:SecHTTP,安全超文本传输协议,是HTTP扩展,使用TCP的80端口。HTTPS:HTTP+SSL,使用TCP的443端口。大部分web应用...

TN-C、TN-S、TT、IT供电系统详解及对比

TN-C、TN-S、TT、IT供电系统是低压配电系统中常见的四种接地方式,它们各自有不同的特点和适用场景。一、系统介绍TN-C供电系统①定义:整个系统中,工作零线(N线)与保护零线(PE线)是合一的,...

网络应用服务器(三)(网络应用程序服务器)

#头条创作挑战赛#1、DNS协议:域名解析协议,用于把主机域名解析为对应的IP地址。是一个分布式数据库,C/S工作方式。主要基于UDP协议,少数使用TCP,端口号都是53。常用域名如下2、DNS协议...

腾讯发布混元Turbo S:业界首次无损应用Mamba架构

21世纪经济报道记者白杨北京报道2月27日,腾讯正式发布新一代基座模型——混元TurboS。据腾讯混元团队介绍,混元TurboS在架构方面创新性地采用了Hybrid-Mamba-Transfor...

【收藏】低压配电系统中TT IT TN-S/TN-C/TN-C-S 的区别?

低压配电系统的接地型式选择是电气安全设计的核心环节,TT、IT、TN-S、TN-C、TN-C-S这五种主要接地型式因其结构、保护原理和故障特性的显著差异,在工程应用中有不同的适用范围和限制条件。如若发...

金万维公有云平台如何实现C/S架构软件快速SaaS化

金万维作为国内领先的企业信息化垂直B2B平台运营商,拥有超过5000家管理软件合作伙伴,掌握管理软件一线的发展动态,因此深知传统管理软件近年来面对的困境和问题。而SaaS却在软件行业内发展迅猛势如燎原...

随时随地做翻译:B/S架构的传奇时代到来

随着新兴技术的发展和大数据时代的到来,翻译作为连接各国语言和文化的工具,更是具有前所未有的拓展空间。传统的在计算机辅助翻译软件(CAT)上进行翻译的模式,受到时间和空间的限制,导致翻译过程中面临层层障...

BS和CS 架构的介绍(一篇就够了)(cs和bs架构的含义)

简介C/S又称Client/Server或客户/服务器模式。服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如Oracle、Sybase、Informix或SQLServer。...

物管王(包租婆)软件架构与B/S和C/S架构的优点和缺点比较

一、B/S系统架构的优点和缺点优点:1)客户端无需安装,有Web浏览器即可。2)BS架构可以直接放在广域网上,通过一定的权限控制实现多客户访问的目的,交互性较强。3)BS架构无需升级多个客户端,升级服...

监听器入门看这篇就够了(怎么检查车上有没有被别人安装监听器)

什么是监听器监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。。为什么我们要使用监听器?...

购物车【JavaWeb项目、简单版】(java购物车的实现原理)

①构建开发环境免费学习资料获取方式导入需要用到的开发包建立程序开发包②设计实体书籍实体publicclassBook{privateStringid;privat...

基础篇-SpringBoot监听器Listener的使用

1.监听器Listener简介1.1监听器Listener介绍Listener是JavaWeb的三大组件(Servlet、Filter、Listener)之一,JavaWeb中的监听器主要用...

你在 Spring Boot3 整合 JWT 实现 RESTful 接口鉴权时是否遇到难题?

各位后端开发小伙伴们!在日常使用SpringBoot3搭建项目时,RESTful接口的鉴权至关重要。而JWT技术,作为一种简洁且高效的鉴权方式,被广泛应用。但大家是不是在整合过程中遇到过各...

javaWeb RSA加密使用(rsa加密java代码)

加密算法在各个网站运用很平常,今天整理代码的时候看到了我们项目中运用了RSA加密,就了解了一下。先简单说一下RSA加密算法原理,RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要...

取消回复欢迎 发表评论: