Rust依赖分析工具determinator的使用,determinator可高效解析和管理项目中的依赖关系

use determinator::{Determinator, rules::DeterminatorRules};
use guppy::{CargoMetadata, graph::DependencyDirection};
use std::path::Path;

// guppy 接受 `cargo metadata` JSON 输出。为这些示例使用预先存在的固定装置。
let old_metadata = CargoMetadata::parse_json(include_str!("../../../fixtures/guppy/metadata_guppy_78cb7e8.json")).unwrap();
let old = old_metadata.build_graph().unwrap();
let new_metadata = CargoMetadata::parse_json(include_str!("../../../fixtures/guppy/metadata_guppy_869476c.json")).unwrap();
let new = new_metadata.build_graph().unwrap();

let mut determinator = Determinator::new(&old, &new);

// 确定器支持从 TOML 文件读取的自定义规则。
let rules = DeterminatorRules::parse(include_str!("../../../fixtures/guppy/path-rules.toml")).unwrap();
determinator.set_rules(&rules).unwrap();

// 确定器期望传入更改的文件列表。
determinator.add_changed_paths(vec!["guppy/src/lib.rs", "tools/determinator/README.md"]);

let determinator_set = determinator.compute();
// determinator_set.affected_set 包含直接或间接受更改影响的工作区包。
for package in determinator_set.affected_set.packages(DependencyDirection::Forward) {
    println!("affected: {}", package.name());
}
// 完整示例代码
use determinator::{Determinator, rules::DeterminatorRules};
use guppy::{CargoMetadata, graph::DependencyDirection};
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 加载旧版本的 cargo metadata
    let old_metadata = CargoMetadata::parse_json(include_str!("old_metadata.json"))?;
    let old_graph = old_metadata.build_graph()?;
    
    // 加载新版本的 cargo metadata
    let new_metadata = CargoMetadata::parse_json(include_str!("new_metadata.json"))?;
    let new_graph = new_metadata.build_graph()?;
    
    // 创建确定器实例
    let mut determinator = Determinator::new(&old_graph, &new_graph);
    
    // 加载自定义规则(如果有)
    let rules = DeterminatorRules::parse(include_str!("path-rules.toml"))?;
    determinator.set_rules(&rules)?;
    
    // 添加更改的文件路径
    determinator.add_changed_paths(vec![
        "src/lib.rs",
        "Cargo.toml",
        "README.md"
    ]);
    
    // 计算受影响的包
    let determinator_set = determinator.compute();
    
    // 输出受影响的包
    println!("受影响的包:");
    for package in determinator_set.affected_set.packages(DependencyDirection::Forward) {
        println!("- {}", package.name());
    }
    
    Ok(())
}

// 示例 path-rules.toml 文件内容
/*
[ignore]
paths = [
    "*.md",
    "docs/**",
    "*.txt"
]

[force_affected]
paths = [
    "rust-toolchain",
    ".github/workflows/*.yml"
]

[[path_dependencies]]
path = "scripts/**"
packages = ["my-crate"]
*/

确定器

确定 Rust 工作区中在两个提交之间更改了哪些包。

一个典型的持续集成系统在每个拉取请求或提议的更改上运行每个构建和每个测试。在大型单体仓库中,大多数提议的更改对大多数包没有影响。目标确定器决定,给定一个提议的更改,哪些包可能发生了更改。

确定器设计用于 Diem Core 工作区,这是一个这样的单体仓库。

平台支持

Unix 平台:确定器工作并受支持。 Windows:实验性支持。路径规范化可能仍然存在错误:请报告它们!

工作原理

Rust 包在以下一个或多个更改时可能表现不同:

包的源代码或 Cargo.toml。 依赖项。 构建或测试环境。 确定器从多个来源收集数据,并通过 guppy 处理,以确定需要重新测试哪些包。

文件更改

确定器接受两个修订版本之间的文件更改列表作为输入。对于每个提供的文件:

确定器查找最接近文件的包并将其标记为已更改。 如果文件在包之外,确定器假设所有内容都需要重新构建。 文件更改列表可以从 Git 等源代码控制系统中获取。此 crate 提供了一个辅助工具,简化了枚举文件列表的过程,同时处理一些棘手的边缘情况。

这些简单规则可能需要针对特定场景进行自定义(例如忽略某些文件,或如果包外的文件更改则标记包更改)。对于这些情况,确定器允许您指定自定义规则。

依赖项更改

如果以下一个或多个更改,则假定依赖项已更改:

对于工作区依赖项,其源代码。 对于第三方依赖项,其版本或功能集。 它所依赖的环境中的某些内容。 确定器在工作区中的每个包上运行 Cargo 构建模拟。对于每个包,确定器确定其任何依赖项(包括功能集)是否已更改。这些模拟使用以下方式完成:

启用开发依赖项(默认;可以自定义) 主机和目标平台都设置为当前平台(默认;可以自定义) 每个包的三组功能: 无功能启用 默认功能 所有功能启用 如果这些模拟构建中的任何一个表明工作区包有任何依赖项更改,则将其标记为已更改。

环境更改

构建或测试运行的环境是可能影响它的任何非源代码部分。这包括但不限于:

使用的 Rust 编译器版本 crate 依赖的系统库 crate 依赖的环境变量 测试依赖的外部服务 默认情况下,确定器假设环境在运行之间保持不变。

要表示环境的更改,您可能需要找到方法将这些更改表示为检入存储库的文件,并为它们添加自定义规则。例如:

使用 rust-toolchain 文件表示 Rust 编译器的版本。有一个默认规则,如果 rust-toolchain 更改,则导致完整运行。 在 CI 配置文件中记录所有环境变量,例如 GitHub Actions 工作流文件,并添加自定义规则以在任何一个文件更改时进行完整运行。 尽可能使测试独立且不访问网络。如果您只有少数测试进行网络调用,则无条件运行它们。

自定义行为

确定器遵循的标准规则在某些情况下可能需要调整:

某些文件应被忽略。 如果某些文件或包更改,可能需要完整的测试运行。 可能需要插入 Cargo 不知道的虚拟依赖项。 对于这些情况,确定器允许指定自定义规则。确定器还附带一组默认规则,用于常见文件,如 .gitignore 和 rust-toolchain。

限制

虽然确定器可以为 CI 和本地工作流带来显著好处,但其模型与 Cargo 的模型有很大不同。在将确定器用于您的项目之前,请了解这些限制。

为了获得最佳结果,考虑除了基于确定器的运行之外,偶尔进行完整运行。您可能希望配置您的 CI 系统对拉取请求使用确定器,并在主分支上每隔几小时安排完整运行,以防确定器遗漏某些内容。

构建脚本和 include/exclude 指令

确定器无法运行构建脚本。声明对文件或环境变量的依赖的标准 Cargo 方法是在构建脚本中输出 rerun-if-changed 或 rerun-if-env-changed 指令。这些指令必须通过自定义规则复制。

确定器不跟踪 Cargo.toml 中的 include 和 exclude 字段。这是因为确定器对更改的看法并不总是与这些字段一致。例如,包通常包括 README 文件,但确定器有一个默认规则来忽略它们。

如果包包含其外部的文件,要么将其移动到包中(推荐),要么为其添加自定义规则。排除项可以作为导致这些文件被忽略的自定义规则复制。

工作区外的路径依赖项

确定器可能无法确定工作区外路径依赖项的更改。确定器依赖元数据来确定非工作区依赖项是否已更改。元数据包括:

版本号 来源,例如 crates.io 或 Git 存储库中的修订版 这种方法适用于对 crates.io 或其他包存储库的依赖,因为对其源代码的更改必然需要版本更改。

这种方法也适用于 Git 依赖项。它甚至适用于在 Cargo.toml 中未固定到确切修订版的 Git 依赖项,因为 Cargo.lock 记录确切的修订版。

此方案可能不适用于路径依赖项,因为磁盘上的文件可以在没有版本升级的情况下更改。cargo build 可以识别这些更改,因为它比较磁盘上文件的 mtime,但确定器无法做到这一点。

对于使用工作区的大多数项目来说,这不是预期的问题。如果将来有需求,如果非工作区路径依赖项在同一存储库中,可以添加对它们的更改的支持。

替代方案和权衡

看待确定器的一种方式是作为一种缓存失效。通过这个视角,构建或测试系统的主要目的是缓存结果,并根据某些参数使这些缓存失效。当确定器将包标记为已更改时,它会使该包的任何缓存结果失效。

有几种其他方法可以设计缓存系统:

Cargo 和 GNU Make 等其他系统中内置的缓存,基于文件修改时间。 Mozilla 的 sccache 和其他"自下而上"的基于哈希的缓存构建系统。 Bazel、Buck 和其他"自上而下"的基于哈希的缓存构建系统。 这些其他系统最终做出不同的权衡:

Cargo 可以使用构建脚本来跟踪文件和环境的更改。但是,它依赖于在同一台机器上完成先前的构建。此外,无法使用 Cargo 缓存测试结果,只能用于构建。 sccache 要求跨机器的路径精确,并且无法缓存某些类型的 Rust 工件。此外,无法将其用于测试结果,只能用于构建。 Bazel 和 Buck 对环境不影响构建结果有严格的要求。它们也不能与 Cargo 无缝集成。 确定器适用于构建和测试,但无法跟踪文件和环境的更改,必须依赖自定义规则。此方案可能产生假阴性和假阳性。

虽然确定器针对测试运行,但它也适用于构建。如果您希望将确定器用于构建运行,考虑将其与另一层缓存堆叠:

使用确定器作为第一遍来过滤出未更改的包。 然后使用像 sccache 这样的系统来获得基于哈希的构建缓存。

灵感

此确定器受到 Facebook 主要源代码存储库中使用的目标确定器的启发,并与之共享其名称。

贡献

请参阅 CONTRIBUTING 文件了解如何提供帮助。

许可证

此项目可根据 Apache 2.0 许可证或 MIT 许可证的条款获得。


1 回复

Rust依赖分析工具determinator的使用指南

工具介绍

determinator是一个高效的Rust依赖分析工具,专门用于解析和管理Rust项目中的依赖关系。它能够快速分析Cargo.toml文件,识别项目依赖项,并提供详细的依赖树可视化,帮助开发者更好地理解和管理项目依赖结构。

主要功能

  • 依赖关系解析和分析
  • 依赖冲突检测
  • 依赖版本兼容性检查
  • 依赖树可视化展示
  • 重复依赖识别

安装方法

# 使用cargo安装
cargo install determinator

# 或者从源码构建
git clone https://github.com/rust-lang/determinator.git
cd determinator
cargo install --path .

基本使用方法

1. 分析项目依赖

# 在项目根目录下运行
determinator analyze

# 指定特定项目路径
determinator analyze --path /path/to/your/project

2. 生成依赖树

# 生成文本格式的依赖树
determinator tree

# 生成JSON格式的输出
determinator tree --format json

# 限制依赖树深度
determinator tree --depth 3

3. 检查依赖冲突

# 检查依赖版本冲突
determinator check-conflicts

# 显示详细的冲突信息
determinator check-conflicts --verbose

4. 查找重复依赖

# 查找重复的依赖项
determinator find-duplicates

使用示例

假设有一个Rust项目,其Cargo.toml包含以下依赖:

[dependencies]
serde = "1.0"
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }

运行分析命令:

determinator analyze

输出示例:

依赖分析结果:
- 直接依赖: 3个
- 传递依赖: 47个
- 存在潜在冲突: 0个
- 重复依赖: 2个

生成依赖树:

determinator tree

输出示例:

my_project v0.1.0
├── serde v1.0.130
├── serde_json v1.0.72
│   └── serde v1.0.130
└── tokio v1.12.0
    ├── tokio-macros v1.5.0
    └── ...

完整示例demo

以下是一个完整的示例项目和使用determinator进行分析的完整流程:

  1. 首先创建一个新的Rust项目:
# 创建新项目
cargo new determinator-demo
cd determinator-demo
  1. 编辑Cargo.toml文件,添加依赖:
[package]
name = "determinator-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1.0"
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
reqwest = "0.11"
  1. 安装determinator工具:
# 安装determinator
cargo install determinator
  1. 运行依赖分析:
# 分析项目依赖
determinator analyze

预期输出:

依赖分析结果:
- 直接依赖: 4个
- 传递依赖: 89个
- 存在潜在冲突: 0个
- 重复依赖: 3个
  1. 生成依赖树:
# 生成文本格式的依赖树
determinator tree --depth 2

预期输出:

determinator-demo v0.1.0
├── reqwest v0.11.18
│   ├── base64 v0.21.2
│   ├── bytes v1.4.0
│   └── ...
├── serde v1.0.163
├── serde_json v1.0.96
│   └── serde v1.0.163
└── tokio v1.29.1
    ├── tokio-macros v2.1.0
    └── ...
  1. 检查依赖冲突:
# 检查依赖版本冲突
determinator check-conflicts --verbose
  1. 查找重复依赖:
# 查找重复的依赖项
determinator find-duplicates

高级选项

# 排除开发依赖
determinator analyze --no-dev-deps

# 指定输出格式
determinator tree --format graphviz

# 保存结果到文件
determinator analyze --output results.json

注意事项

  1. 确保在项目根目录(包含Cargo.toml的目录)下运行命令
  2. 工具需要网络连接来获取最新的crate信息
  3. 对于大型项目,分析可能需要一些时间

通过determinator工具,开发者可以更好地理解和优化项目的依赖结构,避免潜在的依赖冲突和版本问题。

回到顶部