从一到无穷大 #38:讨论 “Bazel 集成仅使用 Cmake 的依赖项目” 通用方法
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。
文章目录
- 正文
- 样例代码
正文
Bazel
项目引用仅使用Cmake
依赖项目,目前业界最为普遍的集成方法是:将依赖项目中需要的全部文件打包成一个Bazel
中的Target
。
原生支持Bazel
的项目一般会使用细粒度的Target
划分项目,就像Cmake
中在不同的模块使用add_library
和target_include_directories
打包成.a
,最后在生成可执行程序时一并链接,一来可以增加测试代码的编译速度,二来项目划分也更为清晰。
Bazel Cpp
集成一个复杂项目时一般存在很多麻烦,包括不限于:
- 符号冲突
- 多个编译单元编译选项不同导致实例化不同,链接失败
- 编译选项确实或错误
- 繁杂的库依赖,包括依赖的依赖
- 特殊版本库依赖
所以如果把所有的代码集成到一个Target
同时编译,开始报错会非常多,而且因为多线程编译,每次的报错还不太一样。很自然的思路就是:是否可以逐模块引入依赖项目?
来想下一般Cmake
的编译流程:
- 各个模块所有的文件执行预处理,编译,汇编,生成多个
.o
文件,每一个cpp
是一个编译单元 ar
将一个模块的文件打包为一个静态库,此时还没有链接,每个.a
中符号调用还没有分配偏移地址- 生成可执行文件,链接基础依赖库和之前生成的所有静态库
Bazel
的原理和上述流程基本一致,但是有一个更强的保证,即多个Target
之间不允许循环依赖。
这有助于让代码的结构更为清晰,但是对于细粒度的集成依赖来说是一切灾难的开始。
举个简单的例子:
// A.cpp
#include "A.h"int main()
{return 0;
}// A.h
#include "B.h"// B.cpp
#include "A.h"// B.h
#include "xxxxxx"
这种情况下Cmake
是不存在循环依赖的,因为不存在头文件的互相依赖,B.o
和A.o
在链接阶段会互相找到符号的定义。但是在Bazel
中就不一样了,因为Target
必须包含对方的定义,也就成了:
// BUILD.a
cc_library(name = "A",srcs = [ "A.h", "A.cpp" ],includes = ["lib"],deps = ["//xxx:B",],
)// BUILD.b
cc_library(name = "B",srcs = [ "B.h", "B.cpp" ],includes = ["lib"],deps = ["//xxx:A",],
)
还没有进入链接阶段,在Bazel
的准备阶段就已经报错循环引用了。这种情况就只能把A
和B
包含为一个Target
。
如何判断Bazel
集成仅使用Cmake
的依赖项是否可以细粒度拆分呢?步骤其实很清晰,即:
- 把编译的过程看做一个有向图
- 每个
cpp
文件是一个节点 cpp
文件包含的.h
和cpp
文件对应的.h
包含的所有.h
为有向边
这种情况下判断是否存在环。
此时对上一轮发现的环执行缩点,忽略不是环的节点,但是保留缩点后的和其他缩点节点的边,如果还存在环就要继续缩点,直到不存在环。最差的结果是最后只有一个点。
缩点的原始节点就是在bazel
中必须包含在一个Target
的文件。
其实一般顶级开源项目的模块划分都很清晰,一般不会出现多个模块之间大规模的互相引用,但是出现后这种判断Cmake
项目是否可以逐模块拆分为Bazel
的方法非常有效。
但是有一个问题,执行完这个分析后得出的不存在环的结论文件级别的,这个时候最差的情况是需要大规模的逐文件去写bazel
中对应Target
,虽然看起来这个流程是可以自动化的,但是确实没有精力去研究这个了。
这里就有两个劣势:
- 逐文件写
Target
过于复杂,有些本末倒置,越复杂的项目Target写的越复杂,而且极难修改 - 如果要升级依赖的项目,对应项目存在大规模路径变动,上面的步骤就要再来一次了
所以综上所属,“Bazel
集成仅使用Cmake
的依赖项目” 的通用方法就是:
- 把所有的文件打包成一个
Target
- 复杂依赖项目的集成需要对代码结构有所了解,最小化引入
样例代码
这里是一个上面提到的缩点的代码实现,原则上可以判断全部cpp项目的依赖关系,判断是否可以 “轻松” 的拆分为Bazel
的Target
。
import os
import re
from collections import defaultdictEXCLUDED_DIRS = {'tests', 'test', 'benchmarks', 'fuzzer', 'docs', 'examples', 'tool', "experimental"}def find_cpp_files(directory):"""Find all .cpp files in the given directory, excluding certain subdirectories."""cpp_files = []for root, dirs, files in os.walk(directory):dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]for file in files:if file.endswith('.cpp'):cpp_files.append(os.path.join(root, file))return cpp_filesdef extract_includes(cpp_file):"""Extract included header files from a .cpp file."""includes = []with open(cpp_file, 'r') as f:for line in f:match = re.match(r'^\s*#\s*include\s+"([^"]+)"', line)if match:includes.append(match.group(1))return includesdef build_dependency_map(cpp_files):"""Build a map of cpp files to their header file dependencies, including .h files."""dependency_map = {}for cpp_file in cpp_files:includes = extract_includes(cpp_file)relative_path = os.path.relpath(cpp_file, "/data1/exercise/velox/")base_name = os.path.splitext(relative_path)[0]dependencies = [os.path.splitext(include)[0]for include in includes if os.path.splitext(include)[0] != base_name]if base_name == 'velox/type/Tokenizer':print("===============", dependencies)h_file_path = os.path.splitext(cpp_file)[0] + '.h'if os.path.exists(h_file_path):h_includes = extract_includes(h_file_path)dependencies.extend([os.path.splitext(include)[0] for include in h_includesif os.path.splitext(include)[0] != base_name])if base_name == 'velox/type/Tokenizer':print("===============", dependencies)dependency_map[base_name] = list(set(dependencies)) return dependency_mapdef find_cycles(dependency_map):"""Detect cycles in the dependency map and return all cycle paths."""visited = set()stack = set()cycles = []def dfs(node, path):if node in stack:cycle_start_index = path.index(node)cycles.append(path[cycle_start_index:] + [node])return Trueif node in visited:return Falsevisited.add(node)stack.add(node)path.append(node)for neighbor in dependency_map.get(node, []):dfs(neighbor, path)stack.remove(node)path.pop()return Falsefor node in dependency_map:if node not in visited:dfs(node, [])return cycles# 此时只需要关心缩点后的超级点,因为其他点已经确定不存在循环依赖
def build_scc_graph(cycles, dependency_map):"""Build a new graph with strongly connected components (SCCs)."""scc_map = {}scc_to_nodes_map = defaultdict(list)for i, cycle in enumerate(cycles):for node in cycle:scc_map[node] = f"SCC_{i}" scc_to_nodes_map[f"SCC_{i}"].append(node)#print(f" Node {node} added to SCC_{i}")scc_graph = defaultdict(set)for node, scc in scc_map.items():for neighbor in dependency_map.get(node, []):if neighbor in scc_map and scc_map[neighbor] != scc:scc_graph[scc].add(scc_map[neighbor])print("\nSCC to Node List Mapping:")for scc, nodes in scc_to_nodes_map.items():print(f"{scc}: {nodes}")return scc_graph, scc_mapdef detect_cycles_in_scc_graph(scc_graph):"""Detect cycles in the SCC graph and return cycles with their corresponding SCCs."""visited = set()stack = set()cycles = []def dfs(node, path):if node in stack:cycle_start_index = path.index(node)cycles.append(path[cycle_start_index:] + [node]) return Trueif node in visited:return Falsevisited.add(node)stack.add(node)path.append(node)for neighbor in scc_graph.get(node, []):dfs(neighbor, path)stack.remove(node)path.pop()return Falsefor node in scc_graph:if node not in visited:dfs(node, [])return cycles def main(directory):cpp_files = find_cpp_files(directory)dependency_map = build_dependency_map(cpp_files)cycles = find_cycles(dependency_map)if cycles:print("发现循环依赖:")# for cycle in cycles:# print(" -> ".join(cycle))scc_graph, scc_map = build_scc_graph(cycles, dependency_map)scc_cycles = detect_cycles_in_scc_graph(scc_graph)if scc_cycles:print("缩点后的图中存在循环依赖:")for cycle in scc_cycles:print(" -> ".join(cycle))scc_nodes = [node for node in cycle if node in scc_map]print(f"SCC {cycle}: 包含节点 {scc_nodes}")else:print("缩点后的图中不存在循环依赖。")else:print("系统中不存在循环依赖。")if __name__ == "__main__":directory_to_check = "/data1/exercise/xxxxxxxx"main(directory_to_check)
参考:
- GNU GCC使用ld链接器进行链接的完整过程是怎样的?
- c++基础-头文件相互引用与循环依赖问题
相关文章:

从一到无穷大 #38:讨论 “Bazel 集成仅使用 Cmake 的依赖项目” 通用方法
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。 文章目录 正文样例代码 正文 Bazel项目引用仅使用Cmake依赖项目,目前业界最为普遍…...

Python飞舞蝙蝠
目录 系列文章 写在前面 完整代码 代码分析 写在后面 系列文章 序号直达链接爱心系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码…...
shodan搜索引擎——土豆片的网安之路
工作原理: 在服务器上部署了各种扫描器,如漏洞扫描器,硬件扫描器,目录扫描器等等,24小时不停的扫描,批量对IP地址扫描 优点:方便,很快得到最新扫描结果,漏洞信息 缺点…...

uniapp 报错Invalid Host header
前言 在本地使用 nginx 反向代理 uniapp 时,出现错误 Invalid Host header 错误原因 因项目对 hostname 进行检查,发现 hostname 不是预期的,所以,报错 Invalid Host header 。 解决办法 这样做是处于安全考虑。但࿰…...
删除 AzureArcSetup 安装程序及提示
删除 AzureArcSetup 安装程序及提示 文章目录 删除 AzureArcSetup 安装程序及提示一、基础环境二、适用场景三、过程和方法 版权声明:本文为CSDN博主「杨群」的原创文章,遵循 CC 4.0 BY-SA版权协议,于2024年10月31日首发于CSDN,转…...
NGPT:在超球面上进行表示学习的归一化 Transformer
在超球面上进行表示学习的归一化 Transformer 1. 研究背景2. nGPT 的核心贡献超球面上的网络参数优化作为超球面上的变度量优化器更快的收敛速度 3. 从 GPT 到 nGPT 的演变标记嵌入和输出逻辑 层和块自注意力块MLP 块有效学习率在 ADAM 中的应用总结 4. 实验结果训练加速网络参…...

云原生Istio基础
一.Service Mesh 架构 Service Mesh(服务网格)是一种用于处理服务到服务通信的专用基础设施层。它的主要目的是将微服务之间复杂的通信和治理逻辑从微服务代码中分离出来,放到一个独立的层中进行管理。传统的微服务架构中&#x…...
Word2Vec优化与提升技巧
随着自然语言处理领域的快速发展,Word2Vec 已成为常见的词向量生成工具。然而,单纯依赖默认设置往往不能在实际业务需求中取得最佳效果。通过调整模型的参数、优化算法以及合理处理大规模语料库,可以显著提升模型的表现和效率,适应复杂的应用场景。这篇文章将带你深入了解 …...

Java 开发——(下篇)从零开始搭建后端基础项目 Spring Boot 3 + MybatisPlus
上篇速递 - Spring Boot 3 MybatisPlus 五、静态资源访问 1. 基础配置 在 Spring Boot 中访问静态资源非常方便。Spring Boot 默认支持从以下位置加载静态资源: /META-INF/resources//resources//static//public/ 这些目录下的文件可以直接通过 URL 访问。 例…...
Redis 线程控制 问题
前言 相关系列 《Redis & 目录》《Redis & 线程控制 & 源码》《Redis & 线程控制 & 总结》《Redis & 线程控制 & 问题》 参考文献 《Redis分布式锁》 Redis如何实现分布式锁? Redis是单进程单线程的,指令执行时不会…...

005 IP地址的分类
拓扑结构如下 两台主机处于同一个网关下,通过ping命令检测,可以连通 &nbps; 拓扑结构如下 使用ping 检查两台电脑是否相通, 因为网络号不一样,表示两台电脑不在同一个网络,因此无法连通 拓扑结构如下 不在同一网络的PC要相…...

Java 并发工具(12/30)
目录 Java 并发工具 1. Executor 框架 1.1 线程池 1.2 ExecutorService 和 Future 2. 同步辅助类 2.1 CountDownLatch 2.2 Semaphore 3. 并发集合 3.1 ConcurrentHashMap 总结与后续 Java 并发工具 在多线程编程中,高效管理线程和任务至关重要。Java 提供…...

filebeat+elasticsearch+kibana日志分析
1 默认配置 1.1 filebeat filebeat-7.17.yml,从网关中下载k8s的配置,指定es和kibana的配置 通过kibana查询可以查询到日志了,但此时还不知道具体怎么用。 1.2 kibana 在Discover中创建索引格式:filebeat-*,得到如下图…...

Google Recaptcha V2 简单使用
最新的版本是v3,但是一直习惯用v2,就记录一下v2 的简单用法,以免将来忘记了 首先在这里注册你域名,如果是本机可以直接直接填 localhost 或127.0.0.1 https://www.google.com/recaptcha/about/ 这是列子 网站密钥:是…...

Rust编程中的浮点数比较
缘由:在看Rust编写的代码,发现了一行浮点数等于比较的代码,于是编辑如下内容。 在Rust中,进行浮点数比较时需要特别小心,因为浮点数由于精度限制无法精确表示小数,可能会导致直接比较(如 &…...

java访问华为网管软件iMaster NCE的北向接口
最近做的一个项目,需要读取华为一个叫iMaster NCE的网管软件的北向接口。这个iMaster NCE(以下简称NCE)用于管理项目的整个网络,尤其是光网络。业主要求我们访问该软件提供的对外接口,读取一些网络信息,比如…...

UV紫外相机
在产业设备领域,运用相机进行检测的需求很大,应用也很多样,对于图像传感器性能的期望逐年提升。在这样的背景下,可拍摄紫外线(UV:Ultra Violet)图像的相机拥有越来越广泛的应用场景。将UV照明和…...

第十八届联合国世界旅游组织/亚太旅游协会旅游趋势与展望大会在广西桂林开幕
10月19日,第十八届联合国世界旅游组织/亚太旅游协会旅游趋势与展望大会(以下简称“大会”)在广西桂林开幕,来自美国、英国、德国、俄罗斯、柬埔寨等25个国家约120名政府官员、专家学者和旅游业界精英齐聚一堂,围绕“亚洲及太平洋地区旅游业&a…...

Effective Java(第三版) _ 创建和销毁对象
一、前言 《Effective Java》 这本书,在刚从事 Java 开发的时候就被老师推荐阅读过,当时囫囵吞枣的看了一部分,不是特别的理解,也就搁置了,现在已经更新到第三版了,简单翻阅了一下,发现有些条例…...

你的EA无法运行的几种常见原因
大多数情况下,EA正常运行是指其能够自动开仓交易,毕竟EA的主要目的是根据某种策略自动进行交易。如果从网上下载或其他途径获得的EA在开始时能够正常交易,但在修改参数后却不再交易,可能的问题是什么呢?下面列举了一些…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

Matlab实现任意伪彩色图像可视化显示
Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中,如何展示好看的实验结果图像非常重要!!! 1、灰度原始图像 灰度图像每个像素点只有一个数值,代表该点的亮度(或…...

Mysql故障排插与环境优化
前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...

网页端 js 读取发票里的二维码信息(图片和PDF格式)
起因 为了实现在报销流程中,发票不能重用的限制,发票上传后,希望能读出发票号,并记录发票号已用,下次不再可用于报销。 基于上面的需求,研究了OCR 的方式和读PDF的方式,实际是可行的ÿ…...
JavaScript 标签加载
目录 JavaScript 标签加载script 标签的 async 和 defer 属性,分别代表什么,有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...