Skip to content

Commit bc9a611

Browse files
authored
Merge pull request #122 from harvard-lil/fat-binoc-distribution
Fat binoc: bundle format packs, pause per-plugin publishing, add ABI canary (v0.2.0)
2 parents f733c6a + fabaf18 commit bc9a611

19 files changed

Lines changed: 430 additions & 200 deletions

File tree

.github/workflows/publish.yml

Lines changed: 3 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ on:
44
push:
55
tags:
66
- "binoc-v*"
7-
- "binoc-sqlite-v*"
8-
- "binoc-stat-binary-v*"
7+
# binoc-sqlite and binoc-stat-binary publishing is paused; both ship
8+
# in-process via the fat `binoc` wheel. See
9+
# docs/adr/2026-06-30-fat_binoc_distribution_and_abi_canary.md.
910
- "binoc-sdk-v*"
1011
workflow_dispatch:
1112

@@ -107,180 +108,6 @@ jobs:
107108
with:
108109
packages-dir: dist
109110

110-
build-binoc-sqlite-wheels:
111-
name: Build binoc-sqlite wheels (${{ matrix.artifact }})
112-
if: startsWith(github.ref_name, 'binoc-sqlite-v')
113-
runs-on: ${{ matrix.os }}
114-
permissions:
115-
contents: read
116-
strategy:
117-
fail-fast: false
118-
matrix:
119-
include:
120-
- os: ubuntu-latest
121-
artifact: linux-x86_64
122-
target: x86_64-unknown-linux-gnu
123-
manylinux: auto
124-
interpreter: python3.10
125-
- os: windows-latest
126-
artifact: windows-x86_64
127-
target: x86_64-pc-windows-msvc
128-
manylinux: off
129-
interpreter: python
130-
- os: macos-15-intel
131-
artifact: macos-x86_64
132-
target: x86_64-apple-darwin
133-
manylinux: off
134-
interpreter: python
135-
- os: macos-14
136-
artifact: macos-aarch64
137-
target: aarch64-apple-darwin
138-
manylinux: off
139-
interpreter: python
140-
steps:
141-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
142-
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
143-
with:
144-
python-version: "3.10"
145-
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1
146-
with:
147-
command: build
148-
working-directory: model-plugins/binoc-sqlite
149-
target: ${{ matrix.target }}
150-
manylinux: ${{ matrix.manylinux }}
151-
args: --release --interpreter ${{ matrix.interpreter }} --out dist
152-
sccache: "true"
153-
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
154-
with:
155-
name: binoc-sqlite-dist-${{ matrix.artifact }}
156-
path: model-plugins/binoc-sqlite/dist
157-
158-
build-binoc-sqlite-sdist:
159-
name: Build binoc-sqlite sdist
160-
if: startsWith(github.ref_name, 'binoc-sqlite-v')
161-
runs-on: ubuntu-latest
162-
permissions:
163-
contents: read
164-
steps:
165-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
166-
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1
167-
with:
168-
command: sdist
169-
working-directory: model-plugins/binoc-sqlite
170-
args: --out dist
171-
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
172-
with:
173-
name: binoc-sqlite-dist-sdist
174-
path: model-plugins/binoc-sqlite/dist
175-
176-
publish-binoc-sqlite:
177-
name: Publish binoc-sqlite to PyPI
178-
if: startsWith(github.ref_name, 'binoc-sqlite-v')
179-
runs-on: ubuntu-latest
180-
needs: [build-binoc-sqlite-wheels, build-binoc-sqlite-sdist]
181-
environment:
182-
name: pypi-binoc-sqlite
183-
url: https://pypi.org/project/binoc-sqlite/
184-
permissions:
185-
contents: read
186-
id-token: write
187-
steps:
188-
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
189-
with:
190-
pattern: binoc-sqlite-dist-*
191-
path: dist
192-
merge-multiple: true
193-
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
194-
with:
195-
packages-dir: dist
196-
197-
build-binoc-stat-binary-wheels:
198-
name: Build binoc-stat-binary wheels (${{ matrix.artifact }})
199-
if: startsWith(github.ref_name, 'binoc-stat-binary-v')
200-
runs-on: ${{ matrix.os }}
201-
permissions:
202-
contents: read
203-
strategy:
204-
fail-fast: false
205-
matrix:
206-
include:
207-
- os: ubuntu-latest
208-
artifact: linux-x86_64
209-
target: x86_64-unknown-linux-gnu
210-
manylinux: auto
211-
interpreter: python3.10
212-
- os: windows-latest
213-
artifact: windows-x86_64
214-
target: x86_64-pc-windows-msvc
215-
manylinux: off
216-
interpreter: python
217-
- os: macos-15-intel
218-
artifact: macos-x86_64
219-
target: x86_64-apple-darwin
220-
manylinux: off
221-
interpreter: python
222-
- os: macos-14
223-
artifact: macos-aarch64
224-
target: aarch64-apple-darwin
225-
manylinux: off
226-
interpreter: python
227-
steps:
228-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
229-
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
230-
with:
231-
python-version: "3.10"
232-
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1
233-
with:
234-
command: build
235-
working-directory: model-plugins/binoc-stat-binary
236-
target: ${{ matrix.target }}
237-
manylinux: ${{ matrix.manylinux }}
238-
args: --release --interpreter ${{ matrix.interpreter }} --out dist
239-
sccache: "true"
240-
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
241-
with:
242-
name: binoc-stat-binary-dist-${{ matrix.artifact }}
243-
path: model-plugins/binoc-stat-binary/dist
244-
245-
build-binoc-stat-binary-sdist:
246-
name: Build binoc-stat-binary sdist
247-
if: startsWith(github.ref_name, 'binoc-stat-binary-v')
248-
runs-on: ubuntu-latest
249-
permissions:
250-
contents: read
251-
steps:
252-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
253-
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1
254-
with:
255-
command: sdist
256-
working-directory: model-plugins/binoc-stat-binary
257-
args: --out dist
258-
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
259-
with:
260-
name: binoc-stat-binary-dist-sdist
261-
path: model-plugins/binoc-stat-binary/dist
262-
263-
publish-binoc-stat-binary:
264-
name: Publish binoc-stat-binary to PyPI
265-
if: startsWith(github.ref_name, 'binoc-stat-binary-v')
266-
runs-on: ubuntu-latest
267-
needs: [build-binoc-stat-binary-wheels, build-binoc-stat-binary-sdist]
268-
environment:
269-
name: pypi-binoc-stat-binary
270-
url: https://pypi.org/project/binoc-stat-binary/
271-
permissions:
272-
contents: read
273-
id-token: write
274-
steps:
275-
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
276-
with:
277-
pattern: binoc-stat-binary-dist-*
278-
path: dist
279-
merge-multiple: true
280-
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
281-
with:
282-
packages-dir: dist
283-
284111
publish-binoc-sdk:
285112
name: Publish binoc-sdk to crates.io
286113
if: startsWith(github.ref_name, 'binoc-sdk-v')

Cargo.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ members = [
55
"binoc-stdlib",
66
"binoc-cli",
77
"binoc-python",
8+
"binoc-abi-canary",
89
"model-plugins/binoc-sqlite",
910
"model-plugins/binoc-row-reorder",
1011
"model-plugins/binoc-stat-binary",
@@ -21,6 +22,7 @@ default-members = [
2122
"binoc-core",
2223
"binoc-stdlib",
2324
"binoc-cli",
25+
"binoc-abi-canary",
2426
"model-plugins/binoc-sqlite",
2527
"model-plugins/binoc-row-reorder",
2628
"model-plugins/binoc-stat-binary",

binoc-abi-canary/Cargo.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "binoc-abi-canary"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
license.workspace = true
7+
repository.workspace = true
8+
homepage.workspace = true
9+
documentation.workspace = true
10+
description = "ABI canary: a native renderer built as a cdylib and loaded over the plugin C ABI in tests, so the boundary stays compiler/linker-enforced"
11+
publish = false
12+
13+
[lib]
14+
name = "binoc_abi_canary"
15+
crate-type = ["cdylib", "rlib"]
16+
17+
# `export_plugin!` emits a `#[cfg(feature = "python")]` pymodule. This crate
18+
# deliberately has no `python` feature (no pyo3); tell check-cfg the value is
19+
# expected rather than declaring a feature that would fail to build if enabled.
20+
[lints.rust]
21+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("python"))'] }
22+
23+
[dependencies]
24+
binoc-sdk = { workspace = true }
25+
serde_json = { workspace = true }
26+
27+
[dev-dependencies]
28+
libloading = "0.9.0"

binoc-abi-canary/src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//! ABI canary: a native renderer exported over the plugin C ABI.
2+
//!
3+
//! This crate exists to keep the renderer ABI boundary *honest*. It is a real,
4+
//! separately-compiled `cdylib` that goes through [`binoc_sdk::export_plugin!`],
5+
//! and [`tests/abi_crossing.rs`](../tests/abi_crossing.rs) loads the built
6+
//! artifact over `libloading` — the same path `binoc-python` uses — and asserts
7+
//! a render round-trips.
8+
//!
9+
//! Why this matters: compiling plugins in-process (the fat-`binoc` wheel) means
10+
//! the compiler no longer forces plugin-facing types to be expressible across a
11+
//! process boundary. This crate restores that guarantee for the stable
12+
//! (renderer) tier: if the renderer ABI drifts to something that cannot cross
13+
//! `extern "C"` + JSON, `export_plugin!` fails to compile here, and if the wire
14+
//! contract drifts, the crossing test fails to load or round-trip. It is also
15+
//! the template each rule family must satisfy when it graduates into
16+
//! `plugin_abi`. See
17+
//! `docs/adr/2026-06-30-fat_binoc_distribution_and_abi_canary.md`.
18+
19+
use binoc_sdk::{BinocResult, Changeset, Renderer, RendererDescriptor};
20+
21+
/// A trivial renderer that echoes a few open-vocabulary IR fields back as JSON.
22+
/// Its only job is to exercise the real `cdylib` → `libloading` crossing.
23+
#[derive(Default)]
24+
pub struct EchoRenderer;
25+
26+
impl Renderer for EchoRenderer {
27+
fn descriptor(&self) -> RendererDescriptor {
28+
RendererDescriptor::new("canary.echo", "json")
29+
}
30+
31+
fn render(&self, changesets: &[Changeset], config: &serde_json::Value) -> BinocResult<String> {
32+
let root_action = changesets
33+
.first()
34+
.and_then(|changeset| changeset.root.as_ref())
35+
.map(|root| root.action.clone())
36+
.unwrap_or_else(|| "none".to_string());
37+
Ok(serde_json::json!({
38+
"changesets": changesets.len(),
39+
"root_action": root_action,
40+
"mode": config.get("mode"),
41+
})
42+
.to_string())
43+
}
44+
}
45+
46+
binoc_sdk::export_plugin! {
47+
module: binoc_abi_canary,
48+
renderers: [EchoRenderer],
49+
}

0 commit comments

Comments
 (0)