作者: 吴翱翔
通常一个大型 Rust 项目都会用 cargo workspace 来管理 workspace 下面有多个 member 也叫 package 或者叫 crate
伞结构?
在《张汉东的Rust实战课》鉴赏 Rust 各个知名项目的视频分集中
经常会提到某某项目用的是 伞结构,但是 伞结构 又是什么意思呢?

借助 rust-analyzer 可视化 API 如上图就是 clippy 源码内各个库的依赖关系
可见 clippy_lints package 往下依赖很多子 package 但各个子 package 之间没有任何互相依赖
所以 clippy 源码这种项目就叫 伞结构 通过可视化工具发现确实很像 一把倒立的雨伞
本文介绍基于 rust-analyzer 公有 API 对项目中各个 package 依赖关系进行可视化
导入 rust-analyzer 库
rust-analyzer 的 lsp-server 的工作原理是 vscode 创建 rust-analyzer 子进程
然后 vscode 跟 rust-analyzer 之间通过两个管道借助 STDIN/STDOUT 进行通信
其实 rust-analyzer 还可以作为一个库调用它的 API
由于公开的接口尚未稳定频繁改动,所以 rust-analyzer 并没上传到 crates.io
我们可以将 rust-analyzer 源码下载到本地,在 Cargo.toml 下加上以下内容就可以引入
[dependencies]# rust-analyzer commit hash dd21ad6a5e8ffa166c97447212d3da0f86555aeerust-analyzer = { path = "../rust-analyzer/crates/rust-analyzer" }project_model = { path = "../rust-analyzer/crates/project_model" }paths = { path = "../rust-analyzer/crates/paths" }syntax = { path = "../rust-analyzer/crates/syntax" } # ASTbase_db = { path = "../rust-analyzer/crates/base_db" }hir = { path = "../rust-analyzer/crates/hir" }hir_expand = { path = "../rust-analyzer/crates/hir_expand" }ide = { path = "../rust-analyzer/crates/ide" }ide_db = { path = "../rust-analyzer/crates/ide_db" }vfs = { path = "../rust-analyzer/crates/vfs" }
load_cargo API 加载需要分析的项目
假设我们想要分析 rust-analyzer 源码中各个模块库的调用关系
let manifest_path = "/home/w/repos/clone_repos/rust-analyzer/Cargo.toml";let manifest_path: paths::AbsPathBuf = manifest_path.try_into().unwrap();let manifest = project_model::ProjectManifest::from_manifest_file(manifest_path).unwrap();let workspace = project_model::ProjectWorkspace::load(manifest,&project_model::CargoConfig::default(),&|_| {},).unwrap();
通过 project_model::ProjectWorkspace::load 加载出 workspace 信息后
此时我们可以遍历打印出该项目的 cargo workspace 下面总共有多少个 crate
// traverse all cargo_package(members) in cargo_workspacefor package in workspace.to_roots() {if !package.is_local {continue;}let package_path: &std::path::Path = package.include[0].as_ref();println!("found package {}", package_path.to_str().unwrap());}
接着开始分析项目并生成 graphviz 格式的依赖关系图
let (analysis_host, _vfs, _proc_macro_srv_opt) =rust_analyzer::cli::load_cargo::load_workspace(workspace,&rust_analyzer::cli::load_cargo::LoadCargoConfig {load_out_dirs_from_check: false,with_proc_macro: false,prefill_caches: false,},).unwrap();let analysis = host.analysis();// graphviz 文件格式的 dot 图let is_include_std_and_dependencies_crate = false;let dot: String = analysis.view_crate_graph(is_include_std_and_dependencies_crate).unwrap().unwrap();// 再把 dot 图字符串写入到文件中let file_path = concat!(env!("CARGO_MANIFEST_DIR"), "/target/graph.gv");let mut f = std::fs::OpenOptions::new().write(true).create(true).truncate(true).open(file_path).unwrap();std::io::Write::write_all(&mut f, dot.as_bytes()).unwrap();// 最后调用 xdot 可视化 graphvizlet is_success = std::process::Command::new("xdot").arg(file_path).spawn().unwrap().wait().unwrap().success();assert!(is_success);

更简单的可视化方法
我在阅读 rust-analyzer 源码后发现其实 rust-analyzer 本身就提供 “crate graph” 的 vscode 如下图

