Skip to content

c_standard on a dependency is accepted and ignored: its C units are compiled at the root package's standard #695

Description

@yspbwx2010

[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:

  1. 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.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions