]> git.proxmox.com Git - perlmod.git/blame - perlmod-macro/src/package.rs
macro: ensure bootstrap functions happen only once
[perlmod.git] / perlmod-macro / src / package.rs
CommitLineData
b813397b 1use std::env;
b03c1494 2
83f19b95 3use proc_macro2::{Ident, Span, TokenStream};
b03c1494 4
83f19b95 5use quote::quote;
b03c1494 6use syn::AttributeArgs;
83f19b95 7use syn::Error;
b03c1494 8
b813397b 9use crate::attribs::ModuleAttrs;
b03c1494
WB
10
11const MODULE_HEAD: &str = r#"
83f19b95 12require DynaLoader;
b03c1494 13
83f19b95
WB
14sub bootstrap {
15 my ($pkg) = @_;
16 my ($mod_name) = {{LIB_NAME}};
08c14b8f 17 my $bootstrap_name = 'boot_' . ($pkg =~ s/::/__/gr);
b03c1494 18
83f19b95
WB
19 my @dirs = (map "-L$_/auto", @INC);
20 my $mod_file = DynaLoader::dl_findfile("#;
241e69ee
WB
21
22#[cfg(debug_assertions)]
83f19b95 23const MODULE_HEAD_DEBUG: &str = r#"'-L./target/debug', "#;
241e69ee
WB
24
25#[cfg(not(debug_assertions))]
26const MODULE_HEAD_DEBUG: &str = "";
27
83f19b95
WB
28const MODULE_HEAD_2: &str = r#"@dirs, $mod_name);
29 die "failed to locate shared library for '$pkg' (lib${mod_name}.so)\n" if !$mod_file;
b03c1494 30
83f19b95
WB
31 my $lib = DynaLoader::dl_load_file($mod_file)
32 or die "failed to load library '$mod_file'\n";
b03c1494 33
83f19b95
WB
34 my $sym = DynaLoader::dl_find_symbol($lib, $bootstrap_name);
35 die "failed to locate '$bootstrap_name'\n" if !defined $sym;
36 my $boot = DynaLoader::dl_install_xsub($bootstrap_name, $sym, "src/FIXME.rs");
37 $boot->();
38}
39
40__PACKAGE__->bootstrap;
b03c1494 41
83f19b95
WB
421;
43"#;
b03c1494
WB
44
45struct Export {
46 rust_name: Ident,
2991a46a 47 perl_name: Option<Ident>,
b03c1494 48 xs_name: Ident,
4b5b75f1 49 prototype: Option<String>,
b03c1494
WB
50}
51
52pub struct Package {
daf48419 53 pub attrs: ModuleAttrs,
b03c1494
WB
54 exported: Vec<Export>,
55}
56
57impl Package {
58 pub fn with_attrs(attr: AttributeArgs) -> Result<Self, Error> {
59 Ok(Self {
60 attrs: ModuleAttrs::try_from(attr)?,
61 exported: Vec::new(),
62 })
63 }
64
4b5b75f1
WB
65 pub fn export_named(
66 &mut self,
67 rust_name: Ident,
68 perl_name: Option<Ident>,
69 xs_name: Ident,
70 prototype: Option<String>,
71 ) {
b03c1494
WB
72 self.exported.push(Export {
73 rust_name,
2991a46a 74 perl_name,
b03c1494 75 xs_name,
4b5b75f1 76 prototype,
b03c1494
WB
77 });
78 }
79
83f19b95
WB
80 pub fn bootstrap_function(&self) -> TokenStream {
81 let mut newxs = TokenStream::new();
82 for export in &self.exported {
83 let perl_name = export.perl_name.as_ref().unwrap_or(&export.rust_name);
84 let sub_name = format!("{}::{}\0", self.attrs.package_name, perl_name);
85 let sub_lit = syn::LitByteStr::new(sub_name.as_bytes(), perl_name.span());
86
87 let xs_name = &export.xs_name;
88
4b5b75f1
WB
89 let prototype = match export.prototype.as_deref() {
90 Some(proto) => quote! {
91 concat!(#proto, "\0").as_bytes().as_ptr() as *const i8
92 },
93 None => quote!(::std::ptr::null()),
94 };
95
83f19b95
WB
96 newxs.extend(quote! {
97 RSPL_newXS_flags(
98 #sub_lit.as_ptr() as *const i8,
99 #xs_name as _,
100 concat!(::std::file!(), "\0").as_bytes().as_ptr() as *const i8,
4b5b75f1 101 #prototype,
83f19b95
WB
102 0,
103 );
104 });
105 }
106
b94cbb8f 107 let bootstrap_name = format!("boot_{}", self.attrs.package_name).replace("::", "__");
83f19b95
WB
108 let bootstrap_ident = Ident::new(&bootstrap_name, Span::call_site());
109
110 quote! {
111 #[no_mangle]
112 pub extern "C" fn #bootstrap_ident(
113 _cv: &::perlmod::ffi::CV,
114 ) {
b94cbb8f
WB
115 static ONCE: ::std::sync::Once = ::std::sync::Once::new();
116 ONCE.call_once(|| {
117 unsafe {
118 use ::perlmod::ffi::RSPL_newXS_flags;
83f19b95 119
b94cbb8f
WB
120 let argmark = ::perlmod::ffi::pop_arg_mark();
121 argmark.set_stack();
83f19b95 122
b94cbb8f
WB
123 #newxs
124 }
125 });
83f19b95
WB
126 }
127 }
128 }
129
b03c1494 130 pub fn write(&self) -> Result<(), Error> {
241e69ee
WB
131 let mut source = format!(
132 "package {};\n{}{}{}",
133 self.attrs.package_name, MODULE_HEAD, MODULE_HEAD_DEBUG, MODULE_HEAD_2
134 );
b03c1494 135
b03c1494 136 if let Some(lib) = &self.attrs.lib_name {
76f6a079 137 source = source.replace("{{LIB_NAME}}", &format!("('{lib}')"));
b03c1494 138 } else {
b813397b 139 let lib_name = get_default_lib_name(Span::call_site())?;
76f6a079 140 source = source.replace("{{LIB_NAME}}", &format!("('{lib_name}')"));
b03c1494
WB
141 }
142
06a18771
WB
143 let file_name = self
144 .attrs
145 .file_name
146 .clone()
147 .unwrap_or_else(|| format!("{}.pm", self.attrs.package_name.replace("::", "/")));
148
149 let path = std::path::Path::new(&file_name);
b03c1494 150 if let Some(parent) = path.parent() {
f888c202 151 std::fs::create_dir_all(parent).map_err(io_err)?;
b03c1494 152 }
f888c202 153 std::fs::write(path, source.as_bytes()).map_err(io_err)?;
b03c1494
WB
154
155 Ok(())
156 }
9525acd6
WB
157
158 pub fn mangle_package_name(&self) -> String {
159 self.attrs.mangle_package_name()
160 }
b03c1494 161}
06a18771 162
f888c202
WB
163fn io_err<E: ToString>(err: E) -> Error {
164 Error::new(Span::call_site(), err.to_string())
165}
166
167pub fn get_default_lib_name(why: Span) -> Result<String, Error> {
9de06554
WB
168 env::var("CARGO_PKG_NAME")
169 .map(|s| s.replace('-', "_"))
170 .map_err(|err| {
171 format_err!(
172 why,
173 "failed to get CARGO_PKG_NAME environment variable: {}",
174 err
175 )
176 })
b813397b 177}