Skip to main content

Build and release

Build​

The official DuckDB extension-ci-tools makefiles are the one path, and just build wraps it:

make configure # once: builds configure/venv (Python + the sqllogictest runner)
make debug # the official path, also what CI runs
# -> build/debug/duckfn_open.duckdb_extension

Where the artifact lands and how it is loaded:

just build runs make configure && make debug. make release is the optimized version of the same flow. On Windows make has to run inside Git Bash.

Justfile​

CommandWhat it does
just buildmake configure && make debug
just sql "SELECT op_open_path(NULL)"Build, then run one statement and exit
just replA DuckDB REPL with the extension loaded
just lintcargo clippy --all-targets -- -D warnings
just testThe official build and the sqllogictest run
just docs_csvExport the function descriptions to target/function_descriptions.csv
just docs_build / just docs_startBuild / serve this documentation site
just rename <name>Rewrite the extension name everywhere
just release_*The release flow below
just build_wasm / build_wasm_eh / test_wasmLocal overrides: they print one line saying this extension has no wasm build (see No WebAssembly build)
just sync-common / just check-commonUpdate / verify scripts/common.just (below)

Every recipe here lives in scripts/common.just — a file shared by every extension project and imported by the root Justfile (just --list shows the merged set). Its source of truth is the duckfn repository: just sync-common pulls the latest copy, just check-common reports when this one differs. Put project-specific commands in the root Justfile rather than editing the copy; see AGENTS.md.

That is exactly how the wasm recipes got turned off: the shared copy still defines build_wasm, build_wasm_eh and test_wasm, and the root Justfile re-declares all three (under set allow-duplicate-recipes := true) to call one private no_wasm recipe. Editing the copy instead would lose the change on the next just sync-common.

Two stale comments in the shared copy

just --list prints the comments that come with scripts/common.just, and two of them still describe the template's wasm setup: docs_build / docs_start say the build needs the extension under docs/static/duckdb-extensions/, and config_env adds the wasm32-unknown-emscripten target. Neither applies here — the site has no runnable blocks and nothing to preload, and config_env's useful half is its rustup override set. Those comments are deliberately not edited away.

Release flow​

A release is four steps, and only the second one is manual:

StepCommand
0. Pre-flightjust release_check (clippy + build); just test when the change warrants it
1. Bump the versionjust release_bump 0.1.0
2. Commit and taggit commit … then just release_tag 0.1.0
3. Watch CIjust release_ci, then gh run watch <run-id>
4. Next development versionjust release_dev 0.1.1-dev.0

The version lives in Cargo.toml ([package] version) and nowhere else: scripts/release.sh bump rewrites that one line, updates the occurrences in the docs and the CI comments, syncs Cargo.lock and fixes docs/extension-version.ts, which is where the documentation site gets the version number it prints. The tag has to match Cargo.toml, because cargo writes the version into the built extension — a mismatch would publish a release claiming another version.

Only real releases get a tag. A version like 0.1.1-dev.0 stays on the branch: no tag, no release, no site deployment.

What a tag triggers​

Pushing v*.*.* starts Main Extension Distribution Pipeline:

  • Main Extension Distribution Pipeline — builds the extension for every supported desktop platform (the wasm architectures are excluded through the workflow's exclude_archs input — see No WebAssembly build), runs the tests, then creates (or updates) a GitHub Release for that tag with the built binaries attached as <extension>-<arch>.duckdb_extension. Release notes are the commits since the previous version tag.
  • Deploy Docs is not started by the tag but by that pipeline finishing: it builds docs/ and publishes it to GitHub Pages. The site is a static one — it fetches nothing from the release (its sql fences are code blocks, not live examples). It needs the one-time Settings → Pages → Source: GitHub Actions setting.

Pull requests run the build and the tests only; publishing is gated on the ref being a version tag.

Installing a release​

Download the asset for your platform from the release page, rename it to duckfn_open.duckdb_extension (DuckDB takes the entry-point symbol from the file name, and LOAD fetches no URLs), then:

duckdb -unsigned -c "LOAD './duckfn_open.duckdb_extension';"

A locally built extension needs duckdb -unsigned; a released one that a user downloads also has to be loaded with that flag, because it is not signed by DuckDB's distribution key. That is what the community-extension route fixes: once registered, INSTALL … FROM community fetches a signed build for the user's platform.

No WebAssembly build​

This project does not build for wasm, and that is a design decision rather than a missing step: a launcher is desktop software — there is no desktop to hand a file to inside a browser — and open has no emscripten implementation, writing compile_error! on that target, so a wasm build would not even reach the linker.

The exclusion is done all the way through, and each landing point is deliberate:

WhereWhat it looks like now
Cargo.tomlNo wasm [[example]] (the template's staticlib forward is gone), and open is an ordinary [dependencies] entry with no [target.'cfg(…)'.dependencies] gate.
src/One crate root, src/lib.rs (the template's src/wasm_lib.rs is deleted), and no #[cfg(target_arch = "wasm32")] stand-ins in src/extension/launcher.rs.
.github/workflows/MainDistributionPipeline.ymlexclude_archs: 'wasm_mvp;wasm_eh;wasm_threads', so the Release carries native-platform assets only.
Justfilebuild_wasm, build_wasm_eh and test_wasm are overridden to print one line; the shared copy in scripts/common.just keeps its originals and is not edited.
docs/No runnable SQL blocks: every sql fence is a static code block, so the examples run in a local DuckDB after a LOAD (see Quick start).

Restoring wasm would mean redoing all five rows at once, not flipping one switch — so when you add a dependency, do not budget for a wasm target and do not reach for a [[example]] staticlib.

The .cargo/config.toml of a project that does build wasm needs -sSIDE_MODULE=2 under [target.wasm32-unknown-emscripten] rustflags (a cdylib there has to link as a side module). This repository carries no such entry, only the Windows crt-static one: it has no emscripten target to link.