2026-05-05 14:39:00 +00:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# Step 1a Phase C Task 4 / spec v19 §11 + §12.3 boundary invariants.
|
|
|
|
|
#
|
|
|
|
|
# Verifies the following Jian crate boundary invariants from outside the
|
|
|
|
|
# Rust build system. Run from the repo root.
|
|
|
|
|
#
|
2026-05-16 16:01:35 +00:00
|
|
|
# (The former Invariant 1 — "the app crate must not depend directly on
|
2026-05-16 04:45:07 +00:00
|
|
|
# any jian-* crate" — was dropped in Phase 1 Task 1.2 along with the
|
2026-06-19 13:55:33 +00:00
|
|
|
# old placeholder crate. The Phase 7.3 reorg's `op-app` composition-root
|
|
|
|
|
# crate was removed 2026-06-19 as an orphan — nothing depended on it and
|
|
|
|
|
# the editor-UI composition already lives in op-editor-ui. Reinstate an
|
|
|
|
|
# equivalent facade check if a real app crate is reintroduced.)
|
2026-05-05 14:39:00 +00:00
|
|
|
#
|
2026-07-20 16:15:03 +00:00
|
|
|
# Invariant 2 (§11.1, §12.3 — REVISED 2026-07-20): mobile targets
|
|
|
|
|
# (both CI architectures for Android and iOS) must NOT pull
|
feat(shell-native): extend widget stack to iOS + Android cargo check
Per 2026-05-10 user directive ("extend, jian 最后也会需要 ios 和
android"): lift the desktop-only cfg gate so the widget render
stack (skia-safe + jian-skia + NativeBackend + widget_host)
compiles for iOS (`aarch64-apple-ios`) AND Android
(`aarch64-linux-android`) cargo check too. Mobile shells now have
a real widget-rendering surface to target in Step 1f, and the
"shell-core widgets are platform-agnostic" claim from spec §1.4
is now compile-verified across desktop trio + mobile pair + wasm.
Cargo.toml restructure (`crates/openpencil-shell-native/Cargo.toml`):
- New `[target.'cfg(any(macos, linux, windows, ios, android))']`
block for the cross-platform widget stack: `skia-safe = "0.97"`
(default-features = false; binary-cache + textlayout) and
`jian-skia` (textlayout). Both pull on every desktop trio +
mobile pair target.
- Existing desktop-only block kept for the GUI host stack: adds
`gl` to skia-safe's features (iOS deprecated GL — Metal goes
in Step 1f; Android GL/Vulkan via the platform provider not
via skia-safe's bundled bindings here), plus glutin / glutin-
winit / winit / scopeguard / jian-host-desktop. Cargo
deduplicates: skia-safe resolves to one crate-version with
feature-union (binary-cache + textlayout from the wider block
+ gl from the desktop block on desktop-only).
src/lib.rs gate lift:
- `pub mod backend;` and `pub mod widget_host;` cfg now includes
`target_os = "ios"` and `target_os = "android"`. `pub use`
re-exports follow.
- `canvas_view_stub` stays desktop-only (uses glow GL-isolation
probe with no mobile equivalent).
- Comment block at the cfg site cites the user directive +
Step 1f handoff (real EaglProvider / AndroidEglProvider impls
+ Metal / Vulkan / event integration).
Boundary script revision (`tools/check-jian-boundaries.sh`):
- Invariant 2 was: mobile targets must NOT pull jian-host-desktop
OR jian-skia. Per the user directive, jian-skia is now ALLOWED
on mobile (the widget render stack uses it). jian-host-desktop
remains forbidden — it carries winit / glutin / desktop
GLContextProvider impls that have no mobile equivalent.
- Header comment block + active grep narrowed accordingly. The
Step 1f path through EaglProvider / AndroidEglProvider is the
spec-blessed mobile host plugin point (no IPC / CLI needed).
Verification:
- `cargo check -p openpencil-shell-native --target
aarch64-apple-ios` — green (skia-bindings + jian-skia +
shell-native all compile)
- `cargo check -p openpencil-shell-native --target
aarch64-linux-android` — green (same)
- `cargo check -p openpencil-shell-native` — green (no desktop
regression)
- `cargo check -p openpencil-shell-core --target
wasm32-unknown-unknown` — green (shell-core stays wasm32-clean)
- `cargo test -p openpencil-shell-core --test widgets_static` —
21/21 passing (widget logic untouched)
- `bash tools/check-wasm-bundle.sh` — PASS:
- 0 env.* imports
- 622 156 bytes gzip = 59% of 1 MiB ceiling (no web regression)
- `bash tools/check-widget-boundary.sh` — PASS
- `bash tools/check-jian-boundaries.sh` — 4/4 invariants pass
(Invariant 2 revised to allow jian-skia on mobile)
What's still mobile-pending (Step 1f scope):
- `EaglProvider` (iOS) — Metal-backed `GlContextProvider` impl
(skia-safe `metal` feature when iOS host actually runs)
- `AndroidEglProvider` (Android) — GL/Vulkan-backed impl
- Mobile host runners (UIKit AppDelegate / Activity wrappers)
- Mobile event translation (jian-host-ios / jian-host-android —
siblings of jian-host-desktop)
- `inspector_window` example is desktop-only by design (winit +
SharedSkiaContext::new_desktop); mobile shells will land their
own UIKit / Activity runners that consume the SAME
`WidgetHostNative::paint(&mut frame, width)` surface
The widget glue itself (NativeFrameBackend + WidgetHostNative)
is platform-agnostic in shape — no winit / glutin / EGL types
leak in. Step 1f mobile work plugs in providers, not widgets.
2026-05-10 02:16:36 +00:00
|
|
|
# `jian-host-desktop` (desktop GUI host glue — winit / glutin /
|
2026-07-20 16:15:03 +00:00
|
|
|
# GLContextProvider impl that has no mobile equivalent) or `rquickjs*`
|
|
|
|
|
# (desktop script generation; upstream has no mobile bindings). Per the
|
2026-05-17 01:33:41 +00:00
|
|
|
# 2026-05-10 user directive (jian will eventually need iOS and
|
|
|
|
|
# Android), `jian-skia` is now ALLOWED on mobile targets — the
|
feat(shell-native): extend widget stack to iOS + Android cargo check
Per 2026-05-10 user directive ("extend, jian 最后也会需要 ios 和
android"): lift the desktop-only cfg gate so the widget render
stack (skia-safe + jian-skia + NativeBackend + widget_host)
compiles for iOS (`aarch64-apple-ios`) AND Android
(`aarch64-linux-android`) cargo check too. Mobile shells now have
a real widget-rendering surface to target in Step 1f, and the
"shell-core widgets are platform-agnostic" claim from spec §1.4
is now compile-verified across desktop trio + mobile pair + wasm.
Cargo.toml restructure (`crates/openpencil-shell-native/Cargo.toml`):
- New `[target.'cfg(any(macos, linux, windows, ios, android))']`
block for the cross-platform widget stack: `skia-safe = "0.97"`
(default-features = false; binary-cache + textlayout) and
`jian-skia` (textlayout). Both pull on every desktop trio +
mobile pair target.
- Existing desktop-only block kept for the GUI host stack: adds
`gl` to skia-safe's features (iOS deprecated GL — Metal goes
in Step 1f; Android GL/Vulkan via the platform provider not
via skia-safe's bundled bindings here), plus glutin / glutin-
winit / winit / scopeguard / jian-host-desktop. Cargo
deduplicates: skia-safe resolves to one crate-version with
feature-union (binary-cache + textlayout from the wider block
+ gl from the desktop block on desktop-only).
src/lib.rs gate lift:
- `pub mod backend;` and `pub mod widget_host;` cfg now includes
`target_os = "ios"` and `target_os = "android"`. `pub use`
re-exports follow.
- `canvas_view_stub` stays desktop-only (uses glow GL-isolation
probe with no mobile equivalent).
- Comment block at the cfg site cites the user directive +
Step 1f handoff (real EaglProvider / AndroidEglProvider impls
+ Metal / Vulkan / event integration).
Boundary script revision (`tools/check-jian-boundaries.sh`):
- Invariant 2 was: mobile targets must NOT pull jian-host-desktop
OR jian-skia. Per the user directive, jian-skia is now ALLOWED
on mobile (the widget render stack uses it). jian-host-desktop
remains forbidden — it carries winit / glutin / desktop
GLContextProvider impls that have no mobile equivalent.
- Header comment block + active grep narrowed accordingly. The
Step 1f path through EaglProvider / AndroidEglProvider is the
spec-blessed mobile host plugin point (no IPC / CLI needed).
Verification:
- `cargo check -p openpencil-shell-native --target
aarch64-apple-ios` — green (skia-bindings + jian-skia +
shell-native all compile)
- `cargo check -p openpencil-shell-native --target
aarch64-linux-android` — green (same)
- `cargo check -p openpencil-shell-native` — green (no desktop
regression)
- `cargo check -p openpencil-shell-core --target
wasm32-unknown-unknown` — green (shell-core stays wasm32-clean)
- `cargo test -p openpencil-shell-core --test widgets_static` —
21/21 passing (widget logic untouched)
- `bash tools/check-wasm-bundle.sh` — PASS:
- 0 env.* imports
- 622 156 bytes gzip = 59% of 1 MiB ceiling (no web regression)
- `bash tools/check-widget-boundary.sh` — PASS
- `bash tools/check-jian-boundaries.sh` — 4/4 invariants pass
(Invariant 2 revised to allow jian-skia on mobile)
What's still mobile-pending (Step 1f scope):
- `EaglProvider` (iOS) — Metal-backed `GlContextProvider` impl
(skia-safe `metal` feature when iOS host actually runs)
- `AndroidEglProvider` (Android) — GL/Vulkan-backed impl
- Mobile host runners (UIKit AppDelegate / Activity wrappers)
- Mobile event translation (jian-host-ios / jian-host-android —
siblings of jian-host-desktop)
- `inspector_window` example is desktop-only by design (winit +
SharedSkiaContext::new_desktop); mobile shells will land their
own UIKit / Activity runners that consume the SAME
`WidgetHostNative::paint(&mut frame, width)` surface
The widget glue itself (NativeFrameBackend + WidgetHostNative)
is platform-agnostic in shape — no winit / glutin / EGL types
leak in. Step 1f mobile work plugs in providers, not widgets.
2026-05-10 02:16:36 +00:00
|
|
|
# widget render stack (skia-safe + jian-skia + NativeBackend +
|
|
|
|
|
# widget_host) compiles for iOS / Android cargo check, leaving
|
|
|
|
|
# only the host-specific (Metal / Vulkan / event) integration to
|
|
|
|
|
# Step 1f via the existing `EaglProvider` / `AndroidEglProvider`
|
|
|
|
|
# plugin point.
|
2026-05-05 14:39:00 +00:00
|
|
|
#
|
2026-05-16 16:01:35 +00:00
|
|
|
# Invariant 3 (§11.1, §1.2): wasm32 builds of `op-host-web`
|
2026-05-05 14:39:00 +00:00
|
|
|
# must NOT pull `jian-host-desktop` or `jian-skia` (skia-safe build.rs
|
|
|
|
|
# fails on wasm32; Jian-core is wasm32-clean per P0.5 and is the only
|
|
|
|
|
# Jian crate allowed in the bundle).
|
|
|
|
|
#
|
2026-05-16 16:01:35 +00:00
|
|
|
# Invariant 4 (§1.2): `op-host-web` must NOT depend on
|
2026-05-05 14:39:00 +00:00
|
|
|
# `jian-host-desktop` at all — even as a non-default optional dep.
|
|
|
|
|
#
|
|
|
|
|
# Exit codes:
|
|
|
|
|
# 0 — all invariants pass.
|
|
|
|
|
# 1+ — one or more invariants fail; the failing crate names are
|
|
|
|
|
# echoed before the script exits.
|
|
|
|
|
#
|
|
|
|
|
# Dependencies: `cargo`, `jq` (for cargo metadata JSON parsing).
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
|
|
|
echo "check-jian-boundaries.sh: \`jq\` is required but not installed." >&2
|
|
|
|
|
echo " apt: sudo apt-get install -y jq" >&2
|
|
|
|
|
echo " brew: brew install jq" >&2
|
|
|
|
|
exit 2
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-20 16:15:03 +00:00
|
|
|
# ── Invariant 2: mobile targets don't pull desktop-only backends. ────
|
feat(shell-native): extend widget stack to iOS + Android cargo check
Per 2026-05-10 user directive ("extend, jian 最后也会需要 ios 和
android"): lift the desktop-only cfg gate so the widget render
stack (skia-safe + jian-skia + NativeBackend + widget_host)
compiles for iOS (`aarch64-apple-ios`) AND Android
(`aarch64-linux-android`) cargo check too. Mobile shells now have
a real widget-rendering surface to target in Step 1f, and the
"shell-core widgets are platform-agnostic" claim from spec §1.4
is now compile-verified across desktop trio + mobile pair + wasm.
Cargo.toml restructure (`crates/openpencil-shell-native/Cargo.toml`):
- New `[target.'cfg(any(macos, linux, windows, ios, android))']`
block for the cross-platform widget stack: `skia-safe = "0.97"`
(default-features = false; binary-cache + textlayout) and
`jian-skia` (textlayout). Both pull on every desktop trio +
mobile pair target.
- Existing desktop-only block kept for the GUI host stack: adds
`gl` to skia-safe's features (iOS deprecated GL — Metal goes
in Step 1f; Android GL/Vulkan via the platform provider not
via skia-safe's bundled bindings here), plus glutin / glutin-
winit / winit / scopeguard / jian-host-desktop. Cargo
deduplicates: skia-safe resolves to one crate-version with
feature-union (binary-cache + textlayout from the wider block
+ gl from the desktop block on desktop-only).
src/lib.rs gate lift:
- `pub mod backend;` and `pub mod widget_host;` cfg now includes
`target_os = "ios"` and `target_os = "android"`. `pub use`
re-exports follow.
- `canvas_view_stub` stays desktop-only (uses glow GL-isolation
probe with no mobile equivalent).
- Comment block at the cfg site cites the user directive +
Step 1f handoff (real EaglProvider / AndroidEglProvider impls
+ Metal / Vulkan / event integration).
Boundary script revision (`tools/check-jian-boundaries.sh`):
- Invariant 2 was: mobile targets must NOT pull jian-host-desktop
OR jian-skia. Per the user directive, jian-skia is now ALLOWED
on mobile (the widget render stack uses it). jian-host-desktop
remains forbidden — it carries winit / glutin / desktop
GLContextProvider impls that have no mobile equivalent.
- Header comment block + active grep narrowed accordingly. The
Step 1f path through EaglProvider / AndroidEglProvider is the
spec-blessed mobile host plugin point (no IPC / CLI needed).
Verification:
- `cargo check -p openpencil-shell-native --target
aarch64-apple-ios` — green (skia-bindings + jian-skia +
shell-native all compile)
- `cargo check -p openpencil-shell-native --target
aarch64-linux-android` — green (same)
- `cargo check -p openpencil-shell-native` — green (no desktop
regression)
- `cargo check -p openpencil-shell-core --target
wasm32-unknown-unknown` — green (shell-core stays wasm32-clean)
- `cargo test -p openpencil-shell-core --test widgets_static` —
21/21 passing (widget logic untouched)
- `bash tools/check-wasm-bundle.sh` — PASS:
- 0 env.* imports
- 622 156 bytes gzip = 59% of 1 MiB ceiling (no web regression)
- `bash tools/check-widget-boundary.sh` — PASS
- `bash tools/check-jian-boundaries.sh` — 4/4 invariants pass
(Invariant 2 revised to allow jian-skia on mobile)
What's still mobile-pending (Step 1f scope):
- `EaglProvider` (iOS) — Metal-backed `GlContextProvider` impl
(skia-safe `metal` feature when iOS host actually runs)
- `AndroidEglProvider` (Android) — GL/Vulkan-backed impl
- Mobile host runners (UIKit AppDelegate / Activity wrappers)
- Mobile event translation (jian-host-ios / jian-host-android —
siblings of jian-host-desktop)
- `inspector_window` example is desktop-only by design (winit +
SharedSkiaContext::new_desktop); mobile shells will land their
own UIKit / Activity runners that consume the SAME
`WidgetHostNative::paint(&mut frame, width)` surface
The widget glue itself (NativeFrameBackend + WidgetHostNative)
is platform-agnostic in shape — no winit / glutin / EGL types
leak in. Step 1f mobile work plugs in providers, not widgets.
2026-05-10 02:16:36 +00:00
|
|
|
# `cargo tree` honours `--target` cfg-gates so only the deps that
|
|
|
|
|
# actually compile under the mobile target are listed. `jian-skia` IS
|
|
|
|
|
# allowed on mobile (2026-05-10 user directive — widget render stack
|
|
|
|
|
# extends to iOS / Android); we still forbid `jian-host-desktop`
|
|
|
|
|
# because it pulls winit / glutin / desktop GLContextProvider impls
|
2026-07-20 16:15:03 +00:00
|
|
|
# that have no mobile equivalent. QuickJS is also forbidden: its
|
|
|
|
|
# rquickjs-sys package has no generated bindings for these targets and
|
|
|
|
|
# script generation is only used by the desktop `gl-host` Preview path.
|
|
|
|
|
for target in \
|
|
|
|
|
aarch64-linux-android \
|
|
|
|
|
x86_64-linux-android \
|
|
|
|
|
aarch64-apple-ios \
|
|
|
|
|
aarch64-apple-ios-sim; do
|
2026-05-16 16:01:35 +00:00
|
|
|
tree_mobile="$(cargo tree -p op-host-native \
|
2026-05-05 14:39:00 +00:00
|
|
|
--target "$target" \
|
|
|
|
|
--prefix none \
|
|
|
|
|
--edges normal,build 2>/dev/null || true)"
|
|
|
|
|
forbidden_mobile="$(echo "$tree_mobile" \
|
2026-07-20 16:15:03 +00:00
|
|
|
| grep -oE '\b(jian-host-desktop|rquickjs(-core|-sys)?)\b' \
|
2026-05-05 14:39:00 +00:00
|
|
|
| sort -u || true)"
|
|
|
|
|
if [ -n "$forbidden_mobile" ]; then
|
2026-07-20 16:15:03 +00:00
|
|
|
echo "INVARIANT 2 FAILED ($target): forbidden desktop-only crates in closure:" >&2
|
2026-05-05 14:39:00 +00:00
|
|
|
echo "$forbidden_mobile" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# ── Invariant 3: wasm32 has no jian-host-desktop / jian-skia. ──────────
|
|
|
|
|
# `jian-core` IS allowed (P0.5 wasm32-clean).
|
2026-05-16 16:01:35 +00:00
|
|
|
tree_wasm="$(cargo tree -p op-host-web \
|
2026-05-05 14:39:00 +00:00
|
|
|
--target wasm32-unknown-unknown \
|
|
|
|
|
--prefix none \
|
|
|
|
|
--edges normal,build 2>/dev/null || true)"
|
|
|
|
|
forbidden_wasm="$(echo "$tree_wasm" \
|
|
|
|
|
| grep -oE '\bjian-(host-desktop|skia)\b' \
|
|
|
|
|
| sort -u || true)"
|
|
|
|
|
if [ -n "$forbidden_wasm" ]; then
|
2026-05-16 16:01:35 +00:00
|
|
|
echo "INVARIANT 3 FAILED: wasm32 op-host-web pulls forbidden Jian crates:" >&2
|
2026-05-05 14:39:00 +00:00
|
|
|
echo "$forbidden_wasm" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-05-16 16:01:35 +00:00
|
|
|
# ── Invariant 4: op-host-web has no jian-host-desktop dep. ───
|
2026-05-05 14:39:00 +00:00
|
|
|
# Distinct from invariant 3 (which checks the resolved closure on the
|
|
|
|
|
# wasm32 target): this checks the manifest itself across all targets.
|
|
|
|
|
# `cargo tree --all-targets` would include dev-deps; we explicitly
|
|
|
|
|
# filter `--edges normal,build` for the manifest-level invariant.
|
2026-05-16 16:01:35 +00:00
|
|
|
shell_web_deps="$(cargo tree -p op-host-web \
|
2026-05-05 14:39:00 +00:00
|
|
|
--prefix none \
|
|
|
|
|
--edges normal,build 2>/dev/null \
|
|
|
|
|
| grep -E '\bjian-host-desktop\b' || true)"
|
|
|
|
|
if [ -n "$shell_web_deps" ]; then
|
2026-05-16 16:01:35 +00:00
|
|
|
echo "INVARIANT 4 FAILED: op-host-web depends on jian-host-desktop:" >&2
|
2026-05-05 14:39:00 +00:00
|
|
|
echo "$shell_web_deps" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-20 16:15:03 +00:00
|
|
|
echo "check-jian-boundaries.sh: all mobile/Jian boundary invariants pass."
|