Skip to content

feat: compile .c sources via a separate C rule#1

Merged
Sunrisepeak merged 1 commit intomainfrom
feat/c-language-support
May 8, 2026
Merged

feat: compile .c sources via a separate C rule#1
Sunrisepeak merged 1 commit intomainfrom
feat/c-language-support

Conversation

@Sunrisepeak
Copy link
Copy Markdown
Member

Summary

Adds a parallel C-language compile path so mcpp can build packages with
.c sources (and depend on real-world C libraries like mbedtls). Previously
every non-.cppm source went through one cxx_object rule with
-std=c++23 -fmodules; cc1plus rejects mainstream C constructs (implicit
void* conversion from malloc/calloc, restrict, ...), so the only
projects that built were ones whose .c files happened to be C++-clean.

What changed

  • BuildConfig (src/manifest.cppm) gains cflags / cxxflags / cStandard.
    Both the [build] TOML section and the mcpp = {} xpkg segment
    (synthesize_from_xpkg_lua) parse them — index entries can carry
    per-package flags.
  • src/build/ninja_backend.cppm: when the plan contains any .c source,
    derive a sibling C compiler from the resolved C++ binary
    (g++ → gcc, clang++ → clang, c++ → cc, prefix preserved for
    cross toolchains like x86_64-linux-musl-gcc) and emit cc / cflags /
    c_object alongside the existing cxx_* set. cflags defaults to
    -std=c11 plus the shared opt / pic / sysroot / -B / include baseline
    and the user tail.
  • Compile-edge dispatch picks cxx_module for .cppm, c_object for
    .c, cxx_object otherwise. Dyndep phase-1 skips .c (no P1689
    scan / -fmodules); the static-deps fallback skips BMI implicits for
    the same reason.
  • src/modgraph/scanner.cppm short-circuits .c with an empty
    SourceUnit so benign C identifiers (import_foo, module_t) can't
    be misparsed as module statements.

Verification

  • Unit: two new cases in tests/unit/test_manifest.cpp cover both parse
    paths ([build] and mcpp = {} segment).
  • E2E: new tests/e2e/26_c_language_support.sh scaffolds a mixed
    .c + modular-.cpp project, asserts build.ninja has c_object /
    cc / cflags, exercises user-supplied flag forwarding, and runs
    the resulting binary end-to-end.
  • Real library: built mbedtls 3.6.1 as a path dependency. All 108
    library/*.c files compile via the new c_object rule and link
    into a binary that prints the FIPS 180-4 sha256("abc") test
    vector and an AES-128-ECB sample correctly.
  • Full local e2e suite: 30 passed, 2 failed. The two failures
    (28_target_static.sh, 30_pack_modes.sh) are pre-existing — both
    reproduce on the v0.0.1 release binary unchanged.

Test plan

  • mcpp build (worktree, builds mcpp itself)
  • mcpp test — all 9 unit binaries pass
  • tests/e2e/26_c_language_support.sh (new)
  • Mixed-language smoke project: restrict + implicit void* + import std
  • mbedtls 3.6.1 path dep: 108 .c → linked binary → FIPS test vector
  • CI green

mcpp's ninja backend previously routed every non-.cppm source through one
`cxx_object` rule that invokes g++ with `-std=c++23 -fmodules`. .c files
went through cc1plus, which rejects mainstream C constructs (implicit
`void*` conversion from malloc/calloc, the `restrict` keyword, ...) and
silently warns on `-std=c++23`. Real-world C libraries — verified against
mbedtls 3.6.1's 108 sources — cannot build under that path.

Add a parallel C compile path:

* `BuildConfig` gains `cflags`, `cxxflags`, `cStandard`. Both the
  `[build]` TOML section and the `mcpp = {}` xpkg segment parse them, so
  index entries can carry per-package flags.
* The ninja backend probes the plan for `.c` sources; when present it
  derives a sibling C compiler from the resolved C++ binary
  (`g++ -> gcc`, `clang++ -> clang`, `c++ -> cc`, with prefix
  preserved for cross toolchains like `x86_64-linux-musl-gcc`) and
  emits `cc` / `cflags` / `c_object` alongside the existing `cxx_*`
  set. `cflags` carries `-std=$c_standard` (default `c11`) plus the
  shared opt / pic / sysroot / -B / include baseline and the user
  tail.
* Compile-edge dispatch picks `cxx_module` for `.cppm`, `c_object` for
  `.c`, and `cxx_object` otherwise. dyndep phase-1 skips `.c` so they
  don't go through P1689 / `-fmodules`; the static-deps fallback skips
  BMI implicit inputs for the same reason.
* Module scanner short-circuits `.c` files with an empty `SourceUnit`
  to avoid any chance of a benign C identifier
  (`import_foo`, `module_t`) being misparsed as a module statement.

Coverage: two unit tests in `test_manifest.cpp` cover both parse paths,
and a new `tests/e2e/26_c_language_support.sh` scaffolds a mixed
`.c` + modular-`.cpp` project, asserts the generated `build.ninja`
contains the expected `c_object` rule / `cc` variable / extension
dispatch, exercises user-supplied `cflags`/`cxxflags` forwarding, and
runs the resulting binary end-to-end.
@Sunrisepeak Sunrisepeak merged commit cc5d92b into main May 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant