Skip to main content

pmpx_plugin/
export.rs

1//! [`export!`](macro@crate::export) -- turn a [`PackageManager`](crate::PackageManager)
2//! implementation into a whole C ABI shell.
3
4/// Generate the plugin's entire C ABI shell: the entry symbol, plus the `name` / `family` /
5/// `command` / `free_*` shims.
6///
7/// The usage is one line; `create` must be the path of a function returning
8/// `Box<dyn PackageManager>`, not a type name, because you may want to inject construction
9/// parameters in it:
10/// ```ignore
11/// pub fn create() -> Box<dyn pmpx_plugin::PackageManager> { Box::new(MyPlugin) }
12/// pmpx_plugin::export!(create);
13/// ```
14///
15/// This shape is agreed with the wrapper project generated by [`crate-plugin-kit`]; the two sides
16/// must match.
17///
18/// Every call builds a new instance and deliberately caches nothing -- in one process the host
19/// calls `name` / `family` / `command` once each.
20///
21/// [`crate-plugin-kit`]: https://crates.io/crates/crate-plugin-kit
22#[macro_export]
23macro_rules! export {
24    ($create:path) => {
25        #[doc(hidden)]
26        fn __pmpx_instance() -> ::std::boxed::Box<dyn $crate::PackageManager> {
27            $create()
28        }
29
30        #[doc(hidden)]
31        unsafe extern "C" fn __pmpx_name() -> $crate::abi::PmpxStr {
32            $crate::abi::leak_str(__pmpx_instance().name())
33        }
34
35        #[doc(hidden)]
36        unsafe extern "C" fn __pmpx_family() -> $crate::abi::PmpxStr {
37            $crate::abi::leak_str(__pmpx_instance().family().as_str())
38        }
39
40        #[doc(hidden)]
41        unsafe extern "C" fn __pmpx_command(
42            project_root: $crate::abi::PmpxStr,
43            matched: *const $crate::abi::PmpxStr,
44            matched_len: ::std::primitive::usize,
45            verb: ::std::primitive::u32,
46            args: *const $crate::abi::PmpxStr,
47            args_len: ::std::primitive::usize,
48            out: *mut $crate::abi::PmpxCommand,
49        ) -> ::std::primitive::u32 {
50            // `guard` is not optional: a panic crossing the `extern "C"` boundary aborts the
51            // process and the host cannot save it.
52            $crate::abi::guard(move || {
53                let plugin = __pmpx_instance();
54                // SAFETY: the validity of the arguments and of out is guaranteed by the caller
55                // (the host) under the `PmpxPluginV1::command` contract; this just hands them to
56                // an implementation under that same contract.
57                unsafe {
58                    $crate::abi::dispatch_command(
59                        &*plugin,
60                        project_root,
61                        matched,
62                        matched_len,
63                        verb,
64                        args,
65                        args_len,
66                        out,
67                    )
68                }
69            })
70        }
71
72        #[doc(hidden)]
73        unsafe extern "C" fn __pmpx_free_str(s: $crate::abi::PmpxStr) {
74            // SAFETY: the host only calls this through the pointer in the vtable, and the vtable
75            // is only produced by this macro, so whatever comes back is necessarily this side's
76            // leak_* result.
77            unsafe { $crate::abi::free_str(s) }
78        }
79
80        #[doc(hidden)]
81        unsafe extern "C" fn __pmpx_free_command(c: *mut $crate::abi::PmpxCommand) {
82            // SAFETY: as above -- only a struct filled in by this side's write_command reaches
83            // here.
84            unsafe { $crate::abi::free_command(c) }
85        }
86
87        #[doc(hidden)]
88        static __PMPX_ENTRY: $crate::abi::PmpxPluginV1 = $crate::abi::PmpxPluginV1 {
89            abi_version: $crate::abi::ABI_VERSION,
90            rustc_version: $crate::abi::build_rustc(),
91            target: $crate::abi::build_target(),
92            name: __pmpx_name,
93            family: __pmpx_family,
94            command: __pmpx_command,
95            free_str: __pmpx_free_str,
96            free_command: __pmpx_free_command,
97        };
98
99        /// The plugin's single entry symbol. Generated by `pmpx_plugin::export!`; do not write it
100        /// by hand.
101        /// It uses `#[unsafe(no_mangle)]` rather than `#[no_mangle]`: the latter is a hard error
102        /// in edition 2024.
103        #[unsafe(no_mangle)]
104        pub extern "C" fn pmpx_plugin_entry_v1() -> *const $crate::abi::PmpxPluginV1 {
105            &__PMPX_ENTRY
106        }
107    };
108}