Skip to content

Commit 5d71824

Browse files
authored
fix(build): name artifacts from the target — cross builds relinked every time (B3) (#342)
* docs(b3): target-aware artifact naming — the real symptom is a relink every build Writes up B3 properly and corrects what the original §6.5 claimed. The original said it was 'symmetrically wrong' — Windows→Linux produces mcpp.exe for an ELF, Linux→Windows produces mcpp for a PE. The second half is false. Measured: a Linux host cross-compiling to x86_64-windows-gnu produces b3probe.exe (PE32+), because mingw's GCC driver appends .exe itself when the -o name has no extension. mcpp never participates in that decision. The actual defect is elsewhere and matters more. ninja is told the output is bin/foo while GCC writes bin/foo.exe, so the declared file never exists and ninja reruns the link edge on every build. Verified by mtime across two consecutive builds — the artifact is relinked every time. Incremental builds are effectively off for PE targets, which is the path CI exercises daily. That also explains why nothing caught it: 102_mingw_cross_wine.sh looks for the real artifact (find -name '*.exe'), not for what ninja declared, so both of its assertions hold while the inconsistency sits underneath them. Two further findings the fix has to account for: - naming is an (os, env) function, not an os one. windows-gnu uses the GNU convention (libfoo.a); only windows-msvc is foo.lib. The current _WIN32 branch hardcodes the latter, so building a static library with mingw ON a Windows host is already misnamed today — a pre-existing defect unrelated to cross-compilation. - the blast radius is much smaller than §6.5 feared. e2e and CI need essentially no changes, precisely because they match the real artifact. Also confirms the other 15 exe_suffix references are correct host semantics (locating ninja / xlings / clang++ on the build machine) and must not be touched; the change is confined to src/build/plan.cppm. * docs(b3): resolve both open questions with a traced consumer chain Q1 — windows-gnu static lib foo.lib -> libfoo.a: DO IT IN THE SAME PR. The worry was that it changes a host build's output name. Tracing every consumer shows the blast radius is empty: - mcpp package deps link at OBJECT level (plan.cppm:836 splices dependency .o files into lu.objects). A static library is never produced OR read as part of an internal dependency edge. - external prebuilt libs come through free-form ldflags; the name is written in the package descriptor, mcpp never spells it. - [runtime] library_dirs is a directory, not a name; the Windows side only filters on the .dll extension for runtime deployment. - fingerprint.cppm carries no artifact name but does carry MCPP_VERSION, so any bump already rotates target/<triple>/<fp>/. No mixed state, no cache migration, no "cache clean" advice needed. So the only consumer is whoever takes the artifact outside mcpp — and the name they get today is wrong: mingw's ar emits a GNU archive named foo.lib, claiming an MSVC convention it does not satisfy. That is a pre-existing correctness bug, not a nice-to-have, and shipping the (os, env) rule half-way would leave a state harder to explain than the bug. Q2 — PE import libs: DO NOT MODEL THEM YET. Draw the boundary instead. All five shared-library e2e tests declare "# requires: elf", and that capability is only added on the Linux branch of run_all.sh — Darwin gets "macos", Windows gets "windows". Shared libraries have therefore never been verified end to end on PE *or* Mach-O. This reframes the question: it is not a missing feature, it is a path that was never walked while the code carries branches that look like it was. Those branches are speculation: mingw tolerates linking a .dll directly, MSVC's link.exe cannot — and the branch keys on the host constant, so it points the wrong way under cross-compilation anyway. Recommends rejecting SharedLibrary on non-ELF targets with a clear error before attempting to support it. An untested branch that also refuses to say no is the hardest kind of debt — it can neither be trusted nor deleted, because nobody knows who depends on it. Same shape as the offline-first code that a TTL gate had quietly made unreachable. Splits the work into three PRs accordingly; import lib support gets its own design doc, gated on shared-library coverage existing for PE and Mach-O first. * ci(cross): do not restore target/ in the windows->linux job The job builds twice — once for the host, then once for x86_64-linux-musl — and a cache-restored BMI tree makes the second build read std BMIs that no longer match what the dependency BMIs were compiled against: mcpplibs.cmdline: error: import 'std' has CRC mismatch GCC bakes a CRC of each imported module's BMI into the importer, so the two have to come from the same build round. A cache that restores one without the other is not a partial speedup, it is an unbuildable tree. Systematic, not flaky: it reproduced on rerun, and only in this job. The other two cross jobs cache ~/.mcpp and ~/.xlings but deliberately never target/ — this now follows the same convention. It stayed hidden until a PR touched neither mcpp.toml nor .xlings.json, since those two files key the sandbox cache; every earlier run had been a cold miss. That also means it would have reddened every subsequent PR, so it is fixed ahead of the B3 work rather than alongside it. * test(cross): pin that a cross build declares the artifact it produces Adds ArtifactNaming (a (os, env) function on the target triple) plus the regression assertion for a defect that exists on HEAD today. plan.cppm's target_output() spells the artifact suffix from mcpp::platform::exe_suffix — a HOST constant. Cross-compiling Linux -> PE that yields `bin/foo` while mingw's GCC driver writes `bin/foo.exe`, so the file ninja was told to produce never appears. ninja finds the declared output missing on every run and reruns the link edge forever. The e2e asserts both the cause and its observable consequence: that the declared ninja output exists, and that an up-to-date rebuild does not change the artifact's mtime. Verified RED before the fix: FAIL: ninja declares output 'bin/relinkprobe' but that file does not exist actually produced: relinkprobe.exe => the link edge can never be satisfied, so it reruns every build The unit tests cover ArtifactNaming's own logic, including the part a single _WIN32 branch cannot express: windows-gnu uses the GNU convention (libfoo.a) while windows-msvc uses foo.lib. They pass a deliberately bogus host answer, so any assertion leaking through to the host axis fails loudly. None of the other cross tests could have caught this: they look for the REAL artifact (find -name '*.exe'), not for what ninja declared, so both of their assertions hold while the inconsistency sits underneath them. Refs .agents/docs/2026-08-03-b3-target-aware-artifact-naming.md * fix(build): name artifacts from the target, not from the build host target_output() spelled the suffix and library affixes from mcpp::platform::{exe_suffix,lib_prefix,static_lib_ext,shared_lib_ext} — host constants selected by #if defined(_WIN32)/__APPLE__. On a host build the host and target answers coincide, which is why it survived; they diverge the moment host != target. The consequence was not cosmetic. Cross-compiling Linux -> PE, ninja was told to produce `bin/foo` while mingw's GCC driver writes `bin/foo.exe`, so the declared output never existed and ninja reran the link edge on every single build. Incremental builds were effectively off for PE targets — the path CI exercises daily. Naming now comes from ArtifactNaming, resolved once per plan from the target triple. It is an (os, env) function, not an os one: x86_64-windows-gnu -> libfoo.a (GNU/mingw) x86_64-windows-msvc -> foo.lib (MSVC) A single _WIN32 branch cannot express that, which is why building a static library with mingw ON a Windows host produced `foo.lib` — a GNU archive wearing an MSVC name. That is a behaviour change for that configuration, and it fixes a name that was already wrong. An empty triple means "build for this machine", and only there is the host answer correct, so it is threaded in as the fallback rather than read directly. Host builds are therefore bit-for-bit unchanged. shared_library_link_flags gets the same treatment: whether a consumer links a full path (PE), uses @loader_path (Mach-O) or $ORIGIN (ELF) is a property of what we build FOR. Keying it on the host pointed it the wrong way under cross builds. Also refuses SharedLibrary on non-ELF targets. Every shared-library e2e declares `# requires: elf` and run_all.sh grants that only on Linux, so those paths have never been verified on PE or Mach-O — mingw's ld tolerates linking a .dll directly, MSVC's link.exe cannot, and neither has an import library because mcpp does not model one. A clear refusal beats emitting an artifact nothing has ever checked. Verified: - e2e 183 red before, green after - 53 unit tests pass - 08_shared_library, 64_shared_soname_runtime_alias, 55/57_*_shared_artifact, 102_mingw_cross_wine all pass — the soname alias edge is the one the design doc flagged as historically fragile Refs .agents/docs/2026-08-03-b3-target-aware-artifact-naming.md * release: 2026.8.3.3 Bumps the BUILDING pair only (mcpp.toml + fingerprint.cppm); the bootstrap pin in .xlings.json stays at 2026.8.3.2 until this release exists and is reachable through the index. CHANGELOG calls out the windows-gnu static library rename explicitly: it is the one change here that alters a HOST build's output name (foo.lib -> libfoo.a on Windows + mingw), and it corrects a name that was already wrong.
1 parent 9a696d2 commit 5d71824

10 files changed

Lines changed: 838 additions & 44 deletions

.agents/docs/2026-08-03-b3-target-aware-artifact-naming.md

Lines changed: 398 additions & 0 deletions
Large diffs are not rendered by default.

.agents/docs/2026-08-03-windows-host-linux-cross-design.md

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -643,21 +643,36 @@ return std::filesystem::path("bin") /
643643
```
644644
645645
`exe_suffix` / `lib_prefix` / `static_lib_ext` / `shared_lib_ext` **四个都是 host 常量**
646-
(`platform/common.cppm:18-29`),却用来命名 **target** 产物。与 B2 完全同构,而且**对称地错**:
646+
(`platform/common.cppm:18-33`),却用来命名 **target** 产物。与 B2 同构。
647647
648-
| 方向 | 现状 | 应当 |
649-
|---|---|---|
650-
| Windows → linux-musl | `mcpp.exe`(却是 ELF) | `mcpp` |
651-
| Linux → windows-gnu | `mcpp`(却是 PE) | `mcpp.exe` |
652-
653-
**为什么本期不修**:改产物命名是行为变更,会同时动到 `tests/e2e/102_mingw_cross_wine.sh`、
654-
release 打包路径和任何用户脚本;而两个方向的现状都**不致命**(扩展名在 Linux 上无意义,
655-
Windows 命令行也能跑无扩展名的 PE)。把一个有回归面的重命名塞进本 PR,违背了 §1.3 定下的
656-
「单 PR 但提交分层、失败可归因」的初衷。
657-
658-
**修的时候要一起改的四个常量**,并且要注意 `runtime_aliases_for_target()`(`plan.cppm:215`)
659-
依赖 `target_output()` 的结果去比对 soname —— 见 [[soname-alias-explicit-ninja-goals]],
660-
那条边曾经因为类似改动漏生成过。
648+
> ### ⚠️ 本节初版的判断有误,已在专项文档中更正
649+
>
650+
> 初版写的是「对称地错:Linux→Windows 产出 `mcpp` 却是 PE」。**后半句是错的** ——
651+
> 实测 Linux 主机交叉到 `x86_64-windows-gnu`,产物就是 `b3probe.exe`(PE32+),
652+
> 因为 **mingw 的 GCC driver 自己会补 `.exe`**,mcpp 从未参与这个决定。
653+
>
654+
> 真正的症状是另一件事,而且更实际:**ninja 声明的输出是 `bin/foo`,GCC 写出 `bin/foo.exe`,
655+
> 声明的那个文件从来不存在 ⇒ 每次 `mcpp build` 都重跑链接边**。实测连续两次构建
656+
> 产物 mtime 会变。也就是说 Linux→Windows 是**功能缺陷(增量构建失效)**,
657+
> Windows→Linux 才只是「ELF 顶着 `.exe`」的观感问题。
658+
659+
**完整分析、修复方案与验证判据见专项文档:**
660+
`2026-08-03-b3-target-aware-artifact-naming.md`
661+
662+
要点摘录:
663+
- 改动面只有 `src/build/plan.cppm` 一个文件;其余 15 处 `exe_suffix` 引用都是找主机上的
664+
`ninja`/`xlings`/`clang++` 等,**是正确的 host 语义,不要动**
665+
- 正确的命名是 **(os, env) 二元函数**:`windows-gnu` 用 GNU 约定(`libfoo.a`),
666+
`windows-msvc` 才是 `foo.lib` —— 现行 `_WIN32` 分支写死后者,**Windows 主机上用 mingw
667+
构建静态库今天就已经命名错了**,与交叉无关的存量缺陷
668+
- e2e/CI 基本不用改(与本节初版的担心相反):`102_mingw_cross_wine.sh` 找的是真实产物
669+
而非 ninja 声明,改前改后都匹配
670+
- `runtime_aliases_for_target()`(`plan.cppm:215`)依赖 `target_output()` 比对 soname,
671+
必须一起改并补断言 —— 见 [[soname-alias-explicit-ninja-goals]]
672+
673+
**为什么当期没修**:发现时 #339 已进入 CI 验证阶段,把一个尚未查清真实形态的改动塞进去,
674+
会毁掉 §1.3 定下的「提交分层、失败可归因」。事后看这个决定是对的 —— 初版对症状的描述
675+
本身就是错的,当场改只会改错方向。
661676
662677
---
663678

.github/workflows/cross-build-test.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,25 @@ jobs:
307307
steps:
308308
- uses: actions/checkout@v4
309309
- uses: ./.github/actions/bootstrap-mcpp
310+
with:
311+
# Do NOT restore target/ in a cross job. This job builds twice — once
312+
# for the host, then once for x86_64-linux-musl — and a restored BMI
313+
# tree makes the second build read `std` BMIs that no longer match
314+
# what the dependency BMIs were compiled against:
315+
#
316+
# mcpplibs.cmdline: error: import 'std' has CRC mismatch
317+
#
318+
# GCC bakes a CRC of each imported module's BMI into the importer, so
319+
# the two have to come from the same build round; a cache that
320+
# restores one without the other is not a partial speedup, it is an
321+
# unbuildable tree. It reproduced on rerun, and only in this job —
322+
# the other two cross jobs below cache ~/.mcpp and ~/.xlings but
323+
# deliberately never target/, which is the convention this now follows.
324+
#
325+
# It stayed hidden until a PR touched neither mcpp.toml nor
326+
# .xlings.json: those two files key the sandbox cache, so every
327+
# earlier run had been a cold miss.
328+
cache-target: 'false'
310329

311330
- name: Build mcpp from source (self-host)
312331
shell: bash

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [2026.8.3.3] — 2026-08-03
7+
8+
### 修复
9+
10+
- **交叉构建每次都重新链接(增量构建对 PE 目标实际失效)。** `plan.cppm``target_output()``mcpp::platform::{exe_suffix,lib_prefix,static_lib_ext,shared_lib_ext}` 拼产物名 —— 这四个是**主机**常量,由 `#if defined(_WIN32)/__APPLE__` 选择。主机构建下「这台机器叫什么」与「产物该叫什么」恰好同解,所以它一直没被发现;`host ≠ target` 时两者分岔。
11+
12+
后果不是命名难看:Linux 交叉到 PE 时,ninja 被告知产出 `bin/foo`,而 mingw 的 GCC driver 写出的是 `bin/foo.exe` —— **声明的那个文件从来不存在**,ninja 每次都发现输出缺失并重跑链接边。这条路径正是 CI 每天在跑的。
13+
14+
产物命名现在由 `ArtifactNaming` 决定,每个 plan 从 target triple 求值一次。**它是 (os, env) 二元函数,不是 os 一元**:`x86_64-windows-gnu` 用 GNU 约定(`libfoo.a`),`x86_64-windows-msvc` 才是 `foo.lib`。空 triple 表示「为本机构建」,只有那时主机答案才是对的,因此它作为回退传入而非被直接读取 —— **主机构建逐位不变**
15+
16+
同样的处理给了 `shared_library_link_flags`:消费者链接一个共享库时用完整路径(PE)、`@loader_path`(Mach-O)还是 `$ORIGIN`(ELF),是**产物**的属性;按主机求值在交叉时方向就是反的。
17+
18+
- **`windows-gnu` 静态库命名错误(行为变更)。** 在 Windows 主机上用 mingw 工具链构建静态库,产物此前叫 `foo.lib` —— 一个 GNU archive 顶着 MSVC 的名字,MSVC 拿不去用。现在按 GNU 约定命名为 `libfoo.a`。这修正的是一个**今天就是错的**名字,与交叉编译无关。
19+
20+
### 改进
21+
22+
- **非 ELF 目标上声明 `SharedLibrary` 现在明确报错,而不是产出未经验证的东西。** 全部 5 个共享库 e2e 都声明 `# requires: elf`,而这个 capability 只在 Linux 上授予 —— 也就是说共享库在 PE 与 Mach-O 上**从未被端到端验证过**。相关分支是推测代码:mingw 的 ld 容忍直接链 `.dll`,MSVC 的 `link.exe` 不行,而两者都没有 import library 可链,因为 mcpp 还没有建模它。
23+
24+
一段既没有测试覆盖、又不肯明确拒绝的分支是最难清理的债 —— 它既不能被信任,也不能被删除,因为没人知道谁在依赖它。先把边界写死,等真要支持时再补(前置条件是先有 PE/Mach-O 的共享库覆盖)。
25+
626
## [2026.8.3.2] — 2026-08-03
727

828
### 新增

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.3.2"
3+
version = "2026.8.3.3"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/plan.cppm

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import mcpp.toolchain.cppfly;
1313
import mcpp.toolchain.detect;
1414
import mcpp.toolchain.dialect;
1515
import mcpp.toolchain.fingerprint;
16+
import mcpp.toolchain.triple;
1617
import mcpp.platform;
1718

1819
export namespace mcpp::build {
@@ -197,29 +198,52 @@ std::vector<std::string> dependency_name_candidates(
197198
return out;
198199
}
199200

200-
std::filesystem::path target_output(const mcpp::manifest::Target& t) {
201+
// The naming this MACHINE would use for its own binaries. Correct only for a
202+
// host-target build; passed to artifact_naming() as the fallback for an empty
203+
// triple, and never consulted directly when a target triple is present.
204+
mcpp::toolchain::triple::ArtifactNaming host_artifact_naming() {
205+
return {
206+
.exeSuffix = mcpp::platform::exe_suffix,
207+
.libPrefix = mcpp::platform::lib_prefix,
208+
.staticLibExt = mcpp::platform::static_lib_ext,
209+
.sharedLibExt = mcpp::platform::shared_lib_ext,
210+
.sharedNeedsImportLib = mcpp::platform::is_windows,
211+
};
212+
}
213+
214+
mcpp::toolchain::triple::ArtifactNaming naming_for(const mcpp::toolchain::Toolchain& tc) {
215+
auto t = mcpp::toolchain::triple::parse(tc.targetTriple);
216+
return mcpp::toolchain::triple::artifact_naming(
217+
t ? *t : mcpp::toolchain::triple::Triple{}, host_artifact_naming());
218+
}
219+
220+
// What the artifact is CALLED — a property of the target, not of this machine.
221+
// Reading the host constants here made ninja declare an output the compiler
222+
// never writes (Linux -> PE: declared `bin/foo`, produced `bin/foo.exe`), so
223+
// the link edge could never be satisfied and reran on every build.
224+
std::filesystem::path target_output(const mcpp::manifest::Target& t,
225+
const mcpp::toolchain::triple::ArtifactNaming& n) {
201226
if (t.kind == mcpp::manifest::Target::Library) {
202227
return std::filesystem::path("bin") /
203-
std::format("{}{}{}", mcpp::platform::lib_prefix, t.name,
204-
mcpp::platform::static_lib_ext);
228+
std::format("{}{}{}", n.libPrefix, t.name, n.staticLibExt);
205229
}
206230
if (t.kind == mcpp::manifest::Target::SharedLibrary) {
207231
return std::filesystem::path("bin") /
208-
std::format("{}{}{}", mcpp::platform::lib_prefix, t.name,
209-
mcpp::platform::shared_lib_ext);
232+
std::format("{}{}{}", n.libPrefix, t.name, n.sharedLibExt);
210233
}
211234
return std::filesystem::path("bin") /
212-
std::format("{}{}", t.name, mcpp::platform::exe_suffix);
235+
std::format("{}{}", t.name, n.exeSuffix);
213236
}
214237

215238
std::vector<std::filesystem::path> runtime_aliases_for_target(
216-
const mcpp::manifest::Target& t) {
239+
const mcpp::manifest::Target& t,
240+
const mcpp::toolchain::triple::ArtifactNaming& n) {
217241
std::vector<std::filesystem::path> aliases;
218242
if (t.kind != mcpp::manifest::Target::SharedLibrary || t.soname.empty()) {
219243
return aliases;
220244
}
221245

222-
auto output = target_output(t);
246+
auto output = target_output(t, n);
223247
if (t.soname != output.filename().string()) {
224248
aliases.push_back(output.parent_path() / t.soname);
225249
}
@@ -232,19 +256,30 @@ bool is_implementation_source(const std::filesystem::path& source) {
232256
|| ext == ".S" || ext == ".s" || ext == ".asm";
233257
}
234258

235-
std::vector<std::string> shared_library_link_flags(const mcpp::manifest::Target& t) {
259+
// How a CONSUMER links against a shared library. Also a target property: PE has
260+
// no rpath and wants an import library, Mach-O uses @loader_path, ELF uses
261+
// $ORIGIN. Keying this on the host pointed it the wrong way under cross builds.
262+
//
263+
// NOTE: shared libraries have never been verified end to end on PE or Mach-O —
264+
// every shared-library e2e declares `# requires: elf`, and that capability is
265+
// only granted on Linux. The PE branch here (linking the .dll path directly)
266+
// is therefore unproven: mingw's ld tolerates it, MSVC's link.exe cannot.
267+
// make_plan() rejects SharedLibrary targets on non-ELF targets rather than
268+
// emitting something unverifiable — see the guard there.
269+
std::vector<std::string> shared_library_link_flags(
270+
const mcpp::manifest::Target& t,
271+
const mcpp::toolchain::triple::ArtifactNaming& n,
272+
const mcpp::toolchain::triple::Triple& target) {
236273
std::vector<std::string> flags;
237-
if constexpr (mcpp::platform::is_windows) {
238-
flags.push_back(target_output(t).generic_string());
274+
const bool pe = n.sharedNeedsImportLib;
275+
const bool macho = target.empty() ? bool(mcpp::platform::is_macos)
276+
: target.os == "macos";
277+
if (pe) {
278+
flags.push_back(target_output(t, n).generic_string());
239279
} else {
240-
flags.push_back("-L" + target_output(t).parent_path().generic_string());
241-
if constexpr (mcpp::platform::supports_rpath) {
242-
if constexpr (mcpp::platform::is_macos) {
243-
flags.push_back("-Wl,-rpath,@loader_path");
244-
} else {
245-
flags.push_back("-Wl,-rpath,'$$ORIGIN'");
246-
}
247-
}
280+
flags.push_back("-L" + target_output(t, n).parent_path().generic_string());
281+
flags.push_back(macho ? "-Wl,-rpath,@loader_path"
282+
: "-Wl,-rpath,'$$ORIGIN'");
248283
flags.push_back("-l" + t.name);
249284
}
250285
return flags;
@@ -384,6 +419,45 @@ make_plan(const mcpp::manifest::Manifest& manifest,
384419
plan.manifest = manifest;
385420
plan.toolchain = tc;
386421
plan.fingerprint = fp;
422+
423+
// Artifact naming and shared-library link shape are properties of the
424+
// TARGET. Resolved once here from tc.targetTriple (empty = host target, in
425+
// which case the host constants ARE the right answer) and threaded down,
426+
// so nothing below reaches for mcpp::platform to describe an output.
427+
const auto targetTriple = [&] {
428+
auto t = mcpp::toolchain::triple::parse(tc.targetTriple);
429+
return t ? *t : mcpp::toolchain::triple::Triple{};
430+
}();
431+
const auto naming = naming_for(tc);
432+
433+
// Shared libraries have never been verified end to end on PE or Mach-O:
434+
// every shared-library e2e declares `# requires: elf`, and run_all.sh only
435+
// grants that capability on Linux. The non-ELF paths through
436+
// shared_library_link_flags are therefore unproven — mingw's ld tolerates
437+
// linking a .dll directly, MSVC's link.exe cannot, and neither has an
438+
// import library to link against because mcpp does not model one.
439+
//
440+
// Refusing is strictly better than emitting something unverifiable: a
441+
// branch that is neither tested nor willing to say no is the hardest kind
442+
// of debt, because it can be neither trusted nor deleted.
443+
if (!targetTriple.empty() && targetTriple.os != "linux") {
444+
for (auto const& t : manifest.targets) {
445+
if (t.kind != mcpp::manifest::Target::SharedLibrary) continue;
446+
return std::unexpected(std::format(
447+
"target '{}': shared libraries are only supported for Linux (ELF) "
448+
"targets today.\n"
449+
" target '{}' is kind=\"shared\"; build it as kind=\"lib\" "
450+
"(static) for this target,\n"
451+
" or build it for a linux target.\n"
452+
" note: PE consumers need an import library and Mach-O needs "
453+
"install-name handling;\n"
454+
" neither is modelled yet, so mcpp refuses rather than "
455+
"producing an artifact\n"
456+
" nothing has ever verified.",
457+
targetTriple.str(), t.name));
458+
}
459+
}
460+
387461
bool experimentalStd = false;
388462
if (auto stdCfg = mcpp::manifest::normalize_cpp_standard(manifest.package.standard)) {
389463
plan.cppStandard = stdCfg->canonical;
@@ -686,7 +760,7 @@ make_plan(const mcpp::manifest::Manifest& manifest,
686760
.packageIndex = i,
687761
.packageName = qname,
688762
.target = t,
689-
.output = target_output(t),
763+
.output = target_output(t, naming),
690764
});
691765
sharedTargetsByPackage[i].push_back(targetIndex);
692766
}
@@ -767,9 +841,9 @@ make_plan(const mcpp::manifest::Manifest& manifest,
767841
// (0.0.104-0.0.106). The failure surfaced far away, as
768842
// `libX11.so: undefined reference to xcb_connect` or a test
769843
// exiting 127.
770-
for (auto const& alias : runtime_aliases_for_target(dep.target))
844+
for (auto const& alias : runtime_aliases_for_target(dep.target, naming))
771845
lu.implicitInputs.push_back(alias);
772-
auto flags = shared_library_link_flags(dep.target);
846+
auto flags = shared_library_link_flags(dep.target, naming, targetTriple);
773847
lu.linkFlags.insert(lu.linkFlags.end(), flags.begin(), flags.end());
774848
}
775849
}
@@ -812,7 +886,7 @@ make_plan(const mcpp::manifest::Manifest& manifest,
812886
lu.kind = LinkUnit::SharedLibrary;
813887
lu.output = dep.output;
814888
lu.soname = dep.target.soname;
815-
lu.runtimeAliases = runtime_aliases_for_target(dep.target);
889+
lu.runtimeAliases = runtime_aliases_for_target(dep.target, naming);
816890
append_package_objects(lu, dep.packageName);
817891
append_direct_shared_deps(lu, dep.packageIndex);
818892
plan.linkUnits.push_back(std::move(lu));
@@ -833,19 +907,19 @@ make_plan(const mcpp::manifest::Manifest& manifest,
833907
lu.targetName = t.name;
834908
if (t.kind == mcpp::manifest::Target::Library) {
835909
lu.kind = LinkUnit::StaticLibrary;
836-
lu.output = target_output(t);
910+
lu.output = target_output(t, naming);
837911
} else if (t.kind == mcpp::manifest::Target::SharedLibrary) {
838912
lu.kind = LinkUnit::SharedLibrary;
839-
lu.output = target_output(t);
913+
lu.output = target_output(t, naming);
840914
lu.soname = t.soname;
841-
lu.runtimeAliases = runtime_aliases_for_target(t);
915+
lu.runtimeAliases = runtime_aliases_for_target(t, naming);
842916
} else if (t.kind == mcpp::manifest::Target::TestBinary) {
843917
lu.kind = LinkUnit::TestBinary;
844-
lu.output = target_output(t);
918+
lu.output = target_output(t, naming);
845919
if (!t.main.empty()) lu.entryMain = projectRoot / t.main;
846920
} else {
847921
lu.kind = LinkUnit::Binary;
848-
lu.output = target_output(t);
922+
lu.output = target_output(t, naming);
849923
if (!t.main.empty()) lu.entryMain = projectRoot / t.main;
850924
}
851925

src/toolchain/fingerprint.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import mcpp.toolchain.detect;
1818

1919
export namespace mcpp::toolchain {
2020

21-
inline constexpr std::string_view MCPP_VERSION = "2026.8.3.2";
21+
inline constexpr std::string_view MCPP_VERSION = "2026.8.3.3";
2222

2323
struct FingerprintInputs {
2424
Toolchain toolchain;

0 commit comments

Comments
 (0)