From 71b300cc444b1d0a04a32359e7927883870c9888 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 28 Sep 2026 20:32:02 +0300 Subject: [PATCH] feat(plugins): drop plugin API v2 A V2 plugin passed the version gate and then failed silently while registering its ribbon, since the ribbon wire types have moved on. The host now loads V3 and newer, so V2 plugins are rejected up front with a version message. The V2 compatibility fixture crate goes too. Fixes #1085 Fixes #987 Co-Authored-By: Claude Opus 5.5 --- crates/ocs_plugin_api/build.rs | 2 +- crates/ocs_plugin_api/src/manifest.rs | 20 +++--- crates/ocs_plugin_api/src/version_info.rs | 5 +- crates/plugin-template-api2/Cargo.toml | 13 ---- crates/plugin-template-api2/src/lib.rs | 79 ----------------------- src/plugin/external.rs | 16 +++-- 6 files changed, 25 insertions(+), 110 deletions(-) delete mode 100644 crates/plugin-template-api2/Cargo.toml delete mode 100644 crates/plugin-template-api2/src/lib.rs diff --git a/crates/ocs_plugin_api/build.rs b/crates/ocs_plugin_api/build.rs index 91f5a9aee..382754826 100644 --- a/crates/ocs_plugin_api/build.rs +++ b/crates/ocs_plugin_api/build.rs @@ -1284,7 +1284,7 @@ fn generate_version_info(out_dir: &Path) { "acadrust_source": opencadcodec.source.as_ref().map(|s| s.to_string()), "rustc_version": rustc_version, "api_version": 7, - "api_version_min_supported": 2, + "api_version_min_supported": 3, "build_timestamp": build_timestamp, }); fs::write(out_dir.join("version_info.json"), info.to_string()).unwrap(); diff --git a/crates/ocs_plugin_api/src/manifest.rs b/crates/ocs_plugin_api/src/manifest.rs index 8bf2ee892..849e63a94 100644 --- a/crates/ocs_plugin_api/src/manifest.rs +++ b/crates/ocs_plugin_api/src/manifest.rs @@ -26,12 +26,14 @@ pub const API_VERSION: u32 = 7; /// Oldest plugin API major the current host still loads. This keeps previously /// compiled cdylibs usable as long as their vtable layout is a prefix of the -/// current `HostApi` trait. -pub const API_VERSION_MIN_SUPPORTED: u32 = 2; +/// current `HostApi` trait. V2 is no longer loaded: its ribbon wire types +/// predate the current ones, so a V2 plugin passed the gate and then failed +/// silently while registering its ribbon (#1085, #987). +pub const API_VERSION_MIN_SUPPORTED: u32 = 3; /// Environment variable that caps the API major accepted by the host at -/// runtime. Set to `5` to disable V6 plugins, `4` to disable V5, or `2` for -/// V2-only mode. +/// runtime. Set to `5` to disable V6 plugins, `4` to disable V5, or `3` for +/// V3-only mode. pub const MAX_API_VERSION_ENV: &str = "OCS_PLUGIN_MAX_API_VERSION"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -112,18 +114,18 @@ mod tests { assert_eq!(effective_max_api_version(), 3); assert!(!host_accepts_plugin_version(4)); assert!(host_accepts_plugin_version(3)); - assert!(host_accepts_plugin_version(2)); + assert!(!host_accepts_plugin_version(2)); std::env::remove_var(MAX_API_VERSION_ENV); } #[test] - fn effective_max_v2_only_mode() { + fn effective_max_below_minimum_is_v3_only_mode() { let _guard = ENV_LOCK.lock().unwrap(); std::env::set_var(MAX_API_VERSION_ENV, "2"); - assert_eq!(effective_max_api_version(), 2); + assert_eq!(effective_max_api_version(), 3); assert!(!host_accepts_plugin_version(4)); - assert!(!host_accepts_plugin_version(3)); - assert!(host_accepts_plugin_version(2)); + assert!(host_accepts_plugin_version(3)); + assert!(!host_accepts_plugin_version(2)); std::env::remove_var(MAX_API_VERSION_ENV); } diff --git a/crates/ocs_plugin_api/src/version_info.rs b/crates/ocs_plugin_api/src/version_info.rs index 177a05a86..da14db317 100644 --- a/crates/ocs_plugin_api/src/version_info.rs +++ b/crates/ocs_plugin_api/src/version_info.rs @@ -148,7 +148,10 @@ mod tests { Some(env!("CARGO_PKG_VERSION")) ); assert_eq!(value["api_version"].as_i64(), Some(crate::API_VERSION as i64)); - assert_eq!(value["api_version_min_supported"].as_i64(), Some(2)); + assert_eq!( + value["api_version_min_supported"].as_i64(), + Some(crate::API_VERSION_MIN_SUPPORTED as i64) + ); // acadrust_version should have a patch component (e.g. "0.4.0"). let opencadcodec = value["acadrust_version"].as_str().expect("acadrust_version string"); diff --git a/crates/plugin-template-api2/Cargo.toml b/crates/plugin-template-api2/Cargo.toml deleted file mode 100644 index b1794533c..000000000 --- a/crates/plugin-template-api2/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "plugin-template-api2" -version = "0.1.0" -edition = "2021" -description = "API v2 compatibility fixture for the out-of-process plugin path." -license = "GPL-3.0-only" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -ocs_plugin_api = { path = "../ocs_plugin_api", features = ["host"] } -acadrust = { git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8a28c21", features = ["serde"] } diff --git a/crates/plugin-template-api2/src/lib.rs b/crates/plugin-template-api2/src/lib.rs deleted file mode 100644 index ee26e847c..000000000 --- a/crates/plugin-template-api2/src/lib.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! API v2 compatibility fixture. -//! -//! Mimics an old plugin compiled against `ocs_plugin_api` v2: it reports API -//! major 2 from `ocs_plugin_api_version()` and implements only the v2 surface -//! (`HostApi` methods up to `start_interactive`). The current host must still -//! be able to load and dispatch it. - -use ocs_plugin_api::host::{BuiltinPlugin, HostApi}; -use ocs_plugin_api::manifest::{ApiVersion, PluginManifest}; -use ocs_plugin_api::ribbon::{CadModule, IconKind, ModuleEvent, RibbonGroup, RibbonItem, ToolDef}; - -static MANIFEST: PluginManifest = PluginManifest { - id: "opencad.my_plugin", - name: "My Plugin", - version: env!("CARGO_PKG_VERSION"), - description: "API v2 fixture plugin.", - api_version: ApiVersion { major: 2 }, - ribbon_order: 60, - xdata_apps: &[], - command_prefixes: &["MP_"], -}; - -struct MyModule; - -impl CadModule for MyModule { - fn id(&self) -> &'static str { - "my_plugin" - } - fn title(&self) -> &'static str { - "My Plugin" - } - fn ribbon_groups(&self) -> &[RibbonGroup] { - static GROUPS: std::sync::OnceLock> = std::sync::OnceLock::new(); - GROUPS.get_or_init(|| { - vec![RibbonGroup { - title: "Tools", - tools: vec![RibbonItem::LargeTool(ToolDef { - id: "MP_HELLO", - label: "Hello", - icon: IconKind::Glyph("*"), - event: ModuleEvent::Command("MP_HELLO".to_string()), - })], - }] - }) - } -} - -struct MyPlugin; - -impl BuiltinPlugin for MyPlugin { - fn manifest(&self) -> &'static PluginManifest { - &MANIFEST - } - fn ribbon(&self) -> Box { - Box::new(MyModule) - } - fn dispatch(&self, host: &mut dyn HostApi, cmd: &str) -> bool { - match cmd { - "MP_HELLO" => { - host.push_info("Hello from API v2 plugin"); - true - } - _ => false, - } - } -} - -// Custom C-ABI export that reports API v2, emulating an older build of -// `ocs_plugin_api::export_plugin!`. -#[no_mangle] -pub extern "C" fn ocs_plugin_api_version() -> u32 { - 2 -} - -#[no_mangle] -pub extern "C" fn ocs_plugin_register() -> *mut Box { - let plugin: Box = Box::new(MyPlugin); - Box::into_raw(Box::new(plugin)) -} diff --git a/src/plugin/external.rs b/src/plugin/external.rs index d32648fc2..b63050e2a 100644 --- a/src/plugin/external.rs +++ b/src/plugin/external.rs @@ -553,16 +553,18 @@ description = "Template plugin" repository = "https://github.com/example/opencad-my-plugin.git" [opencad] -api_version = 2 +api_version = 3 ribbon_order = 60 command_prefixes = ["MP_"] xdata_apps = ["MYPLUGIN_RECORD"] "#; let p = parse_plugin_toml(toml).expect("parsed"); - assert_eq!(p.api_version, 2); + assert_eq!(p.api_version, 3); assert_eq!(p.repository.as_deref(), Some("example/opencad-my-plugin")); assert!(p.command_prefixes.contains(&"MP_".to_string())); - assert!(p.api_compatible(), "V2 plugins must be accepted by the V4 host"); + assert!(p.api_compatible(), "V3 plugins must be accepted by the current host"); + let v2 = parse_plugin_toml("id=\"old\"\napi_version = 2").expect("parsed"); + assert!(!v2.api_compatible(), "V2 plugins are no longer loaded"); } #[test] @@ -710,7 +712,7 @@ rustc_version = "{host}" id = "opencad.test" name = "Test" version = "0.1.0" -api_version = 2 +api_version = 3 [opencad] rustc_version = "rustc 0.0.0-fake" @@ -720,7 +722,7 @@ rustc_version = "rustc 0.0.0-fake" assert!(p.rustc_declared); assert!( p.rustc_compatible(), - "API v2 plugin should bypass rustc gate" + "API v3 plugin should bypass rustc gate" ); assert!(p.loadable()); } @@ -826,7 +828,7 @@ acadrust_source = "{host}" id = "opencad.test" name = "Test" version = "0.1.0" -api_version = 2 +api_version = 3 [opencad] acadrust_source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=0908da7#0908da7b6e4f702a6c78359a57f53e2b79cf39eb" @@ -836,7 +838,7 @@ acadrust_source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=0908 assert!(p.acadrust_declared); assert!( p.acadrust_compatible(), - "API v2 plugin should bypass opencadcodec gate" + "API v3 plugin should bypass opencadcodec gate" ); assert!(p.loadable()); }