在一些场景下我们需要编写一些库,并希望其他程序可以找到这些库并引用。
CMake采用package这个概念来解决这个问题。
关于CMake的find_package文章有很多,但这些文章的内容大多不直观讲了一堆讲不到点子上,让人看了一头雾水。因此我想通过本文从实用角度出发介绍一下CMake的package概念。
CMake关于Package的官方文档:https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html

CMake的Package概念

简单理解Package是有Component组成的,而Component包含了库和头文件。如:

find_package(Qt5 5.1.0 REQUIRED Widgets Xml Sql)

上面代码中Qt5是package,5.1.0是package的版本号,REQUIRED表示必须要找到,WidgetsXmlSql是Component名称。因此上面代码的意思是“必须找到版本为5.1.0的Qt5 Package的名为Widgets、Xml和Sql的Components”。

target_link_libraries(Foo PRIVATE Qt5::Widgets Qt5::Xml Qt5::Sql)

上面代码的意思是在编译目标Foo是链接Qt5WidgetsXmlSql这三个Component提供的库。其实Qt5::WidgetsQt5::XmlQt5::Sql就是库的别名,因此上面代码也等价于

target_link_libraries(Foo PRIVATE qt5wdgets qtxml qt5sql)

最终参与编译的是libqt5wdgets.alibqt5xml.alibqt5sql.a这三个库。上面三个库名是我瞎编的可能与实际不符。

关于Config模式和Module模式

很多文章都提到了find_package有两种查询package的模式,一种是Config模式,另一种是Module模式。这里就不再赘述了,因为对实际使用毫无益处。只需要知道如果库也是用CMake构建的就是Config模式;如果库不是CMake构建的就用Module模式。又因为在团队内部通常构建工具都是统一的,而且现在CMake非常流行,因此本文自关心Config模式。

自定义package

经过以上的介绍,应该清楚所谓的Component只不过是库的别名而已。因此我们自定义package,实际上就是编写一套CMake文件,这些文件里定义了库的头文件路径、库文件路径,并为库文件起个别名即可。
以上工作并不需要我们亲自编写,因为CMake已经给我们提供了EXPORT工具,我们只需要使用EXPORT便可以完成上面的工作。
下面是我自己编写的一个Demo。foo_lib是一个库也就是自定义的package,foo_app是一个应用通过find_package查找foo_lib并引用。install是它俩的安装路径。
在这里插入图片描述

foo_lib

在这里插入图片描述
foo_lib中包含四个文件"foo_lib.h"、“foo_lib.cpp”、“CMakeLists.txt”、“FooLibConfig.cmake.in”

foo_lib.h

foo_lib.h作为库的头文件,这只提供一个方法foo_lib_func()声明

#pragma
void foo_lib_func();

foo_lib.cpp

foo_lib.cpp作为库的源代码文件,实现foo_lib_func()

#include <stdio.h>
#include "foo_lib.h"void foo_lib_func()
{printf("My name is foo lib");
}

CMakeLists.txt

CMakeLists.txt 是CMake的构建文件,也是我们要讲解的重点

cmake_minimum_required(VERSION 3.5)project(FooLib VERSION 1.0.0 LANGUAGES CXX)add_library(${PROJECT_NAME} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/foo_lib.cpp)install(TARGETS ${PROJECT_NAME}EXPORT ${PROJECT_NAME}TargetsLIBRARY DESTINATION libINCLUDES DESTINATION include
)install(FILES foo_lib.h DESTINATION include
)install(EXPORT ${PROJECT_NAME}TargetsFILE ${PROJECT_NAME}Targets.cmakeDESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/${PROJECT_NAME}"NAMESPACE ${PROJECT_NAME}::
)include(CMakePackageConfigHelpers)
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in""${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"INSTALL_DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/${PROJECT_NAME}"
)install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/${PROJECT_NAME}"
)
  • cmake_minimum_required 规定了cmake的版本
  • project 定义了项目属性
  • add_library 表示以项目名编译一个动态库
  • 第一个 install
    第一个install 中最关键的是EXPORT。意思是当前的TARGETS要向外导出。导出的名称为${PROJECT_NAME}Targets
  • 第二个 install
    第二个install用来安装头文件。虽然在第一个install中写了INCLUDES DESTINATION include,但是并没有真正安装头文件,原因很简单因为CMake无法自动推断出哪些头文件需要安装。
  • 第三个 install
    第三个 install的作用是导出Targets文件。第一个install的EXPORT只是表示要导出${PROJECT_NAME}Targets,但是导出到哪并没有指明。因此需要第三个install指明导出文件为${PROJECT_NAME}Targets.cmake,导出的位置为"${CMAKE_INSTALL_PREFIX}/lib/cmake/${PROJECT_NAME}",导出的命名空间为${PROJECT_NAME}::

    上文说的库头文件路径,库文件路径,库文件别名 都会自动生成并保存在${PROJECT_NAME}Targets.cmake

  • include(CMakePackageConfigHelpers)
    为了引入configure_package_config_file方法
  • configure_package_config_file
    configure_package_config_file的所用是生成${PROJECT_NAME}Config.cmake。这个文件名是固定的,调用find_package(xxx)时,cmake就会找xxxConfig.cmake文件并引入。到这就很好理解了,我们只要在${PROJECT_NAME}Config.cmake中引用${PROJECT_NAME}Targets.cmake就可以把库头文件路径、库文件路径、库的别名等信息导入了。
  • 第四个install
    第四个个install的作用是将${PROJECT_NAME}Config.cmake按照到指定路径,好让find_package(xxx)能找到。

FooLibConfig.cmake.in

@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# find_dependency(xxx)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
  • @PACKAGE_INIT@ 是固定写法,这部分内容会被configure_package_config_file替换。
  • include(CMakeFindDependencyMacro) 和 find_dependency(xxx)
    如果你的库还引用了其他库需要在这里追加,如
    include(CMakeFindDependencyMacro) 
    find_dependency(Qt5)
    find_dependency(Boost)
    
  • include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
    作用是引用${PROJECT_NAME}Targets.cmake

编译,现在在foo_lib下执行以下指令

mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=../../install
make install

执行完成后在install目录结构如下
在这里插入图片描述
感兴趣的话可以看看FooLibConfig.cmake和FooLibTargets.cmake的内容,就可以看到库的路径、名称和别名了。

使用自定义Package

foo_app只有两个文件main.cppCMakeLists.txt
在这里插入图片描述

main.cpp

这个文件不必多说

#include "foo_lib.h"int main(int argc, char** argv)
{foo_lib_func();
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(FooApp VERSION 1.0.0)
find_package(FooLib REQUIRED)
add_executable(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE FooLib::FooLib)
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)

这里关键的两步是

  • find_package(FooLib REQUIRED) 查找FooLib库
  • target_link_libraries(${PROJECT_NAME} PRIVATE FooLib::FooLib) 编译时链接FooLib库

编译

在foo_app目录下执行

mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=../../install
make install

执行完后install目录结构如下
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/88234.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88234.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/88234.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MATLAB例程】AOA与TDOA混合定位例程,适用于二维环境、3个锚点的定位|附代码下载链接

本 MATLAB 程序实现了基于 Angle of Arrival (AOA) 与 Time Difference of Arrival (TDOA) 的二维定位方法&#xff0c;通过自适应融合与最小二乘优化&#xff0c;实现对未知目标的高精度估计。本例中固定使用了 3 个基站&#xff08;锚点&#xff09;&#xff0c;算法框架支持…

磐维数据库panweidb集中式集群配置VIP【添加、删除和修改】

0 说明 panweidb集中式集群为了防止主备切换后应用连接无法切换到新主库&#xff0c;需要配置vip&#xff0c;应用可以只通过该ip与数据库连接&#xff0c;不用感知数据库在哪个节点上。 panweidb中配置 VIP主要依赖 CM 组件的 VIP 仲裁功能&#xff0c;通过回调脚本在主备切换…

python的保险业务管理与数据分析系统

前端开发框架:vue.js 数据库 mysql 版本不限 后端语言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 数据库工具&#xff1a;Navicat/SQLyog等都可以 保险行业…

R语言如何接入实时行情接口

目录 1. 安装必要的R包 2. 导入库 3. 连接WebSocket 4. 处理连接成功后的操作 5. 处理接收到的消息 6. 处理连接关闭和错误 7. 发送心跳数据 8. 自动重连机制 9. 启动连接和重连 总结 在数据分析和金融研究中&#xff0c;实时行情数据的获取至关重要&#xff0c;但市…

Redis数据安全性分析

Redis高可用与数据安全机制深度解析前置知识&#xff1a;Redis基础安装与使用&#xff08;主从复制、哨兵集群、Cluster集群搭建&#xff09;一、Redis性能压测工具 工具名称&#xff1a;redis-benchmark核心作用&#xff1a;快速基准测试Redis性能使用场景&#xff1a;评估不同…

差分和前缀和

差分和前缀和的原理、用法和区别。前缀和&#xff08;Prefix Sum&#xff09;核心思想&#xff1a;预处理数组的前缀和&#xff0c;快速回答「区间和查询」 适用场景&#xff1a;数组静态&#xff08;更新少、查询多&#xff09;&#xff0c;需要频繁计算任意区间的和1. 定义与…

C++并发编程-12. 用内存顺序实现内存模型

前情回顾 前文我们介绍了六种内存顺序&#xff0c;以及三种内存模型&#xff0c;本文通过代码示例讲解六种内存顺序使用方法&#xff0c;并实现相应的内存模型。全局一致性模型同步模型(获取和释放)松散模型memory_order_seq_cst memory_order_seq_cst代表全局一致性顺序&#…

AI测试革命:从智能缺陷检测到自愈式测试框架的工业实践

AI测试革命&#xff1a;从智能缺陷检测到自愈式测试框架的工业实践 希望对大家有用&#xff01; 目录AI测试革命&#xff1a;从智能缺陷检测到自愈式测试框架的工业实践希望对大家有用&#xff01;一、传统测试之殇&#xff1a;工业质检的切肤之痛二、智能缺陷检测系统架构1. …

二、深度学习——损失函数

二、损失函数损失函数定义&#xff1a;损失函数是用来衡量模型参数的质量的函数&#xff0c;衡量方式是比较网络输出和真实输出的差异别名&#xff1a;损失函数&#xff08;loss function&#xff09;&#xff0c;代价函数&#xff08;cost function&#xff09;&#xff0c;目…

面向数据报的套接字通道技术详解

数据报通道基础 通道特性与创建方式 java.nio.channels.DatagramChannel类实例代表数据报通道&#xff0c;默认处于阻塞模式。通过configureBlocking(false)方法可将其配置为非阻塞模式。创建数据报通道需调用其静态open()方法&#xff0c;若用于IP组播则需指定组播组的地址类型…

147.在 Vue3 中使用 OpenLayers 地图上 ECharts 模拟飞机循环飞行

&#x1f9e9; 效果预览 &#x1f447; 飞机从多个城市起飞并向其他城市飞行&#xff0c;动画流畅&#xff0c;地图可缩放拖拽&#xff1a; &#x1f4e6; 一、项目技术栈 技术用途Vue 3现代前端框架OpenLayers地图底图渲染ECharts ol-echarts飞机飞行动画渲染ol-echarts将 …

OCR与PDF解析的区别

我们日常所接触的文档中&#xff0c;经常能碰到多语言混合的文档。比如论文试卷、财报研报、跨国票据都含有多种语言和文字。要将文档中的内容识别并提取务必需要使用到OCR技术&#xff0c;而传统的OCR工具在处理这类型文档的时候有局限性。早期的 OCR 系统识别精度有限&#x…

Java 单例类详解:从基础到高级,掌握线程安全与高效设计

作为一名Java开发工程师&#xff0c;你一定对**单例模式&#xff08;Singleton Pattern&#xff09;**不陌生。它是23种经典设计模式中最简单也是最常用的一种&#xff0c;用于确保一个类在整个应用程序中只有一个实例存在。单例广泛应用于系统配置、数据库连接池、日志管理器、…

面向对象设计

你列出的这些属于 C 高级开发中面向对象设计与架构设计的核心知识&#xff0c;也是面试高级工程师岗位必问的内容。下面我按顺序&#xff0c;深入讲解每一项概念、原理、用途&#xff0c;并穿插 C 示例。✅ 1. 设计原则&#xff08;SOLID&#xff09;SOLID 是面向对象设计的五大…

IntelliJ IDEA让我的开发效率翻倍:从新手到高效开发者的进阶之路

IntelliJ IDEA让我的开发效率翻倍&#xff1a;从新手到高效开发者的进阶之路 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用…

css sprites使用

CSS Sprites 是一种将多个小图标或背景图像合并到一个大图中的技术。通过减少HTTP请求次数&#xff0c;可以显著提高页面加载速度。其核心原理是&#xff1a;通过设置元素的背景图&#xff08;background-image&#xff09;为这个大图&#xff0c;然后调整背景位置&#xff08;…

分布式爬虫在电商平台商品数据大规模采集中的技术应用

在电商平台商品数据大规模采集场景中&#xff0c;分布式爬虫凭借其高效、可扩展、抗风险的特性&#xff0c;成为突破单节点爬虫性能瓶颈的核心技术方案。以下从技术架构、关键技术点、电商场景适配及挑战应对四个维度&#xff0c;解析其具体应用&#xff1a;一、分布式爬虫的核…

Linux的`if test`和`if [ ]中括号`的取反语法比较 笔记250709

Linux的if test和if 中括号的取反语法比较 笔记250709 Linux的 test命令&#xff08;或等价中括号写法 [空格expression空格]&#xff09;的用法详解. 笔记250709 四种取反语法: if ! test -e xxx ;then... 和 if test ! -e xxx ;then... 和 if ! [ -e xxx ] ;then... 和 if …

记录使用ubuntu16.04编译aosp(android8.1与10)遇到的问题

一、前言&#xff1a; 本来打算用wsl来编译AOSP&#xff0c;但是折腾了好几天&#xff0c;以失败告终。后来使用vmware反而成功了。 本篇同样会把wsl遇到的问题与尝试记录下来。 环境&#xff1a;vmware ubuntu16.04。 为什么会使用ubuntu16.04呢&#xff0c;因为在公司有一…

hiredis window之RFDMap

简介 RFDMap用于将socket分配映射成连续的文件描述符&#xff0c;同时管理回收的文件描述符&#xff0c;因为ae构架中管理fd与对应事件处理器使用的是数据&#xff0c;fd作为数组下标 结构 #mermaid-svg-zQz2LTrKRi0LQTII {font-family:"trebuchet ms",verdana,arial…