# mcpp Workspace 设计方案

> 2026-05-12 — 多包工作空间支持
> 状态：设计稿（待实现）
> 优先级：中（当前 path 依赖已覆盖基本需求）

## 1. 动机

mcpp 当前支持 `path = "..."` 依赖来引用本地子项目，但缺少统一的多包管理入口。
当一个仓库包含多个相关的库和应用时，用户需要：

- 统一的依赖版本管理（不必在每个子包重复声明版本）
- 共享的 lock 文件（确保所有子包使用相同的依赖版本）
- 共享的 target 目录（避免重复编译公共依赖）
- 选择性构建（只构建某个子包及其依赖）
- 统一的工具链配置（子包可覆盖）

## 2. 设计原则

1. **path 依赖是基础** — workspace 不引入新的依赖语义，member 之间用
   `path = "..."` 声明依赖关系，与非 workspace 项目完全一致。
2. **语言管可见性** — C++23 module 的 export/import 控制接口边界，构建系统
   不做额外的可见性控制。
3. **子包可覆盖** — workspace 提供默认配置（工具链、依赖版本），子包的
   mcpp.toml 可覆盖任何默认值。
4. **渐进式采用** — 已有的 path 依赖项目只需在根目录加 `[workspace]` 即可
   升级为 workspace，无需修改子包。

## 3. 工程文件格式

### 3.1 Workspace 根 mcpp.toml

```toml
# myproject/mcpp.toml
[workspace]
members = [
    "libs/core",
    "libs/http",
    "libs/db",
    "apps/server",
    "apps/cli",
]
# 可选：glob 语法
# members = ["libs/*", "apps/*"]

# 可选：从 members 中排除
exclude = ["libs/experimental"]

# ─── 统一依赖版本管理 ───────────────────────────────
# 子包用 `xxx.workspace = true` 继承这里的版本，
# 或者在自己的 mcpp.toml 中覆盖。

[workspace.dependencies]
cmdline = "0.0.2"
tinyhttps = "0.2.2"

[workspace.dependencies.compat]
mbedtls = "3.6.1"
gtest = "1.15.2"

# ─── 统一工具链配置 ─────────────────────────────────
# 所有 member 默认使用此工具链，各 member 可在自己的
# [toolchain] 中覆盖。

[toolchain]
default = "gcc@16.1.0"

[target.x86_64-linux-musl]
toolchain = "gcc@15.1.0-musl"
linkage = "static"

# ─── Workspace 根可以同时是一个 package ──────────────
# 如果有 [package] 则是 "rooted workspace"（类似 Cargo）
# 如果没有 [package] 则是 "virtual workspace"（纯管理节点）

# [package]
# name = "myproject"
# version = "0.1.0"
```

### 3.2 Member 的 mcpp.toml

```toml
# libs/core/mcpp.toml
[package]
namespace = "myproject"
name = "core"
version = "0.1.0"
description = "Core utilities for myproject"

[targets.core]
kind = "lib"

# 无外部依赖

[dev-dependencies.compat]
gtest.workspace = true              # 继承 workspace 版本 → "1.15.2"
```

```toml
# libs/http/mcpp.toml
[package]
namespace = "myproject"
name = "http"
version = "0.1.0"

[targets.http]
kind = "lib"

[dependencies]
core = { path = "../core" }         # workspace 内部依赖，用 path

[dependencies.compat]
mbedtls.workspace = true            # 继承 → "3.6.1"

[dev-dependencies.compat]
gtest.workspace = true
```

```toml
# apps/server/mcpp.toml
[package]
namespace = "myproject"
name = "server"
version = "0.1.0"

[dependencies]
http = { path = "../../libs/http" }
db   = { path = "../../libs/db" }
cmdline.workspace = true            # 继承 → "0.0.2"

# 覆盖 workspace 工具链
# [toolchain]
# default = "clang@19.0"
```

### 3.3 版本继承规则

```toml
# workspace 根声明
[workspace.dependencies.compat]
mbedtls = "3.6.1"

# member 中继承（三种等价写法）：
[dependencies.compat]
mbedtls.workspace = true            # 最简写法

[dependencies.compat]
mbedtls = { workspace = true }      # 等价

# member 覆盖（不继承，用自己的版本）：
[dependencies.compat]
mbedtls = "4.0.0"                   # 覆盖 workspace 版本
```

## 4. 目录与产物布局

### 4.1 Workspace 目录结构

```
myproject/                          # workspace root
├── mcpp.toml                       # [workspace] + 可选 [package]
├── mcpp.lock                       # 统一 lock 文件（所有 member 共享）
├── target/                         # 统一构建输出（所有 member 共享）
│   └── x86_64-linux-gnu/<fp>/
│       ├── gcm.cache/              # 所有 member 的 BMI 共存
│       ├── obj/
│       │   ├── core/               # per-member 子目录避免冲突
│       │   ├── http/
│       │   ├── db/
│       │   └── server/
│       ├── lib/
│       │   ├── libcore.a
│       │   ├── libhttp.a
│       │   └── libdb.a
│       └── bin/
│           └── server
├── libs/
│   ├── core/
│   │   ├── mcpp.toml
│   │   └── src/core.cppm
│   ├── http/
│   │   ├── mcpp.toml
│   │   └── src/http.cppm
│   └── db/
│       ├── mcpp.toml
│       └── src/db.cppm
└── apps/
    └── server/
        ├── mcpp.toml
        └── src/main.cpp
```

### 4.2 BMI 共享

所有 member 的 BMI 放在同一个 `gcm.cache/` 目录下，因为：

- C++ module 名全局唯一（`myproject.core`、`myproject.http` 不冲突）
- member 之间通过 `import myproject.core;` 直接引用
- ninja 的 dyndep 机制自动处理编译顺序

### 4.3 Lock 文件

统一的 `mcpp.lock` 放在 workspace 根目录：

```toml
# Auto-generated by mcpp. Do not edit by hand.
version = 1
workspace = true

[package."compat.mbedtls"]
version = "3.6.1"
source  = "mcpp-index+https://..."
hash    = "fnv1a:..."
consumers = ["libs/http"]           # 哪些 member 使用了此依赖

[package."mcpplibs.cmdline"]
version = "0.0.2"
source  = "mcpp-index+https://..."
hash    = "fnv1a:..."
consumers = ["apps/server"]
```

`consumers` 字段记录哪些 member 使用了该依赖，便于 `mcpp update -p http`
时精确更新。

## 5. CLI 命令变化

### 5.1 新增 `-p, --package` 选项

```bash
# 在 workspace 根目录执行：

mcpp build                          # 构建所有 member（拓扑排序）
mcpp build -p http                  # 只构建 http 及其依赖（core）
mcpp build -p server                # 构建 server（自动构建 http、db、core）

mcpp test                           # 测试所有 member
mcpp test -p core                   # 只测试 core

mcpp run -p server                  # 运行 server 二进制
mcpp run -p server -- --port 8080   # 带参数运行

mcpp clean                          # 清理 workspace target/
mcpp clean -p http                  # 只清理 http 的编译产物

mcpp publish -p core                # 发布 core 包
mcpp publish -p http                # 发布 http 包
```

### 5.2 Workspace 感知的命令

```bash
# 在 member 子目录中执行：
cd libs/http
mcpp build                          # 等价于在根目录 mcpp build -p http
mcpp test                           # 等价于 mcpp test -p http

# mcpp 自动向上搜索 workspace 根，使用共享 target/ 和 lock
```

### 5.3 Workspace 管理命令

```bash
mcpp workspace list                 # 列出所有 member 及其依赖关系
mcpp workspace graph                # 输出 member 依赖图（文本 DAG）
mcpp workspace new <name>           # 在当前 workspace 中创建新 member
```

## 6. 配置优先级

从高到低（高优先级覆盖低优先级）：

```
1. CLI 参数              --target x86_64-linux-musl --static
2. Member mcpp.toml      [toolchain] / [target.<triple>] / [build]
3. Workspace mcpp.toml   [toolchain] / [target.<triple>]
4. 全局配置               ~/.mcpp/config.toml [toolchain].default
5. 内置默认              gcc@16.1.0 / c++23 / static_stdlib=true
```

### 6.1 工具链继承与覆盖

```toml
# workspace root
[toolchain]
default = "gcc@16.1.0"             # 所有 member 默认使用

# libs/http/mcpp.toml
# 不声明 [toolchain] → 继承 workspace 的 gcc@16.1.0

# apps/server/mcpp.toml
[toolchain]
default = "clang@19.0"             # 覆盖：server 用 clang
```

### 6.2 依赖版本继承与覆盖

```toml
# workspace root
[workspace.dependencies.compat]
mbedtls = "3.6.1"

# libs/http/mcpp.toml
[dependencies.compat]
mbedtls.workspace = true            # 继承 → 3.6.1

# libs/experimental/mcpp.toml
[dependencies.compat]
mbedtls = "4.0.0"                   # 覆盖 → 4.0.0
```

### 6.3 构建配置继承

```toml
# workspace root（可选）
[build]
cxxflags = ["-Wall", "-Werror"]     # 所有 member 默认添加

# libs/core/mcpp.toml
[build]
cxxflags = ["-Wall"]                # 覆盖：core 不要 -Werror
```

## 7. 构建流程

### 7.1 Workspace 构建顺序

```
1. 加载 workspace root mcpp.toml
2. 发现所有 member（展开 glob、排除 exclude）
3. 加载每个 member 的 mcpp.toml
4. 合并 workspace 依赖版本（.workspace = true 替换为实际版本）
5. 解析所有依赖（统一 lock）
6. 构建 member 依赖图（基于 path 依赖关系）
7. 拓扑排序 member
8. 按序构建每个 member:
   a. scanner 扫描该 member 的源文件
   b. 生成该 member 的编译单元（obj 输出到 obj/<member>/ 子目录）
   c. 编译（BMI 输出到共享 gcm.cache/）
   d. 如果是 lib → 链接为 .a / .so
   e. 如果是 bin → 链接为可执行文件
```

### 7.2 增量构建

workspace 构建与 P0/P1/P2 优化兼容：

- **P0（前端脏检查）**：检查 workspace 根的 build.ninja 时间戳 vs 所有
  member 的源文件。未改动 → 直接调 ninja。
- **P1（per-file dyndep）**：per-member 的源文件各自有独立的 .dd 文件，
  修改一个 member 不影响其他 member 的 dyndep。
- **P2（BMI restat）**：member A 的接口不变 → BMI 不变 → 依赖 A 的
  member B 不重编译。

### 7.3 选择性构建 (`-p`)

```bash
mcpp build -p http
```

流程：
1. 从 member 依赖图中找到 http 的传递依赖（core）
2. 只构建 core + http（跳过 db、server、cli）
3. ninja 的 target 限制：`ninja -C ... libs/http/...`

## 8. Workspace 发现机制

### 8.1 向上搜索

```
当前工作目录:  /myproject/libs/http/
   ↓ 找到 /myproject/libs/http/mcpp.toml    → member manifest
   ↓ 继续向上搜索
   ↓ 找到 /myproject/mcpp.toml              → 检查是否有 [workspace]
   ↓ 有 → 这是 workspace 根
   ↓ 验证当前目录是否在 members 中
   ↓ 是 → workspace 模式，主构建目标 = http
```

### 8.2 独立模式 vs Workspace 模式

- 如果向上搜索没找到 `[workspace]` → 独立模式（当前行为）
- 如果找到 `[workspace]` 但当前目录不在 members 中 → 错误提示
- 如果在 workspace 根目录 → 构建所有 member
- 如果在 member 子目录 → 构建该 member

## 9. 虚拟 Workspace vs Rooted Workspace

### 9.1 Rooted Workspace

```toml
# workspace 根同时也是一个 package
[workspace]
members = ["libs/core", "libs/http"]

[package]
name = "myproject"
version = "0.1.0"

[dependencies]
core = { path = "libs/core" }
http = { path = "libs/http" }
```

- 根目录有自己的 `src/`，可以编译为 binary
- member 是根 package 的依赖

### 9.2 Virtual Workspace

```toml
# workspace 根只是管理节点，没有 [package]
[workspace]
members = ["libs/core", "libs/http", "apps/server"]

[workspace.dependencies.compat]
gtest = "1.15.2"
```

- 根目录没有 `src/`，不产出任何产物
- 纯粹用于组织和管理 member

## 10. 实现规划

### Phase 1: 基础 workspace 支持

- `[workspace]` section 解析（manifest.cppm）
- workspace 根发现（cli.cppm）
- member 加载 + 拓扑排序
- `mcpp build` 在 workspace 根构建所有 member
- 统一 lock 文件

### Phase 2: 选择性构建 + 版本继承

- `-p, --package` 选项
- `xxx.workspace = true` 版本继承
- workspace 工具链继承与覆盖
- 在 member 子目录中自动感知 workspace

### Phase 3: 增强功能

- `mcpp workspace list / graph / new`
- per-member 独立产物（.a / .so）
- `mcpp publish -p <member>`
- glob member 发现（`members = ["libs/*"]`）

## 11. 与现有功能的兼容性

| 现有功能 | workspace 影响 | 处理方式 |
|---|---|---|
| path 依赖 | 无影响 | 继续使用，workspace 不改变语义 |
| version 依赖 | lock 文件位置变化 | workspace 模式下 lock 在根目录 |
| BMI 缓存 | 共享 target/ | member 的 obj 用子目录隔离 |
| `mcpp new` | 新增 `--workspace` 选项 | 生成 workspace 骨架 |
| `mcpp add/remove` | 需知道操作哪个 member | 在 member 子目录执行即可 |
| `mcpp pack` | 按 member 打包 | `-p` 指定 member |
| P0/P1/P2 优化 | 兼容 | 检查扩展到所有 member 源文件 |
