205 lines
9.3 KiB
Markdown
205 lines
9.3 KiB
Markdown
# 协作与开发工作流
|
||
|
||
本仓库是 [`xai-org/grok-build`](https://github.com/xai-org/grok-build) 的 patch 化 fork。fork 多了哪些能力见 [README.md](README.md);这里只讲怎么改、怎么同步、怎么不把自己的活弄丢。
|
||
|
||
以 CraftBukkit / Spigot 的模型,在**不接受外部 PR 的上游**之上维护本地改动。
|
||
|
||
## 为什么是 patch,而不是 fork 后直接改
|
||
|
||
上游有两个决定性特征:
|
||
|
||
1. 它是 xAI 内部 monorepo 的**周期性单向导出**。历史是一串线性的 `Synced from monorepo`
|
||
压扁提交,根目录 `SOURCE_REV` 记录对应的内部 commit。上游随时会把整棵树重新投放一次。
|
||
2. 上游 `CONTRIBUTING.md` 明确写着**不接受任何外部 PR 或补丁**。
|
||
|
||
也就是说:改动推不回去,而上游会不断整体覆盖。直接在 fork 上改,每次同步都要打一场
|
||
merge 混战,而且没人说得清「我们到底改了什么」。
|
||
|
||
这正是 CraftBukkit 面对 Mojang 的处境。解法也一样——**把改动本身作为版本库里的唯一真相**:
|
||
|
||
> `patches/` 是源码,`work/` 是编译产物。
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
newgrok/ ← 你提交的仓库
|
||
├── patches/ ★ 唯一真相:0001-*.patch,一个补丁一件事
|
||
├── scripts/ 工作流脚本
|
||
├── upstream.rev 钉住的上游 commit
|
||
├── Makefile
|
||
├── upstream/ 派生:上游的纯净 clone (.gitignore)
|
||
└── work/ 派生:upstream@rev + patches(.gitignore)
|
||
```
|
||
|
||
对照 Spigot:
|
||
|
||
| 这里 | Spigot / BuildTools |
|
||
|------|---------------------|
|
||
| `upstream/` | CraftBukkit 的 clone(纯净上游) |
|
||
| `work/` | 打完补丁的工作目录 |
|
||
| `patches/` | `CraftBukkit-Patches/` |
|
||
| `scripts/apply-patches.sh` | `applyPatches.sh` |
|
||
| `scripts/rebuild-patches.sh` | `rebuildPatches.sh` |
|
||
| `upstream.rev` | `versions/*.json` 里的版本钉 |
|
||
|
||
`upstream/` 和 `work/` 都**不进 git**——它们任何时候都能从 `upstream.rev` + `patches/`
|
||
一字不差地重建。
|
||
|
||
两条规则,违反任意一条都会默默丢掉工作成果:
|
||
|
||
1. **Rust 源码改动写在 `work/`,不要手改 `patches/`。** 补丁文件是生成物,手改之后下次就合不上。
|
||
2. **`make rebuild` 是离开 `work/` 的唯一出口。** `make apply` 会 `git reset --hard` + `git clean -fdx`。没 commit 并且没 export 的改动,下次 apply 就没了。
|
||
|
||
## 日常循环
|
||
|
||
改代码永远在 `work/` 里,**一个提交 = 一个补丁**:
|
||
|
||
```sh
|
||
make apply # 拿到干净的、打好补丁的树
|
||
$EDITOR work/crates/codegen/.../foo.rs # 改
|
||
cd work && git add -A && git commit -m '...' # 提交(提交信息就是补丁标题)
|
||
cd .. && make rebuild # 写回 patches/
|
||
git add patches/ && git commit -m '...' # 提交到 fork 仓库
|
||
```
|
||
|
||
**`make rebuild` 是唯一的出口。** 没跑过它的改动,下次 `make apply` 就没了。
|
||
|
||
修改已有的补丁,用普通的 git 手段就行——`work/` 是个正常的 git 仓库,`base` 标签是上游和我们的分界:
|
||
|
||
```sh
|
||
cd work
|
||
git rebase -i base # 改写、合并、重排、删除任意补丁
|
||
cd .. && make rebuild # patches/ 会整体重新生成
|
||
```
|
||
|
||
随时看当前状态:
|
||
|
||
```sh
|
||
make status
|
||
```
|
||
|
||
## 跟进上游同步
|
||
|
||
```sh
|
||
make update-dry # 只看会拉进来什么,不动任何东西
|
||
make update # 重新钉 upstream.rev,并把补丁前移到新基线
|
||
```
|
||
|
||
`make update` 会:
|
||
|
||
1. `git fetch` 上游,列出 `upstream.rev..origin/main` 之间的新提交和 `SOURCE_REV` 变化
|
||
2. 把 `upstream.rev` 更新为最新
|
||
3. 在新基线上重放 `patches/`
|
||
4. 干净通过则自动 `make rebuild`,让补丁上下文对齐新代码
|
||
|
||
冲突时脚本会停下并给出解决步骤——和处理 rebase 冲突完全一样:
|
||
|
||
```sh
|
||
cd work
|
||
git status # 看冲突文件
|
||
$EDITOR <冲突文件> # 消掉冲突标记
|
||
git add -A
|
||
git am --continue # 或 git am --skip / git am --abort
|
||
cd .. && make rebuild # 把前移后的结果写回去
|
||
```
|
||
|
||
补丁数量多的时候,冲突是这个模型的固有成本,也是它的价值所在:冲突精确地指出上游改动
|
||
撞上了你的哪一处改动,而不是让它悄悄消失。
|
||
|
||
## 全部命令
|
||
|
||
从仓库根目录跑 `make`(`make help` 列出全部目标)。脚本自己把 `~/.cargo/bin` 加进 `PATH`,交互 shell 里不必先有 `cargo`。
|
||
|
||
| 命令 | 作用 |
|
||
|------|------|
|
||
| `make setup` | 安装工具链(rustup / dotslash / protoc) |
|
||
| `make apply` | `upstream.rev + patches/ → work/`;加 `FORCE=1` 丢弃 `work/` 里的未提交改动 |
|
||
| `make rebuild` | `work/` 中 `base` 之上的提交 → `patches/` |
|
||
| `make update` | 跟进最新上游同步并前移补丁 |
|
||
| `make update-dry` | 预览上游差异,不做修改 |
|
||
| `make build` / `make release` | 编译 debug / release 二进制 |
|
||
| `make check` / `make clippy` / `make fmt` | 快速类型检查 / lint / 格式化 |
|
||
| `make test` | 跑测试(`PKG=<crate>` 指定 crate) |
|
||
| `make run` | 编译并启动 TUI |
|
||
| `make status` | 显示当前钉住的 rev、补丁数、`work/` 状态 |
|
||
| `make clean` | 删 `work/target` |
|
||
| `make distclean` | 删 `work/` 和 `upstream/`(不动 `patches/`) |
|
||
|
||
`make build` 之后的额外参数会透传给 cargo,例如 `./scripts/build.sh --release --locked`。
|
||
|
||
**跑单测。** `make test` 是薄封装;要带 filter,直接走 `build.sh`,多出来的参数会原样转给 cargo:
|
||
|
||
```sh
|
||
CARGO_CMD=test PKG=xai-grok-version ./scripts/build.sh test_fork_tag
|
||
CARGO_CMD=test PKG=xai-grok-pager-bin ./scripts/build.sh version_output_writer -- --nocapture
|
||
```
|
||
|
||
## 安全网
|
||
|
||
脚本刻意不会静默吞掉工作成果:
|
||
|
||
- `work/` 有**未提交改动**时,`make apply` 拒绝执行(提示先 commit + rebuild,或 `FORCE=1`)
|
||
- `work/` 的提交数和 `patches/` 的文件数**对不上**时同样拒绝——这基本意味着你忘了 `make rebuild`。故意增删补丁文件时也会触发,检查分不清这两种情况
|
||
- `git am` 进行到一半时,`make rebuild` / `make build` 会拒绝执行,而不是产出半成品
|
||
- `FORCE=1`(即 `make apply FORCE=1`)是两条 apply 护栏的逃生口,意思是「丢掉 `work/`,按现在的 `patches/` 重放」
|
||
- `make apply` 的 `git clean` 显式保留 `work/target`,否则每次打补丁都要付一次冷编译的代价
|
||
|
||
## 补丁格式
|
||
|
||
`rebuild` 用的是:
|
||
|
||
```
|
||
git format-patch --no-stat -N --zero-commit --full-index --no-signature
|
||
```
|
||
|
||
- `--zero-commit` —— 否则每次 rebase 后每个补丁的 `From <sha>` 行都会变,`git diff` 里全是噪音
|
||
- `--full-index` —— 给 `git am --3way` 留下按 blob 哈希回退的余地,上游漂移时更容易自动合上
|
||
|
||
## 构建依赖
|
||
|
||
`make setup` 会处理,这里说明它在做什么:
|
||
|
||
- **Rust 1.94.0** —— 由上游 `rust-toolchain.toml` 钉住,rustup 自动装
|
||
- **protoc** —— 只有 `xai-grok-tools-api` 需要(编译 `proto/grok-tools.proto`)。上游的解析顺序是
|
||
`$PROTOC` → 向上查找 `bin/protoc` → `PATH`。`bin/protoc` 是个
|
||
[DotSlash](https://dotslash-cli.com) wrapper,会按需下载 protoc 29.3;所以 `setup.sh`
|
||
装 dotslash,失败则回退到系统 `protobuf-compiler`
|
||
|
||
两个 git 依赖(`our-forks/async-openai`、`helix-editor/nucleo`)都是公开可访问的。
|
||
|
||
在这台机器上(4 核 / 7 GB),`xai-grok-pager-bin` 冷编译 debug 大约 11 分钟,`work/target` 会长到约 24 GB,二进制大约 612 MB。`cargo check` 和 `cargo build` 各有一份缓存,所以 `make build` 之后再 `make check` 并不快(冷跑大约 6 分钟)。不到真的缺盘,别跑 `make clean` / `make distclean`。迭代时尽量用单 crate(`build.sh` 总会带 `-p`)。
|
||
|
||
## 上游树里该改哪
|
||
|
||
`work/` 里大约 91 个 workspace member。常动的几个:
|
||
|
||
| Crate | 角色 |
|
||
|-------|------|
|
||
| `xai-grok-pager-bin` | 组合根,产出 `xai-grok-pager` 二进制(发行名 `grok`) |
|
||
| `xai-grok-pager` | TUI:scrollback、prompt、modal、渲染;用户手册也在这里 |
|
||
| `xai-grok-shell` | Agent runtime + leader / stdio / headless 入口 |
|
||
| `xai-grok-tools` | 工具实现(terminal、文件编辑、搜索……) |
|
||
| `xai-grok-workspace` | 宿主文件系统、VCS、执行、checkpoint |
|
||
|
||
从上游继承的约束:
|
||
|
||
- **根目录 `Cargo.toml` 是生成的,当只读。** 改各 crate 自己的 `Cargo.toml`。它还带着 `[patch.crates-io]` 里对 `our-forks/async-openai` 的 git pin。
|
||
- **永远指定 crate(`-p <crate>`)。** 全 workspace 编译慢到不现实;`build.sh` 会强制带 `-p`。
|
||
- 动上游代码时,先看它已有的测试还成不成立。
|
||
|
||
## 示例补丁
|
||
|
||
`patches/0001` 和 `0002` 的提交信息标了 `[EXAMPLE PATCH]`,演示「改上游 Rust」和「新增文件」两种补丁,不需要就可以删。`0001` 同时也是 fork 品牌标记(`--version` 打出 `[newgrok]`),删之前想清楚。
|
||
|
||
删补丁文件要带 `FORCE=1`——`work/` 里还留着对应提交,删完之后 `patches/` 比 `work/` 少,安全检查会拦下来(它无法区分「你故意删的」和「你忘了 rebuild」):
|
||
|
||
```sh
|
||
rm patches/0001-*.patch patches/0002-*.patch
|
||
make apply FORCE=1
|
||
```
|
||
|
||
## 许可
|
||
|
||
上游代码为 Apache-2.0(见 `work/LICENSE`)。`patches/` 里的改动同样按 Apache-2.0 分发。
|
||
本仓库是非官方 fork,与 xAI 无关。
|