Skip to content

Commit 3338209

Browse files
ZheFeng7110Sunrisepeakspeak-agent
authored
feat(pm): make mcpp.lock authoritative for git deps (2026.8.1.2) (#330)
* feat: use mcpp.lock commit as offline anchor for git deps - Add LockedGitSource + parse_git_source to lock_io.cppm. - Load mcpp.lock in prepare_build and use recorded branch commit to skip git ls-remote when the local cache still matches. - For tag/rev, reuse cached clone and fail early in --offline when missing. - Update e2e test 24 to assert branch deps do not re-ls-remote on rebuild. - Add unit tests for git source parsing. Closes #329 * fix(pm): address PR #330 review feedback - tests: assert 'from lock' on rebuild instead of vacuous 'ls-remote' - prepare: dedupe cache-key formula via computeGitRoot(resolvedGitRev) - prepare: add anchor.url == spec.git to offline anchor match - prepare: drop 'mcpp update' from offline diagnostics (offline no-op) - commands: refresh stale comment in cmd_update about lockfile semantics - lock_io: split fragment on last '@' (branch names with '@') and reset empty resolvedCommit so the offline branch prints the right hint * fix(e2e): force prepare_build for the 24_git_dependency lock-anchor check A bare second `mcpp build` takes the try_fast_build path when build.ninja is fresh (no source change), so prepare_build is skipped and neither the lock-anchor code nor `git ls-remote` runs — leaving the `from lock` assertion vacuous (it cannot match; same failure mode as the original ls-remote assertion the reviewer flagged). Run `mcpp clean` first so the fast-path cannot fire; mcpp.lock and the git cache (in MCPP_HOME/git) both survive `mcpp clean`, so the next build re-prepares, hits the anchor, prints 'from lock' and skips both ls-remote and the clone. * refactor(pm): make mcpp.lock authoritative for git deps (2026.8.1.2) The lock was read for the first time in the previous commits of this PR, but only as a hint the local clone had to confirm: a recorded commit counted while `~/.mcpp/git/<hash>` existed, and otherwise the branch was re-resolved over the network and the lock rewritten. That hands "which commit do we build" to the survival of a cache directory. `mcpp new` does not gitignore `mcpp.lock` — it is meant to be committed — so the failure is ordinary: clone the project on a second machine, get a different commit, and see the lock quietly change under you. Measured on the new e2e assertion, with the branch moved and the cache evicted: the previous head built v2, this builds the v1 the lock recorded. `cargo build` / `cargo update` split it the same way. Making the lock authoritative also collapses the block into two independent questions, each with at most one network operation and therefore exactly one --offline gate: which commit (tag/rev name one; a branch is answered by the lock, else by ls-remote), and is it on disk (the commit picks the cache directory; a miss is a clone). The separate tag/rev leg goes with it, taking its per-build "from cache" noise line and its "is locked but its local cache is missing" message — which was emitted for deps that were never in the lock. Four defects fixed along the way: - --offline refused git remotes that are local directories. docs/05-mcpp-toml.md defines --offline as "never touch the network ... anything already installed still builds", and prepare.cppm's dependency-download gate draws the same line in a comment; ls-remote/clone against a local path are filesystem reads, so refusing them buys no isolation. Remotes are now classified by shape, which keeps a Windows drive letter (C:\repo — colon, no @) on the local side. - A clone killed between `git clone` and `git checkout` served the wrong commit forever: the directory is named after the commit but was only checked for existence. Branch deps now compare `git rev-parse HEAD` and re-clone on mismatch (tag/rev keep the ref name as identity, so there is nothing to compare). - The clone depended on `cd` changing drive on Windows. cmd.exe needs `cd /d`, and MCPP_HOME routinely sits on a different drive than the project; every other cross-drive site in the repo (process.cppm, msvc.cppm) writes /d, this one did not. `git -C <dir>` needs no shell at all. - An unreadable mcpp.lock was a plain warning, invisible to --strict. Per src/diag.cppm that is the degraded channel: the engine silently does less than asked — every git branch dep falls back to the network. Also: parse_git_source no longer splits a tag/rev whose name contains '@' (only branch entries carry @<commit>, which is what the writer emits), the cmd_update comment now records the right causality, and docs/CHANGELOG cover the new semantics in both languages. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --------- Co-authored-by: sunrisepeak <speakshen@163.com> Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 7f1489d commit 3338209

10 files changed

Lines changed: 466 additions & 57 deletions

File tree

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [2026.8.1.2] — 2026-08-01
7+
8+
### 新增
9+
10+
- **`mcpp.lock` 成为 git 依赖的解析输入,而且是权威的那一份。** 在此之前 lock 只写不读:`branch = "develop"` 这类依赖每次进 `prepare_build` 都要打一次 `git ls-remote`,而写进 lock 的那个 commit 从来没有人看过。现在**先读 lock**——分支已经解析过就直接用记录的 commit,不再有网络往返。
11+
12+
「权威」是这里的关键词,不是「缓存的提示」。一个只在本地克隆恰好还在时才生效的 lock,等于把「构建哪个 commit」这个决定交给了缓存目录的存活状况:清一次 `~/.mcpp/git`、或者换一台机器 clone 同一个工程,构建就会静默滑到分支的新头上,并把 lock 改写成新值——**`mcpp new` 生成的 `.gitignore` 并不忽略 `mcpp.lock`,它本来就是要提交的**。实测(见 e2e 24 新增断言):分支移到 v2、缓存删除后,旧行为构建出 v2,新行为构建出 lock 里记的 v1。要新的分支头依然只有一条路——`mcpp update <dep>`,它丢掉 lock 条目,下次构建重新解析。
13+
14+
连带把这块的结构收敛成两个各自独立的问题,各带**恰好一个** `--offline` 闸:**(1) 是哪个 commit** —— `tag`/`rev` 自带答案,`branch` 由 lock 回答、否则 `ls-remote`;**(2) 它在不在盘上** —— commit 选定缓存目录,miss 就 clone。原来 tag/rev 有一条自己的分支腿、打一行每次构建都出现的 `from cache` 噪声,并且在 dep 根本没进 lock 时报「is locked but its local cache is missing」;这条腿现在整个不需要了。
15+
16+
### 修复
17+
18+
- **`--offline` 不再拒绝本地 git 远端。** `docs/05-mcpp-toml.md` 写明 `--offline` 的语义是「完全不碰网络……已安装的东西照常构建」,`prepare.cppm` 里依赖下载闸的注释也把线画在同一处:「这条线以上全是本地操作,一个依赖齐备的离线构建必须成功」。但 `git = "../sibling-repo"` 这种指向本地目录的远端,`ls-remote`/`clone` 都只是文件系统读取,拒绝它买不到任何隔离性。现在按远端形态判定——`file://`、以及不带 scheme 也不是 `git@host:path` 的存在路径,算本地(Windows 盘符 `C:\repo` 含冒号但不含 `@`,因此仍归本地)。
19+
20+
- **克隆被中途杀掉后不再永久提供错误的 commit。** 缓存目录以 commit 命名,但内容是 `git clone` 之后再 `git checkout` 两步做出来的;进程死在两步之间,目录名和 HEAD 就对不上了,而后续构建只检查目录存不存在。现在分支依赖会比对 `git rev-parse HEAD`,不符即删除重克隆(tag/rev 以 ref 名为身份,无可比之物)。
21+
22+
- **git 克隆在 Windows 上不再依赖 `cd` 跨盘。** 克隆命令用的是 `git clone … && cd <dir> && git checkout`,而 cmd.exe 的 `cd` 不带 `/d` 是不换盘的——缓存根(`MCPP_HOME`)与工程不同盘是常态。仓库里其它跨盘位置(`process.cppm``msvc.cppm`)都写了 `cd /d`,这里漏了。改用 `git -C <dir>`,连 shell 都不需要。
23+
24+
- **`mcpp.lock` 读取失败改用 degraded 通道。**`src/diag.cppm` 的划分,「引擎做得比要求的少」属于 Degraded 且必须给出 impact——lock 读不出来,每个 git 分支依赖都会退回网络解析、并可能越过记录的 commit。原来记的是普通 warning,`--strict` 提升不到它。
25+
26+
- **`parse_git_source` 不再切开名字里带 `@` 的 tag/rev。** 写侧只对 `branch` 追加 `@<commit>`,解析侧却无条件按最后一个 `@` 切分,于是一个叫 `v1.0@rc1` 的 tag 会被读成 ref `v1.0` + commit `rc1`。现在只有 branch 走这条切分(且仍按最后一个 `@`,以容纳 `feat@v2` 这类分支名)。
27+
628
## [2026.8.1.1] — 2026-08-01
729

830
### 修复

docs/05-mcpp-toml.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,10 @@ glfw = "3.4" # Explicit namespace, skips the mcpplibs-first c
339339
[dependencies]
340340
mylib = { path = "../mylib" }
341341

342-
# Git dependency
342+
# Git dependency — pick exactly one of tag / branch / rev
343343
[dependencies]
344344
mylib = { git = "https://github.com/user/mylib.git", tag = "v1.0.0" }
345+
applib = { git = "https://github.com/user/applib.git", branch = "develop" }
345346

346347
# Long-form dep spec: features and backend knobs
347348
[dependencies]
@@ -356,6 +357,25 @@ package declares `[features]` but does not include the requested feature (includ
356357
the result of backend desugaring), a warning is issued by default, and an error
357358
under `mcpp build --strict`.
358359

360+
**Git dependencies and `mcpp.lock`**: a `tag` or `rev` already names a fixed point
361+
in history, but a `branch` moves. The first build resolves the branch to a commit
362+
and records it in `mcpp.lock`, and every later build rebuilds **that** commit — the
363+
lock is authoritative, not a cache hint, so deleting `~/.mcpp/git` or moving to
364+
another machine cannot quietly put you on a newer tip. Ask for the newer tip
365+
explicitly:
366+
367+
```bash
368+
mcpp update mylib # drop the recorded commit; the next build re-resolves it
369+
mcpp update # same, for every dependency
370+
```
371+
372+
Because the recorded commit is enough to decide what to build, a rebuild with the
373+
clone already in `~/.mcpp/git` makes no network request at all and works under
374+
`--offline`. Only two things need the network: resolving a branch that has no
375+
commit in the lock, and cloning a commit that is not cached yet. A `git =` value
376+
that names a local directory (or a `file://` URL) needs neither, so it is never
377+
refused offline.
378+
359379
**SemVer constraints**:
360380

361381
```toml
@@ -454,7 +474,7 @@ Controls, in order of precedence:
454474

455475
| Control | Effect |
456476
|---|---|
457-
| `--offline` (any command) | Never touch the network — no index refresh, no downloads, no toolchain auto-install. Anything already installed still builds |
477+
| `--offline` (any command) | Never touch the network — no index refresh, no downloads, no toolchain auto-install, no `git ls-remote`/`clone`. Anything already installed still builds, including git deps whose commit is in `mcpp.lock` and whose clone is cached |
458478
| `MCPP_OFFLINE=1` | Same, for a whole shell session or CI job |
459479
| `[index] auto_refresh = false` in `~/.mcpp/config.toml` | Never refresh the index automatically; downloads still work |
460480

docs/zh/05-mcpp-toml.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,10 @@ glfw = "3.4" # 显式 namespace, 不走 mcpplibs 优先候选
310310
[dependencies]
311311
mylib = { path = "../mylib" }
312312

313-
# Git 依赖
313+
# Git 依赖 —— tag / branch / rev 三选一
314314
[dependencies]
315315
mylib = { git = "https://github.com/user/mylib.git", tag = "v1.0.0" }
316+
applib = { git = "https://github.com/user/applib.git", branch = "develop" }
316317

317318
# 长式 dep spec:features 与 backend 旋钮
318319
[dependencies]
@@ -325,6 +326,21 @@ feature(库若支持该旋钮,应在自己的 `[features]` 中声明 `backend-*`
325326
若目标包声明了 `[features]` 但不含所请求的 feature(含 backend 脱糖结果),
326327
默认给出 warning,`mcpp build --strict` 下报错。
327328

329+
**Git 依赖与 `mcpp.lock`**:`tag``rev` 本身就指向历史中的固定点,而 `branch`
330+
是会动的。首次构建把分支解析成一个 commit 并写进 `mcpp.lock`,此后每次构建都重建
331+
**那个** commit —— lock 是权威而不是缓存提示,所以删掉 `~/.mcpp/git` 或换一台机器
332+
都不会悄悄把你挪到更新的分支头上。要新的分支头,得显式要:
333+
334+
```bash
335+
mcpp update mylib # 丢掉记录的 commit,下次构建重新解析
336+
mcpp update # 同上,对所有依赖
337+
```
338+
339+
既然记录的 commit 已经足以决定构建什么,那么在 `~/.mcpp/git` 里已有克隆的情况下,
340+
重新构建完全不发网络请求,`--offline` 下照常工作。只有两件事需要网络:解析一个在
341+
lock 里没有 commit 的分支,以及克隆一个尚未缓存的 commit。`git =` 若指向本地目录
342+
(或 `file://` URL),这两件事都不需要网络,因此离线下也绝不会被拒绝。
343+
328344
**SemVer 约束**:
329345

330346
```toml
@@ -443,7 +459,7 @@ mcpp index status # 看本地现状:状态、年龄、修订号
443459

444460
| 开关 | 作用 |
445461
|---|---|
446-
| `--offline`(任意命令) | 完全不碰网络——不刷索引、不下载、不自动装工具链。已安装的东西照常构建 |
462+
| `--offline`(任意命令) | 完全不碰网络——不刷索引、不下载、不自动装工具链,也不发 `git ls-remote`/`clone`。已安装的东西照常构建,包括 commit 已在 `mcpp.lock`、克隆已在缓存里的 git 依赖 |
447463
| `MCPP_OFFLINE=1` | 同上,作用于整个 shell 会话或 CI job |
448464
| `~/.mcpp/config.toml``[index] auto_refresh = false` | 永不自动刷新索引,但下载仍然可用 |
449465

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "2026.8.1.1"
3+
version = "2026.8.1.2"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

0 commit comments

Comments
 (0)