Skip to content

Commit a655b95

Browse files
committed
test(resources): 序号判据两条路线依次 TRY;记录 msvc 支零 CI 覆盖
Windows job 红在 `windres could not read back ...resapp.mcpp.o`: `llvm-windres` 只是 llvm-rc 的单向包装、没有 `-J coff`,而它和 GNU binutils 的 windres **都匹配 `*windres*`** ⇒ 按工具名分派是错的。改成 反读 → llvm-readobj 依次 TRY,只有两条都不可用才硬失败。 同一条失败还暴露:native Windows 默认工具链走的是 GNU 支不是 msvc 支 (target 目录 `x86_64-windows-msvc` 而产物是 `.o`)⇒ `.res` 那一支在 两个 CI job 里都不执行,e2e 的 `case *.res` 是死分支。这条已记进设计 文档 §F.11 作为明确缺口。
1 parent 3d27313 commit a655b95

2 files changed

Lines changed: 49 additions & 30 deletions

File tree

.agents/docs/2026-08-07-windows-resources-and-version-identity-design.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,3 +550,23 @@ FAIL: llvm-readobj not found under /home/runner/.mcpp
550550
rc 工具从 `build.ninja` 的 `rc =` 绑定里取而不走 PATH——mcpp 本来就是 payload 相对解析的(裸 `windres` 在 PATH 上是 xlings shim),问 PATH 会用**另一个**工具去检查这个产物。msvc 那一支保留 llvm-readobj:能走到那一支的配置里,LLVM 载荷就是默认工具链本身。
551551
552552
**判据:一条断言要用的工具,必须来自它所断言的那条路径本身。** 「本机装得全」是最容易把环境假设藏起来的地方——三次改判据里,前两次都是本机绿、别处红。
553+
554+
## F.11 第四条:`*windres*` 这个名字什么都不告诉你 + msvc 那一支其实零 CI 覆盖
555+
556+
F.10 改成 windres 反读后,mingw job 绿了,**Windows job 红了**:
557+
558+
```
559+
FAIL: windres could not read back .../res/resapp.mcpp.o
560+
```
561+
562+
两件事同时暴露:
563+
564+
1. **`llvm-windres` 不能反读。** 它只是 `llvm-rc` 的单向包装,没有 `-J coff`。而它和 GNU binutils 的 `windres` **都匹配 `*windres*`** ⇒ 按工具名分派是错的。修法:**两条路线依次 TRY**(反读 → llvm-readobj),只有两条都不可用才硬失败。
565+
2. **native Windows 上默认工具链走的是 GNU 支,不是 msvc 支。** 证据在失败信息里:target 目录是 `x86_64-windows-msvc`,而产物是 **`.o` 不是 `.res`** ⇒ `dialect_for(tc).id != "msvc"`,`find_rc_tool` 取了 GNU 分支并选中 `llvm-windres.exe`,lld-link 照单全收。
566+
567+
**⚠️ 推论:`.res` 那一支(rc.exe / llvm-rc + 偏移-40 字节判据)在两个 CI job 里都不执行。** mingw job 是 GNU,Windows job 也是 GNU。也就是说:
568+
569+
- e2e 里 `case "$RES_ART" in *.res)` 是**死分支**——这也正是为什么「方言无关的那条判据」不是锦上添花而是唯一的那条。
570+
- **F.1 修的那个 PATH 切分缺陷(msvc 下找不到 `rc.exe`)没有任何 CI 覆盖**,只有单测 `EnvListSplitsOnSemicolonsOnly` 守着字符串切分本身。要真正覆盖它需要一个 `# requires: msvc` 的资源 e2e(`msvc@system` 工具链),本批不做,**明确记为缺口**。
571+
572+
**判据:「本机/某个 job 绿」不等于「这条分支跑过」。判断一条 fork 是否被覆盖,要看产物形态(`.res` vs `.o`),不能看 job 名字里有没有 windows。**

tests/e2e/_windows_resources_body.sh

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -123,42 +123,41 @@ esac
123123
RC_TOOL=$(sed -n 's/^rc *= *//p' "$BUILD_DIR/build.ninja" | head -1)
124124
[ -n "$RC_TOOL" ] || fail "no 'rc =' binding in build.ninja" "$BUILD_DIR/build.ninja"
125125

126+
# Two routes, TRIED in order rather than dispatched on the tool's name: GNU
127+
# binutils' windres round-trips, `llvm-windres` does not (it only wraps llvm-rc,
128+
# one way) — and both answer to `*windres*`, so the name tells you nothing. Only
129+
# when NEITHER route exists is this a hard failure; silently skipping the
130+
# assertion is how #365 shipped in the first place.
131+
#
126132
# $1 = file to inspect, $2 = log suffix, $3 = "ordinal" | "string"
127133
version_name_is() {
128134
case "$3" in
129-
ordinal) _want='^[[:space:]]*1[[:space:]]+VERSIONINFO' ;;
130-
string) _want='"VS_VERSION_INFO"[[:space:]]+VERSIONINFO' ;;
135+
ordinal) _want='^[[:space:]]*1[[:space:]]+VERSIONINFO' ; _n='Name: (ID 1)' ;;
136+
string) _want='"VS_VERSION_INFO"[[:space:]]+VERSIONINFO' ; _n='Name: VS_VERSION_INFO' ;;
131137
esac
132-
case "$RC_TOOL" in
133-
*windres*)
134-
"$RC_TOOL" -J coff -O rc -i "$1" -o "rt.$2.rc" 2>"rt.$2.err" \
135-
|| fail "windres could not read back $1" "rt.$2.err"
136-
grep -qiE 'VERSIONINFO' "rt.$2.rc" \
137-
|| fail "$1 carries no version resource at all" "rt.$2.rc"
138+
139+
# Route 1 — windres back to rc SOURCE. Readable, and no PE parser needed.
140+
if [ -n "$RC_TOOL" ] \
141+
&& "$RC_TOOL" -J coff -O rc -i "$1" -o "rt.$2.rc" 2>"rt.$2.err" \
142+
&& grep -qiE 'VERSIONINFO' "rt.$2.rc"; then
138143
grep -qE "$_want" "rt.$2.rc" \
139144
|| fail "expected the version resource to be named by $3 in $1" "rt.$2.rc"
140-
;;
141-
*)
142-
# rc.exe / llvm-rc cannot read back, so use llvm-readobj — which ships
143-
# with the LLVM payload that IS the default Windows toolchain, i.e. the
144-
# only configuration that reaches this branch. Same discriminator, in
145-
# the resource directory: `Name: (ID 1)` vs `Name: VS_VERSION_INFO`.
146-
READOBJ=$(ls "$MCPP_HOME"/registry/data/xpkgs/xim-x-llvm/*/bin/llvm-readobj \
147-
"$MCPP_HOME"/registry/data/xpkgs/xim-x-llvm/*/bin/llvm-readobj.exe \
148-
2>/dev/null | head -1)
149-
[ -n "$READOBJ" ] || fail "no way to read back $1 (no windres, no llvm-readobj) — silently skipping this assertion is how #365 shipped" b1.log
150-
"$READOBJ" --coff-resources "$1" > "readobj.$2.log" 2>&1 \
151-
|| fail "llvm-readobj could not read $1" "readobj.$2.log"
152-
grep -q 'Type: VERSIONINFO' "readobj.$2.log" \
153-
|| fail "$1 carries no version resource at all" "readobj.$2.log"
154-
case "$3" in ordinal) _n='Name: (ID 1)' ;; string) _n='Name: VS_VERSION_INFO' ;; esac
155-
# The window is the VERSIONINFO type table only — an icon is also
156-
# `(ID 1)`, so matching anywhere in the file would pass for the wrong
157-
# reason.
158-
grep -A5 'Type: VERSIONINFO' "readobj.$2.log" | grep -qF "$_n" \
159-
|| fail "expected the version resource to be named by $3 in $1" "readobj.$2.log"
160-
;;
161-
esac
145+
return 0
146+
fi
147+
148+
# Route 2 — the resource DIRECTORY, via llvm-readobj.
149+
READOBJ=$(ls "$MCPP_HOME"/registry/data/xpkgs/xim-x-llvm/*/bin/llvm-readobj \
150+
"$MCPP_HOME"/registry/data/xpkgs/xim-x-llvm/*/bin/llvm-readobj.exe \
151+
2>/dev/null | head -1)
152+
[ -n "$READOBJ" ] || fail "no way to read back $1: '$RC_TOOL' cannot round-trip and no llvm-readobj was found under $MCPP_HOME" "rt.$2.err"
153+
"$READOBJ" --coff-resources "$1" > "readobj.$2.log" 2>&1 \
154+
|| fail "llvm-readobj could not read $1" "readobj.$2.log"
155+
grep -q 'Type: VERSIONINFO' "readobj.$2.log" \
156+
|| fail "$1 carries no version resource at all" "readobj.$2.log"
157+
# The window is the VERSIONINFO type table only — an icon is also `(ID 1)`,
158+
# so matching anywhere in the file would pass for the wrong reason.
159+
grep -A5 'Type: VERSIONINFO' "readobj.$2.log" | grep -qF "$_n" \
160+
|| fail "expected the version resource to be named by $3 in $1" "readobj.$2.log"
162161
}
163162
version_name_is "$RES_ART" art ordinal
164163

0 commit comments

Comments
 (0)