Babashka 1.13.220 gets FFI
Babashka 1.13.220 gets FFI
Today babashka 1.13.220 is released, with a new babashka.ffi namespace for calling C libraries directly from Babashka scripts. The babashka.ffi library is also available as a standalone library for JVM Clojure, so you can use it in your Clojure projects as well. Note that the API is still experimental, although no changes are currently planned. It just needs more exposure and your feedback :).
今天 Babashka 1.13.220 正式发布,带来了全新的 babashka.ffi 命名空间,用于直接从 Babashka 脚本中调用 C 语言库。babashka.ffi 库也作为独立库提供给 JVM Clojure 使用,因此你也可以在自己的 Clojure 项目中使用它。请注意,该 API 目前仍处于实验阶段,尽管暂时没有修改计划。它只是需要更多的曝光和你的反馈 :)。
Calling C
调用 C 语言
This example loads libz from your system and requests the version.
此示例从你的系统中加载 libz 并获取其版本号。
(require '[babashka.ffi :as ffi :refer [defcfn]])
(def zlib (ffi/load-system-library "z"))
(def zlib-version (ffi/cfn zlib "zlibVersion" [] :string))
(zlib-version) ;;=> "1.3.1"
This example loads an OS-specific library for doing math: 此示例加载了一个特定于操作系统的数学库:
(ffi/load-library {:mac "libm.dylib" :linux "libm.so.6" :windows "ucrtbase.dll"})
(defcfn cos "cos" [:double] :double)
(defcfn pow "pow" [:double :double] :double)
(cos 0.0) ;;=> 1.0
(pow 2.0 10.0) ;;=> 1024.0
To get a feeling for how to use it in larger, non-trivial projects, read the library guide. Some of the API decisions like defcfn are clearly inspired by coffi, so I want to thank Joshua Suskalo for leading the way with his excellent library. But babashka.ffi is not simply a copy of coffi. It does a few things differently. You can provide an explicit library (or a function or delay that resolves to one) to defcfn for example. Also it has a place concept (inspired by Specter’s paths) that efficiently lets you read from and write to structs and unions. Like coffi, babashka.ffi builds on java.lang.foreign and makes you manage memory explicitly through arenas. One benefit of this is that you’ll get exceptions rather than segfaults that tear down your REPL and you can use with-open to release allocated memory.
若想了解如何在更大、更复杂的项目中使用它,请阅读库指南。一些 API 设计决策(如 defcfn)显然受到了 coffi 的启发,因此我要感谢 Joshua Suskalo 用他出色的库引领了方向。但 babashka.ffi 并非 coffi 的简单复制,它在某些方面有所不同。例如,你可以为 defcfn 提供一个显式的库(或解析为库的函数或 delay)。此外,它还引入了“位置(place)”概念(受 Specter 的路径启发),可以高效地读写结构体和联合体。与 coffi 一样,babashka.ffi 构建在 java.lang.foreign 之上,并要求你通过 arenas 显式管理内存。这样做的好处是,你将获得异常而不是导致 REPL 崩溃的段错误(segfault),并且可以使用 with-open 来释放已分配的内存。
Install
安装
To use babashka.ffi and libraries that build on it, you have to use a dynamically linked version of babashka. On Mac and Windows this was always the default. On Linux, the static binary was preferred historically since it did not depend on your system’s libc and zlib. In this release we flip this default to a mostly-static binary: all the shared C libraries that babashka needs are statically linked, and glibc is the only dynamically linked part. The aarch64 binary, although it carries -static in its name, was already built this way. Babashka on Linux is built in a container that pins the glibc version to the lowest one possible so it should work on all mainstream LTS versions of Linux today. If you still prefer the fully static binary, you can use the install script with the --static flag.
要使用 babashka.ffi 及其构建的库,你必须使用动态链接版本的 Babashka。在 Mac 和 Windows 上,这一直是默认设置。在 Linux 上,历史上更倾向于使用静态二进制文件,因为它不依赖于系统的 libc 和 zlib。在此版本中,我们将默认设置更改为“准静态”二进制文件:Babashka 所需的所有共享 C 库均已静态链接,只有 glibc 是动态链接的。aarch64 二进制文件尽管名称中带有 -static,但其实早已采用这种方式构建。Linux 上的 Babashka 是在一个将 glibc 版本固定为尽可能低版本的容器中构建的,因此它应该可以在当今所有主流的 Linux LTS 版本上运行。如果你仍然偏好完全静态的二进制文件,可以使用带有 --static 标志的安装脚本。
If you use a package manager or a GitHub Action to install babashka, it may not yet be up to date with this new policy. If that is the case, feel free to open an issue at the babashka GitHub repo and I’ll reach out to get this fixed. Meanwhile you can install babashka using the installer script on GitHub to a temporary directory to get a second installation of babashka with FFI enabled: 如果你使用包管理器或 GitHub Action 安装 Babashka,它可能尚未更新以符合此新策略。如果是这种情况,请随时在 Babashka GitHub 仓库中提交 issue,我会跟进解决。同时,你可以使用 GitHub 上的安装脚本将 Babashka 安装到临时目录,从而获得一个启用了 FFI 的 Babashka 安装实例:
$ curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install
$ bash install --dir /tmp/bb-test
$ /tmp/bb-test/bb -e "(require '[babashka.ffi :as ffi]) (ffi/load-system-library \"z\")"
The installer script probes your system for the supported glibc version and falls back to the fully static version when necessary. 安装脚本会探测你系统支持的 glibc 版本,并在必要时回退到完全静态版本。
Demos
演示
To validate the design of babashka.ffi, I built a few shiny demos:
为了验证 babashka.ffi 的设计,我构建了一些炫酷的演示:
pacman.clj: pac-man with the classic ghost personalities (requires raylib)pacman.clj: 带有经典幽灵个性的吃豆人(需要 raylib)doom.clj: a raycaster with textures and sprites (requires raylib)doom.clj: 带有纹理和精灵的射线投射器(需要 raylib)helitorus.clj: a helix around a torus (requires raylib)helitorus.clj: 环绕圆环的螺旋线(需要 raylib)gtk4.clj: a native GTK 4 window rendering from an atomgtk4.clj: 从 atom 渲染的原生 GTK 4 窗口portaudio.clj: an arpeggio through a realtime audio callbackportaudio.clj: 通过实时音频回调实现的琶音python.clj: embedded CPython calling back into Clojurepython.clj: 嵌入式 CPython 回调 Clojure
A one-liner to try these demos: 尝试这些演示的一行命令:
$ bb -e '(load-string (slurp "https://raw.githubusercontent.com/babashka/ffi/main/examples/pacman.clj"))'
FFI-based libraries
基于 FFI 的库
To validate the design of babashka.ffi even more, a couple of new libraries were born. These libraries mostly resemble existing pods but now use FFI to fulfill similar use cases.
为了进一步验证 babashka.ffi 的设计,诞生了几个新库。这些库大多类似于现有的 pods,但现在使用 FFI 来实现类似的使用场景。
babashka.sqlitebabashka.duckdbbabashka.postgresfilewatcher
One cool thing you could not do with a pod before is defining a Clojure function in SQLite: 以前使用 pod 无法做到的一件很酷的事情是,在 SQLite 中定义 Clojure 函数:
(require '[babashka.sqlite :as sq])
(sq/with-conn [db nil]
(sq/create-function! db "initials" (fn [s] (apply str (map first (clojure.string/split s #" ")))))
(sq/query db ["select initials(?) i" "gerald jay sussman"]))
;;=> [{:i "gjs"}]
Tasks: :exec-fn composition
任务::exec-fn 组合
This release also has some really nice task improvements: :exec-fn tasks now compose through :depends. A task can depend on another CLI task, and the dependency’s options parse, coerce and show up in --help and shell completion:
此版本还带来了一些非常棒的任务改进::exec-fn 任务现在可以通过 :depends 进行组合。一个任务可以依赖于另一个 CLI 任务,并且依赖项的选项可以被解析、强制转换,并显示在 --help 和 shell 自动补全中:
{:tasks {compile {:exec-fn build/compile-sources :cli {:spec {:release {:coerce :boolean}}}}
jar {:depends [compile] :exec-fn build/jar}}}
$ bb jar --help
...
Inherited options:
--release
Also you can now directly provide :exec-args on a task:
此外,你现在可以直接在任务上提供 :exec-args:
{:tasks {deploy {:exec-fn deploy/run :exec-args {:env "staging"}}}}
A :cmd tree can now be provided through a var, whose namespace is loaded on demand:
现在可以通过 var 提供 :cmd 树,其命名空间按需加载:
{:tasks {cli {:cmd my.project.cli/commands}}}
AI disclosure
AI 披露
While developing FFI and while validating the design through examples and writing libraries, I have made use of LLM assistance. 在开发 FFI 以及通过示例验证设计和编写库的过程中,我使用了 LLM 辅助。
Wrapping up
总结
Hope you’ll like these new features! The full changelog can be found here. 希望你喜欢这些新功能!完整的更新日志可以在这里找到。