# 传递依赖解析
> **另请参阅:** [`dependency-linkage.zh.md`](dependency-linkage.zh.md) —— 每个拉取到的依赖如何融入你的构建产物(shared-external、static-embedded、static-external 之间的取舍)。
## 概述
CCGO 现在支持自动传递依赖解析。运行 `ccgo install` 时,它会自动发现并安装依赖的依赖、确定正确的构建顺序,并检测循环依赖。
## 解析优先级
对每个 `[[dependencies]]` 条目,`ccgo fetch` 按下列源类型顺序决定字节
来源。第一个与依赖声明字段匹配的种类胜出:
1. **`path = "..."`** —— 本地路径依赖。从消费者目录中的相对/绝对路径
软链或拷贝过来。
2. **`git = "..."`(搭配 `branch` / `tag` / `rev`)** —— Git 依赖。
浅克隆到 `~/.ccgo/git/<repo>/`。
3. **`zip = "..."`** —— 归档依赖。`https://` 直接下载,`file://` 或本地
路径直接读取,再解压到 `.ccgo/deps/<name>/`。
4. **注册表解析** —— 当 `[registries]` 非空、且该依赖未写
`path`/`git`/`zip` 源时启用。ccgo 按 TOML 声明顺序遍历所有注册表
(或 `[[dependencies]].registry` 指定的那一个),取首个未 yanked、
版本号精确匹配的 `VersionEntry`,然后走两条子路径之一:
* **Archive** —— 当 `VersionEntry.archive_url` 已填:下载该字节流、
SHA-256 校验、解压。
* **Git+tag 回退** —— 当 `archive_url` 为空,但 `PackageEntry.repository`
已填(`ccgo publish index` 总会从项目的 `git remote` 自动写入):
执行 `git clone --branch <tag>` 拉源码仓库。Lockfile 记录
`source = "registry+<index-url>"` 加 `locked.git.revision`,保证
`--locked` 重新拉取的字节确定性。
索引 schema 与 `[registries]` 配置详见
[`features/registry.zh.md`](features/registry.zh.md)。
5. **仅版本号的本地缓存回退** —— 当上面四种都没解析成功,且依赖的
`version` 字段非空时,ccgo 查 `~/.ccgo/packages/<name>/<version>/`
(由源项目里的 `ccgo install` 写入的缓存),把其中内容拷贝到
`.ccgo/deps/<name>/`。这与 Cargo / Maven 的工作流一致 —— 依赖以
名称+版本号识别,位置完全在开发机缓存里。
当第 4 步返回"未命中"时会平滑落到第 5 步 —— 已有的、不在任何已配置
注册表中的 `version`-only 依赖仍可正常工作。`[registries]` 为空时,
第 4 步整段跳过。这就是注册表层既可选又可渐进的原因:旧的
`git`/`zip` 声明永远有效。
## 功能
### 1. 传递依赖发现
当一个被安装的依赖自带 `CCGO.toml` 且其中声明了依赖时,CCGO 会:
- 自动读取该依赖的 CCGO.toml
- 发现其依赖(传递依赖)
- 递归解析整个依赖树中的所有依赖
- 同时处理 git 与 path 类型的依赖
### 2. 依赖图可视化
CCGO 会以可视化的树形输出展示依赖:
- 直接依赖(在你的 CCGO.toml 中声明)
- 传递依赖(依赖的依赖)
- 共享依赖(被多个包引用)
- 来源信息(git URL、路径、版本)
示例输出:
```
Dependency tree:
mylib v1.0.0
├── fmt v9.1.0 (git: https://github.com/fmtlib/fmt)
│ └── gtest v1.12.0 (git: https://github.com/google/googletest)
└── json v3.11.2 (git: https://github.com/nlohmann/json)
3 unique dependencies found, 4 total (1 shared)
```
### 3. 拓扑排序确定构建顺序
CCGO 使用拓扑排序确定正确的安装顺序:
- 没有依赖的依赖最先安装
- 依赖必须在其依赖者之前安装
- 通过尊重依赖链确保构建成功
示例:
```
📦 Installing in dependency order:
1. gtest
2. fmt
3. mylib
```
### 4. 循环依赖检测
CCGO 会检测循环依赖并报告完整的循环路径:
```
Error: Circular dependency detected: libA -> libB -> libC -> libA
```
### 5. 版本冲突警告
当多个包依赖同一个依赖的不同版本时,CCGO 会:
- 对版本冲突发出警告
- 当前使用首次出现的版本
- 给出清晰的警告以便你解决冲突
示例:
```
⚠️ Version conflict for 'fmt': have 9.1.0, need 10.0.0
```
### 6. 最大深度保护
为防止无限递归,CCGO 将依赖深度限制在 50 层,超出则报错。
## 实现
### 架构
依赖解析系统由三个主要组件组成:
#### 1. 依赖图(`src/dependency/graph.rs`)
- **DependencyNode**:携带元信息的单个依赖
- **DependencyGraph**:管理整个依赖图
- **环检测**:基于 DFS 的循环依赖发现算法
- **拓扑排序**:使用 Kahn 算法确定构建顺序
- **树形格式化**:美化打印依赖树
- **统计**:计算独立、共享、总依赖数
#### 2. 依赖解析器(`src/dependency/resolver.rs`)
- **DependencyResolver**:编排依赖解析的主解析器
- **递归解析**:递归遍历依赖树
- **路径解析**:处理传递依赖中的相对路径
- **缓存**:visited 集合防止重复处理
- **错误处理**:解析失败时优雅降级
#### 3. install 命令集成(`src/commands/install.rs`)
- 调用解析器构建依赖图
- 显示依赖树与统计信息
- 用拓扑排序确定安装顺序
- 出错时回退到直接依赖
- 按正确顺序安装依赖
### 数据结构
```rust
pub struct DependencyNode {
pub name: String,
pub version: String,
pub source: String, // git+url 或 path+path
pub dependencies: Vec<String>, // 直接依赖
pub depth: usize, // 依赖树中的深度
pub config: DependencyConfig, // 原始配置
}
pub struct DependencyGraph {
nodes: HashMap<String, DependencyNode>,
edges: Vec<(String, String)>, // (from, to) 边
roots: HashSet<String>, // 根依赖
}
```
### 关键算法
#### 环检测(DFS)
```rust
pub fn detect_cycles(&self) -> Option<Vec<String>>
```
使用带递归栈的深度优先搜索检测环。如发现则返回环路径。
#### 拓扑排序(Kahn 算法)
```rust
pub fn topological_sort(&self) -> Result<Vec<String>>
```
通过入度计算实现 Kahn 算法以确定构建顺序。
## 使用
### 基本用法
直接运行 `ccgo install`,CCGO 会自动:
1. 解析传递依赖
2. 显示依赖树
3. 显示安装顺序
4. 按正确顺序安装所有依赖
```bash
ccgo install
```
### 哪些会被解析
给定如下项目结构:
**项目 CCGO.toml:**
```toml
[package]
name = "myapp"
version = "1.0.0"
[[dependencies]]
name = "libA"
version = "1.0.0"
path = "../libA"
```
**libA CCGO.toml:**
```toml
[package]
name = "libA"
version = "1.0.0"
[[dependencies]]
name = "libB"
version = "2.0.0"
path = "../libB"
```
**libB CCGO.toml:**
```toml
[package]
name = "libB"
version = "2.0.0"
# 无依赖
```
执行 `ccgo install` 时会:
1. 发现 libA(直接依赖)
2. 读取 libA 的 CCGO.toml
3. 发现 libB(传递依赖)
4. 读取 libB 的 CCGO.toml
5. 确定顺序:libB → libA → myapp
6. 先装 libB,再装 libA
## 测试
实现包含全面的测试:
### 单元测试
位于 `src/dependency/resolver.rs` 与 `src/dependency/graph.rs`:
- **test_simple_resolution**:基础单依赖
- **test_transitive_dependencies**:依赖链(A → B → C)
- **test_circular_dependency_detection**:环检测(A → B → C → A)
- **test_shared_dependency**:菱形结构(A → C,B → C)
- **test_missing_ccgo_toml**:处理无 CCGO.toml 的依赖
- **test_version_conflict_warning**:检测版本冲突
- **test_max_depth_exceeded**:防止无限递归
- **test_simple_graph**:基础图操作
- **test_cycle_detection**:环检测算法
- **test_shared_dependency**(图):共享依赖统计
运行测试:
```bash
cargo test dependency
```
## 局限与未来工作
### 当前局限
1. **版本解析**:当前采用"首个版本胜出"策略。需要正式的语义化版本解析。
2. **工作区依赖**:尚未完整实现工作区继承。
3. **锁文件**:尚未生成锁文件以保证可复现构建。
4. **依赖打补丁**:尚不支持覆盖传递依赖。
### 计划中的增强
1. **智能版本解析**:
- 语义化版本感知
- 最小版本选择
- 版本约束求解
2. **锁文件支持**:
- 生成包含精确版本的 CCGO.lock
- 安装时校验锁文件
- update 命令刷新锁文件
3. **依赖 vendor**:
- 下载并缓存依赖
- 支持离线构建
- 可复现构建
4. **依赖覆盖**:
- 通过 CCGO.toml 给依赖打补丁
- 替换 URL 用于镜像
- 版本钉死
5. **构建期依赖**:
- 区分仅构建依赖
- 开发依赖
- 可选依赖
## 相关文件
- `src/dependency/mod.rs` —— 模块定义
- `src/dependency/graph.rs` —— 依赖图实现(约 450 行)
- `src/dependency/resolver.rs` —— 依赖解析器(约 620 行)
- `src/commands/install.rs` —— install 命令集成
- `src/config/ccgo_toml.rs` —— CCGO.toml 配置
## 参考
- **拓扑排序**:[Kahn 算法](https://en.wikipedia.org/wiki/Topological_sorting)
- **环检测**:[深度优先搜索](https://en.wikipedia.org/wiki/Cycle_detection)
- **语义化版本**:[semver.org](https://semver.org/)
## 更新日志
### v3.0.11 (2025-01-21)
- ✅ 实现传递依赖解析
- ✅ 添加带环检测的依赖图
- ✅ 添加拓扑排序以确定正确的构建顺序
- ✅ 添加依赖树可视化
- ✅ 添加版本冲突检测(仅警告)
- ✅ 集成至 install 命令
- ✅ 添加完整测试套件(7 个 resolver 测试 + 3 个 graph 测试)
---
*该功能是 Rust CLI 重写(spec 001-rust-cli-rewrite)的一部分,目标是零 Python 依赖。*