- 包管理器
- 开发工具
- CLI
【免费下载链接】pnpm
Fast, disk space efficient package manager
导读
pnpm 的 Rust 实现(pnpm/目录下的 Cargo workspace)为 Cargo 生态引入了一套与cargo行为对齐的依赖解析能力。本文基于 fix-cargo-git-workspaces.md 记录的两项变更展开:pnpm install现在可以接受省略包版本号的 Rust git 依赖,并为含有 git 依赖的 Cargo workspace 自动生成 Cargo.lock 锁文件。读完本文,你将掌握这两项能力的适用场景、底层解析流程(基于 PubGrub 求解器与 cargo-lock 序列化),以及仓库中对应的源码与测试验证位置。
一、背景:pnpm 的 Cargo 兼容依赖解析器
pnpm 主仓库同时维护着 TypeScript 版实现(pnpm11/)与 Rust 原生实现(pnpm/)。在 Rust 侧,cargo-resolvercrate(见 pnpm/crates/cargo-resolver/Cargo.toml)承担了"Cargo 兼容依赖解析"职责,其依赖选型直接体现了实现路径:
| 依赖 | 作用 |
|---|---|
cargo-lock | 解析与序列化Cargo.lock文件(v4 格式) |
cargo-util-schemas | 复用 Cargo 官方对稀疏索引条目(sparse index entry)的 schema |
pubgrub | 通用依赖求解器,负责版本选择与回溯 |
semver | 版本号与版本区间(VersionReq)的解析匹配 |
serde/serde_json | 解析cargo metadata输出的 JSON |
crate 的公开 API 集中在 src/lib.rs:
pub use metadata::{git_dependency_sources, resolve_inputs}; pub use registry::{CRATES_IO_SOURCE, ...}; pub use resolution::{missing_index_names, resolve_lockfile};其中git_dependency_sources正是本次 changeset 所涉功能的核心入口之一。
二、核心变更一:接受省略包版本的 Rust Git 依赖
2.1 变更含义
changeset 原文:
pnpm installnow accepts Rust git dependencies that omit a package version.
在 Cargo 语法中,git 依赖通常写作:
[dependencies] some-crate = { git = "https://github.com/example/some-crate", rev = "abc123" }此时依赖不携带版本号。过去 pnpm 的解析路径可能因此失败;本次变更后,这类依赖可以被正确识别并纳入解析流程,无需用户手动补充version字段。
2.2 源码实现:git_dependency_sources
在 pnpm/crates/cargo-resolver/src/metadata.rs#L12-L27 中,函数从cargo metadata文档提取所有 git 依赖源:
/// Git sources declared by the packages in a Cargo metadata document. /// The metadata need not contain a resolved dependency graph. pub fn git_dependency_sources(metadata: &str) -> Result<Vec<cargo_lock::SourceId>> { parse_metadata(metadata)?.packages .into_iter() .flat_map(|package| package.dependencies) .filter_map(|dependency| dependency.source) .filter(|source| source.starts_with("git+")) .map(|source| { source .parse() .into_diagnostic() .wrap_err("parse Cargo git dependency") }) .collect() }关键点:
- 遍历
cargo metadata中所有包的依赖,过滤出source以git+开头的条目; - 将字符串解析为
cargo_lock::SourceId,从而保留原始 URL 及查询参数(如?rev=release、?branch=...、?tag=...); - 该函数不要求元数据包含已解析的依赖图,因此可用于安装早期阶段(此时只有声明,尚无解析结果)。
2.3 测试验证
pnpm/crates/cargo-resolver/src/tests.rs#L23-L32 中的测试git_dependency_sources_preserve_the_requested_revision验证了带?rev=release的 git 依赖能够被原样提取:
fn git_dependency_sources_preserve_the_requested_revision() { let source = "git+https://example.test/repository?rev=release"; ... let sources = git_dependency_sources(&metadata).unwrap(); ... }同一文件中的registry_and_path_dependencies_do_not_require_git_resolution(tests.rs#L34-L40)则反向验证:纯 registry 与 path 依赖不会误入 git 解析分支。
三、核心变更二:为含 Git 依赖的 Workspace 生成 Cargo.lock
3.1 变更含义
changeset 原文:
pnpm also generates Cargo lockfiles for workspaces with git dependencies.
对于包含 git 依赖的 Cargo workspace,pnpm 现在会在安装后生成Cargo.lock,从而固化 git 依赖解析结果(指向的提交、传递依赖版本等),保证后续安装可复现。
3.2 整体流程:resolve_lockfile
生成锁文件的入口是 pnpm/crates/cargo-resolver/src/resolution.rs#L115-L147 的resolve_lockfile:
pub fn resolve_lockfile( metadata: &str, index_files: &BTreeMap<String, String>, source: &str, ) -> Result<String> { let metadata = parse_metadata(metadata)?; let registry = Registry::new(index_files, source)?; let root_dependencies = root_dependencies(&metadata)?; let mut feature_selections = root_feature_selections(®istry, &root_dependencies)?; ... loop { // 迭代求解:解析 → 计算 feature 选择 → 校验 → 收敛则输出 let solution = resolve_with_features(®istry, &root_dependencies, &feature_selections)?; ... if let Some(validated_solution) = validate_selected_graph(...)? { return lockfile_from_solution(&metadata, ®istry, &validated_solution, &selected_features, source); } feature_selections = selected_features; } }流程要点:
- 解析元数据:将
cargo metadata的 JSON 解析为内部模型; - 构建注册表:把稀疏索引文件(sparse index files)加载为可查询的版本列表;
- PubGrub 求解:用
pubgrub的OfflineDependencyProvider进行版本求解(见 resolution.rs#L224-L254),支持跨 semver 兼容线(compatibility line)选择版本并回溯; - Feature 收敛:由于 feature 激活不具备单调性,求解会迭代多轮直至收敛,不收敛时抛出
Cargo feature resolution did not converge; - 序列化输出:最终交给
lockfile_from_solution生成 v4 格式的Cargo.lock。
3.3 锁文件序列化:workspace 成员与 git 依赖的处理
pnpm/crates/cargo-resolver/src/lockfile.rs 负责把求解结果序列化为Cargo.lock(版本固定为ResolveVersion::V4,见 lockfile.rs#L60-L66)。
其中 workspace_packages 专门处理 workspace 自身成员:
- 仅收录
metadata.workspace_members中的包; - 对每个成员,将其依赖分为两类分别锁定:
- registry 依赖:通过
locked_dependency记录所选版本与其 source; - workspace 内部依赖:通过 locked_workspace_dependency 在 workspace 成员中按名字与版本区间匹配,
source记为None(与 Cargo 行为一致:本地 workspace 成员不写 source)。
- registry 依赖:通过
最终所有包按名称排序后写入锁文件,workspace 成员包与 registry 包在同一个packages列表中。
3.4 source 归属规则:git 依赖如何被记录
pnpm/crates/cargo-resolver/src/lockfile.rs#L160-L202 的locked_sources实现了 Cargo 的 source 归属语义:
- 依赖声明指向 crates.io(或未声明 registry 且父包来自 crates.io)→ 记录为
registry+https://github.com/rust-lang/crates.io-index(常量见 registry.rs#L11); - 依赖声明指向当前解析的 registry → 记录为该 registry 的稀疏源(
sparse+...); - 同一 crate 被两个不同 registry 同时要求时,直接报错
crate {} is required from two Cargo registries; - git 依赖的
git+...source 通过cargo_lock::SourceId::from_url写入锁文件,保留?rev、?branch、?tag等片段。
git_dependency_sources与locked_sources分工明确:前者在解析前扫描声明,后者在解析后固化归属。
四、工作流视角:pnpm install如何利用这些能力
结合 resolution.rs 与 metadata.rs 可以推断出安装时的大致链路:
- 对包含 Cargo 项目的 workspace 运行等价于
cargo metadata的采集,得到 JSON 文档; - 调用
git_dependency_sources识别所有git+依赖(不要求版本号),并保留其修订请求(rev/branch/tag); - 通过
resolve_inputs(metadata.rs#L48-L85)对元数据做"瘦身":只保留解析所需的name、version、features与依赖键,并把包 id 替换为序号——这样元数据离开本机(例如交给 pnpr 服务端解析)时不再携带机器相关路径; - 用
missing_index_names(resolution.rs#L35-L66)找出稀疏索引中尚未获取的 crate 名,按需拉取索引文件; - 调用
resolve_lockfile完成 PubGrub 求解并序列化Cargo.lock(v4)。
该链路同样服务于 pnpr(见 pnpr/),即服务端代理解析场景:客户端只上传精简后的依赖图,解析与锁文件生成在远端完成。
五、使用前提与限制
- 本文所述能力位于Rust 原生实现(pnpm/),对应 pnpm/crates/cargo-resolver/ 与 pnpm/crates/cli/Cargo.toml 中对
pnpm-cargo-resolver的引用,而非 pnpm11/ 的 TypeScript 实现; - 求解依赖的版本元数据来自 crates.io 稀疏索引(默认
https://index.crates.io,见 registry.rs#L17),因此网络访问与索引缓存在解析 registry 部分时必不可少; - git 依赖在
cargo metadata输出中以其声明为准(git+前缀),锁文件生成时对 git 包的 source 原样记录;workspace 成员的本地依赖则一律不写 source; - 解析与 feature 选择采用迭代收敛策略,若 feature 组合无法收敛会报错终止,而非静默产出错误锁文件;
- 使用方式以
pnpm install触发,无需额外命令;如需观察具体行为可运行pnpm/crates/cargo-resolver的单元测试(如cargo test -p pnpm-cargo-resolver,测试代码见 tests.rs)。
六、小结
本次 changeset 让 pnpm 的 Rust 实现补齐了两块 Cargo 兼容能力:一是容忍省略版本号的 git 依赖声明(metadata.rs#L14-L27),二是为含 git 依赖的 workspace 生成 v4 格式Cargo.lock(lockfile.rs#L20-L68)。两者共同打通了"声明 git 依赖 → 识别与保留修订 → PubGrub 求解 → 写入锁文件"的完整链路,让 pnpm 管理同时包含 npm 与 Cargo 生态的项目时更加一致、可复现。
- 包管理器
- 开发工具
- CLI
【免费下载链接】pnpm
Fast, disk space efficient package manager
相关推荐
pnpm Cargo 依赖解析:自动回溯并跳过已被 yank 的 crate 版本,生成 Cargo.lock
pnpm Cargo 依赖解析:自动回溯并跳过已被 yank 的 crate 版本,生成 Cargo.lock 导读 本文围绕 pnpm 仓库中的变更记录 .c
包管理器开发工具CLIpnpm Rust 版对 Cargo build-std 的依赖发现支持:`pnpm install` 自动拉取 Rust 标准库依赖
pnpm Rust 版对 Cargo build std 的依赖发现支持: pnpm install 自动拉取 Rust 标准库依赖 导读 本篇文章围绕 pnp
包管理器开发工具CLIpnpm 对 Cargo `[patch]` / `[replace]` 源覆盖的支持:为 Rust 工作区生成并保留 Cargo.lock
pnpm 对 Cargo patch / replace 源覆盖的支持:为 Rust 工作区生成并保留 Cargo.lock pnpm 的 Cargo 依赖支持
包管理器开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考