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
2 changes: 1 addition & 1 deletion scripts/build/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Tables: `cpuTargetFlags` (`-march`/`-mcpu`/`-mtune` — also forwarded to local

**Iterate on a dependency from a local checkout** — `bun bd --local-deps=mimalloc=~/code/mimalloc …` builds that dep from the clone instead of the pinned tarball (no fetch, no patches; edits rebuild incrementally). Any `github-archive` dep the graph compiles (not lolhtml — cargo reads that via `Cargo.toml`); details in `deps/README.md`.

**Add a codegen step** — add a function in `codegen.ts` following the shape of `emitErrorCode` (simple) or `emitCppBind` (needs file-list input). Call it from `emitCodegen()` and add outputs to the right `CodegenOutputs` group (`rustInputs` if the Rust build reads it (the `include!`d generated `.rs` files) — `cppSources` if it's a `.cpp` to compile, `cppAll` if it's a header).
**Add a codegen step** — add a function in `codegen.ts` following the shape of `emitErrorCode` (simple) or `emitCppBind` (needs file-list input). Call it from `emitCodegen()` and add outputs to the right `CodegenOutputs` group (`rustInputs` if the Rust build reads it (the `include!`d generated `.rs` files) — `cppSources` if it's a `.cpp` to compile, `cppAll` if it's a header). The edge's inputs must cover everything the script imports or opens, not just the globbed source list: ninja re-runs the step only for files on the edge, so a module or header the script pulls in from elsewhere (see `extraInputs` in `emitJsModules`) has to be listed too, or edits to it silently leave the outputs stale. Same when teaching an existing script to read a new file. `test/internal/build-codegen-extra-inputs.test.ts` checks the listed edges against their scripts' import closures; add the new step to its table.

**Add a Config field** — add to `Config` interface and `PartialConfig` in `config.ts`, resolve in `resolveConfig()`. If it needs a CLI flag, `build.ts`'s arg parser already handles `--anyfield=value` generically.

Expand Down
54 changes: 41 additions & 13 deletions scripts/build/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,20 @@ function emitErrorCode({ n, cfg, o, dirStamp }: Ctx): void {
o.cppHeaders.push(...cppOutputs);
}

function emitGeneratedClasses({ n, cfg, sources, o, dirStamp }: Ctx): void {
/**
* The `$inherits<Class>()` table. Its indices are baked into two outputs of two
* different steps: replacements.ts rewrites `$inheritsBlob(x)` in every bundled
* module to `$inherits(<index>, x)` (emitJsModules), and generate-classes.ts
* emits the matching `switch (id)` into ZigGeneratedClasses.cpp
* (emitGeneratedClasses). It matches neither step's source glob, so both edges
* list it explicitly; an edit to it has to regenerate both sides together.
*/
function jsClassesTable(cfg: Config): string {
return resolve(cfg.cwd, "src", "jsc", "bindings", "js_classes.ts");
}

/** Exported (with emitJsModules) for test/internal/build-codegen-extra-inputs.test.ts. */
export function emitGeneratedClasses({ n, cfg, sources, o, dirStamp }: Ctx): void {
const script = resolve(cfg.cwd, "src", "codegen", "generate-classes.ts");

const outputs = [
Expand All @@ -598,7 +611,10 @@ function emitGeneratedClasses({ n, cfg, sources, o, dirStamp }: Ctx): void {
n.build({
outputs,
rule: "codegen",
inputs: [script, ...sources.zigGeneratedClasses],
// jsCodegen covers the script's imports (class-definitions.ts, which the
// .classes.ts files import as well, and helpers.ts). Only the .classes.ts
// files go on the command line.
inputs: [script, ...sources.jsCodegen, ...sources.zigGeneratedClasses, jsClassesTable(cfg)],
orderOnlyInputs: [dirStamp],
vars: {
cwd: cfg.cwd,
Expand Down Expand Up @@ -701,15 +717,30 @@ function emitCppBind({ n, cfg, sources, o, dirStamp }: Ctx): void {
o.rustInputs.push(outputRs);
}

function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void {
export function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void {
const script = resolve(cfg.cwd, "src", "codegen", "bundle-modules.ts");

// InternalModuleRegistry.cpp is read by the script (for a sanity check).
const extraInput = resolve(cfg.cwd, "src", "jsc", "bindings", "InternalModuleRegistry.cpp");
// replacements.ts bakes ErrorCode.ts indices into every bundled module
// ($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");
// Everything the script reads from outside sources.js (src/js/**/*.{js,ts})
// and sources.jsCodegen (src/codegen/*.ts). Each is baked into the outputs,
// so an edit to one of them alone has to re-run the step, and ninja only
// does that for files listed here. test/internal/build-codegen-extra-inputs
// checks this list against the script's imports in both directions.
const extraInputs = [
// internal-module-registry-scanner.ts numbers the native modules from this
// list. The numbers end up in InternalModuleRegistry+*.h,
// SyntheticModuleType.h, NativeModuleImpl.h, generated_resolved_source_tag.rs
// and in the require() rewrites inside the bundled modules.
resolve(cfg.cwd, "src", "jsc", "modules", "NativeModuleList.h"),
// bundle-functions.ts writes BunBuiltinNames+extras.h: the private names
// the builtins use minus the ones this header already declares.
resolve(cfg.cwd, "src", "js", "builtins", "BunBuiltinNames.h"),
// replacements.ts bakes each class's index into $inherits(N, ...).
jsClassesTable(cfg),
// replacements.ts bakes ErrorCode.ts indices into every bundled module
// ($makeErrorWithCode(N, ...)); without this dep an ErrorCode.ts edit leaves
// stale error numbers in the JS bundles while the C++ enum regenerates.
resolve(cfg.cwd, "src", "jsc", "bindings", "ErrorCode.ts"),
];

Comment thread
robobun marked this conversation as resolved.
const outputs = [
resolve(cfg.codegenDir, "WebCoreJSBuiltins.cpp"),
Expand All @@ -736,7 +767,7 @@ function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void {
n.build({
outputs,
rule: "codegen",
inputs: [script, ...sources.js, ...sources.jsCodegen, extraInput, errorCodeInput],
inputs: [script, ...sources.js, ...sources.jsCodegen, ...extraInputs],
orderOnlyInputs: [dirStamp],
vars: {
cwd: cfg.cwd,
Expand All @@ -756,9 +787,6 @@ function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void {
function emitBakeCodegen({ n, cfg, sources, o, dirStamp }: Ctx): void {
const script = resolve(cfg.cwd, "src", "codegen", "bake-codegen.ts");

// InternalModuleRegistry.cpp is listed as a dep in CMake for this step too.
// The script doesn't read it; CMake copy-paste. We skip it.

// CMake only declares bake.client.js and bake.server.js as outputs. The
// script also emits bake.error.js (the runtime embeds it). We declare
// all three.
Expand Down
58 changes: 33 additions & 25 deletions scripts/glob-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,35 +140,43 @@ export type Sources = { [K in keyof typeof patterns]: string[] };
*/
export function globAllSources(): Sources {
const result = {} as Sources;
for (const field of Object.keys(patterns) as (keyof Sources)[]) {
result[field] = globSourceList(field);
}
return result;
}

for (const [field, spec] of Object.entries(patterns) as [keyof Sources, SourcePattern][]) {
const excludeExact = new Set<string>();
const excludePrefix: string[] = [];
for (const ex of (spec.exclude ?? []).map(normalize)) {
if (ex.endsWith("/**"))
excludePrefix.push(ex.slice(0, -2)); // keep trailing '/'
else excludeExact.add(ex);
}
const files: string[] = [];
for (const pattern of spec.paths) {
for (const rel of globSync(pattern, { cwd: root })) {
const normalized = normalize(rel);
if (excludeExact.has(normalized)) continue;
if (excludePrefix.some(p => normalized.startsWith(p))) continue;
files.push(resolve(root, normalized));
}
/**
* Glob one source list. Tests of the build scripts use this to feed an
* emitter the real list for the one field it reads, since globbing every
* field (`src/**` walks) is slow under a debug build.
*/
export function globSourceList(field: keyof Sources): string[] {
const spec: SourcePattern = patterns[field];
const excludeExact = new Set<string>();
const excludePrefix: string[] = [];
for (const ex of (spec.exclude ?? []).map(normalize)) {
if (ex.endsWith("/**"))
excludePrefix.push(ex.slice(0, -2)); // keep trailing '/'
else excludeExact.add(ex);
}
const files: string[] = [];
for (const pattern of spec.paths) {
for (const rel of globSync(pattern, { cwd: root })) {
const normalized = normalize(rel);
if (excludeExact.has(normalized)) continue;
if (excludePrefix.some(p => normalized.startsWith(p))) continue;
files.push(resolve(root, normalized));
}

files.sort((a, b) => a.localeCompare(b));
assert(files.length > 0, `Source list '${field}' matched nothing`, {
file: import.meta.url,
hint: `Patterns: ${spec.paths.join(", ")}`,
});

result[field] = files;
}

return result;
files.sort((a, b) => a.localeCompare(b));
assert(files.length > 0, `Source list '${field}' matched nothing`, {
file: import.meta.url,
hint: `Patterns: ${spec.paths.join(", ")}`,
});

return files;
}

/** Forward slashes, no leading ./ — for exclude-set comparisons. */
Expand Down
Loading
Loading