跳转到内容

开发

English · 文档

构建与运行

bash
cargo run                     # 启动空工作区
cargo run -- ./data/logs/     # 或直接打开数据
cargo test                    # 运行测试套件

DuckDB 从随仓库携带的源码构建,因此首次构建耗时很长,还需要几 GB 磁盘空间。dev profile 的依赖以 opt-level = 3 编译,这让首次构建更慢,但开发迭代仍然顺畅——之后只有 ducklocal 自身会重新编译。

工具链。 仓库里既没有 rust-toolchain.toml,也没有 rust-version 字段,因此实际的版本下限由依赖决定:duckdb 要求 1.85.1。CI 直接使用 stable。

被钉住的工具链

UI 工具包、分析应用所依赖的脚本运行时、应用绘制所用的组件目录,是同一个仓库 longbridge/gpui-kit 里的三个 crate,rquickjs 也被 patch 到同一个仓库。四者一起钉在同一个 revision 是有意为之:两个 revision 会让构建里出现两份 gpui-base 或两份 rquickjs,报出来的是 一大片 trait 不匹配,而其中没有一条会说出原因。

bash
./scripts/check-deps.sh

它检查 Cargo 不会替你检查的三件事:依赖与 [patch] 用的是同一个 revision、Cargo.lock 与之一致、 没有任何 crate 在构建里出现两次(一次来自该仓库,一次来自 crates.io)。它只读这两个清单文件, 所以离线、即时。CI 会在跑测试之前执行它。要升级工具包,请把 Cargo.toml 里所有 rev 一起改掉, 执行 cargo update,再跑一次这个脚本。

按 revision 钉依赖,也是这个 crate 无法发布到 crates.io 的原因:git 依赖和 [patch] 在那里都不被接受。 分发走的是下面的 macOS 签名包。

打包 macOS 应用

bash
./scripts/bundle.sh
# 生成 target/release/DuckLocal.app

它会组装好应用包——可执行文件位于 Contents/MacOS/ducklocal,再加上 assets/Info.plistassets/AppIcon.icns——但不做签名。产物在你自己的机器上可以运行,到了其他机器上会被 Gatekeeper 拒绝。

打包、签名与公证

bash
scripts/package-macos.sh <binary> <output.dmg> <version>

请在仓库根目录运行;它会把 DuckLocal.appdmg-root 写到当前工作目录,dmg 就放在它们旁边。脚本在没有凭据时会拒绝运行——未公证的产物在当前 macOS 上用户根本打不开,所以它宁可快速失败,也不愿意意外产出一个。

需要以下内容:

变量含义
NOTARY_KEYApp Store Connect API 密钥(.p8)的路径
NOTARY_KEY_ID该密钥的 Key ID
NOTARY_ISSUER该密钥所属的 issuer UUID
Developer ID Application 签名身份当钥匙串中恰好只有一个时自动检测,否则设置 MACOS_SIGN_IDENTITY

脚本会按参数中的版本号给应用包打上版本,用 --options runtime --timestampscripts/entitlements.plist 对内部可执行文件和应用本身签名,构建一个带 /Applications 符号链接的 UDZO dmg,对 dmg 本身签名,用 notarytool submit --wait --timeout 300m 提交,把公证票据装订到文件上,最后执行 stapler validate 和两次 spctl --assess 校验。

使用前需要了解两件事:

  • 五小时的超时是客户端等待时间,而不是取消。超时后提交仍会在 Apple 那边继续处理,票据稍后依旧可以领取——但票据只对提交时的那串字节有效,所以不要重新构建;请对你提交的那个文件装订票据。
  • 只有 dmg 经过公证,因此内部的 .app 上没有装订票据。从刚挂载的镜像中首次启动时需要联网,以便向 Apple 校验。

必须启用 com.apple.security.cs.disable-library-validation 授权,因为 DuckDB 会用 dlopen 加载它的扩展——httpfs 就是其中之一。

CI 与发布

工作流触发条件作用
ci.yml推送到 main、任意 pull requestmacos-latest 上先执行 scripts/check-deps.sh,再执行 cargo test --locked
release.yml匹配 v* 的 tag,或手动指定版本触发构建 aarch64-apple-darwin,把签名证书导入临时钥匙串,运行 package-macos.sh,上传 ducklocal-macos-arm64.dmg,然后用自动生成的说明创建 GitHub 发布
gh-pages.ymlmain 分支上跳转脚本或工作流有改动时,或手动触发在 GitHub Pages 上发布从旧地址 jetsquirrel.github.io/DuckLocal/ 到新站点的跳转

发布构建需要以下仓库密钥:MACOS_CERTIFICATEMACOS_CERTIFICATE_PWDNOTARY_KEY_BASE64NOTARY_KEY_IDNOTARY_ISSUER

平台支持

Apple 芯片上的 macOS 是唯一受支持的目标平台,而且这是有意为之:打包路径是 .app 加 dmg,没有 Windows 或 Linux 产物,发布工作流也是这么写的。src/ 里没有任何 cfg(target_os),窗口层还带着 X11 和 Wayland feature 一起构建,所以构建出 Linux 版本是可能的——但 CI 里没有任何环节跑它,也没有做过测试。

文档站

网站放在独立的仓库 JetSquirrel/ducklocal-site,以静态资源的形式由 Cloudflare Workers 托管:

地址内容源码
ducklocal.app产品首页home/,静态 HTML 与 CSS
ducklocal.app/docs这些指南,中英文docs/VitePress 站点

应用的改动如果改变了指南里的说法,需要同时向两个仓库提交 pull request。在本地修改指南:克隆站点仓库后,使用 Node.js 22 或更新版本(推荐 24 LTS):

bash
npm --prefix docs ci             # 按锁文件安装依赖
npm --prefix docs run dev        # http://localhost:5173/docs/
sh scripts/build.sh              # 整个站点(产品首页与文档)生成到 dist/site

旧的 GitHub Pages 地址 jetsquirrel.github.io/DuckLocal/ 保留为一组跳转,指向对应的新页面;由本仓库的 gh-pages.yml 发布。

项目结构

路径内容
src/main.rs入口点、提前分流 CLI、GUI 窗口设置
src/cli.rs无界面参数、DuckDB 解析验证、独立连接、JSON 错误
tests/cli.rs真实二进制集成测试,临时数据位于 target/cli-tests
skills/ducklocal/官方 schema 优先 SQL agent skill 与 CLI 参考
src/sources.rs把路径解析成要挂载的文件和要打开的数据库
src/db.rsDuckDB 连接、挂载文件、服务器信息
src/query.rs查询执行、结果序列化、导出
src/profile.rsducklocal profile 背后的列画像
src/schema.rs为侧栏做 catalog 内省
src/analysis/JavaScript 分析应用:脚本运行时、ducklocal host 模块、应用标签页、重载监控
src/app_export/ducklocal export:无界面运行应用,把其语句与结果写成一个独立 HTML 文件
src/history.rs应用自己的数据库:查询历史、已登记的文件、设置
src/s3.rsSigV4 签名与 S3 列举
src/i18n.rs字符串表和语言选择
src/state.rs各视图共享的应用状态
src/ui/编辑器、结果表格、图表、侧栏、对话框、标题栏和状态栏
src/perf_probe.rs仅测试用的性能探针:为随数据规模增长的路径报告耗时、分配次数与分配字节数
assets/图标、Info.plist、README 与文档使用的示意图
scripts/bundle.shpackage-macos.shcheck-deps.sh、授权文件

测试说明

测试套件就是 cargo test。查询执行测试通过内存模式的连接来跑 DuckDB;导出测试同时覆盖 CSV 与 Parquet,后者会把写出的文件用 read_parquet 读回来验证。

基于 Apache-2.0 许可开源发布。