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
835 changes: 835 additions & 0 deletions .agents/docs/2026-08-25-mcpp-target-vocabulary-spec.md

Large diffs are not rendered by default.

472 changes: 472 additions & 0 deletions .agents/docs/2026-08-25-os-toolchain-target-matrix.md

Large diffs are not rendered by default.

70 changes: 68 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,71 @@
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。

## [未发布]
## [2026.8.24.6] — 2026-08-25

### 新增

- **⭐ `x86_64-windows-musl`:Windows 上的 musl 有名字了。**

同一个 `--target x86_64-windows-gnu`,两种体系下的 C 库完全不同 ——
而 mcpp 用同一个名字称呼它们。实测同一份源码:

| | MinGW CRT | musl / openkal |
|---|---|---|
| 体积 | 587,894 | **9,815,552**(16.7×) |
| 依赖 DLL | `KERNEL32`、**`msvcrt.dll`** | `ntdll`、`KERNEL32`、`SHELL32` |

⭐ MinGW 的 C 库不是自足的:`printf`/`malloc` 的实现在目标机自带的
`msvcrt.dll` 里。musl 是自足的,整个 C 库静态链入,只经 openkal 调
Win32 原语。**两者是不同的东西,不该共用一个名字。**

⚠️ **这个名字 LLVM 拼不出来。** 实测 llvm 22.1.8:

```
clang++ --target=x86_64-pc-windows-musl -c t.cpp
#5 llvm::MCWinCOFFStreamer::emitCGProfileEntry(...)
```

崩在 COFF 写出器,不是诊断而是 ICE。Windows 上四个非 MSVC 环境
`gnu`/`cygnus`/`itanium`/`musl`,前三个能编,只有 `musl` 死;
预定义宏显示它从未被建模(无 `__MINGW32__`)。

⭐ **于是 mcpp 的名字与交给 clang 的三元组必须是两个字符串**,而它们
本来就是 —— 构建报告里那个箭头两侧就是:

```
Target x86_64-windows-musl → x86_64-w64-windows-gnu
^ 回答「C 库是谁」 ^ 回答「遵循哪套对象 ABI」
```

⚠️ 该行 pin 为 `llvm@22.1.8`,不是偏好:全局默认为 gcc 时,空 pin 会让
它落到 musl-gcc 载荷并报「没有 C++ 前端」—— 一条关于缺前端的消息,
而真正的问题是只有 clang 能发这个目标。裸机各行同理。

⚠️ 档为 `preview`:`verified` 的定义是「构建**并运行**过」,而端到端
可用还差一环 —— `openkal-musl@0.3.3` 精确钉死 `openkal-windows = "0.1.3"`,
索引里已有的 0.1.4 到不了消费者。生态链条另行推进。

### 修复

- **构建期体系下 `x86_64-windows-gnu` 的名实不符,此前静默。**

```
x86_64-linux-gnu 名字说 gnu,事实是 musl → ⚠️ 警告
x86_64-windows-gnu 名字说 gnu,事实是 musl → ❌ 静默
```

`check_request()` 豁免了非「C 库轴」的平台,理由是「Windows 上 `gnu`
命名对象 ABI 而非 C 库」—— **只对了一半**:那一段捆着对象 ABI(被兑现)
与 MinGW 的 C 运行时(被图替换),第二件正是该函数存在的意义。

判据改为「轴 ∈ {CLibrary, ObjectAbi}」。⚠️ 裸机的 `elf` 继续豁免 ——
它在任何平台上都不命名 C 库,对它说「请求了 `elf` C ABI」是胡话。
消息在对象 ABI 轴上额外说明 ABI 那一半**未受影响**。

误伤由既有的 `fromGraph()` 挡住:实测传统预构建路径下
`c-abi gnu (payload)` 不进警告。

- **暂存跨主版本副本时,与源码相邻的私有头没有跟着走。**

当同一个包的两个主版本同时出现在一张图里,解析器会把其中一份改名暂存到
Expand All @@ -27,6 +88,12 @@
现在,凡是包含了被暂存源码的目录,其中未被暂存的文件一并原样带过去。没有被
暂存源码的目录不会被访问,所以代价与暂存量成正比。

### 文档

- docs/16 新增「三套词表,以及它们为何不同」(中英双份):GCC / LLVM /
mcpp 三套的形状、`x86_64-w64-mingw32` 为何把实现放在 OS 位、
clang 如何把它重拼为 `windows-gnu`,以及 mcpp 为何要保留自己那一套。

## [2026.8.24.5] — 2026-08-25

### 改进
Expand Down Expand Up @@ -2656,7 +2723,6 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的
独立提供,不读取 cfg)。



### 修复

- **Linux llvm 工具链链接失败 `cannot open Scrt1.o/crti.o/crtn.o`(#195)**:clang-with-cfg
Expand Down
125 changes: 125 additions & 0 deletions docs/16-the-target-triple.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,131 @@ convention the **objects follow**, which several layers must agree on. Reading
it as `c++-abi libc++` is a second wrong answer, since libstdc++ sits on the
same ABI.

## Three Vocabularies, And Why They Differ

A triple is written by three parties that do not share a convention, and mcpp
translates between them. Knowing which one a string belongs to removes most of
the confusion the third segment causes.

### The shape

```
<arch> - <vendor> - <os> - <env>
```

Every field may be omitted and the compiler fills what is missing. `vendor` is
historical and carries almost nothing today — it is filled as `unknown` unless
the target has a reason to say otherwise:

```
x86_64-linux-gnu → x86_64-unknown-linux-gnu
aarch64-macos → aarch64-unknown-macos
```

A three-field spelling is a four-field one with a field elided, and which field
was elided follows from what parses.

### GCC and clang do not select targets the same way

| | GCC | clang |
|---|---|---|
| target | fixed when the compiler was **built** | chosen at **run time** |
| how to ask | `-dumpmachine` | `-dumpmachine`, or `--target=` |
| to cross-compile | use a **different executable** | pass a flag |

Measured:

```
g++ → x86_64-linux-gnu (emits nothing else)
x86_64-w64-mingw32-g++ → x86_64-w64-mingw32 (emits nothing else)
clang++ → x86_64-unknown-linux-gnu, and --target= changes it
```

⭐ This is why the prebuilt system passes no `--target`: the payload's compiler
is named after the one target it can emit, and choosing a target means choosing
a payload. It is also why the build-time system needs only one compiler.

### MinGW names itself in the GCC convention

MinGW's own triple is `x86_64-w64-mingw32`:

| Field | Value | Why |
|---|---|---|
| arch | `x86_64` | |
| vendor | `w64` | the project is `mingw-w64`, distinguishing it from the stalled original `mingw32` |
| os | **`mingw32`** | ⭐ MinGW puts *itself* in the OS field |
| env | (absent) | three fields is the whole name |

The convention descends from autoconf's `config.guess`, where the OS field names
the target's runtime environment — and in the GNU toolchain's view MinGW *is* a
distinct environment, with its own headers, its own C runtime and its own
`configure` branches. The `32` is historical; `w64` is what says the name refers
to the 64-bit-capable project.

LLVM does not accept that view. It holds that the OS is `windows` and that MinGW
is one ABI environment on top of it, so it re-spells the name — measured:

```
x86_64-w64-mingw32 → x86_64-w64-windows-gnu
x86_64-pc-mingw32 → x86_64-pc-windows-gnu
```

```
GCC x86_64 - w64 - mingw32 - (none)
^vendor ^os
LLVM x86_64 - unknown - windows - gnu
^vendor ^os ^env
```

⭐ **`mingw32` is split out of the OS field into `windows` plus `gnu`.** The
value `gnu` exists because LLVM needed a name for the half that was left over.
Its meaning is "the MinGW/Itanium ABI lineage" and it has never meant "the C
library is glibc" on Windows — the same word carries different duties under
different operating systems, and that is LLVM's vocabulary rather than mcpp's
invention.

### What mcpp keeps

| Vocabulary | Example | Read by |
|---|---|---|
| GCC / autoconf | `x86_64-w64-mingw32` | the prebuilt payload's compiler, by its file name |
| LLVM | `x86_64-w64-windows-gnu` | `clang --target=` |
| **mcpp** | `x86_64-windows-gnu` | the target table, the output directory, `cfg()`, a packed ABI tag |

mcpp's own form must map onto both. The arrow in the build report is that
mapping, from the third vocabulary to the second:

```
Target x86_64-windows-gnu → x86_64-w64-windows-gnu
^ mcpp ^ LLVM
```

⚠️ Keeping the third vocabulary separate is what lets mcpp name something LLVM
cannot. Measured on llvm 22.1.8, `windows` with a `musl` environment is accepted
by the triple parser and crashes the compiler:

```
clang++ --target=x86_64-pc-windows-musl -c t.cpp
#5 llvm::MCWinCOFFStreamer::emitCGProfileEntry(...)
```

The crash is in the COFF writer: clang has decided the output is COFF and has no
Windows-musl path to configure the streamer with. Of the four non-MSVC Windows
environments it knows — `gnu`, `cygnus`, `itanium`, `musl` — the first three
compile and only `musl` dies, and the predefined macros show why the environment
was never modelled:

| Triple | Macros |
|---|---|
| `…-windows-gnu` | `__GNUC__` `__MINGW32__` `_WIN32` |
| `…-windows-msvc` | `_MSC_VER` `_WIN32` |
| `…-windows-musl` | `__GNUC__` `_WIN32` — **no `__MINGW32__`** |

So a musl C library on Windows cannot be named to clang, and must still be named
by mcpp, because mcpp's name answers a different question: **which C library**,
where LLVM's answers **which object ABI**. Both are needed and they are not the
same string.

## Custom Targets

A triple outside mcpp's table needs an explicit section, which is also how a
Expand Down
117 changes: 117 additions & 0 deletions docs/zh/16-the-target-triple.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,123 @@ Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu selects the Itanium
横切事项。把它读成 `c++-abi libc++` 是第二个错误答案,因为 libstdc++
坐在同一套 ABI 上。

## 三套词表,以及它们为何不同

一个三元组由三方书写,而三方并不共用一套约定;mcpp 在它们之间翻译。
知道手上这个字符串属于哪一套,第三段带来的困惑就消掉大半。

### 形状

```
<arch> - <vendor> - <os> - <env>
```

每一段都可省,省掉的由编译器补。`vendor` 是历史遗留,今天几乎不承重 ——
除非目标有理由说别的,否则一律填 `unknown`:

```
x86_64-linux-gnu → x86_64-unknown-linux-gnu
aarch64-macos → aarch64-unknown-macos
```

三段写法是四段省掉一段,省的是哪一段由「能否解析」决定。

### GCC 与 clang 选目标的方式根本不同

| | GCC | clang |
|---|---|---|
| 目标 | **构建编译器时**就定死 | **运行时**选 |
| 怎么问 | `-dumpmachine` | `-dumpmachine`,或 `--target=` |
| 交叉编译 | 换**另一个可执行文件** | 传一个 flag |

实测:

```
g++ → x86_64-linux-gnu (只能发这个)
x86_64-w64-mingw32-g++ → x86_64-w64-mingw32 (只能发这个)
clang++ → x86_64-unknown-linux-gnu,而 --target= 可改
```

⭐ 这就是传统预构建体系**不传 `--target`** 的原因:那份载荷的编译器以它唯一
能发的目标命名,选目标等于选载荷。也是构建期体系只需要一个编译器的原因。

### MinGW 按 GCC 的约定给自己命名

MinGW 自己的三元组是 `x86_64-w64-mingw32`:

| 段 | 值 | 为什么 |
|---|---|---|
| arch | `x86_64` | |
| vendor | `w64` | 项目名 `mingw-w64`,用以区别于已停滞的原 `mingw32` 项目 |
| os | **`mingw32`** | ⭐ MinGW 把**自己**放在 OS 位 |
| env | (无) | 三段就是全名 |

这套约定源自 autoconf 的 `config.guess`,那里 OS 段命名的是目标的运行环境 ——
而在 GNU 工具链的世界观里 MinGW **就是**一个独立环境:有自己的头文件、
自己的 C 运行时、自己的 `configure` 分支。名字里的 `32` 是历史遗留,
`w64` 才表示这是那个支持 64 位的项目。

LLVM 不接受这个世界观。它认为 OS 是 `windows`,MinGW 只是其上的一种 ABI
环境,于是把这个名字重拼 —— 实测:

```
x86_64-w64-mingw32 → x86_64-w64-windows-gnu
x86_64-pc-mingw32 → x86_64-pc-windows-gnu
```

```
GCC x86_64 - w64 - mingw32 - (无)
^vendor ^os
LLVM x86_64 - unknown - windows - gnu
^vendor ^os ^env
```

⭐ **`mingw32` 从 OS 位被拆成 `windows` 加 `gnu`。** `gnu` 这个取值之所以
存在,正是因为 LLVM 需要给拆剩下的那一半起个名字。它的含义是
「MinGW/Itanium 这一支 ABI」,在 Windows 上从来不是「C 库是 glibc」——
同一个词在不同操作系统下承担不同职责,这是 LLVM 词表的既有事实,
不是 mcpp 的发明。

### mcpp 保留的那一套

| 词表 | 例 | 谁读 |
|---|---|---|
| GCC / autoconf | `x86_64-w64-mingw32` | 预构建载荷的编译器,以文件名的形式 |
| LLVM | `x86_64-w64-windows-gnu` | `clang --target=` |
| **mcpp** | `x86_64-windows-gnu` | 目标表、输出目录、`cfg()`、打包的 ABI tag |

mcpp 自己那套必须能映到前两套。构建报告里那个箭头就是这个映射,
从第三套到第二套:

```
Target x86_64-windows-gnu → x86_64-w64-windows-gnu
^ mcpp ^ LLVM
```

⚠️ 把第三套词表独立出来,正是 mcpp 能命名 LLVM 命名不了的东西的原因。
实测 llvm 22.1.8:`windows` 配 `musl` 环境能被三元组解析器接受,
而编译器会崩:

```
clang++ --target=x86_64-pc-windows-musl -c t.cpp
#5 llvm::MCWinCOFFStreamer::emitCGProfileEntry(...)
```

崩点在 **COFF 写出器**:clang 已认定输出是 COFF,却没有一条 Windows-musl
的路径去把 streamer 需要的状态配齐。它认识的四个非 MSVC Windows 环境 ——
`gnu`、`cygnus`、`itanium`、`musl` —— 前三个都能编,只有 `musl` 死;
预定义宏说明了这个环境从未被建模:

| 三元组 | 宏 |
|---|---|
| `…-windows-gnu` | `__GNUC__` `__MINGW32__` `_WIN32` |
| `…-windows-msvc` | `_MSC_VER` `_WIN32` |
| `…-windows-musl` | `__GNUC__` `_WIN32` —— **无 `__MINGW32__`** |

所以 Windows 上的 musl C 库**无法**向 clang 命名,而 mcpp 仍然必须为它命名,
因为 mcpp 的名字回答的是另一个问题:**C 库是谁**,而 LLVM 的名字回答
**遵循哪套对象 ABI**。两者都需要,且不是同一个字符串。

## 自定义目标

不在 mcpp 表内的三元组需要一个显式段落,而这也是一块板子声明
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.24.5"
version = "2026.8.24.6"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
Loading
Loading