跳到主要内容

构建与发布

本地构建​

Makefile 引入了 DuckDB 官方的 extension-ci-tools makefile,因此命令与所有 DuckDB 扩展一致:

命令作用
make configure创建 Python venv,记录目标平台与扩展版本。需要 Python 3 与网络,只需执行一次。
make debug构建 cdylib、追加扩展元数据,并把产物复制到 build/debug/extension/<name>/<name>.duckdb_extension。
make release带优化的同一套流程。
make test用 debug 构建跑 sqllogictest 套件。
make clean / make clean_all清理构建产物;clean_all 会连 configure/ 一起清掉。

常用组合在 Justfile 里已经封装好:

just build # make configure && make debug(官方工具链)
just sql "SELECT double_it5(21);" # 构建后 LOAD 并执行一条语句
just test # make configure debug test
just doc # cargo doc -p duckfn

Makefile 里有四个设置值得了解:

EXTENSION_NAME=duckfn
USE_UNSTABLE_C_API=1
TARGET_DUCKDB_VERSION=v1.5.6
TARGET_INFO += --example $(EXTENSION_NAME) --features quack

USE_UNSTABLE_C_API=1 决定了产出的扩展只能在兼容版本的 DuckDB 里、并加 -unsigned 才能加载。 TARGET_DUCKDB_VERSION 指明写入元数据时针对的版本 —— 又因为 ABI 类型是 unstable,这个值被当作 DuckDB 发行版本号来读,必须与引擎逐字相等。两种 ABI 类型的差别见 DuckDB 版本兼容性。EXTENSION_NAME 必须与 test/extension/entry.rs 里的 duckfn_entrypoint!、以及 sqllogictest 文件里的 require 保持一致。 TARGET_INFO 里带 --example $(EXTENSION_NAME):扩展产物来自 [[example]] duckfn 这个 target; 再带上 --features quack 决定示例树会不会被编译(它挂在默认关闭的 quack feature 上)。 TARGET_INFO 是 DuckDB 官方 makefile 唯一会原样拼进 cargo build 的变量。少了后面这一项,产出的是 一个没有入口符号的 cdylib。

WebAssembly​

wasm 构建需要先装一次 Emscripten 目标:

rustup target add wasm32-unknown-emscripten
just build_wasm

由于最终链接由 emcc 完成,该目标要的是 staticlib 而不是 cdylib。crate-type 不能按 target 覆写,所以这两者都放在同一个 [[example]] duckfn target 上(根 Cargo.toml 里 crate-type = ["cdylib", "staticlib"]):原生构建取 cdylib,wasm 构建取 .a,而 lib 本身只剩一个 rlib。两个扩展产物因此都出自 test/extension/ 的同一次编译 —— 这点很关键:多编一份就会把每个 函数注册两次,而且在 wasm 上重复的入口符号会直接链接失败。剩下的交给 make:它用 emcc 把归档链成 side module,再补上扩展元数据。

lib 保持纯 rlib,还有一层给下游项目的好处:cargo 同样照依赖的 crate-type 办事,cdylib 一旦写在 lib 上,每个下游项目编 wasm 时都要把 duckfn 的 cdylib 也链一遍 —— 而那条路径下 rustc 不会给 emcc 传 -sSIDE_MODULE=2,emcc 于是按独立模块链接,报:

wasm-ld: error: libstandalonewasm.a(__main_void.o): undefined symbol: main

扩展项目在 duckfn ≤ 0.0.16 时要在自己的 .cargo/config.toml 里加 [target.wasm32-unknown-emscripten] rustflags = ["-C", "link-arg=-sSIDE_MODULE=2"] 绕开它; duckfn 0.0.17 起 lib 只有 rlib,这段就不需要了。

持续集成​

.github/workflows/MainDistributionPipeline.yml 把主要工作交给 DuckDB 的共享工作流:

on:
push:
tags: ['v*.*.*']
workflow_dispatch:

jobs:
duckdb-stable-build:
uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@v1.5-variegata
with:
duckdb_version: v1.5.6
ci_tools_version: v1.5-variegata
extension_name: duckfn
extra_toolchains: rust;python3
exclude_archs: 'linux_amd64_musl'

该共享工作流会为所有受支持平台(含 WebAssembly 目标)构建并测试扩展,本仓库只需要声明用哪个 DuckDB 版本、 需要哪些工具链。

发布​

第二个作业把推送的 tag 变成 GitHub Release:

  1. 下载全部 duckfn-*-extension-* 产物。
  2. 把 *.duckdb_extension 与 *.duckdb_extension.wasm 收敛为 duckfn-<arch>.duckdb_extension。
  3. 用上一个 v* tag 以来的提交记录生成发布说明。
  4. 创建 Release;若已存在则上传覆盖。

因此发版流程就是:打 vX.Y.Z tag、推送、等三个作业跑完 —— 产物、GitHub Release,以及下面的 crate。

发布 crate​

同一个 tag 也会把两个库 crate 发到 crates.io,作为那条流水线的第三个作业。它的触发条件比上面两个 作业更严:只认版本 tag 的 push,手动 workflow_dispatch(即使在 main 上)不构建、不发版、 更不会发 crate —— crates.io 上的版本删不掉、只能 yank,这道门值得守住。

鉴权用 crates.io 可信发布:由 rust-lang/crates-io-auth-action 拿 workflow 的 OIDC token 换一个 30 分钟有效的发布 token, 仓库里不存长期有效的 CARGO_REGISTRY_TOKEN。

顺序很重要 —— duckfn 依赖 duckfn-macro = "=0.0.18",而发布时这个依赖是从 registry 解析的(不看本地 path),所以作业先发 duckfn-macro,等它在 sparse index 里可见,再发 duckfn。

CI 用不了时才走本地:

just release_publish # publish_macro_dry → publish_macro → publish_dry → publish

版本来自 workspace:

[workspace.package]
version = "0.0.18"
rust-version = "1.86"

示例扩展随本包一起发布,而不是独立 crate,所以也不会单独上传。

而且发布出去的 duckfn 包并不只有运行时:根 Cargo.toml 的 include 会把示例扩展 (test/extension/**、src/bin/duckfn.rs)、它的 sqllogictest 用例 (test/sql/**/*.test)、文档站正文(docs/README.md、docs/docs/** 与 docs/i18n/ 下的简体 中文译文)、demo.sh、README 与许可证一起打进去。所以解包即得完整文档和一份可以直接跑的示例 —— 这正是把它们放进同一个包的意义。确切清单用 cargo package -p duckfn --list 查看。

示例只在打开 quack feature 时才参与编译(cargo build --features quack),所以它进包对任何依赖 duckfn 的下游项目都没有影响。

文档站​

docs/ 下的 Docusaurus 站点由 .github/workflows/DeployDocs.yml 部署:它在上面那条多平台构建 跑完之后触发,而不是在推 tag 的那一刻;也可以在 Actions 页面手动触发。普通提交不会构建它。 等流水线是必要的:部署出去的页面预加载的是已发布的 wasm 扩展,所以 Release 必须先存在 —— 在 推 tag 时部署会和构建抢跑,拉到的还是上一个 release。

它从 actions/configure-pages 读取 Pages 地址,用 npm run build 构建两种语言,然后发布产物。 本地相反:同一个站点用的是 just build_wasm_eh 产出的扩展,不碰 release(just test_wasm 会 构建它并跑一遍示例)。

github-pages 环境带保护规则,需要在 Settings -> Environments -> github-pages -> Deployment branches and tags 中列出允许部署的 ref —— 现在是分支 main(workflow_run 跑在默认分支上),加上手动触发时用的 v*.*.* tag。

接下来​