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}