Skip to content

refactor(main): port main from C to Zig - #169

Merged
ata-sesli merged 1 commit into
mainfrom
refactor/main-zig
Nov 11, 2026
Merged

ata-sesli merged 1 commit into
mainfrom
refactor/main-zig

Conversation

@ata-sesli

Copy link
Copy Markdown
Member

What does this PR do?

Ports src/main.c, the last C module in the planned port order (internal/cbm is out of scope), to Zig. It follows the pattern used by the earlier ports.

The program behind main() moves to src/main.zig as cbm_main, declared in the new src/main.h. That covers:

  • subcommand dispatch;
  • the one-shot cli mode;
  • the MCP server on stdio;
  • the watcher re-index callback;
  • the parent-death watchdogs;
  • signal handling;
  • the Windows UTF-8 argv rebuild.

checkExports in cbm_zig.zig checks cbm_main against its header.

Why main() itself stays in C

src/main.c shrinks from 857 lines to a short shim: int main(...) { return cbm_main(argc, argv, CBM_VERSION); }. Two constraints keep it in C:

  • Duplicate main. The test runners link the same libcbm_zig.a and define their own main() in tests/test_main.c. The Zig library is one object, so a main exported from it would clash with theirs.
  • Release version. The version is a C define: scripts/build.sh passes -DCBM_VERSION through CFLAGS_EXTRA. The Windows release links a Zig library that CI prebuilds in a separate job without that define. Reading the version in C keeps --version correct there.

Keeping the shim means no changes to build.zig, the Windows CI job or scripts/build.sh.

Other changes

  • cbm_zig_c.h: includes main.h. On Windows it also includes shellapi.h (CommandLineToArgvW), tlhelp32.h (parent PID lookup) and signal.h.
  • Makefile.cbm: lists src/main.zig and src/main.h as Zig library inputs.
  • Comments: comments that pointed at main.c now point at main.zig.

Behavior

The port is meant to behave identically: same help text, exit codes, shutdown order and watchdog policy. One small difference: the cli path now frees its argument buffer when server creation fails, where the C version leaked it.

Verification

Run locally:

  • libcbm_zig.a compiles for native macOS, x86_64-linux-gnu and x86_64-windows-gnu, the last two with Zova. cbm_main is exported and the library has no main symbol.
  • zig fmt --check src build.zig passed.
  • scripts/check-build-sources.sh passed.

Not run locally:

  • No full binary link or runtime smoke test (--version, cli, MCP stdio). CI is the check for those.
  • clang-format (not installed here).
  • The test suite.

Checklist

  • Commits are signed off (DCO)
  • Tests pass (make -f Makefile.cbm test) — left to CI
  • Lint passes (make -f Makefile.cbm lint-ci) — left to CI
  • Test coverage — no new tests; the entry point has no test harness

The program behind main() — subcommand dispatch, the one-shot CLI mode,
the MCP server loop, the watcher callback, the parent-death watchdogs,
signal handling and the Windows UTF-8 argv rebuild — moves to
src/main.zig as cbm_main, declared in src/main.h.

main() itself stays in a 3-line src/main.c that calls cbm_main:
- the test runners link the same Zig library and define their own
  main(), so the library cannot define one;
- the release version is a C define (scripts/build.sh passes
  -DCBM_VERSION via CFLAGS_EXTRA), and the Windows release links a Zig
  library that CI prebuilds without it.

The Windows declarations the port needs (CommandLineToArgvW, Toolhelp,
signal) are added to cbm_zig_c.h. Comments that pointed at main.c now
point at main.zig.

Signed-off-by: ata-sesli <atasesli@protonmail.com>
@ata-sesli
ata-sesli merged commit 2cdc629 into main Nov 11, 2026
10 checks 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