Repository navigation
c_standard on a dependency is accepted and ignored: its C units are compiled at the root package's standard #695
Description
[build] c_standard only takes effect on the package being built. On a dependency, the value is parsed and accepted but never used. This applies to a path dependency, to a workspace member reached from another member, and to an index package, whether the value is in the package's own mcpp.toml or in a descriptor. The dependency's C units are compiled with the root package's c_standard, which defaults to c11. It also works in the other direction: a root that declares c_standard = "c99" puts -std=c99 on every C unit of every dependency.
I am not sure which behavior is intended, because the code supports both readings:
- The compile path treats the C standard as graph-wide. The file-level
$cflagsgets its-std=from the root manifest (flags.cppm L1014-L1016, assembled at L1081). A unit's own$unit_cflagscarry its package'scflagsorprivateBuild.cflags, but no standard (scanner.cppm L1466-L1477). The [workspace.build] defines are lost for sibling path dependencies #690 design record listsc_standardamong the keys that are read from the root manifest only, and calls that consistent (§3.1). - The dependency cache and the fingerprint treat it as belonging to each package. cache_key.cppm L581-L584 adds
__c_standard=<the package's own value>under the comment "A package may pin its own C standard; it reaches its own C units." That code came in with feat(build): scope the dependency cache per package, and make hits actually skip work (2026.7.30.2) #317. prepare_inputs.cppm adds each package'sc_standardto the fingerprint (L734-L737). The cache key includes the root's standard too (L529), so the cache itself is keyed correctly. - Manifests and descriptors are written as if the setting were per package. docs/04 §3.4 shows
c_standard = "c99"on a pure C library (L1658-L1666). openkal-musl declaresc_standard = "c99"(0.19.1 mcpp.toml L233). In mcpp-index, about a dozen descriptors declarec99, compat.ffmpeg declaresc17and compat.freetype declaresgnu11.
Whatever the intent, the current behavior is hard to diagnose, because nothing reports that the value was dropped. Three index descriptors record it as a problem with the value itself. compat.libaio says that c_standard = "gnu11" "accepts the string and still emits -std=c11", and it uses -D_GNU_SOURCE instead (source). compat.libinput and compat.libdrm carry the same note. mcpplibs/mcpp-index#277, mcpplibs/mcpp-index#281, mcpplibs/mcpp-index#298 and mcpplibs/mcpp-index#299 each ran into it. The value itself is fine: the same gnu11 works on the root package, as shown below. The standard reaches dependencies the same way the root's include directories did until #691 stopped that, through the file-level flags that every unit reads.
Reproduction
mcpp 2026.9.21.3, llvm@22.1.8, Linux x86_64 (host target), glibc 2.44. The setup is two sibling directories.
cdep/mcpp.toml:
[package]
name = "cdep"
version = "0.1.0"
[targets.cdep]
kind = "lib"
[build]
c_standard = "gnu11"
sources = ["src/cdep.c"]cdep/src/cdep.c:
#ifdef __STRICT_ANSI__
#error "strict ISO mode: this package's c_standard = \"gnu11\" did not reach this unit"
#endif
int cdep_answer(void) { return 42; }app/mcpp.toml:
[package]
name = "app"
version = "0.1.0"
[dependencies]
cdep = { path = "../cdep" }
[toolchain]
default = "llvm@22.1.8"app/src/main.cpp:
extern "C" int cdep_answer();
int main() { return cdep_answer() == 42 ? 0 : 1; }app$ mcpp build
Resolving toolchain
Resolved llvm@22.1.8 → @mcpp/registry/data/xpkgs/xim-x-llvm/22.1.8/bin/clang++
Target x86_64-unknown-linux-gnu
Inferred sources [src/**/*.{cppm,cpp,cc,c,S,s,asm}]
Inferred target app (bin from src/main.cpp)
Compiling app v0.1.0 (.)
Compiling cdep (path)
error: build failed
failed: obj/mcpplibs_cdep/src/cdep.o
.../cdep/src/cdep.c:2:2: error: "strict ISO mode: this package's c_standard = \"gnu11\" did not reach this unit"
2 | #error "strict ISO mode: this package's c_standard = \"gnu11\" did not reach this unit"
| ^
1 error generated.
In the generated build.ninja, the file-level line starts with cflags = -std=c11 -O0 -g .... The cdep.o edge has unit_cflags = -D__MCPP_TARGET_LINUX__=1 and no -std=.
Next, put the same key on the consumer instead:
# app/mcpp.toml
[build]
c_standard = "gnu11"The build now succeeds, and cdep.c's entry in compile_commands.json carries -std=gnu11. With c_standard = "c99" on app instead, cdep.c is compiled with -std=c99 and fails as before.
The result is the same in these other setups:
- With
--target x86_64-linux-musl, after addingopenkal-llvm-runtime = "0.15.1"to app. openkal-musl 0.19.1 declaresc99in its own manifest, yet all 1343 of its C units follow the root:-std=c11by default, and-std=gnu11or-std=c99when app declares one of those. - When app and cdep are the two members of one workspace (
mcpp build -p app). - With a descriptor-defined package. compat.zlib 1.3.2's descriptor says
c_standard = "c11". A consumer that declaresc_standard = "c99"gets-std=c99on all 15 zlib units.
I tested on 2026.9.21.3 and could not run 2026.9.25.1. #691 changed how a workspace member inherits from the workspace when it is reached as a dependency. However, the lines cited above are unchanged on main, and the #690 record describes the root-only reading, so I expect the same result there.
Suggestion
Either of these would remove the silent part:
- Per package: carry each package's own standard to its own C units.
make_planalready does this for C++.implementationStandardFlag(plan.cppm L1712-L1769) appends the compiler's spelling of-std=to a C++-layer provider'spackageCxxflagswhen its level differs from the graph's. Doing the same for.cunits andpackageCflagswould work because$unit_cflagscomes after$cflagsin thec_objectrule, so the package's flag wins on GCC and Clang. It would need the same MSVC skip that the file-level flag has at flags.cppm L1076-L1082. The cache key already covers it. - Graph-wide: keep one C standard per graph. In that case, warn about or refuse
c_standardon a non-root package and in a descriptor, and say so in docs/04. The docs/07 merge table listsc_standardas a member-level value, even though it only has an effect when that member is the one being built. Under this option, the cache-key comment and the per-package fingerprint entry would no longer describe anything real.
Environment
mcpp self env (home paths shortened):
MCPP_HOME = <home>
xlings binary = <home>/registry/bin/xlings
xlings pinned = 2026.9.16.1
xlings home = <home>/registry
config = <home>/config.toml
build cache = <home>/build-cache/v1
meta cache = <home>/cache
default toolchain = (none — run `mcpp toolchain install gcc 16.1.0`)
Index repos:
mcpplibs https://github.com/mcpplibs/mcpp-index.git [artifact] (default)
Toolchain = gcc 20260810 (x86_64-pc-linux-gnu)
std module src = /usr/include/c++/16/bits/std.cc
Linux x86_64, ldd (GNU libc) 2.44.
Activity
2026.9.26.1: paths in UTF-8, a C standard per package, a graph link w…
mcpp 2026.9.26.1 as the measured client, and the graph's own Linux ta…
speak-agent commentedon Sep 25, 2026
Fixed by #698, released as 2026.9.26.1 (commit
f61b46632).c_standardwas parsed, entered the cache key and the fingerprint, and was never applied: the file-level$cflagscarried the root package's standard, so every dependency's C units compiled at the root's value.The fix:
c11; it never comes from the consumer (decision D2 of the record). The file-level$cflagscarries the default (src/build/flags.cppm:1062), and a package that declares another value has its-std=appended to its own C units in the plan (src/build/plan.cppm:1751-1800), including a C entrymainthat the plan synthesizes outside the unit loop.src/build/cache_key.cppm:589), so two consumers that differ only inc_standardshare one cache entry for a C dependency.cl.exeis not given a per-package/std:in this release. A declared standard it does not apply is reported as a warning instead of being dropped silently (CStandardPerPackage.ClRecordsWhatItDoesNotApply). Mapping the valuescl.exeaccepts waits for a CI row that builds the index withcl.exe(record §12.1, W3b).Criteria: the unit tests
CStandardPerPackage.*(seven,tests/unit/test_c_standard_per_package.cpp), andtests/e2e/777_c_standard_applies_to_the_package_that_declares_it.sh, which fails on 2026.9.25.1 and passes on 2026.9.26.1. mcpp-index#469 builds the whole index with 2026.9.26.1 and corrects the three descriptor comments that attributed a failure tognu11. The published-artefact sandbox check (the root atc99, one dependency atgnu11, one at the defaultc11; the program runs) passes on 2026.9.26.1, re-run today.Update with
xlings update mcpp -y.Record:
.agents/docs/2026-09-25-issues-693-696-triage-and-repair-plan.md§4.Closing.