- 后端
- Web框架
【免费下载链接】fountain
一个用于服务器应用开发的综合工具库。 - 零配置文件 - 环境变量和命令行参数配置 - 约定优于配置 - 深刻利用仓颉语言特性 - 只需要开发动态链接库,fboot负责加载、初始化并运行 - 覆盖服务器应用开发的各种场景
本文介绍 fountain 仓库中
docs/模块依赖图/下的三套生成脚本:它们为根 README.md 的「模块依赖关系」章节产出两张图(分层连线图module-dependencies.svg与依赖矩阵module-dependency-matrix.svg),并给出依赖抽取、API 引用量统计等排查手段。读完你将掌握:什么才算一条真实依赖(cjpm.toml声明 + 源码 import 双层判定)、如何一键重画两张图、连线图的布局与避让路由算法、矩阵图的读图方式,以及"重跑产物一致、自检全绿"的工程约定。
这套工具的定位:README 里那两张图是怎么来的
fountain 根 README.md 的「模块依赖关系」章节里有两张图:
- 分层连线图
.assets/README/module-dependencies.svg:箭头A → B表示 A 依赖 B,按依赖层级分层排布,适合一眼看懂"谁在底层、谁在上层"; - 依赖矩阵
.assets/README/module-dependency-matrix.svg:行 = 依赖方、列 = 被依赖方,适合按模块精确查询"它依赖了谁 / 谁依赖了它"。
这两张图并非手工维护,而是由 docs/模块依赖图/ 下的三个 Python 脚本自动生成。三个脚本分工如下(见 docs/模块依赖图/README.md 的产物表格):
| 产物 | 生成脚本 |
|---|---|
.assets/README/module-dependencies.svg(分层连线图) | gen_layered_svg.py |
.assets/README/module-dependency-matrix.svg(依赖矩阵) | gen_matrix_svg.py |
| 依赖表 / 统计(排查用) | extract_deps.py,另有--usage(每条边的 API 引用次数)、--declared(连未引用的声明一起列)、--json |
其中extract_deps.py是数据源与口径的唯一权威(它的 docstring 明确写着"改『什么算依赖』只改这里"),两个绘图脚本都以模块方式import extract_deps复用其graph()、api_usage()、top_edges()、summary()等函数。
核心口径:什么才算一条真实依赖(双层判定)
这是整套工具的基石。仓颉语言里,要用某个模块的 API 必须把它声明成直接依赖(间接依赖的符号不可见),因此:
「
cjpm.toml的[dependencies]里声明了fountain::X」 且 「该模块src/下的非测试源码里真的import了fountain::X…」——两条同时成立才算一条边。
从源码看,这两层在 extract_deps.py 的scan()中分别实现:
- 声明层:读取各模块
cjpm.toml,用正则切出[dependencies]段,匹配"fountain::([A-Za-z0-9_]+)"形式条目(#注释行跳过); - 引用层:
imports_dep()遍历该模块src/下所有.cj文件(src_files()会剔除*_test.cj),用IMPORT_FMT正则匹配import fountain::<dep>…。该正则在 extract_deps.py 中支持带可见性修饰的 import:
IMPORT_FMT = (r'^\s*(?:(?:public|protected|internal|private)\s+)?import\s+' r'fountain::{}(?![A-Za-z0-9_])')即public import/protected import/internal import/private import都算引用,普通import自然也算。
声明了但源码未引用的边不进图——它们只是多余的声明(比如f_orm/cjpm.toml里声明了一批模块,但某条没在源码中出现),不代表真实依赖关系;间接可达关系也不画(比如 A 依赖 B、B 依赖 C,A 与 C 之间没有直接边,即使 A 能通过 B 间接用到 C 的能力也不画)。两条图只画 used 边,这保证了图上的每一条边都能在源码里找到对应的 import 证据。
以根 README 对图的说明为旁证(README.md):"只画『cjpm.toml声明了、且源码里真的 import 了对方 API』的依赖",与脚本实现完全一致。
一键重画:三条命令与产物路径
模块依赖变化后,只需两条命令即可重画两张图。原文档给出的流程为(docs/模块依赖图/README.md):
cd docs/模块依赖图 python3 extract_deps.py # 先看抽取结果:modules=… declared=… used=… unused=… python3 gen_layered_svg.py # → ../../.assets/README/module-dependencies.svg python3 gen_matrix_svg.py # → ../../.assets/README/module-dependency-matrix.svg环境要求极低:只要 Python 3,无任何第三方依赖(两个绘图脚本只用标准库math/os/random/sys/xml.etree.ElementTree)。在笔者的验证环境(Python 3,仓库当前状态)上实际运行python3 extract_deps.py,得到:
modules=44 declared=238 used=219 unused=19即当前仓库抽取到44 个模块(含fboot、fleet),[dependencies]中声明了 238 条fountain::*依赖,其中219 条被源码真正引用(进图),19 条属于"声明了但未引用"(不进图,可用--declared列出以便清理)。
两个绘图脚本都支持可选输出路径参数(python3 gen_layered_svg.py [输出路径]),默认分别写:
docs/模块依赖图/../../.assets/README/module-dependencies.svgdocs/模块依赖图/../../.assets/README/module-dependency-matrix.svg
排查利器:extract_deps.py 的命令行参数
extract_deps.py既是绘图脚本的数据源,也是一个独立的依赖排查工具,支持四个参数:
python3 extract_deps.py # 依赖表 + 统计(默认只列已引用的边) python3 extract_deps.py --declared # 连「声明了但未引用」的边一起列(标 UNUSED,便于清理 cjpm.toml) python3 extract_deps.py --usage # 每条边的 API 引用次数(连线图靠它决定颜色深浅) python3 extract_deps.py --json # 输出 JSON(含 declared / used / usage) python3 extract_deps.py --usage | head # 看引用量最大的边--declared:对每个模块额外输出[UNUSED: …]列表,标出那些声明了却没引用的依赖,是清理cjpm.toml冗余声明的直接依据;--usage:按 API 引用次数降序列出每条边,--json则输出结构化数据供其它脚本消费。实测引用量最大的边(节选):
493.2 f_orm -> f_base 412.7 f_data -> f_concurrent 347.5 f_orm -> f_data 344.3 f_llm -> f_data注意--usage输出的次数可能带小数(如 493.2),这是 API 引用量口径里"同名符号均摊"的结果,见下节。
API 引用量的精确口径
api_usage()(extract_deps.py)统计"A 的非测试源码里出现 B 的公开符号名的次数",有四处严格的过滤规则:
- import 行本身不计——只看真正的使用处(正则
ANY_IMPORT匹配到import开头的行直接跳过); - 注释与字符串字面量不计——
_clean()先用正则剥掉块注释/*…*/、行注释//…和字符串"…",避免把文档、文案里的名字误算成 API 引用; - 同名符号按候选数均摊——若某个符号同时属于 A 依赖的多个模块(如
Exception可能同时是f_exception和标准库的名字),出现一次就按 1/N 分给每个候选模块,不重复计给每一个; - A 自己也声明了同名符号则不计——
symbols()用PUBLIC_DECL正则(public … (class|interface|struct|enum|func|macro|let|var|const|type|prop|operator))收集各模块对外公开的符号名,若nm in syms[a],说明该符号归属不明,直接跳过。
symbols()有缓存(_SYM_CACHE、_PAT_CACHE),多脚本重复调用时不重复扫描源码。均摊后可能出现.5这类一位小数,最终round(v, 1)保留。
每模块前三:top_edges
top_edges(k=3)(extract_deps.py)按 API 引用量给每个模块的依赖排序,取前三名;次数相同时按依赖名字典序定序,保证结果可复现。两张图共用同一条规则:连线图中前三名画彩色实线、其余画灰色虚线;矩阵图中前三名画彩色格、其余画浅灰格。实测main_edges数量会随仓库演进变化,重画时脚本会在输出中打印。
连线图(gen_layered_svg.py)的布局与避让路由
gen_layered_svg.py产出的分层连线图在可读性上做了大量设计,原文档归纳了其中的要点,源码里可以一一对应:
- 分层布局:
level = 到汇点(无依赖的模块)的最长路径(递归lvl()),越底层 level 越小,行从上到下排布;节点宽度NW按最长模块名自适应(中文字符按 1.04 倍、ASCII 按 0.55 倍估宽),而不是固定宽度,省掉大片空白; - 行内布局:节点少的行把省下的横向空间摊到行内间隙(
HGAP=96下限、MAX_HGAP=210上限),并叠加行级横向错位(optimize_shifts对每行试探 9 个偏移比例,取交叉数最小者),把"所有长边挤成一条竖直走廊"的问题错开; - 行内排序:多起点启发式搜索——先按 3 个确定性起点(原始顺序 / 入度降序 / 出度降序)加 12 个固定种子随机打乱共 15 个候选,每个候选跑多轮「重心法 + 相邻交换」(
barycenter10 轮 +transpose预算 150 次),目标函数是沿真实路径统计的几何交叉数(crossings沿避让折线每 32px 采样一次 x,统计相邻采样间顺序翻转的对数——折线 y 单调,翻转一次即相交一次);只对交叉数最好的 3 个起点做相邻交换; - 跨层长边避让路由:这是连线图最精巧的部分。
channel_x()对每条span > 1的跨层长边,在中间每一层挑选一条通道 x——通道只能落在该层"节点之间的空隙"或行两端外侧(free_slots扣掉与节点框的净距CLR=7),同一空隙内的多条边按自然插值 x 均匀铺开,因此线永远不会压到任何节点框;路径中竖直段用L,行间过渡用控制点在竖直中点的三次贝塞尔(path_d),曲线不会超出两端 x 范围; - 出/入边锚点分散:
anchors_at让一个节点的多条出边沿底边、多条入边沿顶边均匀分散,避免"一点引出一束线"; - 交互:每个节点带
<title>(含被依赖数、自身依赖数、API 引用合计),CSS 悬停节点时高亮其直连边(svg:has(#nd-{n}:hover) g.e[data-src=…]),剥离 CSS 也不影响静态观感。
配色:黄金角散布 + 引用量分档
配色规则(原文档约定 + 源码实现):
- 每个模块一个不重复的色相:
hues()用黄金角137.508°按顺序散布((i * GOLDEN) % 360),44 个模块互不撞色; - 边的深浅 = 引用量:
shade_of(u)先把引用量做log1p归一化,再分6 档(LEVELS = 6,1 档最浅、6 档最深);pair_hex在模块色相上按档位调明度(light=0.80 → dark=0.34),引用越多越深; - 两张图共用这套色:连线图与矩阵图都调用
extract_deps.hues(),且矩阵图按(level, name)同序分配,保证同一模块在两张图里同色。
节点样式还承担语义:被依赖数 ≥ 10(HUB)的模块画蓝框(hub),无依赖的基础层画绿框(foundation),其余为浅色框。
自检与可复现
脚本运行结束时会打印自检报告(源码 gen_layered_svg.py):
header overflow:页眉文字是否横向超出画布 / 纵向压进节点区——必须none;node crossings:逐条采样复核"线压节点"——必须none(实测会打印none(没有线压到节点框));overlaps:节点框是否互相重叠——必须none;colors unique:色相互不重复——打印yes及模块数/色相数。
产物可复现:random.Random(20261008)固定随机种子,同一脚本连跑两次两张图md5应一致。这保证 CI 或协作场景下重画不会产生随机漂移。
矩阵图(gen_matrix_svg.py)的读图方式与布局
gen_matrix_svg.py产出依赖矩阵,它与连线图是同一份数据(extract_deps.graph())、同一条配色规则的另一种读法:
- 两轴同一顺序:按
(level, name)升序(基础层在左上)排列,因此依赖只落在对角线右上侧;左下三角铺一层浅底(结构上不可能有依赖); - 查询场景更快:看某模块依赖了谁 → 看该行;看谁依赖了某模块 → 看该列;
- 格子颜色:该行 API 引用量前三的依赖画彩色格(色相 = 依赖方、深浅 = 引用量,与连线图一致),其余画浅灰格
#e2e8f0;每个格子带<title>(含引用次数与"前三/未进前三"标注),悬停可看; - 列名下边的数字 = 该模块被依赖次数(in-degree):原文档特别说明这个数字"原先放在网格底行,已上移"到列名正下方,紧贴列名,视觉上直接回答"谁是被依赖最多的模块";被依赖 ≥ 10 的模块列名加粗;
- 网格每 5 格加深分隔线,便于数格子定位;
- 自检项较少:
header overflow与colors unique两项必须干净。
矩阵图的行/列标签都是模块短名,加上行列数字后,非常适合在评审依赖关系时逐模块核对。
约定速览:改脚本时保持一致
原文档把改脚本时必须遵守的约定集中列了出来,这里汇总为一张速查表:
| 约定 | 内容 |
|---|---|
| 不写死路径 | 仓库根按脚本位置推断(脚本在docs/模块依赖图/下,产物写../../.assets/README/,见ROOT = abspath(…/../..)) |
| 什么算依赖 | cjpm.toml声明了fountain::X且src/(非_test.cj)里import了fountain::X…;声明了但未引用、间接可达都不画 |
| API 引用量 | A 源码里出现 B 公开符号名的次数;import 行、注释、字符串不计;同名符号按候选数均摊;A 自己声明的同名符号不计 |
| 每模块前三 | 按引用量排序(并列按依赖名定序,保证可复现),前三画彩色实线 / 彩色格,其余灰色虚线 / 浅灰格——两张图同一规则 |
| 配色 | 每模块一个不重复色相(黄金角散布);边/格深浅 = 引用量(log 归一 6 档,越多越深);两张图同色 |
| 连线图布局 | 节点宽度按最长模块名自适应;行内摊开 + 行级错位;行内排序用多起点重心法 + 相邻交换,目标 = 几何交叉数;跨层长边只在"节点间隙 / 行外侧"路由 |
| 矩阵图数字 | 列名下边紧贴一个数字 = 该模块被依赖次数(原在网格底行,已上移) |
| 产物可复现 | 固定随机种子,同一脚本连跑两次两张图 md5 一致 |
| 自检全绿 | 连线图:header overflow / node crossings / overlaps / colors unique;矩阵图:header overflow / colors unique |
| 示例应用不进图 | fdemo、fcoder、frpcdemo列在EXCLUDE里(见 extract_deps.py),根 README 的模块清单只含库模块与fleet |
其中EXCLUDE的实现值得一提:scan()里glob所有*/cjpm.toml后按目录名过滤掉三个示例应用目录,因此它们的依赖(包括未纳入中心仓的私有模块)不会污染图。
与技能 fountain-tag 同源
原文档最后一条约定是:fountain-tag/scripts/deps/是同一套脚本的技能副本,差别只有两点——仓库根按FOUNTAIN_ROOT> git 仓库根 > 当前目录的顺序推断,以及产物路径不同。改完docs/模块依赖图/下的脚本后,需要运行:
python3 <技能目录>/scripts/deps/sync_from_repo.py # 同步同源脚本 check_sync.py --run # 校验同源与产物一致需要说明的是:当前仓库的 skills/ 目录下只包含cangjie-doc-lookup与fountain-developer两个技能,fountain-tag技能副本并不在本仓库内,同步操作应在安装该技能的本地环境执行。这条约定保证了"同一套口径、同一份产物"在仓库与技能侧保持一致,避免两份脚本各改各的导致依赖图口径漂移。
小结
docs/模块依赖图/下的三套脚本构成了 fountain 仓库一套自洽、可复现、可自检的模块依赖可视化体系:
- 口径统一:一切依赖边都经过「
cjpm.toml声明 + 源码 import」双层判定,声明了没用的边和间接可达关系一律不进图,图上的每一条边都能在源码里找到证据; - 一数据两视图:
extract_deps.py产出统一数据,连线图看整体分层、矩阵图查单模块明细,配色与"每模块前三"规则跨图一致; - 工程化程度高:无第三方依赖、固定随机种子保证产物可复现、内置 4 项自检、示例应用隔离、与技能侧脚本保持同源同步。
对 fountain 开发者来说,这套工具的实际价值在于:新增或调整模块依赖后,跑一遍三条命令就能得到两张最新、准确且干净的依赖图;用--declared能顺手清理cjpm.toml里的冗余声明;用--usage能识别"谁在重度依赖谁",为模块拆分与架构演进提供量化依据。
- 后端
- Web框架
【免费下载链接】fountain
一个用于服务器应用开发的综合工具库。 - 零配置文件 - 环境变量和命令行参数配置 - 约定优于配置 - 深刻利用仓颉语言特性 - 只需要开发动态链接库,fboot负责加载、初始化并运行 - 覆盖服务器应用开发的各种场景
相关推荐
NetExec模块依赖管理工具:解析与解决依赖
NetExec模块依赖管理工具:解析与解决依赖 在网络执行工具(NetExec)的日常使用中,依赖管理是确保工具稳定性和功能完整性的关键环节。你是否曾遇到过模块
网络安全渗透测试CLI应用安全Pydeps:Python模块依赖可视化工具
Pydeps:Python模块依赖可视化工具 安装指南 Pydeps是一款强大的Python包,专注于以图形方式展现Python模块间的依赖关系。要安装Pyde
开发工具Pilot Shell auto_approve_plan详解:模型切换流程中的ExitPlanMode授权
Pilot Shell auto_approve_plan详解:模型切换流程中的ExitPlanMode授权 如果你在用 Pilot Shell 搭配 Clau
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考