175 lines
7.6 KiB
Markdown
175 lines
7.6 KiB
Markdown
# Vendored dependencies
|
||||
|
|
|
|||
|
|
One file per dependency. Each file exports a `Dependency` object that tells
|
|||
|
|
the build system where to fetch the source, how to build it, and what
|
|||
|
|
libraries/headers it provides.
|
|||
|
|
|
|||
|
|
## Adding a dependency
|
|||
|
|
|
|||
|
|
1. Copy `hdrhistogram.ts` (the simplest direct dep) to `<name>.ts`
|
|||
|
|
2. Fill in `name`, `repo`, `commit`, `sources`, `includes`, `provides.includes`
|
|||
|
|
3. Add `import { <name> } from "./<name>.ts"` + entry in `allDeps` array in `index.ts`
|
|||
|
|
4. `bun run scripts/build/phase3-test.ts` to verify it builds
|
|||
|
|
|
|||
|
|
That's it. For most deps you're done. If the dep's build is too entangled
|
|||
|
|
to list sources by hand (zlib-ng's per-file SIMD flags are about the
|
|||
|
|
limit), use `kind: "nested-cmake"` instead — see `NestedCmakeBuild` in
|
|||
|
|
`../source.ts` for the fields.
|
|||
|
|
|
|||
|
|
**`name` must match the directory on disk** (`vendor/<name>/`). If your repo
|
|||
|
|
is `oven-sh/WebKit`, name it `"WebKit"` — that's what `git clone` creates.
|
|||
|
|
Case-sensitive filesystems enforce this.
|
|||
|
|
|
|||
|
|
**Ordering in `allDeps` matters:**
|
|||
|
|
|
|||
|
|
- Put deps with `fetchDeps: ["X"]` AFTER X in the list
|
|||
|
|
- Link order: deps that PROVIDE symbols go after deps that USE them
|
|||
|
|
|
|||
|
|
## Removing a dependency
|
|||
|
|
|
|||
|
|
1. Delete `<name>.ts`
|
|||
|
|
2. Remove from `allDeps` in `index.ts`
|
|||
|
|
3. If any other dep has `fetchDeps: ["<name>"]`, remove that reference
|
|||
|
|
|
|||
|
|
## Updating a commit
|
|||
|
|
|
|||
|
|
Change the `commit` field. That's it. The build system computes a source
|
|||
|
|
identity hash from `sha256(commit + patch_contents)` — changing the commit
|
|||
|
|
invalidates `.ref`, triggers re-fetch, and everything downstream rebuilds.
|
|||
|
|
|
|||
|
|
The `.github/workflows/update-<name>.yml` jobs do this automatically by
|
|||
|
|
sed'ing the `const <NAME>_COMMIT = "..."` line. If you rename that
|
|||
|
|
constant, update the workflow too.
|
|||
|
|
|
|||
|
|
**For `direct` deps:** the source list is hardcoded, so a bump that adds
|
|||
|
|
or removes a `.c`/`.cpp` upstream needs a matching list edit here. CI
|
|||
|
|
catches a missed addition (link error on the unresolved symbol); a
|
|||
|
|
removed file fails at compile. Either way the auto-bump PR goes red,
|
|||
|
|
which is the cue to diff the upstream `CMakeLists.txt`.
|
|||
|
|
|
|||
|
|
## Iterating on a dep from a local checkout
|
|||
|
|
|
|||
|
|
To hack on a vendored dep (e.g. chase a bug in the mimalloc or libuv fork)
|
|||
|
|
without cutting a commit and bumping the pin each round, point the build at
|
|||
|
|
a git clone:
|
|||
|
|
|
|||
|
|
```sh
|
|||
|
|
git clone https://github.com/oven-sh/mimalloc ~/code/mimalloc
|
|||
|
|
bun bd --local-deps=mimalloc=~/code/mimalloc test foo.test.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Any `github-archive` dep the graph compiles or includes can be redirected
|
|||
|
|
(so not lolhtml, which cargo reads from `vendor/lolhtml` via the workspace
|
|||
|
|
`Cargo.toml` — point that path at your checkout instead); several at once
|
|||
|
|
with `name=path,name=path`. Cross-dep references (`depSourceDir()`, e.g.
|
|||
|
|
lsquic's `-I` into boringssl) follow the redirect. The checkout is compiled
|
|||
|
|
as-is — no fetch, no `.ref` stamp, and the dep's `patches` are **not**
|
|||
|
|
applied (they target the pinned tarball), so start the clone from the pinned
|
|||
|
|
commit if you want an identical baseline. Switching a dep between pinned and
|
|||
|
|
local moves its `-I` path, so the first build after the switch recompiles
|
|||
|
|
every TU that sees the dep's headers; after that, edits are picked up
|
|||
|
|
incrementally: `direct` deps through the compiler depfiles,
|
|||
|
|
`nested-cmake`/`cargo` deps by re-invoking their inner build every run. The
|
|||
|
|
build banner shows `local:<name>` while this is on. Don't edit
|
|||
|
|
`vendor/<name>/` in place instead — it is wiped whenever the pin or patches
|
|||
|
|
change. WebKit has its own switch (`--webkit=local`).
|
|||
|
|
|
|||
|
|
## Common fields
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export const mydep: Dependency = {
|
|||
|
|
name: "mydep",
|
|||
|
|
|
|||
|
|
// Source tarball. Fetched from GitHub's archive endpoint (no git history,
|
|||
|
|
// just the files at `commit`). Most deps use this.
|
|||
|
|
//
|
|||
|
|
// Other kinds: `prebuilt` (download pre-compiled .a, e.g. WebKit default),
|
|||
|
|
// `local` (user-managed checkout — WebKit declares it because its clone is
|
|||
|
|
// too slow to automate; any github-archive dep becomes one via
|
|||
|
|
// `--local-deps`, see below), `in-tree` (source in src/).
|
|||
|
|
source: () => ({ kind: "github-archive", repo: "owner/repo", commit: "..." }),
|
|||
|
|
|
|||
|
|
// Optional: macro name for bun_dependency_versions.h (process.versions).
|
|||
|
|
// Omit if this dep shouldn't appear there.
|
|||
|
|
versionMacro: "MYDEP",
|
|||
|
|
|
|||
|
|
// Optional: .patch files applied after extraction, or overlay files
|
|||
|
|
// copied into source root (e.g. inject a CMakeLists.txt).
|
|||
|
|
patches: ["patches/mydep/fix-something.patch"],
|
|||
|
|
|
|||
|
|
// Optional: deps whose SOURCE must be ready before this one builds
|
|||
|
|
// (for -I cross-dep headers). See libarchive for an example.
|
|||
|
|
fetchDeps: ["zlib"],
|
|||
|
|
|
|||
|
|
// How to build. `direct` lists sources explicitly; emitDirect compiles
|
|||
|
|
// each as a first-class cc/cxx edge and the resulting .o's go straight
|
|||
|
|
// into bun's link line. See `DirectBuild` in ../source.ts for all
|
|||
|
|
// optional fields (lang/pic/defines/headers/codegen).
|
|||
|
|
build: cfg => ({
|
|||
|
|
kind: "direct",
|
|||
|
|
sources: ["src/foo.c", "src/bar.c"],
|
|||
|
|
includes: [".", "include"],
|
|||
|
|
// defines: { MYDEP_STATIC: 1 },
|
|||
|
|
// cflags: ["-std=c11"],
|
|||
|
|
// headers: { "config.h": "..." }, // Hand-written config.h.
|
|||
|
|
}),
|
|||
|
|
|
|||
|
|
// What this dep exposes to bun's own compile. `libs` is ignored for
|
|||
|
|
// direct builds (the .o's are already on the link line); `includes`
|
|||
|
|
// are relative to the SOURCE dir.
|
|||
|
|
provides: cfg => ({
|
|||
|
|
libs: [],
|
|||
|
|
includes: ["include"],
|
|||
|
|
// defines: ["MY_DEP_STATIC=1"], // Preprocessor defines for bun's compile.
|
|||
|
|
}),
|
|||
|
|
|
|||
|
|
// Optional: skip this dep on some platforms.
|
|||
|
|
enabled: cfg => !cfg.windows,
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Build types
|
|||
|
|
|
|||
|
|
- **`direct`**: Sources compiled as first-class `cc` edges in our ninja
|
|||
|
|
graph — no sub-process. Best for deps with a stable, small file list and
|
|||
|
|
no configure-time codegen we can't replicate. See `DirectBuild` in
|
|||
|
|
`../source.ts`. Prefer this over `nested-cmake` when feasible: it skips a
|
|||
|
|
cmake configure (often 5–20s of try_compile probes) and lets LTO see
|
|||
|
|
across the dep boundary into bun's call sites.
|
|||
|
|
- **`nested-cmake`**: Runs `cmake --fresh -B ...` then `cmake --build`.
|
|||
|
|
See `NestedCmakeBuild` in `../source.ts` for all fields.
|
|||
|
|
- **`cargo`**: Rust deps (currently just lolhtml). See `CargoBuild` in `../source.ts`.
|
|||
|
|
- **`none`**: Header-only or prebuilt. No build step; `.ref` stamp is the output.
|
|||
|
|
|
|||
|
|
## Worked examples
|
|||
|
|
|
|||
|
|
- **hdrhistogram.ts** / **libdeflate.ts** — simplest direct deps
|
|||
|
|
- **mimalloc.ts** — direct build, single unity TU compiled as C++
|
|||
|
|
- **tinycc.ts** — direct build with a build-time codegen tool
|
|||
|
|
- **zlib.ts** — direct build with per-source SIMD `-m` flags + `.h.in` substitution
|
|||
|
|
- **libarchive.ts** / **cares.ts** — direct build with hand-written per-target config.h
|
|||
|
|
- **boringssl.ts** — direct build with NASM assembly (win-x64) and a large gen/ manifest
|
|||
|
|
- **sqlite.ts** — direct build, in-tree source (lives in `src/`, not `vendor/`)
|
|||
|
|
- **libuv.ts** — `enabled: cfg => cfg.windows` for a platform-only dep
|
|||
|
|
- **lolhtml.ts** — cargo build with rustflags
|
|||
|
|
- **webkit.ts** — `nested-cmake` (`sourceSubdir`, `preBuild`) and `prebuilt`
|
|||
|
|
|
|||
|
|
## How the three-step build works
|
|||
|
|
|
|||
|
|
Each dep becomes three ninja build statements, each with `restat = 1`:
|
|||
|
|
|
|||
|
|
1. **fetch** → `vendor/<name>/.ref` stamp
|
|||
|
|
- Downloads tarball, extracts, applies patches
|
|||
|
|
- `.ref` contains `sha256(commit + patches)[:16]`
|
|||
|
|
- restat: if identity unchanged, no write, downstream pruned
|
|||
|
|
2. **configure** → `buildDir/deps/<name>/CMakeCache.txt`
|
|||
|
|
- `cmake --fresh -B <dir> -D...`
|
|||
|
|
- `--fresh` drops the cache so stale -D values don't persist
|
|||
|
|
- restat: inner cmake might not touch cache
|
|||
|
|
3. **build** → `.a` files
|
|||
|
|
- `cmake --build <dir> --target ...`
|
|||
|
|
- restat: inner ninja no-ops if nothing changed
|
|||
|
|
|
|||
|
|
`restat` is what makes incremental builds fast — if step N was a no-op,
|
|||
|
|
ninja prunes everything after it.
|