Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion Doc/ARCH.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Arrows in the dependency graph point from a project to what it **depends on**.
| **ReBuildTool.CppCompiler** | The core. Parses C++ build rules, resolves modules/targets, drives compile + link + archive through platform toolchains and SDKs. ~9k LOC. |
| **ReBuildTool.CSharpCompiler** | Compiles the user's `*.target.cs` / `*.module.cs` rule files into a `CompileRules.dll` at runtime (Roslyn-based). |
| **ReBuildTool.IDE** | Generators that emit Visual Studio (`.vcxproj`/`.sln`) and CMake projects. |
| **ReBuildTool.Ini** | The `IIniProject` implementation — an alternate, INI-driven project front end run alongside the C++ project. |
| ~~**ReBuildTool.Ini**~~ | Removed. `Program.cs` no longer creates an `IIniProject`; `ServiceContext.Config.cs::InitFromIni()` remains as a stub that returns `false`. |
| **ReBuildTool.Common** | Shared utilities: `Shell` (process wrapper), `NiceIO` path helpers, misc. |
| **ReBuildTool.Updater** | Standalone self-update executable, shipped next to `rbt`. |
| **ReBuildTool.CppCompiler.Standalone** | Thin standalone host around the C++ compiler for isolated runs/testing. |
Expand Down Expand Up @@ -121,6 +121,68 @@ These rule files are **not** compiled ahead of time. At runtime
recompiled when a rule file (or `rbt` itself) is newer — with a bounded retry
counter for load failures.

### 4.1 Package management and why it runs first

`CppBuildProject.Parse()` calls `RestorePackages()` **before** `ParseRules()`.
That ordering is forced by the step above: `CompileRules.dll` is loaded exactly
once with `Assembly.LoadFile` and .NET cannot unload it, so there is no way to
compile rules, discover a dependency, and then add its rules to the same
assembly. Every package's `.module.cs` therefore has to be on disk before the
glob runs.

That constraint is also why the manifest is JSON rather than C#: resolving
transitive dependencies means repeatedly reading the manifest of a package that
has not been downloaded yet, which a plain file read does trivially and a
compile-and-load cycle cannot.

```
Parse()
├─ RestorePackages() IPackageService (ReBuildTool.Service/PackageService)
│ ├─ read <ProjectRoot>/RBTPackage.json (absent → returns immediately, zero cost)
│ ├─ PackageResolver: depth-first walk
│ │ fetch → read the package's own manifest → recurse
│ │ exact pins only; conflicting pins and cycles are hard errors
│ ├─ IPackageFetcher per source Git (clone/fetch/reset)
│ │ HttpArchive (download, sha256, unpack)
│ │ Path (used in place)
│ │ Vcpkg (install, then describe as binary)
│ └─ write RBTPackage.lock.json only when changed
├─ PackageModuleBinder synthesizes a rule for a binary package;
│ installs a consumer-supplied overlay rule
└─ ParseRules() globs Source/ + package roots + generated rules
```

Key types in `ReBuildTool.Service/PackageService/`: `PackageManifest`,
`PackageLockFile`, `PackageResolver`, `PackageRestoreService`, and
`Fetchers/IPackageFetcher`. The download / extract / SHA256 helpers the archive
fetcher needs live in `ReBuildTool.Common/Misc/` (`Downloader`,
`ArchiveExtractor`, `Hashing`) — the first networking in rbt that is not a
shell-out to git.

Above them, in `ReBuildTool.CppCompiler/Package/`: `PackageModuleBinder`
generates a module rule for a prebuilt binary package and installs a
consumer-supplied `overlay` rule, and `PackageArtifactSelector` picks the right
prebuilt artifact at `Setup` time. Generating a rule file, rather than
registering an `IModuleInterface` straight into `ModuleRules`, keeps everything
downstream working unchanged: the `ModuleRulePaths` lookup in `InitAllRule`
(which throws for a module it has no path for), the `SetupInternal` lifecycle,
the `_API` macro codegen, all four IDE generators and the HeaderTool plugin.

The `--Offline` / `--ForceRestore` / `--UpdateLock`
flags live in `ReBuildTool.CppCompiler/Project/PackageArgs.cs` — deliberately in
that assembly rather than beside the service, because `CmdParser` discovers
argument groups by scanning `AppDomain.CurrentDomain.GetAssemblies()` and .NET
loads assemblies lazily.

Packages contribute **modules and extensions, never targets**: a `*.target.cs`
inside a package is ignored, so what gets built stays the consuming project's
decision. Restored packages land in `<ProjectRoot>/Packages/`, not under
`Intermedia/` — see §8.

`CppTargetRule.GitLibraries` is the superseded predecessor of all this. It is
marked `[Obsolete]` and never read; it could not have worked, for the ordering
reason above.

---

## 5. End-to-end build flow
Expand Down Expand Up @@ -264,6 +326,22 @@ platform / configuration / architecture, e.g.:
<Platform>/<Config>/<Arch>/ObjectCache/ per-source .obj/.o mirror of Source/
```

Restored packages deliberately sit **outside** that tree:

```
<ProjectRoot>/
RBTPackage.json dependency manifest, hand written
RBTPackage.lock.json resolved commits, generated, commit it
Packages/<name>/ materialized package (git clone, or absent
for a path dependency, which is used in place)
```

`Packages/` is not under `Intermedia/` because `Clean()` empties that directory,
and `CleanIfNeed()` triggers a clean on its own whenever the rbt binaries are
newer than the last build — every dependency would be re-downloaded after each
rebuild and each rbt update. Restore adds `/Packages/` to the project's
`.gitignore`.

The `ObjectCache` mirrors the source tree so incremental timestamp checks
(`IsCompileUnitUpToDate`) can map each source file to its object deterministically.

Expand All @@ -289,6 +367,7 @@ The `ObjectCache` mirrors the source tree so incremental timestamp checks
| CLI dispatch | `ReBuildTool/Program.cs` |
| Service wiring | `ReBuildTool.Service/Context/ServiceContext*.cs` |
| Rule compile + load | `ReBuildTool.CppCompiler/Project/CppBuildProject.cs` |
| Package restore / resolution | `ReBuildTool.Service/PackageService/` (start at `PackageResolver.cs`) |
| Compile scheduling / incremental | `ReBuildTool.CppCompiler/Common/CppBuilder.Process.Compile.cs` |
| Adding a toolchain | `ReBuildTool.CppCompiler/ToolChain/IToolChain.cs` + an existing `ToolChain/<Name>/` |
| SDK discovery | `ReBuildTool.CppCompiler/SDK/` |
Expand Down
67 changes: 66 additions & 1 deletion Doc/ARCH.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ ReBuildTool 是一个用 **.NET 8** 编写的 C++ 构建系统,设计思路借
| **ReBuildTool.CppCompiler** | 核心。解析 C++ 构建规则、解析 module/target,通过各平台工具链与 SDK 驱动 compile + link + archive,约 9k 行。 |
| **ReBuildTool.CSharpCompiler** | 在运行时把用户的 `*.target.cs` / `*.module.cs` 规则文件编译为 `CompileRules.dll`(基于 Roslyn)。 |
| **ReBuildTool.IDE** | 生成器,输出 Visual Studio(`.vcxproj`/`.sln`)与 CMake 工程。 |
| **ReBuildTool.Ini** | `IIniProject` 实现——一套基于 INI 的备选 project 前端,与 C++ project 并行运行。 |
| ~~**ReBuildTool.Ini**~~ | 已移除。`Program.cs` 不再创建 `IIniProject`;`ServiceContext.Config.cs::InitFromIni()` 仅作为返回 `false` 的桩保留。 |
| **ReBuildTool.Common** | 共享工具:`Shell`(进程封装)、`NiceIO` 路径辅助等。 |
| **ReBuildTool.Updater** | 独立的自更新可执行文件,与 `rbt` 一起分发。 |
| **ReBuildTool.CppCompiler.Standalone** | 围绕 C++ 编译器的轻量独立宿主,用于隔离运行 / 测试。 |
Expand Down Expand Up @@ -102,6 +102,56 @@ CppCompiler CSharpCompiler IDE Ini
`NeedReBuildRuleAssembly()` 通过比较时间戳,仅在某个规则文件(或 `rbt` 本身)更新时才重新
编译规则 DLL——并对加载失败设有次数上限的重试。

### 4.1 包管理,以及它为什么必须跑在最前面

`CppBuildProject.Parse()` 会在 `ParseRules()` **之前**调用 `RestorePackages()`。
这个顺序是被上一节的机制强制的:`CompileRules.dll` 用 `Assembly.LoadFile` 只加载一次,
.NET 无法卸载它,因此不存在「先编译规则、再发现依赖、再把依赖的规则加进同一个程序集」
这条路。所有包的 `.module.cs` 都必须在 glob 之前就位于磁盘上。

同样的约束决定了清单格式必须是 JSON 而不是 C#:解析传递依赖意味着反复读取「尚未下载的包」
的清单,普通文件读取轻而易举,而「编译 + 加载」的循环做不到。

```
Parse()
├─ RestorePackages() IPackageService(ReBuildTool.Service/PackageService)
│ ├─ 读取 <ProjectRoot>/RBTPackage.json (不存在则立即返回,零开销)
│ ├─ PackageResolver:深度优先遍历
│ │ 拉取 → 读取该包自己的清单 → 递归
│ │ 只接受精确 pin;pin 冲突与依赖成环均为硬错误
│ ├─ 按来源分派 IPackageFetcher Git(clone/fetch/reset)
│ │ HttpArchive(下载、sha256 校验、解压)
│ │ Path(原地使用)
│ │ Vcpkg(install 后描述为二进制包)
│ └─ 写出 RBTPackage.lock.json 仅在内容变化时
├─ PackageModuleBinder 为二进制包合成规则;
│ 安装消费方提供的 overlay 规则
└─ ParseRules() glob Source/ + 包根目录 + 生成的规则目录
```

主要类型在 `ReBuildTool.Service/PackageService/` 下:`PackageManifest`、
`PackageLockFile`、`PackageResolver`、`PackageRestoreService`,以及
`Fetchers/IPackageFetcher`。压缩包 fetcher 需要的下载 / 解压 / SHA256 三个辅助类放在
`ReBuildTool.Common/Misc/`(`Downloader`、`ArchiveExtractor`、`Hashing`)——
这是 rbt 里第一处不靠 shell 调用 git 的网络访问。

在它们之上,`ReBuildTool.CppCompiler/Package/` 里:`PackageModuleBinder` 负责为预编译
二进制包生成模块规则、并安装消费方提供的 `overlay` 规则;`PackageArtifactSelector` 负责在
`Setup` 阶段挑选正确的预编译产物。选择「生成规则文件」而不是「直接把 `IModuleInterface`
注册进 `ModuleRules`」,是为了让下游的一切原样继续工作:`InitAllRule` 里的
`ModuleRulePaths` 查找(找不到路径就抛异常)、`SetupInternal` 生命周期、`_API` 宏代码生成、
四种 IDE 生成器,以及 HeaderTool 插件。`--Offline` / `--ForceRestore` / `--UpdateLock` 三个参数
定义在 `ReBuildTool.CppCompiler/Project/PackageArgs.cs` —— 特意放在该程序集而不是服务旁边,
因为 `CmdParser` 通过扫描 `AppDomain.CurrentDomain.GetAssemblies()` 发现参数组,
而 .NET 的程序集是惰性加载的。

包提供的是**模块和 extension,绝不是 target**:包里的 `*.target.cs` 会被忽略,
构建什么始终由消费方项目决定。restore 出来的包放在 `<ProjectRoot>/Packages/`,
不在 `Intermedia/` 下 —— 见 §8。

`CppTargetRule.GitLibraries` 是这套机制被取代掉的前身。它已标记 `[Obsolete]` 且从未被读取;
基于上面的顺序原因,它本来也不可能工作。

---

## 5. 端到端构建流程
Expand Down Expand Up @@ -225,6 +275,20 @@ stdout/stderr 重定向到日志,并且由于并行编译共享一个非线程
`ObjectCache` 镜像源码树,使增量时间戳检查(`IsCompileUnitUpToDate`)能确定性地把每个源文件
映射到它的目标文件。

restore 出来的包特意放在这棵树**之外**:

```
<ProjectRoot>/
RBTPackage.json 依赖清单,手写
RBTPackage.lock.json 解析到的 commit,工具生成,应当提交
Packages/<name>/ 物化后的包(git clone;path 依赖原地使用,
不会出现在这里)
```

`Packages/` 不放在 `Intermedia/` 下,是因为 `Clean()` 会清空该目录,而且只要 rbt 的二进制
比上次构建新,`CleanIfNeed()` 就会自行触发一次 clean —— 那样每次 rebuild、每次 rbt 升级
都要重新下载全部依赖。restore 会把 `/Packages/` 加进项目的 `.gitignore`。

---

## 9. 分发与更新
Expand All @@ -245,6 +309,7 @@ stdout/stderr 重定向到日志,并且由于并行编译共享一个非线程
| CLI 分派 | `ReBuildTool/Program.cs` |
| 服务装配 | `ReBuildTool.Service/Context/ServiceContext*.cs` |
| 规则编译 + 加载 | `ReBuildTool.CppCompiler/Project/CppBuildProject.cs` |
| 包 restore / 解析 | `ReBuildTool.Service/PackageService/`(从 `PackageResolver.cs` 读起) |
| 编译调度 / 增量 | `ReBuildTool.CppCompiler/Common/CppBuilder.Process.Compile.cs` |
| 新增工具链 | `ReBuildTool.CppCompiler/ToolChain/IToolChain.cs` + 已有的 `ToolChain/<Name>/` |
| SDK 探测 | `ReBuildTool.CppCompiler/SDK/` |
Expand Down
Loading
Loading