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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 规则)在第一分钟就同时撞上了这两个。
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand Down
21 changes: 20 additions & 1 deletion src/build/ninja_backend.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
2 changes: 1 addition & 1 deletion src/version.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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
87 changes: 87 additions & 0 deletions tests/e2e/190_link_rspfile_newlines.sh
Original file line number Diff line number Diff line change
@@ -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 "<whole command>"`, 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"
Loading