[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:
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 adding openkal-llvm-runtime = "0.15.1" to app. openkal-musl 0.19.1 declares c99 in its own manifest, yet all 1343 of its C units follow the root: -std=c11 by default, and -std=gnu11 or -std=c99 when 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 declares c_standard = "c99" gets -std=c99 on 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_plan already does this for C++. implementationStandardFlag (plan.cppm L1712-L1769) appends the compiler's spelling of -std= to a C++-layer provider's packageCxxflags when its level differs from the graph's. Doing the same for .c units and packageCflags would work because $unit_cflags comes after $cflags in the c_object rule, 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_standard on a non-root package and in a descriptor, and say so in docs/04. The docs/07 merge table lists c_standard as 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.
[build] c_standardonly 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'sc_standard, which defaults to c11. It also works in the other direction: a root that declaresc_standard = "c99"puts-std=c99on every C unit of every dependency.I am not sure which behavior is intended, because the code supports both readings:
$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).__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.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_SOURCEinstead (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 samegnu11works 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:
cdep/src/cdep.c:
app/mcpp.toml:
app/src/main.cpp:
In the generated build.ninja, the file-level line starts with
cflags = -std=c11 -O0 -g .... The cdep.o edge hasunit_cflags = -D__MCPP_TARGET_LINUX__=1and no-std=.Next, put the same key on the consumer instead:
The build now succeeds, and cdep.c's entry in compile_commands.json carries
-std=gnu11. Withc_standard = "c99"on app instead, cdep.c is compiled with-std=c99and fails as before.The result is the same in these other setups:
--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.mcpp build -p app).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:
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.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):Linux x86_64,
ldd (GNU libc) 2.44.