build: make the tinycc option reach the Rust side - #39052
Conversation
`--tinycc=off` (and the Android / FreeBSD defaults) only removed libtcc from the dependency graph. build_options.rs still emitted ENABLE_TINYCC as a hard-coded target predicate and bun_tcc_sys gated its tcc_* externs on a copy of the same predicate, so a linux/macos/windows build with the option off failed to link with undefined tcc_* symbols. rust.ts now passes --check-cfg=cfg(bun_tinycc) always and --cfg=bun_tinycc when cfg.tinycc, like socket_fault_injection. build_options.rs emits ENABLE_TINYCC = cfg!(bun_tinycc), tcc_externs! switches between the extern block and the stubs on the same cfg, and Cargo.toml registers the cfg for bare cargo, which compiles the stubs.
|
Updated 12:05 PM PT - Aug 15th, 2026
❌ @robobun, your commit 4a5c794 has some failures in 🧪 To try this PR locally: bunx bun-pr 39052That installs a local version of the PR into your bun-39052 --bun |
|
Status
|
|
Warning Review limit reached
Next review available in: 29 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (6)
Comment |
There was a problem hiding this comment.
LGTM — mechanical build-plumbing fix that follows the existing socket_fault_injection / bun_asan / bun_debug pattern exactly.
What was reviewed:
- Verified
cfg.tinyccin config.ts defaults to!(android || freebsd), so CI-built binaries are unchanged (linux/darwin/windows still get externs, android/freebsd still get stubs). - Confirmed
bun_tcc_sys/Cargo.tomlhas[lints] workspace = true, so the rootunexpected_cfgsregistration reaches it for barecargo check. - Checked
ffi_body.rs:993— theENABLE_TINYCCruntime gate is intact and now agrees with the extern/stub selection by construction. - No CODEOWNER-protected paths touched.
Extended reasoning...
Overview
This PR fixes --tinycc=off builds, which currently fail to link with 14 undefined tcc_* symbols because the option only reaches the C dependency graph (removes libtcc from the link) but not the Rust build (which still declares the externs). The fix threads cfg.tinycc through as a --cfg=bun_tinycc rustflag, and both the tcc_externs! macro's extern/stub selection and ENABLE_TINYCC (the runtime gate in bun:ffi's cc()) now key on that one cfg instead of three hand-maintained copies of the android/freebsd platform list.
Files touched: scripts/build/rust.ts (adds the cfg to rustflags), scripts/build/buildOptionsRs.ts (emits cfg!(bun_tinycc) instead of a hard-coded target predicate), src/tcc_sys/tcc.rs (gates on bun_tinycc instead of target_os), Cargo.toml (registers the cfg for bare cargo), src/bun_core/env.rs (doc comment only), and a new test block in test/internal/source-lints/build-rust.test.ts.
Security risks
None. This is build-configuration plumbing with no runtime code-path changes for shipped binaries. No user input, network, crypto, or auth surface.
Level of scrutiny
Medium — build infrastructure is load-bearing, but this change is a copy of an established pattern. The exact same shape (--check-cfg always, --cfg conditionally, registered in root unexpected_cfgs, gates both #[cfg] code and a cfg!() constant) already exists three times over for bun_asan, bun_debug, and socket_fault_injection in the lines immediately adjacent to the additions. The PR description explicitly addresses the two non-obvious consequences (RUSTFLAGS change triggers one full rebuild; bare cargo check now compiles the stub arm, matching the other cfgs' convention).
Other factors
- The new source-lint tests are configure-time only (no cargo invocation), use
tempDirfrom harness, and pin the full chain: generatedbuild_options.rs→ rustflags → Cargo.toml registration →tcc_externs!gating → no strayfn tcc_*elsewhere insrc/. The tests are hermetic and would fail on main. - I confirmed
bun_tcc_sysinherits workspace lints, so the Cargo.tomlunexpected_cfgsentry actually reaches the crate that uses the cfg. - The runtime consumer (
ffi_body.rs:993) still readsbun_core::Environment::ENABLE_TINYCCand early-returns; that constant now derives from the same cfg the externs gate on, so the two cannot disagree. - No CODEOWNER paths, no outstanding reviewer comments, no prior automated review from me.
Problem
--tinycc=offon linux, macOS or Windows fails to link:ld.lld: error: undefined symbol: tcc_new(and the other 13tcc_*symbols), every reference coming fromlibbun_rust.a(<bun_tcc_sys::tcc::State>::newand the other wrappers). Log in the details block below.scripts/build/deps/tinycc.tsisenabled: cfg => cfg.tinycc, so libtcc leaves the link, but the Rust build is configured identically either way:scripts/build/buildOptionsRs.ts:69emittedENABLE_TINYCCas a hard-coded!cfg!(any(target_os = "android", target_os = "freebsd"))(the option's default from config.ts, not its value), andsrc/tcc_sys/tcc.rs:30,35gated thetcc_*extern block and its stubs on a third copy of the same target list. The Zig build passed the option through as-Denable_tinycc=${cfg.tinycc}; the Rust port replaced that with these copies.ENABLE_TINYCCwould still betrueandcc()would call into a libtcc that is not there. The same applies to any change to the platform list in config.ts (for example enabling FreeBSD, feat(ffi): enable bun:ffi on FreeBSD #31528): it does not reach Rust unless both copies are edited too.Fix
rust.ts(cargoBuildInvocation) passes--check-cfg=cfg(bun_tinycc)always and--cfg=bun_tinyccexactly whencfg.tinycc, the waysocket_fault_injectionis passed today; that option has the same shape (a C symbol and the Rust extern referencing it have to be present or absent together).buildOptionsRs.tsemitsENABLE_TINYCC = cfg!(bun_tinycc);tcc_externs!picks the extern block undercfg(bun_tinycc)and the stubs undercfg(not(bun_tinycc));Cargo.tomlregisters the cfg inunexpected_cfgsnext to the other RUSTFLAGS cfgs;env.rsdocuments the constant.#[cfg]on an extern block cannot read a constant, so a cfg is the only thing that can carry the option to the externs, and derivingENABLE_TINYCCfrom that same cfg makes the runtime gate and the set of declared symbols agree by construction in every build, rather than through three lists kept in sync by hand. config.ts stays the one place that knows which platforms lack TinyCC.cfg.tinyccresolves to true on linux/darwin/windows (externs,ENABLE_TINYCC == true, as before) and false on android/freebsd (stubs,false, as before). The new--check-cfgflag changes RUSTFLAGS, so the first Rust build after this lands is a full one.cargo check,rust:check-all, clippy, miri) runs without rust.ts's rustflags and now compiles the stub arm on every target, where it previously compiled the extern arm on most of them. Both arms expand from the same macro input, and the extern arm is compiled by everybun bdand CI build. This is the existing convention forbun_asan/bun_debug/socket_fault_injection.test/internal/source-lints/build-rust.test.ts, newtinycc optionblock: resolves linux, windows and freebsd configs with and without the option, generates build_options.rs into a temp dir, and checks that the cfg it names is declared bycargoBuildInvocation()'s rustflags and set exactly whencfg.tinycc; that Cargo.toml registers it; and thattcc_externs!gates on that cfg with no target predicate, with no otherfn tcc_*anywhere in src. The 8 new tests fail on main; the tcc.rs test also fails with onlysrc/reverted.--tinycc=off: cargo is not re-run and the relink fails with 14 undefinedtcc_*symbols. With this change: 21 deps, links,cc()throwsbun:ffi cc() is not available in this build (TinyCC is disabled),dlopenandJSCallbackstill work,nmfinds notcc_*symbol. Defaultbun bdafterwards:tcc_*present,test/js/bun/ffi/cc.test.tspasses.cargo check -p bun_tcc_sysandcargo clippy -p bun_tcc_sysare clean both without flags and withRUSTFLAGS="--cfg bun_tinycc"(the workspace denies warnings).bunx tsc -p scripts/build/tsconfig.json: same 11 pre-existing errors before and after, none in the touched files.Background
tinyccoption (Config.tinyccin scripts/build/config.ts,--tinycc=on|offon the command line) defaults to off on Android and FreeBSD, which the vendored oven-sh/tinycc cannot target. It decides whether thetinyccdependency (libtcc, compiled straight into bun's link) is built. bun:ffi uses TinyCC only forcc();dlopen,JSCallbackand friends are implemented in the engine.build_options.rsis generated at configure time by buildOptionsRs.ts from the resolvedConfigandinclude!d bybun_core.bun_core::Environment::ENABLE_TINYCCcomes from it;ffi_body.rschecks it at the top ofcc()and throws before touching TinyCC.tcc_externs!in src/tcc_sys/tcc.rs is the one place thetcc_*C functions are declared. The wrappers that call them are reachable from generated JS bindings, so rustc emits the references regardless of the runtime check; a build without libtcc therefore needs same-named stub definitions (whichunreachable!()) in place of the extern block for the link to resolve.--cfg=<name>inCARGO_ENCODED_RUSTFLAGS, with a matching--check-cfgso rustc knows the name. Theunexpected_cfgslist in the root Cargo.toml declares the same names for cargo runs that do not go through rust.ts; those runs see every such cfg unset.Related: #38913 does the same for the
logsoption and touches adjacent lines in rust.ts, buildOptionsRs.ts and Cargo.toml; whichever lands second has a trivial rebase. After this, #31528 (FreeBSD support) only needs the config.ts and dependency changes.Link failure on main with
--tinycc=offConfigure-only evidence for the same thing:
bun scripts/build.ts --profile=release --tinycc=off --build-dir=/tmp/x --configure-onlyproduces a build.ninja with no libtcc edges, while/tmp/x/codegen/build_options.rsand the cargo edge'sCARGO_ENCODED_RUSTFLAGSare byte-identical to a default configure.With this change, the same
--tinycc=offbuild links, and: