pmpx-plugin 0.0.0

Plugin contract for pmpx: the PackageManager trait and the stable C ABI that carries it across dlopen.
Documentation
//! [`export!`](macro@crate::export) —— 把 [`PackageManager`](crate::PackageManager)
//! 实现变成一整套 C ABI 外壳。

/// 生成插件的整个 C ABI 外壳。
///
/// 用法是**一行**:
///
/// ```ignore
/// impl pmpx_plugin::PackageManager for MyPlugin { /* ... */ }
///
/// /// 工厂函数。crate-plugin-kit 生成的 wrapper 工程也会调它。
/// pub fn create() -> Box<dyn pmpx_plugin::PackageManager> {
///     Box::new(MyPlugin)
/// }
///
/// pmpx_plugin::export!(create);
/// ```
///
/// # 它生成了什么
///
/// | 符号 | 作用 |
/// | ---- | ---- |
/// | `pmpx_plugin_entry_v1` | 唯一入口。返回一张 `'static` 的 [`PmpxPluginV1`] 表 |
/// | `name` / `family` shim | 调你的实现,把 `&str` 泄漏成 [`PmpxStr`] |
/// | `command` shim | 读输入 → 调 [`crate::PackageManager::command`] → 写输出 |
/// | `free_str` / `free_command` | 把上面产出的内存还回来 |
///
/// # 输入必须是函数的路径
///
/// `create` 必须是一个返回 `Box<dyn PackageManager>` 的函数。**不能传类型名**,
/// 因为你可能想在里面做构造参数注入。
///
/// 这个形状不是随意定的:[`crate-plugin-kit`] 生成的 wrapper 工程里,
/// `src/lib.rs` 的内容正是 `<你的 crate>::create` 的调用 —— 两边必须对得上。
///
/// [`crate-plugin-kit`]: https://crates.io/crates/crate-plugin-kit
/// [`PmpxPluginV1`]: crate::abi::PmpxPluginV1
/// [`PmpxStr`]: crate::abi::PmpxStr
///
/// # 每次调用都新建实例,刻意不缓存
///
/// 宿主一个进程只调几次(`name` / `family` 各一次,`command` 一次),
/// 而缓存需要 `OnceLock` + 静态量 + `Send + Sync` 约束。为几次 `Box::new` 引入这些
/// 不划算,也让生成的代码更难读。
#[macro_export]
macro_rules! export {
    ($create:path) => {
        #[doc(hidden)]
        fn __pmpx_instance() -> ::std::boxed::Box<dyn $crate::PackageManager> {
            $create()
        }

        #[doc(hidden)]
        unsafe extern "C" fn __pmpx_name() -> $crate::abi::PmpxStr {
            $crate::abi::leak_str(__pmpx_instance().name())
        }

        #[doc(hidden)]
        unsafe extern "C" fn __pmpx_family() -> $crate::abi::PmpxStr {
            $crate::abi::leak_str(__pmpx_instance().family().as_str())
        }

        #[doc(hidden)]
        unsafe extern "C" fn __pmpx_command(
            project_root: $crate::abi::PmpxStr,
            matched: *const $crate::abi::PmpxStr,
            matched_len: ::std::primitive::usize,
            verb: ::std::primitive::u32,
            args: *const $crate::abi::PmpxStr,
            args_len: ::std::primitive::usize,
            out: *mut $crate::abi::PmpxCommand,
        ) -> ::std::primitive::u32 {
            // `guard` 在这里不是可选项:从 Rust 1.81 起,让 panic 越过 `extern "C"`
            // 边界会直接 abort,宿主侧的 catch_unwind 完全救不了。必须由插件自己兜住。
            $crate::abi::guard(move || {
                let plugin = __pmpx_instance();
                // SAFETY: 参数与 out 的有效性由调用方(宿主)按 `PmpxPluginV1::command`
                // 的约定保证;这里只是把它们转交给同一份约定下的实现。
                unsafe {
                    $crate::abi::dispatch_command(
                        &*plugin,
                        project_root,
                        matched,
                        matched_len,
                        verb,
                        args,
                        args_len,
                        out,
                    )
                }
            })
        }

        #[doc(hidden)]
        unsafe extern "C" fn __pmpx_free_str(s: $crate::abi::PmpxStr) {
            // SAFETY: 这个函数只会被宿主拿 vtable 里的指针调,而 vtable 只由本宏产出,
            // 所以传回来的必然是本侧 leak_* 的成果。
            unsafe { $crate::abi::free_str(s) }
        }

        #[doc(hidden)]
        unsafe extern "C" fn __pmpx_free_command(c: *mut $crate::abi::PmpxCommand) {
            // SAFETY: 同上 —— 只有本侧 write_command 填充过的结构体会走到这里。
            unsafe { $crate::abi::free_command(c) }
        }

        #[doc(hidden)]
        static __PMPX_ENTRY: $crate::abi::PmpxPluginV1 = $crate::abi::PmpxPluginV1 {
            abi_version: $crate::abi::ABI_VERSION,
            rustc_version: $crate::abi::build_rustc(),
            target: $crate::abi::build_target(),
            name: __pmpx_name,
            family: __pmpx_family,
            command: __pmpx_command,
            free_str: __pmpx_free_str,
            free_command: __pmpx_free_command,
        };

        /// 插件的唯一入口符号。由 `pmpx_plugin::export!` 生成,**不要手写**。
        ///
        /// 用 `#[unsafe(no_mangle)]` 而不是 `#[no_mangle]`:后者在 edition 2024 里是
        /// 硬错误。`unsafe(...)` 这种写法在 edition 2021 与 2024 下都成立,
        /// 所以插件用哪个 edition 都能编。
        #[unsafe(no_mangle)]
        pub extern "C" fn pmpx_plugin_entry_v1() -> *const $crate::abi::PmpxPluginV1 {
            &__PMPX_ENTRY
        }
    };
}