Repository navigation
A required llvm resolves to the NDK's version on a fresh home; llvm.libcxx cannot be used by a c++20 graph; a tool's dependency-rooted source passes MAX_PATH on Windows #641
Description
Activity
4.
mcpp packtakes no--featuresMeasured. mcpp 2026.9.14.3,
mcpp pack --format setup --features windows-installeron windows-2022 (andmcpp pack --format bogus --features xlocally):error: unknown option: --features.build,runandtestregister the option (src/cli.cppm);packdoes not, and neither does any environment channel.Why it matters. A package that needs a host tool only for one distribution format cannot scope it:
[target.<sel>.feature-deps.<f>]withtools = [...]is the declaration for "only when asked", andpackis the command that asks. A UI framework's Windows Setup.exe runs a second program of the application's (its installer interface, a tool of the application'sinstallerpackage), which CMake builds only for a package build; under mcpp the declaration has to be unconditional on the Windows rows, so every Windows build compiles that tool (and the framework under it) once.Expected.
mcpp pack --features <LIST>activates root-package features for both passes of a dispatched format, asmcpp build --featuresdoes for a build.CI check. An e2e case whose root declares a feature-gated
tools = [...]dependency and a build program that requiresmcpp::dep_binunder--format <f>:mcpp pack --format <f> --features <gate>succeeds, andmcpp buildwithout the feature builds no tool.5. A shared dependency does not link against a graph-built standard library
Measured. macos-15, mcpp 2026.9.14.3, llvm 22.1.8. An application at c++23 declares
[target.'cfg(any(os = "macos", os = "ios"))'.dependencies] llvm.libcxx = "22.1.8.2"and its framework dependency withlinkage = "shared". Linking the dependency'slib<fw>.dylibfails with every libc++ / libc++abi symbol undefined (__cxa_guard_acquire,vtable for std::invalid_argument,std::out_of_range::~out_of_range(), …), followed by the freestanding hintnothing in this build defines operator new. The same application with the framework linked statically builds, packs and runs, and so does the shared framework with the toolchain's libc++.Reading.
llvm.libcxxprovides the hosted standard library and the build links with-nostdlib++. The program's link gets the package's archives, and the dependency's shared-library link does not, so a shared object built in that graph has no C++ runtime to resolve against.Expected. A shared library linked in a graph whose standard library is a graph package links that package the way the program does (or resolves it from one shared copy), so the shared form of a dependency is available with the application's own standard library.
CI check. On macOS: a root with
llvm.libcxxand a path dependencykind = "shared"(orlinkage = "shared") builds, andotool -Lon the dependency names no libc++ dylib.- added a commit that references this issue
on Sep 14, 2026 Released in mcpp 2026.9.15.2 (#644), with
llvm.libcxx22.1.8.3 andopenkal-llvm-runtime0.9.7 in the index (mcpplibs/mcpp-index#428). The design and measurement records are.agents/docs/2026-09-15-641-642-*.md.1. A required llvm on a fresh home. The version is now taken from the pins that name the same payload, so
requires = ["mcpp:compiler=llvm"]resolvesllvm@22.1.8. The comparison also failed in the other direction:compiler=emsdkandcompiler=android-ndkfound no pin and said no row pins one; they now take their own payload's pin.2.
llvm.libcxxunder a c++20 graph. Two causes. The package wrote[build] cxx_standard, a key no engine has read, so it stated no standard at all; and the engine had no way to compile a standard library's sources at a level other than the graph's. From 2026.9.15.2 a package that provides the C++ layer and states[package] standardcompiles its implementation units at exactly that level, while its std module and every other unit stay at the graph's level.llvm.libcxx22.1.8.3 statesstandard = "c++23"; a root atstandard = "c++20"over it builds and runs (measured on Linux, macOS and the three iOS rows in the package's CI). The rule is scoped to C++-layer providers; for an ordinary library, a header whose declarations depend on__cpluspluswould split its objects from its consumers', so the one-standard rule stands there.3. MAX_PATH in a tool build. Of the 271 characters, 181 preceded
obj/, so shortening the object address alone could not fix it. Three changes: a source outside its declaring package is addressedobj/<declaring>/__pkg/<owner>/<path inside the owner>(or__ext/<hash>when no package contains it), which no longer grows with the distance between the two roots; the host-tool sub-build runs in<cache>/tool/.build/<hash>; and the engine subcommands ninja invokes open files through extended-length paths on Windows. Measured on windows-2022 (LongPathsEnabled= 1): a tool build whose sub-build paths reach 279 characters builds (e2e 698).4.
mcpp pack --features. Accepted, and applied to every build pass of the pack, both passes of a dispatched format included.mcpp run --format <f> --features <list>now hands its features to the pack it performs; before, the pack built without them.5. A shared dependency over
llvm.libcxx. The runtime package's objects are linked into the program and compiled with hidden visibility, so a dependency's C++ shared library had no C++ runtime (on Linuxld.lldrefused the program instead of the dylib). mcpp now refuses the build before compiling (reasonshared-library-cxx-runtime) and names the way out: link the dependency static on its edge (offered when the package does not itself statekind = "shared"), or state a private copy of the runtime for shared libraries:[build] cxx_runtime = { shared = "self-contained" }
Under that statement each shared library links the runtime package's objects. Two properties to know, both measured through
llvm.libcxx's CI on Linux and macOS arm64:- each image holds its own type information for libc++'s classes, so a
std::runtime_errorthrown in the shared library is not caught by that class in the program (a class the library defines is); - on Apple platforms every unit of such a graph is compiled with hidden visibility, so the shared library exports only the declarations marked
[[gnu::visibility("default")]], as a framework's export macro already does for its CMake shared build.
llvm.libcxx'sexamples/shared-dependencyis the complete shape.- each image holds its own type information for libc++'s classes, so a
- added a commit that references this issue
on Sep 15, 2026
中文摘要
在 mcpp 2026.9.14.3 上把一个 C++20 框架的应用迁到 macOS 12.0 下限(
llvm.libcxx)时实测到两个引擎问题,外加一个 Windows 工具构建的路径问题:requires = ["mcpp:compiler=llvm"]在没有安装 llvm 的全新 home 上选出llvm@30.0.16248370。 回退路径从目标行的 pin 里挑“llvm 家族”的最高版本,而android-ndk@30.0.16248370归一到 llvm 家族,于是 NDK 的版本号被当成 llvm 版本去安装,失败。llvm.libcxx编译不过。 一个模块图只有一个标准,llvm.libcxx自己声明的cxx_standard = "c++23"不生效;libc++ 22 的源码需要 C++23(std::string::resize_and_overwrite)。结果:处在 c++20 基线的应用无法在 macOS / iOS 使用源码构建的标准库。.ddi读不回来。每项都附最小复现与可在 CI 上验证的判据。
1. A required compiler family resolves to the Android NDK's version
Measured. macOS 15 runner, empty
~/.mcpp(no toolchain installed), mcpp 2026.9.14.3, a root package withmcpp build:Cause.
resolve_required_family(src/build/prepare.cppm) has two sources for the version of a required family: what is installed, then "the vocabulary's own pins". The second loop keeps everyknown_targets()row whose pin parses to the requested family:android-ndk@30.0.16248370(andemsdk@…) parse toFamily::Llvm— correctly, the compiler is clang — so the highest "llvm" version among the pins is the NDK's30.0.16248370, and the resulting specllvm@30.0.16248370names a payload that does not exist. With any llvm installed the first source answers and the bug is invisible, which is why it shows only on a fresh home (a CI runner whose cache key changed).Expected. The fallback considers only pins whose payload is the family's own (
llvm@…, notandroid-ndk@…oremsdk@…), e.g. by comparingToolchainSpec::payloadName/ the pin's package name rather than the normalised family.CI check. An e2e case with
MCPP_HOMEempty and a root that depends on a package declaringrequires = ["mcpp:compiler=llvm"]: the resolved spec is anllvm@<version>that exists in the index (and the build proceeds), on a Linux and a macOS runner.2.
llvm.libcxxcannot be used by a c++20 graphMeasured. macOS 15 and the
aarch64-ios-simrow, mcpp 2026.9.14.3, llvm 22.1.8 installed, a root atstandard = "c++20"depending onllvm.libcxx = "22.1.8.2":The same graph at
standard = "c++23"builds (the iOS rows have been built that way since the package landed for #630).Why it matters.
llvm.libcxxis how an application gets a standard library built for its own deployment floor on the Apple rows: on macOS it lets an application keep the 12.0 floor a CMake build of the same project uses (the payload's static libc++ is built for 14.0), and on iOS it is the only libc++ whose objects match its headers (#630). A framework whose ABI baseline is C++20 therefore cannot build its applications at C++20 on either Apple row, and a framework that declares the package for its consumers cannot build itself at its own root on macOS.Cause. A module graph has one standard (
docs/07-workspace.md§4), so the package's[build] cxx_standard = "c++23"is not applied; libc++ 22's implementation sources are written for C++23, which is how upstream builds them. The "declares a higher standard" diagnostic is deliberately not emitted for an index package, so nothing points at the cause either.Asks (either would do).
llvm.libcxx,std.cppm/std.compat.cppm) stay at the graph's level. This is safe for exactly the reason the one-standard rule exists: no BMI crosses those units. At minimum for a package thatprovides = ["hosted-standard-library"], which is where the implementation's own language level is not the consumer's business.CI check. The existing Apple fixtures that depend on
llvm.libcxx(plugins'ios-app-consumer, thellvm.libcxxpackage's own jobs) built once more with the root atstandard = "c++20": the build succeeds and the program runs.3. Windows: a tool's source under a dependency's root exceeds MAX_PATH in the tool store
Measured. windows-2022, mcpp 2026.9.14.3, a path package built as a host tool (
tools = [...]) whose build program adds a source that lies under its dependency's root (mcpp::source("<dependency root>/platform/windows/x.cpp")):Reading. The unit is owned by the dependency (longest root), so its object directory mirrors the path from the requesting package to it. The scratch is 133 characters,
target\x86_64-pc-windows-msvc\<16>\adds 48, and the.ddipath is 90: 271 in all. The scan step reports success, and the.ddiis then not readable through the relative pathmcpp dyndepopens. A source added from a directory outside every package root is filed asobj/<name>instead (measured on Linux), which keeps the path well under the limit. Not isolated further than that; the inference is MAX_PATH on the relative open.Expected. Paths the engine opens itself are long-path safe on Windows (
\\?\-prefixed absolute paths), and/or the tool store layout leaves room for a dependency's mirrored object directories.CI check. A Windows e2e case: a tool package whose build program adds a source under its dependency's root, built through
tools = [...]from a consumer; the tool builds.