开发
构建与运行
cargo run # 启动空的内存工作区
cargo run -- ./data/logs/ # 或直接打开数据
cargo test --workspace # 运行测试套件,包括 Quick Look 引擎DuckDB 从仓库自带的源码构建,首次构建耗时很长,还要占几 GB 磁盘。dev profile 的依赖以 opt-level = 3 编译,首次构建因此更慢,但开发时的迭代不受拖累:之后只有 ducklocal 自身会重新编译。
精简构建。 cargo build --profile compact --locked 会在 target/compact/ 下生成体积更小的二进制。compact profile 继承 release(LTO、剥离符号、panic = "abort"),但 Rust 代码按体积优化(opt-level = "s"),libduckdb-sys 仍保持 opt-level = 3,DuckDB 的扫描、过滤和聚合循环照样快。下载或磁盘体积比界面和查询速度更要紧时用它,大小和速度都请在目标机器上实测。发布工作流和它产出的安装包用的是普通的 release profile。
工具链。 仓库里没有 rust-toolchain.toml,也没有 rust-version 字段,实际的版本下限由依赖决定:duckdb 要求 1.85.1。CI 直接使用 stable。
钉住版本的工具包
UI 工具包 gpui-kit 来自仓库 longbridge/gpui-kit,特意钉在同一个 git revision 上。如果混用两个 revision,构建里就会出现两份 gpui-base,编译失败时报出一堆 trait 不匹配的错误,却没有一条指出真正的原因。
./scripts/check-deps.sh这个脚本检查 Cargo 不管的几件事:对该仓库的所有依赖用的是同一个 revision;Cargo.lock 与之一致;没有哪个 crate 在构建里出现两次(一次来自该仓库,一次来自 crates.io)。脚本只读这两个清单文件,不联网,瞬间完成。CI 在跑测试之前会先执行它。升级工具包时,把 Cargo.toml 里所有 rev 一起改掉,执行 cargo update,再跑一次这个脚本。
按 git revision 钉依赖,也是 DuckLocal 无法发布到 crates.io 的原因:crates.io 不接受 git 依赖。分发靠的是下面的各平台发布包。
打包 macOS 应用
./scripts/bundle.sh
# 生成 target/release/DuckLocal.app脚本会组装出应用包(可执行文件位于 Contents/MacOS/ducklocal,外加 assets/Info.plist、assets/AppIcon.icns 和 Quick Look 扩展),但不做用于分发的签名。产物在自己的机器上能运行,换到其他机器上 Gatekeeper 会拒绝打开。
Quick Look 扩展
在访达里按空格预览 Parquet、DuckDB、CSV、TSV 和 JSON Lines 文件,靠的是应用 Contents/PlugIns 里的应用扩展 DuckLocalPreview.appex。它分两半:
quicklook/是 workspace 里的一个 crate,ducklocal-quicklook:用 Rust 写的预览引擎,编译成静态库。它用和应用相同的内置 DuckDB 打开文件(同一份锁文件,DuckDB 只编译一次),把列和前 50 行渲染成一个自包含的 HTML 页面。扩展是独立的沙盒进程,不能调用应用里的代码。quicklook/extension/是包在外面的 Swift 外壳:PreviewProvider.swift、C 头文件、扩展自己的Info.plist(声明它能预览的内容类型)和沙盒授权文件。
scripts/build-quicklook.sh <DuckLocal.app> [签名身份]脚本先构建引擎(cargo build --release -p ducklocal-quicklook;设置了 RUST_TARGET 时按该目标构建,发布工作流就是这样复用应用已构建好的 DuckDB),再用 swiftc 针对 macOS 12 编译 Swift 外壳并链接引擎,复制 Info.plist 并写入应用的版本号,最后用扩展自己的授权文件给 .appex 签名;没给签名身份时做 ad hoc 签名。bundle.sh 调用它时不带签名身份。应用启动过一次或完成注册后,macOS 就能找到这个扩展。
打包、签名与公证
scripts/package-macos.sh <binary> <output.dmg> <version>在仓库根目录运行。脚本会把 DuckLocal.app 和 dmg-root 写到当前工作目录,dmg 也放在旁边。没有凭据时脚本拒绝运行:未公证的产物在当前 macOS 上用户根本打不开,与其一不小心产出一个,不如尽早报错。
需要以下内容:
| 变量 | 含义 |
|---|---|
NOTARY_KEY | App Store Connect API 密钥(.p8)的路径 |
NOTARY_KEY_ID | 该密钥的 Key ID |
NOTARY_ISSUER | 该密钥所属的 issuer UUID |
| Developer ID Application 签名身份 | 钥匙串中恰好只有一个时自动检测,否则需设置 MACOS_SIGN_IDENTITY |
脚本依次执行以下步骤:
- 构建应用包,版本号取自参数。
- 用
build-quicklook.sh构建 Quick Look 扩展,以 Developer ID 和扩展自己的沙盒授权签名,绝不用scripts/entitlements.plist:应用扩展必须启用沙盒,应用本身没有。 - 由内向外、不用
--deep,以--options runtime --timestamp和scripts/entitlements.plist先给内部可执行文件、再给应用本身签名,并用codesign --verify --deep --strict校验。 - 构建一个带
/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 request | macos-latest:先执行 scripts/check-deps.sh、cargo fmt --all --check、cargo clippy --locked --workspace --all-targets -- -D warnings,再执行 cargo test --locked --workspace(应用和 Quick Look crate)。windows-latest:执行 cargo test --locked(跳过 perf_probe 测试),再用 scripts/check-windows.py 检查 debug 可执行文件。ubuntu-22.04:执行 cargo build --locked |
release.yml | 匹配 v* 的 tag,或手动指定版本触发 | 三个构建:aarch64-apple-darwin 经 package-macos.sh 签名、公证为 ducklocal-macos-arm64.dmg;Linux 在 Ubuntu 22.04 上打成 ducklocal-linux-x86_64.tar.gz;Windows 先由 scripts/check-windows.py 检查 release 可执行文件,再打成 ducklocal-windows-x86_64.zip。发布任务等三个构建都完成(任何一个失败,整个发布就失败),再用自动生成的说明创建 GitHub 发布 |
gh-pages.yml | main 分支上跳转脚本或工作流有改动时,或手动触发 | 在 GitHub Pages 上发布从旧地址 jetsquirrel.github.io/DuckLocal/ 到新站点的跳转 |
发布构建需要以下仓库密钥:MACOS_CERTIFICATE、MACOS_CERTIFICATE_PWD、NOTARY_KEY_BASE64、NOTARY_KEY_ID 和 NOTARY_ISSUER。
平台支持
| 平台 | 发布包 | 说明 |
|---|---|---|
| macOS 12+,Apple 芯片 | .dmg,已签名并公证,含 Quick Look 扩展 | CI 中运行测试,包括 Quick Look crate |
| Linux x86_64 | 二进制的 .tar.gz | 在 Ubuntu 22.04 上构建,glibc 版本够旧,大多数发行版都能用;系统库与 gpui-kit 自己的 CI 所装的一致 |
| Windows x86_64 | ducklocal.exe 的 .zip | CI 中运行测试。没有代码签名:没有 Windows 证书,所以首次启动时 SmartScreen 会弹出警告 |
测试在 macOS 和 Windows 上运行;Linux 在每个 pull request 上都会编译,但不跑测试。Windows 也跑测试,是因为它的路径(盘符、\、没有 $HOME)正是 macOS 上发现不了的出错点。随后 scripts/check-windows.py(只用 Python 标准库,在 Windows 上运行)不启动界面就检查可执行文件:是 GUI 子系统,所以不会弹出控制台窗口;内嵌了鸭子图标;--version 和一次 query 能输出到重定向的标准输出。没有 Intel Mac 版本。
快捷键用 gpui 的 secondary 修饰键绑定:在 macOS 上是 ⌘,在 Windows 和 Linux 上是 Ctrl。如果绑定成 cmd,在 Windows 和 Linux 上就成了 Windows 键,所有快捷键都按不出来。
文档站
网站放在独立的仓库 JetSquirrel/ducklocal-site,以静态资源的形式由 Cloudflare Workers 托管:
| 地址 | 内容 | 源码 |
|---|---|---|
| ducklocal.app | 产品首页 | home/,静态 HTML 与 CSS |
| ducklocal.app/docs | 这些指南,中英文 | docs/,VitePress 站点 |
应用改动后,如果指南里的说法随之变化,需要向两个仓库都提交 pull request。在本地修改指南的前提条件:Node.js 22 或更新版本(推荐 24 LTS),以及一份站点仓库的克隆。然后运行:
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-first SQL agent skill(AI 智能体技能包),附 CLI 参考 |
src/sources.rs | 把路径解析成要挂载的文件和要打开的数据库 |
src/db.rs | DuckDB 连接、挂载文件、服务器信息 |
src/query.rs | 查询执行、结果序列化、导出、EXPLAIN,以及性能分析背后的 EXPLAIN ANALYZE |
src/script.rs | 把编辑器文本拆成“运行”要依次执行的语句,包括点命令 |
src/overview.rs | 结果区的概览标签页:逐列统计与分布 |
src/extensions.rs | 侧栏的扩展标签页:列出扩展,执行 INSTALL、LOAD 和 UPDATE EXTENSIONS |
src/storage.rs | 读取 .duckdb 文件头:文件为什么打不开,能打开的文件哪些 DuckDB 版本能读 |
src/schema_cli.rs | ducklocal schema:可查询的关系和列,大小适合放进提示词 |
src/profile.rs | ducklocal schema --stats 背后的逐列统计 |
src/remote.rs | ducklocal open:与运行中窗口之间的回环通道,双向 |
src/schema.rs | 侧栏所用的 catalog 内省 |
src/spec/ | .dash 仪表盘(dashboard):解析、校验、source、filter、审阅评论(ducklocal comments)、ducklocal lsp,以及仪表盘标签页和其中的图表 |
src/excel.rs | Excel 与 ODS 工作簿,每个 sheet 载入为一张表 |
src/setup.rs | 命令行与 AI…:把 ducklocal 放进 PATH,安装 agent skill |
src/recents.rs | 侧栏列出的最近打开的仪表盘 |
src/history.rs | 应用自己的数据库:查询历史、已登记的文件、设置 |
src/s3.rs | SigV4 签名与 S3 列举 |
src/i18n.rs | 字符串表和语言选择 |
src/state.rs | 各视图共享的应用状态 |
src/ui/ | 编辑器、结果表格、图表和地图(含 OpenStreetMap 瓦片)、侧栏、对话框、标题栏和状态栏、检查更新 |
src/windows.rs | Windows 启动:CLI 命令沿用调用方的控制台,GUI 启动不开控制台 |
quicklook/ | macOS“快速查看”预览:一个供 Swift 扩展链接的 Rust 库,由 scripts/build-quicklook.sh 构建 |
src/assets.rs | 组件库默认图标之外的内嵌图标 |
src/perf_probe.rs | 仅供测试的性能探针:对随数据规模增长的代码路径,统计耗时、分配次数与分配字节数 |
assets/ | 图标(.icns、Windows 的 .ico 和资源脚本)、声明访达交给 DuckLocal 打开的文档类型的 Info.plist、README 与文档使用的示意图 |
scripts/ | bundle.sh、build-quicklook.sh、package-macos.sh、check-deps.sh、check-windows.py、make-icons.py、授权文件 |
测试说明
测试套件是 cargo test --workspace;单写 cargo test 只覆盖应用,不含 quicklook/ crate。查询执行测试通过内存连接来跑 DuckDB;导出测试同时覆盖 CSV 与 Parquet,Parquet 的测试会用 read_parquet 把写出的文件读回来验证。
