CMake构建学习笔记14-依赖库管理工具
如果说做C/C++开发最大的痛点是什么,那么一定是缺少一个官方的统一的包管理器。认真的说,如果你要用C/C++干点什么,至少需要(Windows系统下):
- C/C++语言本身、标准库、以及操作系统API几乎干不了什么,除非你真的想从零开始造轮子。
- 开始找一些现成的实现组成依赖库。最好看能不能找到预编译包或者安装包,即使找到了,由于二进制兼容的问题,你也不一定能够使用。
- 如果没找到预编译包或者安装包,那么就需要自己从源代码进行构建了。如果提供了CMake的构建方式就挺好,万一没有提供,就得自己想办法组织工程进行构建。
- 注意,依赖库本身是需要依赖库的!比如,你构建GDAL的时候会发现PROJ是GDAL的必须依赖,等你开始构建PROJ的时候又发现sqlite3是PROJ必须依赖,而你准备构建sqlite3的时候发现sqlite3是不提供CMake方式的。
- 不谈构建过程中处理的一系列问题。等你把依赖库构建完成了,你就得考虑如何引入了。如果你使用动态链接库,你需要进行头文件、动态库导入库以及动态库相关的配置。如果头文件错了,你会发现无法编译;如果动态库导入库错误,你会发现无法链接;如果动态库不正确,你会发现无法运行。
- 最后开始在源代码中include头文件,调用依赖库相关的功能进行操作。
嗯,说实话笔者光是写这些步骤都觉得有点汗流浃背了。这要是换成使用Python或者JavaScript进行开发,一个install指令,一个import语句就全部搞定,也难怪C/C++开发的效率一直被程序员诟病呢。不过,C/C++领域也不是一直在固步自封,Windows系统下也可以使用一些包管理器,例如vcpkg、Conan、Chocolatey等。个人认为,这些包管理器正在逐渐成熟过程中,不过尚需要一些时间完善,有兴趣的同学可以进行试用。
另外一种方式就是像笔者一样,尝试组织一个属于自己或者自己团队的依赖库管理工具。这样做的原因有三:
- 不同环境下的C/C++包存在二进制兼容的问题。
- 构建Release带调试信息的构建成果,以及符号库文件。
- 有些库包很少见,通用的包管理器不一定收纳。
那么具体如何实现呢?其实不用想的太复杂,我们将所有需要的构建成果都构建到同一个目录,并且将这个目录设置成环境变量。这样,在我们日常的程序中就可以依托这个环境变量配置依赖库。这意味着所有团队成员的代码工程的配置都可以是一样的,我们就可以忽略掉不同软硬件环境的不同,实现了代码项目的一致性。
所以,关键就是将这些常见的组件构建组织起来。不能使用CMake的GUI工具,因为不同的库各自有自己独特的构建选项,最好将其通过脚本记录。不妨将构建的脚本写的完善一点,自动化一点,代码文件从哪里来,最后的构建成果输出到哪里。例如,前面博文中构建libzip库的完整Powershell脚本libzip.ps1为:
param(
[string]$SourceAddress = "https://github.com/nih-at/libzip/archive/refs/tags/v1.10.1.zip",
[string]$SourceZipPath = "../Source/libzip-1.10.1.zip",
[string]$SourceLocalPath = "./libzip-1.10.1",
[string]$Generator,
[string]$MSBuild,
[string]$InstallDir,
[string]$SymbolDir
)
# 检查目标文件是否存在,以判断是否安装
$DstFilePath = "$InstallDir/bin/zip.dll"
if (Test-Path $DstFilePath) {
Write-Output "The current library has been installed."
exit 1
}
# 创建所有依赖库的容器
. "./BuildRequired.ps1"
$Librarys = @("zlib")
BuildRequired -Librarys $Librarys
. "./DownloadAndUnzip.ps1"
DownloadAndUnzip -SourceLocalPath $SourceLocalPath -SourceZipPath $SourceZipPath -SourceAddress $SourceAddress
# 清除旧的构建目录
$BuildDir = $SourceLocalPath + "/build"
if (Test-Path $BuildDir) {
Remove-Item -Path $BuildDir -Recurse -Force
}
New-Item -ItemType Directory -Path $BuildDir
# 转到构建目录
Push-Location $BuildDir
try {
# 配置CMake
cmake .. -G "$Generator" -A x64 `
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
-DCMAKE_PREFIX_PATH="$InstallDir" `
-DCMAKE_INSTALL_PREFIX="$InstallDir" `
-DBUILD_DOC=OFF `
-DBUILD_EXAMPLES=OFF `
-DBUILD_REGRESS=OFF `
-DENABLE_OPENSSL=OFF
# 构建阶段,指定构建类型
cmake --build . --config RelWithDebInfo
# 安装阶段,指定构建类型和安装目标
cmake --build . --config RelWithDebInfo --target install
# 复制符号库
$PdbFiles = @(
"./lib/RelWithDebInfo/zip.pdb"
)
foreach ($file in $PdbFiles) {
Write-Output $file
Copy-Item -Path $file -Destination $SymbolDir
}
}
finally {
# 返回原始工作目录
Pop-Location
}
不得不说Powershell脚本真的很强大,你甚至可以引入第三方脚本中的函数,例如这里的BuildRequired表示预先安装当前库的依赖库(其实就是调用其他库的构建脚本),而DownloadAndUnzip表示从远端把源代码下载下来并且解压到指定文件夹。接下来创建构建文件夹、配置CMake项目、构建项目以及安装项目。最后,我们把这个库符号库给移动到安装目录中去。
就像这样一个一个把需要的依赖库构建脚本写好。有时候需要修改一下构建选项也没什么关系,修改下对应的内容重新构建就好了,这就是写脚本的好处。不过,这样一个一个调用脚本也说不上对库包进行管理了,对比一下一些比较完善的包管理器例如npm,可以再写一个总的用于管理的脚本,将以上构建脚本管理起来,如下Powershell脚本BuildCppDependency.ps1所示:
param(
[string]$Generator,
[string]$MSBuild,
[string]$InstallDir,
[string]$SymbolDir,
[string]$Install,
[string]$List
)
# 创建所有的库的容器
$LibrarySet = [System.Collections.Generic.SortedSet[string]]::new()
$LibrarySet.Add("zlib") > $null
$LibrarySet.Add("libpng") > $null
$LibrarySet.Add("libjpeg") > $null
$LibrarySet.Add("libtiff") > $null
$LibrarySet.Add("giflib") > $null
$LibrarySet.Add("freetype") > $null
$LibrarySet.Add("OpenSceneGraph") > $null
$LibrarySet.Add("eigen") > $null
$LibrarySet.Add("osgQt5") > $null
$LibrarySet.Add("osgQt") > $null
$LibrarySet.Add("minizip") > $null
$LibrarySet.Add("libzip") > $null
$LibrarySet.Add("opencv") > $null
#$LibrarySet.Add("protobuf") > $null
#$LibrarySet.Add("abseil-cpp") > $null
# 检查是否传递了$Install参数
if ($PSBoundParameters.ContainsKey('Install')) {
# 比较时忽略大小写
if ($Install.ToLower() -eq "-all".ToLower()) {
Write-Output "All libraries will be installed soon..."
foreach ($item in $LibrarySet) {
Write-Output "Find the library named $item and start installing..."
# 动态构建脚本文件名并执行
$BuildScript = "./$item.ps1";
& $BuildScript -Generator $Generator -MSBuild $MSBuild -InstallDir $InstallDir -SymbolDir $SymbolDir
}
}
else {
# 查找某个字符串
if ($LibrarySet.Contains("$Install")) {
Write-Output "Find the library named $Install and start installing..."
# 动态构建脚本文件名并执行
$BuildScript = "./$Install.ps1";
& $BuildScript -Generator $Generator -MSBuild $MSBuild -InstallDir $InstallDir -SymbolDir $SymbolDir
}
else {
Write-Output "Cannot find library named $Install !"
}
}
}
elseif ($PSBoundParameters.ContainsKey('List')) {
if ($List.ToLower() -eq "-all".ToLower()) {
Write-Output "The list of all libraries that can currently be installed in the repository is as follows:"
foreach ($item in $LibrarySet) {
Write-Output $item
}
}
}
else {
Write-Host "Please enter the parameters!"
}
再次感叹下Powershell脚本的强大,你甚至可以看到System.Collections.Generic.SortedSet,没错,这就是.Net提供的容器,通过Powershell可以也调用它。上述脚本提供了基本的查看和安装功能,例如查看能安装的库,可使用如下指令:
./BuildCppDependency.ps1 -List -all
安装特定的库:
./BuildCppDependency.ps1 -Generator "Visual Studio 16 2019" `
-MSBuild "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" `
-InstallDir "$env:GISBasic" `
-SymbolDir "$env:GISBasic/symbols" `
-Install libzip
安装所有的库:
./BuildCppDependency.ps1 -Generator "Visual Studio 16 2019" `
-MSBuild "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" `
-InstallDir "$env:GISBasic" `
-SymbolDir "$env:GISBasic/symbols" `
-Install -all
其实一个完善的包管理工具需要的功能还是很多的,也非常复杂。例如包的安装是很容易,如何进行卸载呢?如何升级如何降级呢?是不是可以与IDE进行结合,自动导入依赖库并且进行配置呢?这些问题,就留待以后考虑吧。
最后,就将写的依赖库管理工具构建脚本贡献给大家参考吧:地址。
CMake构建学习笔记14-依赖库管理工具的更多相关文章
- golang学习笔记5 用bee工具创建项目 bee工具简介
golang学习笔记5 用bee工具创建项目 bee工具简介 Bee 工具的使用 - beego: 简约 & 强大并存的 Go 应用框架https://beego.me/docs/instal ...
- Java程序猿的JavaScript学习笔记(9—— jQuery工具方法)
计划按例如以下顺序完毕这篇笔记: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScript ...
- 【转】 C#学习笔记14——Trace、Debug和TraceSource的使用以及日志设计
[转] C#学习笔记14——Trace.Debug和TraceSource的使用以及日志设计 Trace.Debug和TraceSource的使用以及日志设计 .NET Framework 命名空 ...
- Ext.Net学习笔记14:Ext.Net GridPanel Grouping用法
Ext.Net学习笔记14:Ext.Net GridPanel Grouping用法 Ext.Net GridPanel可以进行Group操作,例如: 如何启用Grouping功能呢?只需要在Grid ...
- SQL反模式学习笔记14 关于Null值的使用
目标:辨别并使用Null值 反模式:将Null值作为普通的值,反之亦然 1.在表达式中使用Null: Null值与空字符串是不一样的,Null值参与任何的加.减.乘.除等其他运算,结果都是Null: ...
- golang学习笔记14 golang substring 截取字符串
golang学习笔记14 golang substring 截取字符串golang 没有java那样的substring函数,但支持直接根据 index 截取字符串mystr := "hel ...
- mybatis学习笔记(14)-查询缓存之中的一个级缓存
mybatis学习笔记(14)-查询缓存之中的一个级缓存 标签: mybatis mybatis学习笔记14-查询缓存之中的一个级缓存 查询缓存 一级缓存 一级缓存工作原理 一级缓存測试 一级缓存应用 ...
- Andorid:日常学习笔记(3)——掌握日志工具的使用
Andorid:日常学习笔记(3)——掌握日志工具的使用 使用Android的日志工具Log 方法: Android中的日志工具类为Log,这个类提供了如下方法来供我们打印日志: 使用方法: Log. ...
- Python3+Selenium3+webdriver学习笔记14(等待判断 鼠标事件 )
!/usr/bin/env python -*- coding:utf-8 -*-'''Selenium3+webdriver学习笔记14(等待判断 鼠标事件 )'''from selenium im ...
- 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理
1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...
随机推荐
- 做一个单纯的react-image显示组件
最近项目上有一个需求,在显示图片的时候,需要传递自定义的头部就行认证.google了一番之后,发现没有现成的组件库可以使用[也可能是我没找到],所以请求图片只能采用xhr方式来异步加载.下面就是在做这 ...
- Java项目静态资源映射的几种方式
一.Springboot 1.webjars方式 我们之前使用Maven构建一个Web项目时,在main目录下会存在一个webapp的目录,我们以前都是将所有的页面或静态资源导在这个目录下,但现在使用 ...
- SpringBoot 处理xss攻击
添加依赖 <!-- xss跨站脚本攻击 --> <dependency> <groupId>net.dreamlu</groupId> <arti ...
- 【Azure Developer】一个复制Redis Key到另一个Redis服务的工具(redis_copy_net8)
介绍一个简单的工具,用于将Redis数据从一个redis端点复制到另一个redis端点,基于原始存储库转换为.NET 8:https://github.com/LuBu0505/redis-copy- ...
- 如何做好一场NPS调研?
我们在工作中经常遇到的一个词,那就是"产品NPS调研".当部分项目缺少专业的用研人员时,设计师.产品经理则经常会接受上级的要求,投身于NPS调研工作. 笔者也曾在2022年的某天突 ...
- SparkSQL on K8s 在网易传媒的落地实践
作者:鲁成祥 易顺 随着云原生技术的发展和成熟,大数据基础设施积极拥抱云原生是业内发展的一大趋势.网易传媒在 2021 年成功将 SparkSQL 部署到了 K8s 集群,并实现与部分在线业务的混合部 ...
- PHP 使用非对称加密算法
加密的类型: 在日常设计及开发中,为确保数据传输和数据存储的安全,可通过特定的算法,将数据明文加密成复杂的密文.目前主流加密手段大致可分为单向加密和双向加密. 单向加密:通过对数据进行摘要计算生成密文 ...
- [oeasy]python0095_乔布斯求职_雅达利_atari_breakout_打砖块_布什内尔_游戏机_Jobs
编码进化 回忆上次内容 上次 我们回顾了 电子游戏的历史 从 电子游戏鼻祖 双人网球 到 视频游戏 PingPong 再到 街机游戏 Pong 雅达利 公司 来了 嬉皮士 捣乱? 布什内尔 会如何 应 ...
- AT_arc041_b 题解
洛谷链接&Atcoder 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 给定一个 \(N \times M\) 的矩阵,此矩阵的每一个元素都向上.下.左.右 ...
- [rCore学习笔记 013]GDB跟踪程序
题目要求 请学习 gdb 调试工具的使用(这对后续调试很重要),并通过 gdb 简单跟踪从机器加电到跳转到 0x80200000 的简单过程.只需要描述重要的跳转即可,只需要描述在 qemu 上的情况 ...