Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ sign.json
src/bake/generated.ts
src/generated_enum_extractor.zig
src/jsc/bindings-obj
# The two generators below were removed in the $zig -> $native rename, but a
# build of an older checkout can still leave these on disk; keep them ignored
# so they are never accidentally committed.
src/jsc/bindings/GeneratedJS2Native.zig
src/jsc/bindings/GeneratedBindings.zig
src/bun.js/debug-bindings-obj
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ test("(multi-file test) my feature", async () => {
- **TypeScript** (`src/js/`): Built-in JavaScript modules with special syntax (see JavaScript Modules section)
- **Generated code**: Many `.rs` and `.cpp` files are auto-generated from `.classes.ts` and other sources. The build regenerates them automatically when their inputs change.

You will see `.zig` files alongside many `.rs` files (e.g. `fetch.zig` next to `fetch.rs`). These are the **original Zig implementation, kept only as a porting reference** — they are **not compiled** and **not shipped**. New code goes in `.rs`. When fixing a bug or porting a behavior, the `.zig` sibling is the source of truth for *intended semantics*: read it, then make the `.rs` match. Never add new behavior to a `.zig` file.
You will see `.zig` files alongside many `.rs` files (e.g. `fetch.zig` next to `fetch.rs`). These are the **original Zig implementation, kept only as a porting reference** — they are **not compiled**, **not shipped**, and not read by the build or codegen (the `$native()` JS-binding macro resolves `.rs` files). New code goes in `.rs`. When fixing a bug or porting a behavior, the `.zig` sibling is the source of truth for *intended semantics*: read it, then make the `.rs` match. Never add new behavior to a `.zig` file.

### Core Source Organization

Expand Down
114 changes: 34 additions & 80 deletions scripts/build/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,6 @@ import { writeIfChanged } from "./fs.ts";
import type { Ninja } from "./ninja.ts";
import { quote, quoteArgs } from "./shell.ts";

/**
* Codegen outputs that land in `src/` instead of `codegenDir`. The zig
* compiler refuses to import files outside its source tree, so these two
* generated `.zig` files live in `src/jsc/bindings/` (gitignored).
*
* Consumers of `sources.zig` (the `src/**\/*.zig` glob) must filter these
* out — they're OUTPUTS of codegen, not inputs.
*
* Paths are relative to repo root. This list is the single source of truth;
* `globAllSources()` does NOT hardcode these.
*/
export const zigFilesGeneratedIntoSrc = [
"src/jsc/bindings/GeneratedBindings.zig",
"src/jsc/bindings/GeneratedJS2Native.zig",
] as const;

// The individual emit functions take these four params. Bundled to keep
// signatures short.
interface Ctx {
Expand Down Expand Up @@ -132,8 +116,8 @@ export function registerCodegenRules(n: Ninja, cfg: Config): void {
// ship with the build host's platform baked in.
//
// restat = 1 because most scripts use writeIfNotChanged(). Scripts that
// don't (generate-jssink, ci_info) always write → restat is a no-op for
// them, no harm.
// don't (generate-jssink) always write → restat is a no-op for them, no
// harm.
const env = hostWin
? `set TARGET_PLATFORM=${platform}&& set TARGET_ARCH=${arch}&& `
: `TARGET_PLATFORM=${platform} TARGET_ARCH=${arch} `;
Expand Down Expand Up @@ -210,10 +194,10 @@ export interface CodegenOutputs {
/** All codegen outputs — use for phony target `codegen`. */
all: string[];

/** Outputs that zig `@embedFile`s or imports. */
/** Outputs the Rust build `include!`s or embeds. */
rustInputs: string[];

/** Outputs that zig needs to exist but doesn't embed (debug bake runtime). */
/** Outputs the Rust build needs to exist but doesn't embed (debug bake runtime). */
rustOrderOnly: string[];

/** Generated .cpp files. Compiled alongside handwritten C++ in bun.ts. */
Expand Down Expand Up @@ -245,9 +229,6 @@ export interface CodegenOutputs {
/** The bindgenv2 .cpp outputs (compiled separately from handwritten C++). */
bindgenV2Cpp: string[];

/** The bindgenv2 .zig outputs (legacy artifacts, retained for reference). */
bindgenV2Zig: string[];

/**
* Stamp output from `bun install` at repo root.
* The esbuild tool and the cppbind lezer parser live here. Any
Expand Down Expand Up @@ -280,7 +261,6 @@ export function emitCodegen(n: Ninja, cfg: Config, sources: Sources): CodegenOut
cppHeaders: [],
cppAll: [],
bindgenV2Cpp: [],
bindgenV2Zig: [],
rootInstall,
};

Expand All @@ -302,7 +282,6 @@ export function emitCodegen(n: Ninja, cfg: Config, sources: Sources): CodegenOut
emitGeneratedClasses(ctx);
emitHostExports(ctx);
emitCppBind(ctx);
emitCiInfo(ctx);
emitJsModules(ctx);
Comment thread
robobun marked this conversation as resolved.
emitBakeCodegen(ctx);
emitBindgenV2(ctx);
Expand Down Expand Up @@ -584,11 +563,7 @@ function emitErrorCode({ n, cfg, o, dirStamp }: Ctx): void {
resolve(cfg.cwd, "src", "jsc", "bindings", "ErrorCode.h"),
];

const outputs = [
resolve(cfg.codegenDir, "ErrorCode+List.h"),
resolve(cfg.codegenDir, "ErrorCode+Data.h"),
resolve(cfg.codegenDir, "ErrorCode.zig"),
];
const outputs = [resolve(cfg.codegenDir, "ErrorCode+List.h"), resolve(cfg.codegenDir, "ErrorCode+Data.h")];
Comment thread
robobun marked this conversation as resolved.

n.build({
outputs,
Expand All @@ -597,13 +572,12 @@ function emitErrorCode({ n, cfg, o, dirStamp }: Ctx): void {
orderOnlyInputs: [dirStamp],
vars: {
cwd: cfg.cwd,
desc: "ErrorCode.{zig,h}",
desc: "ErrorCode.h",
args: shJoin(cfg, ["run", script, cfg.codegenDir]),
},
});

o.all.push(...outputs);
o.rustInputs.push(...outputs);
o.cppHeaders.push(outputs[0]!, outputs[1]!);
}

Expand All @@ -617,7 +591,6 @@ function emitGeneratedClasses({ n, cfg, sources, o, dirStamp }: Ctx): void {
resolve(cfg.codegenDir, "ZigGeneratedClasses+DOMClientIsoSubspaces.h"),
resolve(cfg.codegenDir, "ZigGeneratedClasses+DOMIsoSubspaces.h"),
resolve(cfg.codegenDir, "ZigGeneratedClasses+lazyStructureImpl.h"),
resolve(cfg.codegenDir, "ZigGeneratedClasses.zig"),
resolve(cfg.codegenDir, "ZigGeneratedClasses.lut.txt"),
// Rust sibling: include!()'d by src/runtime/generated_classes.rs. Must be
// a declared output so the cargo edge (which lists this in rustInputs)
Expand All @@ -629,12 +602,12 @@ function emitGeneratedClasses({ n, cfg, sources, o, dirStamp }: Ctx): void {
n.build({
outputs,
rule: "codegen",
inputs: [script, ...sources.zigGeneratedClasses],
inputs: [script, ...sources.classesTs],
orderOnlyInputs: [dirStamp],
vars: {
cwd: cfg.cwd,
desc: "ZigGeneratedClasses.{zig,cpp,h}",
args: shJoin(cfg, ["run", script, ...sources.zigGeneratedClasses, cfg.codegenDir]),
desc: "ZigGeneratedClasses.{cpp,h}",
args: shJoin(cfg, ["run", script, ...sources.classesTs, cfg.codegenDir]),
},
});

Expand Down Expand Up @@ -677,15 +650,14 @@ function emitHostExports({ n, cfg, sources, o, dirStamp }: Ctx): void {

o.all.push(output);
// bun_runtime/build.rs panics if this file is absent, so the rust_build edge
// must wait on it — `rustInputs` is the implicit-dep list both zig and the
// cargo edge consume.
// must wait on it — `rustInputs` is the implicit-dep list the cargo edge
// consumes.
o.rustInputs.push(output);
}

function emitCppBind({ n, cfg, sources, o, dirStamp }: Ctx): void {
const script = resolve(cfg.cwd, "src", "codegen", "cppbind.ts");

const output = resolve(cfg.codegenDir, "cpp.zig");
const outputRs = resolve(cfg.codegenDir, "cpp.rs");

// Write the .cpp file list for cppbind to scan. Build system owns the
Expand All @@ -703,7 +675,7 @@ function emitCppBind({ n, cfg, sources, o, dirStamp }: Ctx): void {
writeIfChanged(cxxSourcesFile, cxxSourcesLines.join("\n") + "\n");

n.build({
outputs: [output, outputRs],
outputs: [outputRs],
rule: "codegen",
inputs: [script],
// cppbind scans ALL .cpp files for [[ZIG_EXPORT]] annotations. Every
Expand All @@ -723,39 +695,16 @@ function emitCppBind({ n, cfg, sources, o, dirStamp }: Ctx): void {
orderOnlyInputs: [dirStamp],
vars: {
cwd: cfg.cwd,
desc: "cpp.{zig,rs} (cppbind)",
desc: "cpp.rs (cppbind)",
// cppbind.ts takes: <srcdir> <codegendir> <cxx-sources>. No `run` —
// direct script invocation (`${BUN_EXECUTABLE} ${script} ...`).
args: shJoin(cfg, [script, resolve(cfg.cwd, "src"), cfg.codegenDir, cxxSourcesFile]),
},
});

o.all.push(output, outputRs);
o.all.push(outputRs);
// bun_jsc `include!`s cpp.rs — the cargo edge must order after this.
o.rustInputs.push(output, outputRs);
}

function emitCiInfo({ n, cfg, o, dirStamp }: Ctx): void {
const script = resolve(cfg.cwd, "src", "codegen", "ci_info.ts");
const output = resolve(cfg.codegenDir, "ci_info.zig");

// CMake lists JavaScriptCodegenSources as deps here, but ci_info.ts doesn't
// read any of those files — it's a pure static data generator. The CMake
// dep list is wrong (copy-paste from bundle-modules). We use just the script.
n.build({
outputs: [output],
rule: "codegen",
inputs: [script],
orderOnlyInputs: [dirStamp],
vars: {
cwd: cfg.cwd,
desc: "ci_info.zig",
args: shJoin(cfg, [script, output]),
},
});

o.all.push(output);
o.rustInputs.push(output);
o.rustInputs.push(outputRs);
}

function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void {
Expand All @@ -767,9 +716,20 @@ function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void {
// ($makeErrorWithCode(N, ...)); without this dep an ErrorCode.ts edit leaves
// stale error numbers in the JS bundles while the C++ enum regenerates.
const errorCodeInput = resolve(cfg.cwd, "src", "jsc", "bindings", "ErrorCode.ts");

// Written into src/ (not codegenDir) — see zigFilesGeneratedIntoSrc at top.
const js2nativeZig = resolve(cfg.cwd, zigFilesGeneratedIntoSrc[1]);
// generate-js2native.ts scans .rs paths to resolve $native() keys and
// derive symbol/crate paths. Write the path set at configure time so an
// added/removed/renamed .rs file invalidates this edge without taking the
// full ~1400-file Rust glob as direct inputs.
mkdirSync(cfg.codegenDir, { recursive: true });
const js2nativeRustSources = resolve(cfg.codegenDir, "js2native-rust-sources.txt");
writeIfChanged(
js2nativeRustSources,
sources.rust
.filter(p => p.endsWith(".rs"))
.map(p => relative(cfg.cwd, p).replace(/\\/g, "/"))
.sort()
.join("\n") + "\n",
);

const outputs = [
resolve(cfg.codegenDir, "WebCoreJSBuiltins.cpp"),
Expand All @@ -781,7 +741,6 @@ function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void {
resolve(cfg.codegenDir, "NativeModuleImpl.h"),
resolve(cfg.codegenDir, "SyntheticModuleType.h"),
resolve(cfg.codegenDir, "GeneratedJS2Native.h"),
js2nativeZig,
// Rust sibling: include!()'d by src/runtime/generated_js2native.rs. Must be
// a declared output so the cargo edge re-invokes when bundle-modules.ts /
// generate-js2native.ts changes — the includer shim's mtime never moves.
Expand All @@ -796,6 +755,7 @@ function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void {
outputs,
rule: "codegen",
inputs: [script, ...sources.js, ...sources.jsCodegen, extraInput, errorCodeInput],
implicitInputs: [js2nativeRustSources],
orderOnlyInputs: [dirStamp],
vars: {
cwd: cfg.cwd,
Expand Down Expand Up @@ -885,8 +845,7 @@ function emitBindgenV2({ n, cfg, sources, o, dirStamp }: Ctx): void {
assert(allOutputs.length > 0, "bindgenv2 list-outputs returned no files");

const cppOutputs = allOutputs.filter(p => p.endsWith(".cpp"));
const zigOutputs = allOutputs.filter(p => p.endsWith(".zig"));
const other = allOutputs.filter(p => !p.endsWith(".cpp") && !p.endsWith(".zig"));
const other = allOutputs.filter(p => !p.endsWith(".cpp"));
assert(other.length === 0, `bindgenv2 emitted unexpected output type: ${other.join(", ")}`);

n.build({
Expand All @@ -909,35 +868,30 @@ function emitBindgenV2({ n, cfg, sources, o, dirStamp }: Ctx): void {

o.all.push(...allOutputs);
o.bindgenV2Cpp.push(...cppOutputs);
o.bindgenV2Zig.push(...zigOutputs);
o.rustInputs.push(...zigOutputs);
}

function emitBindgen({ n, cfg, sources, o, dirStamp }: Ctx): void {
const script = resolve(cfg.cwd, "src", "codegen", "bindgen.ts");

// Written into src/ (not codegenDir) — see zigFilesGeneratedIntoSrc at top.
const zigOut = resolve(cfg.cwd, zigFilesGeneratedIntoSrc[0]);
const cppOut = resolve(cfg.codegenDir, "GeneratedBindings.cpp");

// bindgen.ts scans src/ for .bind.ts files itself — this list is only for
// ninja dependency tracking. New .bind.ts files need a reconfigure to be
// picked up (next glob gets them).
n.build({
outputs: [cppOut, zigOut],
outputs: [cppOut],
rule: "codegen",
inputs: [script, ...sources.bindgen],
orderOnlyInputs: [dirStamp],
vars: {
cwd: cfg.cwd,
desc: ".bind.ts → GeneratedBindings.{cpp,zig}",
desc: ".bind.ts → GeneratedBindings.cpp",
args: shJoin(cfg, ["run", script, debugFlag(cfg), `--codegen-root=${cfg.codegenDir}`]),
},
});

o.all.push(cppOut, zigOut);
o.all.push(cppOut);
o.cppSources.push(cppOut);
o.rustInputs.push(zigOut);
}

function emitJsSink({ n, cfg, o, dirStamp }: Ctx): void {
Expand Down
2 changes: 1 addition & 1 deletion scripts/glob-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const patterns = {
paths: ["src/node-fallbacks/*.js"],
},
/** `*.classes.ts` — input to generate-classes codegen */
zigGeneratedClasses: {
classesTs: {
paths: ["src/**/*.classes.ts"],
},
/** built-in modules bundled at build time */
Expand Down
2 changes: 1 addition & 1 deletion src/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ bun_bin` (driven by `scripts/build/rust.ts`). Key crates:

You will see `.zig` siblings next to many `.rs` files — those are the original
implementation kept as a porting reference for _behavior_; they are not
compiled and are not where new code goes.
compiled, not read by the build or codegen, and are not where new code goes.

Conventions:

Expand Down
1 change: 0 additions & 1 deletion src/codegen/bindgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1588,7 +1588,6 @@ writeIfNotChanged(
path.join(codegenRoot, "GeneratedBindings.cpp"),
[...headers].map(name => `#include ${str(name)}\n`).join("") + "\n" + cppInternal.buffer + "\n" + cpp.buffer,
);
writeIfNotChanged(path.join(src, "jsc/bindings/GeneratedBindings.zig"), zig.buffer + zigInternal.buffer);

// Headers
for (const [filename, { functions, typedefs }] of files) {
Expand Down
Loading
Loading