diff --git a/CHANGELOG.md b/CHANGELOG.md index c799ac66..d7fd3d04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,25 @@ > 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。 > 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。 +## [2026.8.5.3] — 2026-08-05 + +### 修复 + +- **链接响应文件按行分隔(`$in_newline`)。** 一条链接边和操作系统之间有**两道**上限,而「改用响应文件」只拆掉了第一道: + + 1. **命令行**:Windows `CreateProcess` 32 KiB;POSIX 下 ninja 用 `sh -c "<整条命令>"`,整条命令是**一个** argv 项,撞的是 `MAX_ARG_STRLEN` 128 KiB。这道在 #344 / PR#345 已经拆掉。 + 2. **响应文件的单行长度**:`link.exe` 上限 128 KiB。所有对象写在一行,于是 + + ``` + fatal error LNK1170: line in command file contains 135135 or more characters + ``` + + mcpp-index 的 `opencv-module` 与 `opencv-module-dnn` 在 windows 上正是死在这里 —— 链接前的 795s / 1166s 编译全部白做。 + + 改成 `rspfile_content = $in_newline` 之后,**没有任何上限再随对象数增长**。全平台同一条规则形状:GNU 与 LLVM 的响应文件解析把任何空白(含换行)当分隔符,而 link.exe / lib.exe 要的正是这种写法。 + + > 同一族的第四次(#274 / #247 / #344 / 本条)。前三次的教训写的是「命令有多长不该有人放在心上」;这次补上的是它的孪生兄弟 —— **一行有多长同样不该**。e2e 190 两面都钉:既断言生成的规则,也断言 ninja 真正写出来的文件(`-d keeprsp`),因为只断言前者的话,ninja 哪天改了 `$in_newline` 的展开方式测试仍会绿。 + ## [2026.8.5.2] — 2026-08-05 修复 `host-module = true`(规则包)的两个缺陷。二者都是 2026.8.5.1 引入的,合起来的效果是:**规则包只能写「手工 printf 指令」的玩具规则**,一旦规则要用它本该用的 API 就编不过。第一个真实使用者(`grpc-m` 的 protoc/gRPC codegen 规则)在第一分钟就同时撞上了这两个。 diff --git a/mcpp.toml b/mcpp.toml index c8d63b1e..141f19b5 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "mcpp" -version = "2026.8.5.2" +version = "2026.8.5.3" description = "Modern C++ build & package management tool" license = "Apache-2.0" authors = ["mcpp-community"] diff --git a/src/build/ninja_backend.cppm b/src/build/ninja_backend.cppm index d9937b06..b1f9a3a9 100644 --- a/src/build/ninja_backend.cppm +++ b/src/build/ninja_backend.cppm @@ -785,7 +785,26 @@ std::string emit_ninja_string(const BuildPlan& plan) { cmd.replace(pos, 3, "@$out.rsp"); append(std::format(" command = {}\n", cmd)); append(" rspfile = $out.rsp\n"); - append(" rspfile_content = $in\n"); + // `$in_newline`, not `$in`: ninja separates by newlines instead + // of spaces. Routing the objects through a response file + // removed the COMMAND-LINE ceiling but left a second one + // nobody had reached yet — link.exe caps a response file's + // LINE at 128 KiB: + // + // fatal error LNK1170: line in command file contains + // 135135 or more characters + // + // which is where mcpp-index's opencv-module landed on windows. + // Same failure shape as the one above it: a build system may + // not have a maximum project size it discovers by crashing. + // Newline separation removes the last per-line bound — no + // ceiling scales with the number of objects any more. + // + // Safe everywhere, so there is still one rule shape: GNU and + // LLVM response-file parsing treat any whitespace as a + // separator, newline included, and link.exe/lib.exe want + // exactly this form. + append(" rspfile_content = $in_newline\n"); } else { append(std::format(" command = {}\n", cmd)); } diff --git a/src/version.cppm b/src/version.cppm index a4eed405..e875dfb4 100644 --- a/src/version.cppm +++ b/src/version.cppm @@ -31,6 +31,6 @@ import std; export namespace mcpp { -inline constexpr std::string_view MCPP_VERSION = "2026.8.5.2"; +inline constexpr std::string_view MCPP_VERSION = "2026.8.5.3"; } // namespace mcpp diff --git a/tests/e2e/190_link_rspfile_newlines.sh b/tests/e2e/190_link_rspfile_newlines.sh new file mode 100755 index 00000000..ecee7cc3 --- /dev/null +++ b/tests/e2e/190_link_rspfile_newlines.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# 190_link_rspfile_newlines.sh — the link response file separates objects by +# NEWLINES, not spaces. +# +# Two ceilings sit between a link edge and the OS, and routing the objects +# through a response file only removes the first: +# +# 1. the COMMAND LINE — Windows CreateProcess 32 KiB, POSIX MAX_ARG_STRLEN +# 128 KiB (ninja spawns `sh -c ""`, so the command is one +# argv entry). Removed by using @rspfile at all — mcpp#344 / PR#345. +# 2. the response file's LINE LENGTH — link.exe caps it at 128 KiB: +# +# fatal error LNK1170: line in command file contains 135135 +# or more characters +# +# which is where mcpp-index's opencv-module landed on windows, with every +# object written onto a single line. +# +# `rspfile_content = $in_newline` removes the second. After it, no ceiling +# scales with the number of objects. +# +# Asserted structurally (the generated rule) AND observably (the file ninja +# actually writes, kept with -d keeprsp) — the first alone would still pass if +# ninja ever changed what $in_newline expands to. +set -e + +TMP=$(mktemp -d) +trap "rm -rf $TMP" EXIT +cd "$TMP" + +mkdir -p multi/src +cat > multi/mcpp.toml <<'EOF' +[package] +name = "multi" +version = "0.1.0" +EOF +# Enough objects that "one per line" is unambiguous — a single-object link +# would look identical either way. +i=1 +while [ "$i" -le 24 ]; do + printf 'int f%d() { return %d; }\n' "$i" "$i" > "multi/src/f$i.cpp" + i=$((i + 1)) +done +printf 'int main() { return 0; }\n' > multi/src/main.cpp + +cd multi +"$MCPP" build > b.log 2>&1 || { cat b.log; echo "FAIL: build"; exit 1; } + +ninja_file=$(find target -name build.ninja | head -1) +[ -n "$ninja_file" ] || { echo "FAIL: no build.ninja"; exit 1; } + +# 1. Structural: no link rule may write its response file on one line. +if grep -qE '^[[:space:]]*rspfile_content = \$in[[:space:]]*$' "$ninja_file"; then + grep -nE '^[[:space:]]*rspfile_content' "$ninja_file" + echo "FAIL: a link rule still writes its response file on ONE line (\$in)" + exit 1 +fi +grep -qE '^[[:space:]]*rspfile_content = \$in_newline[[:space:]]*$' "$ninja_file" || { + grep -nE '^[[:space:]]*rspfile_content' "$ninja_file" + echo "FAIL: no link rule uses \$in_newline"; exit 1; } +echo " ok: link rules declare rspfile_content = \$in_newline" + +# 2. Observable: ninja keeps the response file under -d keeprsp, and it holds +# one object per line rather than all of them on the first. +bdir=$(dirname "$ninja_file") +bin_rel=$(cd "$bdir" && ls bin/ 2>/dev/null | head -1) +[ -n "$bin_rel" ] || { echo "FAIL: no linked binary to inspect"; exit 1; } +(cd "$bdir" && rm -f "bin/$bin_rel" && ninja -d keeprsp "bin/$bin_rel" > /dev/null 2>&1) \ + || { echo "FAIL: relink under -d keeprsp"; exit 1; } + +rsp=$(find "$bdir" -name '*.rsp' | head -1) +[ -n "$rsp" ] || { echo "FAIL: -d keeprsp left no response file"; exit 1; } + +# 25 objects -> 24 newlines (the last line carries no trailing newline). +lines=$(wc -l < "$rsp") +[ "$lines" -ge 20 ] || { + echo "response file has $lines newline(s):"; head -c 300 "$rsp"; echo + echo "FAIL: objects are not one-per-line — the LNK1170 shape is back" + exit 1; } + +# ...and no single line is anywhere near link.exe's 128 KiB cap. +longest=$(awk '{ if (length($0) > m) m = length($0) } END { print m+0 }' "$rsp") +[ "$longest" -lt 4096 ] || { + echo "FAIL: longest response-file line is $longest chars"; exit 1; } +echo " ok: $((lines + 1)) objects, longest response-file line $longest chars" + +echo "OK"