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
8 changes: 6 additions & 2 deletions .github/workflows/ci-macos-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,12 @@ jobs:
run: |
set -euo pipefail
rm -rf /tmp/iosplain && mkdir -p /tmp/iosplain/src && cd /tmp/iosplain
# NO FLOOR STATED: the row takes the located SDK's version, read
# through xcrun, and the artefact's LC_BUILD_VERSION minos says so.
cat > mcpp.toml << 'TOML'
[package]
name = "iosplain"
version = "0.1.0"
[build]
ios_deployment_target = "18.0"
TOML
printf 'import std;\nint main() { std::print("x\\n"); }\n' > src/main.cpp
"$MCPP_DEV" build --target aarch64-ios-sim > mixed.log 2>&1 || { echo "FAIL: import std without llvm.libcxx no longer builds"; cat mixed.log; exit 1; }
Expand All @@ -350,6 +350,10 @@ jobs:
grep -q -- '-isystem[^ ]*xim-x-llvm[^ ]*/c++/v1' "$ninja" && { echo "FAIL: the plain program still takes the payload's C++ headers"; exit 1; }
art=$(ls target/aarch64-ios-sim/*/bin/iosplain | head -1)
otool -L "$art" | grep -q '/usr/lib/libc++.1.dylib' || { echo "FAIL: the plain program does not link the SDK's libc++"; otool -L "$art"; exit 1; }
sdkver=$(xcrun --sdk iphonesimulator --show-sdk-version)
minos=$(otool -l "$art" | awk '/cmd LC_BUILD_VERSION/{f=1} f && $1=="minos"{print $2; exit}')
[ "$minos" = "$sdkver" ] || { echo "FAIL: with no floor stated, minos is '$minos' and the located SDK is $sdkver"; exit 1; }
echo "ok: an unset floor is the located SDK's version ($sdkver)"
echo "ok: import std builds with the hazard named; plain C++ takes the SDK's headers and libc++; the builtins degradation is printed"

# THE SUPPORTED PATH, which is what the `verified` tier claims: a runner
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@

## [Unreleased]

### 图里的 libc++ 之下,预编译 C 库的头文件也要进 std 模块的命令;iOS 行未写的部署下限取 SDK 的版本

`llvm.libcxx` 的 CI 扩到它声明的每一行后量到两处引擎缺口。包提供的 std 模块在
预编译时只拿到包自己的 `std-module-flags` 与图里各层的头文件目录,预编译 C 库
(glibc、Apple SDK)的目录一个都没有:Linux 上它读的是 runner 的 `/usr/include`
而不是载荷的 glibc(没有任何报告显示这一点),macOS 原生行停在 `mbstate_t`。现在
这条命令从每个翻译单元用的同一个生产者(`host_compile_tokens`,C++ 层记为图里的)
取 cfg 旁路、C 库目录、SDK 与部署下限。`docs/20` 曾承诺 iOS 行不写
`ios_deployment_target` 时取 SDK 的默认;实测 clang 对无版本的 `arm64-apple-ios`
拒绝线程局部存储(libc++abi 用到它),所以那个默认比机器上任何 SDK 都旧。现在
定位 SDK 时同时读 `xcrun --sdk <name> --show-sdk-version`,写进同一个槽。

- 判据:`tests/e2e/663` 断言包 std 模块记录的命令点名载荷的 glibc;`ci-macos-ios`
的纯 C++ 夹具不写下限,断言产物 `LC_BUILD_VERSION minos` 等于定位到的 SDK 版本;
`mcpplibs/libcxx` 的 CI 在 linux、aarch64-macos、aarch64-ios、两个模拟器行上各量一次。

### 一个框架与它的生态库仍会撞到的引擎缺口:#630 的十项

#630 汇总了 HuxerUI 在六个平台上落地后引擎仍欠的十项。每一项都先在
Expand Down
9 changes: 6 additions & 3 deletions docs/20-toolchains.md
Original file line number Diff line number Diff line change
Expand Up @@ -723,9 +723,12 @@ Target aarch64-ios-sim → arm64-apple-ios18.0-simulator
which are Apple's own spellings. No `-miphoneos-version-min` flag is emitted:
the triple fully determines the platform and the minimum, and a flag would be a
second place answering a question the triple already answers. Leaving the key
unset is legal and means the SDK's own default, which clang supplies for an
Apple target -- unlike Android, where bionic refuses an unversioned triple
outright.
unset is legal and means the located SDK's own version, which mcpp reads from
`xcrun --sdk <name> --show-sdk-version` and writes into the same slot: clang's
own default for an unversioned iOS triple is older than any SDK on the machine
(it refused thread-local storage, which libc++abi uses; measured on Xcode
16.4), so the triple is versioned before the driver sees it. Android differs
in the other direction: bionic refuses an unversioned triple outright.

### The simulator's shape, and its boundary

Expand Down
6 changes: 4 additions & 2 deletions docs/zh/20-toolchains.md
Original file line number Diff line number Diff line change
Expand Up @@ -649,8 +649,10 @@ Target aarch64-ios-sim → arm64-apple-ios18.0-simulator

这是 Apple 自己的拼法。不发 `-miphoneos-version-min` 标志:三元组已经完全决定了
平台与最低版本,而一个标志会成为第二个回答三元组已经回答过的问题的地方。不写这个键
是合法的,含义是 SDK 自己的默认值 —— 对一个 Apple 目标,clang 会供给它 ——
这与 Android 不同,那里 bionic 会直接拒绝一个不带版本的三元组。
是合法的,含义是定位到的 SDK 自己的版本:mcpp 用 `xcrun --sdk <name> --show-sdk-version`
读出它并写进同一个槽。clang 对不带版本的 iOS 三元组给的默认比机器上任何 SDK 都旧
(它拒绝了 libc++abi 用到的线程局部存储;在 Xcode 16.4 上实测),所以三元组在驱动看到
之前就带上了版本。Android 的差别在另一个方向:bionic 会直接拒绝一个不带版本的三元组。

### 模拟器的形状与它的边界

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.9.13.2"
version = "2026.9.14.1"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
18 changes: 18 additions & 0 deletions modules/platform/src/macos/macos.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ bool sdkroot_answers(const std::filesystem::path& candidate,
// today's behaviour for today's callers.
std::optional<std::filesystem::path> sdk_path(std::string_view sdk = sdk_macos);

// The version the located SDK was made for, as `xcrun --sdk <name>
// --show-sdk-version` reports it ("18.5"). Empty off macOS and when xcrun
// cannot answer. Read for an iOS row whose manifest states no deployment
// floor, since clang's own default for an unversioned iOS triple is older
// than any SDK on the machine (it refused thread-local storage; measured).
std::optional<std::string> sdk_version(std::string_view sdk);

// Built-in default deployment floor (rustc-style: every target has a
// baseline). 14.0 = the floor of the official LLVM static libc++
// archives; with the default-static stdlib this makes `mcpp run`
Expand Down Expand Up @@ -194,6 +201,17 @@ bool sdkroot_answers(const std::filesystem::path& candidate,
return sdk == sdk_macos;
}

std::optional<std::string> sdk_version(std::string_view sdk) {
#if defined(__APPLE__)
auto v = run_capture_trimmed(std::format("xcrun --sdk {} --show-sdk-version 2>/dev/null", sdk));
if (v.empty()) return std::nullopt;
return v;
#else
(void)sdk;
return std::nullopt;
#endif
}

std::optional<std::filesystem::path> sdk_path(std::string_view sdk) {
auto layout = sdk_layout(sdk);
if (!layout) return std::nullopt;
Expand Down
2 changes: 1 addition & 1 deletion modules/versioning/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.9.13.2";
inline constexpr std::string_view MCPP_VERSION = "2026.9.14.1";

} // namespace mcpp
71 changes: 57 additions & 14 deletions src/build/prepare.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import mcpp.modgraph.graph;
import mcpp.modgraph.scanner;
import mcpp.modgraph.validate;
import mcpp.toolchain.clang;
import mcpp.toolchain.hostflags; // the compile-token producer the package std module reuses
import mcpp.toolchain.cppfly;
import mcpp.toolchain.detect;
import mcpp.toolchain.dialect;
Expand Down Expand Up @@ -1664,10 +1665,12 @@ std::string min_platform_version(const mcpp::manifest::Manifest& m,
// one target, which is why they still share this function's single return
// and the single fingerprint slot behind it.
//
// Empty is a legal answer here and not a refusal, unlike Android's. The
// asymmetry is a measured property of the platforms rather than a policy:
// Darwin's driver supplies the SDK's own deployment target when the
// triple carries none, and bionic rejects the unversioned triple outright.
// Empty is a legal answer here and not a refusal, unlike Android's, and
// for the iOS rows prepare fills it with the located SDK's version before
// this is read: an unversioned `arm64-apple-ios` made clang refuse
// thread-local storage (measured, Xcode 16.4), so the driver's own
// default is not the SDK's. Bionic rejects the unversioned triple
// outright, which is the other half of the asymmetry.
if (t.is_ios()) return m.buildConfig.iosDeploymentTarget;
return mcpp::platform::macos::deployment_target(
m.buildConfig.macosDeploymentTarget);
Expand Down Expand Up @@ -2591,6 +2594,20 @@ prepare_build(bool print_fingerprint,
? mcpp::platform::macos::sdk_iphonesim
: mcpp::platform::macos::sdk_iphoneos;
appleSdkLocated = mcpp::platform::macos::sdk_path(which);
// AN UNSET FLOOR IS THE LOCATED SDK'S VERSION, READ RATHER THAN
// LEFT TO THE DRIVER. `docs/20` promised that an unversioned
// triple meant the SDK's own default; measured on macos-15 with
// Xcode 16.4, clang given `arm64-apple-ios` with no version
// refused thread-local storage for the target, which libc++abi
// uses, so the default it chose was older than any SDK on the
// machine. The version `xcrun` reports for the located SDK is the
// one the SDK was made for, and it enters the manifest here so
// that the fingerprint slot, the effective triple and every
// report read one value.
if (appleSdkLocated && m->buildConfig.iosDeploymentTarget.empty()) {
if (auto v = mcpp::platform::macos::sdk_version(which))
m->buildConfig.iosDeploymentTarget = *v;
}
if (!appleSdkLocated) {
// A CODE, BECAUSE THE MATRIX COMPARES REASONS AND NOT ONLY
// OUTCOMES. A refusal with no code is recorded as `other`,
Expand Down Expand Up @@ -10714,16 +10731,6 @@ prepare_build(bool print_fingerprint,
// DWARF. Same function, not a second copy of the decision.
for (auto& f : mcpp::toolchain::graph_runtime_compile_flags(*tc))
flags += " " + f;
// AND THE APPLE CROSS TARGET'S SDK, WHICH THE TOOLCHAIN RESOLUTION
// HAD ALREADY PUT ON THIS CHANNEL AND THIS ASSIGNMENT REPLACES.
// The module's C library is the SDK's on the iOS rows (the
// package supplies the C++ layer alone), and without the sysroot
// the precompile stops on `mbstate_t` inside libc++'s own
// headers. Measured on macos-15 with `llvm.libcxx` over
// `arm64-apple-ios18.0`: twenty "reference to unresolved using
// declaration" errors, every one a C library type.
if (!tc->appleSdkRoot.empty())
flags += " -isysroot " + mcpp::xlings::shq(tc->appleSdkRoot.string());
}
// Everything up to here says which machine the module is for; what
// follows says where its headers are. The codegen step needs only the
Expand Down Expand Up @@ -10779,6 +10786,42 @@ prepare_build(bool print_fingerprint,
// its own translation units see. Measured: without them the module
// reaches musl's <time.h> and stops on `clockid_t', a name that header
// declares only under the macro the package carries.
// THE PREBUILT C LIBRARY'S OWN TOKENS, FROM THE PRODUCER EVERY UNIT
// USES. A package that supplies the C++ layer over a prebuilt C
// library (`llvm.libcxx` over glibc, or over an Apple SDK) has no
// way to name that library's headers in its manifest, and the
// target-side broadcast below carries only graph layers. Without
// these the precompile reads whatever the driver finds on its own:
// on Linux the runner's `/usr/include` rather than the payload's
// glibc, a host dependency no report showed; on macOS nothing, and
// the precompile stops on `mbstate_t`; on the iOS rows nothing, and
// it stopped on the same name. Measured on 2026-09-14 across the
// three. `host_compile_tokens` is what every translation unit of the
// build gets, asked with the C++ layer marked as the graph's so that
// it withholds the payload's libc++ and emits the rest: the cfg
// bypass, the C library's directories, the SDK and the deployment
// floor. Same function, not a second copy.
//
// AND LAST ON THE COMMAND, after the package's own directories:
// `-isystem` order is search order, and libc++'s headers must precede
// the C library's, which libc++ states in as many words (`<cctype>`
// stops the build if it reaches a `<ctype.h>` that is not its own).
// Emitted ahead of them, glibc's `<math.h>` shadowed libc++'s wrapper
// and `<complex>` failed on `std::__builtin_isnan` (measured).
if (tc->cAbiPrebuilt) {
mcpp::toolchain::HostFlagOptions hopt;
hopt.cfgBypass = mcpp::toolchain::HostFlagOptions::CfgBypass::Always;
hopt.cAbiPrebuilt = true;
hopt.cxxFromGraph = true;
hopt.appleSdkRoot = tc->appleSdkRoot;
hopt.macosDeploymentTarget = mcpp::platform::macos::deployment_target(
m->buildConfig.macosDeploymentTarget);
for (auto& t : mcpp::toolchain::host_compile_tokens(
*tc, hopt, mcpp::toolchain::no_escape)) {
const auto q = " " + mcpp::xlings::shq(t);
if (flags.find(q) == std::string::npos) flags += q;
}
}
for (auto& f : targetSideUsage.cxxflags)
flags += " " + mcpp::xlings::shq(f);
tc->stdModuleFlags = flags;
Expand Down
9 changes: 9 additions & 0 deletions tests/e2e/663_a_graph_libcxx_over_the_payloads_c_library.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ grep -q -- '-nostdinc++' "$ninja" || fail "the compile lines carry no -nostdinc+
grep -q -- '-nostdlib++' "$ninja" || fail "the link line carries no -nostdlib++" "$ninja"
grep -q -- 'xim-x-llvm[^ ]*libc++\(abi\)\?\.a' "$ninja" \
&& fail "the link line still names the payload's libc++ archives" "$ninja"
# The package's std module was precompiled against the payload's C library,
# not against whatever the driver found on the machine: the recorded command
# names the glibc payload. Before this assertion the command carried only
# `--no-default-config -nostdinc++` and the runner's /usr/include stood in,
# a host dependency no report showed.
stdjson=$(grep -l 'llvm.libcxx\|libcxx' "$MCPP_HOME"/build-cache/v1/std/*/std-module.json 2>/dev/null | xargs -r ls -t | head -1)
[ -n "$stdjson" ] || fail "no std-module.json records the package's module under $MCPP_HOME/build-cache/v1/std"
grep -q 'xim-x-glibc' "$stdjson" \
|| fail "the package std module's command does not name the payload's glibc" "$stdjson"
bin=$(ls target/*/*/bin/app | head -1)
ldd "$bin" | grep -q 'libc++' && fail "the artefact still links a libc++ shared object" <(ldd "$bin")
out=$("$bin") || fail "the program exited non-zero"
Expand Down
Loading