1use std::collections::HashMap;
12use std::ffi::CStr;
13use std::os::raw::c_char;
14use std::path::{Path, PathBuf};
15
16use libloading::{Library, Symbol};
17
18use super::convert;
19use super::sexp::{self, Sexp};
20use crate::interpreter::value::*;
21use crate::interpreter::Interpreter;
22
23thread_local! {
26 static CURRENT_INTERP: std::cell::Cell<*const Interpreter> = const { std::cell::Cell::new(std::ptr::null()) };
27}
28
29fn callback_find_var(name: &str) -> Option<RValue> {
30 CURRENT_INTERP.with(|cell| {
31 let interp = cell.get();
32 if interp.is_null() {
33 return None;
34 }
35 let interp = unsafe { &*interp };
36 interp.global_env.get(name)
37 })
38}
39
40fn callback_define_var(name: &str, val: RValue) {
41 CURRENT_INTERP.with(|cell| {
42 let interp = cell.get();
43 if interp.is_null() {
44 return;
45 }
46 let interp = unsafe { &*interp };
47 interp.global_env.set(name.to_string(), val);
48 });
49}
50
51fn callback_eval_expr(expr: &RValue) -> Result<RValue, crate::interpreter::value::RError> {
52 use crate::interpreter::value::RError;
53 use crate::interpreter::CallFrame;
54 CURRENT_INTERP.with(|cell| {
55 let interp = cell.get();
56 if interp.is_null() {
57 return Err(RError::other(
58 "no interpreter available for Rf_eval".to_string(),
59 ));
60 }
61 let interp = unsafe { &*interp };
62
63 let boundary = CallFrame {
65 call: None,
66 function: RValue::Null,
67 env: interp.global_env.clone(),
68 formal_args: Default::default(),
69 supplied_args: Default::default(),
70 supplied_positional: Default::default(),
71 supplied_named: Default::default(),
72 supplied_arg_count: 0,
73 is_native_boundary: true,
74 };
75 interp.call_stack.borrow_mut().push(boundary);
76
77 let result = if let RValue::Language(ref lang) = expr {
78 interp
79 .eval_in(lang, &interp.global_env)
80 .map_err(RError::from)
81 } else if let Some(name) = expr.as_vector().and_then(|v| v.as_character_scalar()) {
82 interp
83 .global_env
84 .get(&name)
85 .ok_or_else(|| RError::other(format!("object '{name}' not found")))
86 } else {
87 Ok(expr.clone())
88 };
89
90 interp.call_stack.borrow_mut().pop();
91 result
92 })
93}
94
95fn callback_parse_text(text: &str) -> Result<RValue, crate::interpreter::value::RError> {
96 use crate::interpreter::value::{Language, RError};
97 let ast = crate::parser::parse_program(text)
99 .map_err(|e| RError::other(format!("parse error: {e}")))?;
100 Ok(RValue::Language(Language::new(ast)))
101}
102
103type PkgInitFn = unsafe extern "C" fn(*mut u8);
107
108enum CBuffer {
124 Double {
125 data: Vec<f64>,
126 },
127 Integer {
128 data: Vec<i32>,
129 },
130 Logical {
131 data: Vec<i32>,
132 },
133 Character {
134 ptrs: Vec<*mut c_char>,
136 _owned_strings: Vec<std::ffi::CString>,
139 },
140 Raw {
141 data: Vec<u8>,
142 },
143}
144
145impl CBuffer {
146 fn from_rvalue(val: &RValue) -> Result<Self, String> {
148 match val {
149 RValue::Vector(rv) => match &rv.inner {
150 Vector::Double(d) => {
151 let data: Vec<f64> = d.iter_opt().map(|v| v.unwrap_or(sexp::NA_REAL)).collect();
152 Ok(CBuffer::Double { data })
153 }
154 Vector::Integer(int) => {
155 let data: Vec<i32> = int
156 .iter_opt()
157 .map(|v| match v {
158 Some(i) => i32::try_from(i).unwrap_or(sexp::NA_INTEGER),
159 None => sexp::NA_INTEGER,
160 })
161 .collect();
162 Ok(CBuffer::Integer { data })
163 }
164 Vector::Logical(l) => {
165 let data: Vec<i32> = (0..l.len())
166 .map(|i| match l[i] {
167 Some(true) => 1i32,
168 Some(false) => 0i32,
169 None => sexp::NA_LOGICAL,
170 })
171 .collect();
172 Ok(CBuffer::Logical { data })
173 }
174 Vector::Character(c) => {
175 let mut owned_strings = Vec::with_capacity(c.len());
176 let mut ptrs = Vec::with_capacity(c.len());
177 for i in 0..c.len() {
178 let cstr = match &c[i] {
179 Some(s) => std::ffi::CString::new(s.as_str()).unwrap_or_else(|_| {
180 std::ffi::CString::new("").expect("empty CString")
181 }),
182 None => std::ffi::CString::new("NA").expect("NA CString"),
183 };
184 owned_strings.push(cstr);
185 }
186 for cstr in &owned_strings {
189 ptrs.push(cstr.as_ptr() as *mut c_char);
190 }
191 Ok(CBuffer::Character {
192 ptrs,
193 _owned_strings: owned_strings,
194 })
195 }
196 Vector::Raw(r) => Ok(CBuffer::Raw { data: r.clone() }),
197 Vector::Complex(_) => Err(
198 "complex vectors are not supported by .C() — use .Call() instead".to_string(),
199 ),
200 },
201 RValue::Null => {
202 Ok(CBuffer::Double { data: Vec::new() })
204 }
205 _ => Err(format!(
206 "unsupported argument type for .C(): {}",
207 val.type_name()
208 )),
209 }
210 }
211
212 fn as_void_ptr(&mut self) -> *mut u8 {
214 match self {
215 CBuffer::Double { data } => data.as_mut_ptr() as *mut u8,
216 CBuffer::Integer { data } => data.as_mut_ptr() as *mut u8,
217 CBuffer::Logical { data } => data.as_mut_ptr() as *mut u8,
218 CBuffer::Character { ptrs, .. } => ptrs.as_mut_ptr() as *mut u8,
219 CBuffer::Raw { data } => data.as_mut_ptr(),
220 }
221 }
222
223 fn to_rvalue(&self) -> RValue {
225 match self {
226 CBuffer::Double { data } => {
227 let vals: Vec<Option<f64>> = data
228 .iter()
229 .map(|&v| if sexp::is_na_real(v) { None } else { Some(v) })
230 .collect();
231 RValue::vec(Vector::Double(vals.into()))
232 }
233 CBuffer::Integer { data } => {
234 let vals: Vec<Option<i64>> = data
235 .iter()
236 .map(|&v| {
237 if v == sexp::NA_INTEGER {
238 None
239 } else {
240 Some(i64::from(v))
241 }
242 })
243 .collect();
244 RValue::vec(Vector::Integer(vals.into()))
245 }
246 CBuffer::Logical { data } => {
247 let vals: Vec<Option<bool>> = data
248 .iter()
249 .map(|&v| {
250 if v == sexp::NA_LOGICAL {
251 None
252 } else {
253 Some(v != 0)
254 }
255 })
256 .collect();
257 RValue::vec(Vector::Logical(vals.into()))
258 }
259 CBuffer::Character { ptrs, .. } => {
260 let vals: Vec<Option<String>> = ptrs
261 .iter()
262 .map(|&p| {
263 if p.is_null() {
264 None
265 } else {
266 let cstr = unsafe { CStr::from_ptr(p) };
269 Some(cstr.to_str().unwrap_or("").to_string())
270 }
271 })
272 .collect();
273 RValue::vec(Vector::Character(vals.into()))
274 }
275 CBuffer::Raw { data } => RValue::vec(Vector::Raw(data.clone())),
276 }
277 }
278}
279
280pub struct LoadedDll {
286 pub path: PathBuf,
288 pub name: String,
290 lib: Library,
292 symbols: HashMap<String, *const ()>,
294 pub registered_calls: HashMap<String, *const ()>,
296 pub registered_c_methods: HashMap<String, *const ()>,
298}
299
300unsafe impl Send for LoadedDll {}
303
304impl LoadedDll {
305 pub fn load(path: &Path) -> Result<Self, String> {
308 let name = path
309 .file_stem()
310 .and_then(|s| s.to_str())
311 .unwrap_or("unknown")
312 .to_string();
313
314 let lib = unsafe { Library::new(path) }
318 .map_err(|e| format!("dyn.load(\"{}\") failed: {e}", path.display()))?;
319
320 let mut dll = LoadedDll {
321 path: path.to_path_buf(),
322 name: name.clone(),
323 lib,
324 symbols: HashMap::new(),
325 registered_calls: HashMap::new(),
326 registered_c_methods: HashMap::new(),
327 };
328
329 dll.call_pkg_init(&name);
331
332 Ok(dll)
333 }
334
335 fn call_pkg_init(&mut self, pkg_name: &str) {
337 let init_name = format!("R_init_{pkg_name}");
338 if let Ok(ptr) = self.get_symbol(&init_name) {
339 unsafe {
340 let init: PkgInitFn = std::mem::transmute(ptr);
341 init(std::ptr::null_mut());
343 }
344 self.collect_registered_calls();
346 }
347 }
348
349 fn collect_registered_calls(&mut self) {
351 for (name, ptr) in super::runtime::REGISTERED_CALLS
354 .lock()
355 .expect("lock registered calls")
356 .iter()
357 {
358 self.registered_calls.insert(name.clone(), ptr.0);
359 }
360 for (name, ptr) in super::runtime::REGISTERED_C_METHODS
361 .lock()
362 .expect("lock registered C methods")
363 .iter()
364 {
365 self.registered_c_methods.insert(name.clone(), ptr.0);
366 }
367 }
368
369 pub fn get_c_symbol(&mut self, name: &str) -> Result<*const (), String> {
372 if let Some(&ptr) = self.registered_c_methods.get(name) {
373 return Ok(ptr);
374 }
375 self.get_symbol(name)
377 }
378
379 pub fn get_symbol(&mut self, name: &str) -> Result<*const (), String> {
382 if let Some(&ptr) = self.registered_calls.get(name) {
384 return Ok(ptr);
385 }
386
387 if let Some(&ptr) = self.symbols.get(name) {
389 return Ok(ptr);
390 }
391
392 let c_name =
393 std::ffi::CString::new(name).map_err(|_| format!("invalid symbol name: {name}"))?;
394
395 let sym: Symbol<*const ()> = unsafe {
397 self.lib
398 .get(c_name.as_bytes_with_nul())
399 .map_err(|e| format!("symbol '{name}' not found in {}: {e}", self.path.display()))?
400 };
401
402 let ptr = *sym;
403 self.symbols.insert(name.to_string(), ptr);
404 Ok(ptr)
405 }
406}
407
408impl std::fmt::Debug for LoadedDll {
409 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410 f.debug_struct("LoadedDll")
411 .field("name", &self.name)
412 .field("path", &self.path)
413 .field("symbols", &self.symbols.keys().collect::<Vec<_>>())
414 .field(
415 "registered_calls",
416 &self.registered_calls.keys().collect::<Vec<_>>(),
417 )
418 .field(
419 "registered_c_methods",
420 &self.registered_c_methods.keys().collect::<Vec<_>>(),
421 )
422 .finish()
423 }
424}
425
426impl Interpreter {
431 pub(crate) fn dyn_load(&self, path: &Path) -> Result<String, RError> {
433 CURRENT_INTERP.with(|cell| cell.set(self as *const Interpreter));
436 super::runtime::set_callbacks(super::runtime::InterpreterCallbacks {
437 find_var: Some(callback_find_var),
438 define_var: Some(callback_define_var),
439 eval_expr: Some(callback_eval_expr),
440 parse_text: Some(callback_parse_text),
441 });
442
443 let dll = LoadedDll::load(path).map_err(|e| RError::new(RErrorKind::Other, e))?;
444 let name = dll.name.clone();
445 self.loaded_dlls.borrow_mut().push(dll);
446
447 Ok(name)
451 }
452
453 pub(crate) fn dyn_unload(&self, name: &str) -> Result<(), RError> {
455 let mut dlls = self.loaded_dlls.borrow_mut();
456 let pos = dlls.iter().position(|d| d.name == name);
457 match pos {
458 Some(i) => {
459 dlls.remove(i);
460 Ok(())
461 }
462 None => Err(RError::new(
463 RErrorKind::Other,
464 format!("shared object '{name}' was not loaded"),
465 )),
466 }
467 }
468
469 pub(crate) fn is_symbol_loaded(&self, name: &str) -> bool {
471 let mut dlls = self.loaded_dlls.borrow_mut();
472 dlls.iter_mut().any(|dll| dll.get_symbol(name).is_ok())
473 }
474
475 fn find_native_symbol_with_dll(&self, name: &str) -> Result<(*const (), usize), RError> {
478 let mut dlls = self.loaded_dlls.borrow_mut();
479 for (i, dll) in dlls.iter_mut().enumerate().rev() {
480 if let Ok(ptr) = dll.get_symbol(name) {
481 return Ok((ptr, i));
482 }
483 }
484 Err(RError::new(
485 RErrorKind::Other,
486 format!("symbol '{name}' not found in any loaded DLL"),
487 ))
488 }
489
490 pub(crate) fn find_native_symbol(&self, name: &str) -> Result<*const (), RError> {
492 self.find_native_symbol_with_dll(name).map(|(ptr, _)| ptr)
493 }
494
495 fn find_c_symbol(&self, name: &str) -> Result<*const (), RError> {
497 let mut dlls = self.loaded_dlls.borrow_mut();
498 for dll in dlls.iter_mut().rev() {
499 if let Ok(ptr) = dll.get_c_symbol(name) {
500 return Ok(ptr);
501 }
502 }
503 Err(RError::new(
504 RErrorKind::Other,
505 format!("symbol '{name}' not found in any loaded DLL"),
506 ))
507 }
508
509 pub(crate) fn dot_call(&self, symbol_name: &str, args: &[RValue]) -> Result<RValue, RError> {
522 let fn_ptr = self.find_native_symbol(symbol_name)?;
523
524 let sexp_args: Vec<Sexp> = args.iter().map(convert::rvalue_to_sexp).collect();
526
527 CURRENT_INTERP.with(|cell| cell.set(self as *const Interpreter));
529 super::runtime::set_callbacks(super::runtime::InterpreterCallbacks {
530 find_var: Some(callback_find_var),
531 define_var: Some(callback_define_var),
532 eval_expr: Some(callback_eval_expr),
533 parse_text: Some(callback_parse_text),
534 });
535
536 extern "C" {
539 fn _minir_call_protected(
540 fn_ptr: *const (),
541 args: *const Sexp,
542 nargs: i32,
543 result: *mut Sexp,
544 ) -> i32;
545 fn _minir_get_error_msg() -> *const c_char;
546 fn _minir_bt_count() -> i32;
547 fn _minir_bt_frames() -> *const *const std::ffi::c_void;
548 }
549
550 let mut result_sexp: Sexp = sexp::R_NIL_VALUE;
551 let nargs = i32::try_from(sexp_args.len()).unwrap_or(0);
552 let error_code = unsafe {
553 _minir_call_protected(
554 fn_ptr,
555 sexp_args.as_ptr(),
556 nargs,
557 &mut result_sexp as *mut Sexp,
558 )
559 };
560
561 if error_code != 0 {
563 let error_msg = unsafe {
564 let msg_ptr = _minir_get_error_msg();
565 if msg_ptr.is_null() {
566 "unknown error in native code".to_string()
567 } else {
568 CStr::from_ptr(msg_ptr)
569 .to_str()
570 .unwrap_or("unknown error")
571 .to_string()
572 }
573 };
574
575 let bt_count = unsafe { _minir_bt_count() } as usize;
577 if bt_count > 0 {
578 let bt_raw = unsafe { std::slice::from_raw_parts(_minir_bt_frames(), bt_count) };
579 let native_bt = crate::interpreter::NativeBacktrace {
580 frames: bt_raw.iter().map(|p| *p as usize).collect(),
581 };
582 *self.pending_native_backtrace.borrow_mut() = Some(native_bt);
583 }
584
585 super::runtime::clear_callbacks();
587 CURRENT_INTERP.with(|cell| cell.set(std::ptr::null()));
588 super::runtime::free_allocs();
589 unsafe {
590 for s in sexp_args {
591 sexp::free_sexp(s);
592 }
593 }
594
595 return Err(RError::new(RErrorKind::Other, error_msg));
596 }
597
598 let result = unsafe { convert::sexp_to_rvalue(result_sexp) };
600
601 super::runtime::clear_callbacks();
603 CURRENT_INTERP.with(|cell| cell.set(std::ptr::null()));
604
605 super::runtime::free_allocs();
607
608 unsafe {
610 for (s, arg) in sexp_args.into_iter().zip(args.iter()) {
611 match arg {
612 RValue::List(list)
613 if list
614 .attrs
615 .as_ref()
616 .is_some_and(|a| a.contains_key(".sexp_ptr")) =>
617 {
618 continue; }
620 RValue::Environment(_) => {
621 (*s).data = std::ptr::null_mut();
624 sexp::free_sexp(s);
625 }
626 _ => sexp::free_sexp(s),
627 }
628 }
629 }
630
631 Ok(result)
632 }
633
634 pub(crate) fn dot_external(
639 &self,
640 symbol_name: &str,
641 args: &[RValue],
642 ) -> Result<RValue, RError> {
643 let fn_ptr = self.find_native_symbol(symbol_name)?;
644
645 let func_sym = super::runtime::Rf_install(
648 std::ffi::CString::new(symbol_name)
649 .unwrap_or_default()
650 .as_ptr(),
651 );
652 let pairlist = super::runtime::Rf_cons(func_sym, unsafe { super::runtime::R_NilValue });
653
654 let mut tail = pairlist;
656 for arg in args {
657 let sexp_arg = convert::rvalue_to_sexp(arg);
658 let node = super::runtime::Rf_cons(sexp_arg, unsafe { super::runtime::R_NilValue });
659 unsafe {
661 let pd = (*tail).data as *mut sexp::PairlistData;
662 if !pd.is_null() {
663 (*pd).cdr = node;
664 }
665 }
666 tail = node;
667 }
668
669 CURRENT_INTERP.with(|cell| cell.set(self as *const Interpreter));
671 super::runtime::set_callbacks(super::runtime::InterpreterCallbacks {
672 find_var: Some(callback_find_var),
673 define_var: Some(callback_define_var),
674 eval_expr: Some(callback_eval_expr),
675 parse_text: Some(callback_parse_text),
676 });
677
678 extern "C" {
679 fn _minir_call_protected(
680 fn_ptr: *const (),
681 args: *const Sexp,
682 nargs: i32,
683 result: *mut Sexp,
684 ) -> i32;
685 fn _minir_get_error_msg() -> *const c_char;
686 fn _minir_bt_count() -> i32;
687 fn _minir_bt_frames() -> *const *const std::ffi::c_void;
688 }
689
690 let mut result_sexp: Sexp = sexp::R_NIL_VALUE;
691 let sexp_args = [pairlist];
692 let error_code =
693 unsafe { _minir_call_protected(fn_ptr, sexp_args.as_ptr(), 1, &mut result_sexp) };
694
695 if error_code != 0 {
696 let error_msg = unsafe {
697 let msg_ptr = _minir_get_error_msg();
698 if msg_ptr.is_null() {
699 "unknown error in native code".to_string()
700 } else {
701 CStr::from_ptr(msg_ptr)
702 .to_str()
703 .unwrap_or("unknown error")
704 .to_string()
705 }
706 };
707
708 let bt_count = unsafe { _minir_bt_count() } as usize;
710 if bt_count > 0 {
711 let bt_raw = unsafe { std::slice::from_raw_parts(_minir_bt_frames(), bt_count) };
712 let native_bt = crate::interpreter::NativeBacktrace {
713 frames: bt_raw.iter().map(|p| *p as usize).collect(),
714 };
715 *self.pending_native_backtrace.borrow_mut() = Some(native_bt);
716 }
717
718 super::runtime::clear_callbacks();
719 CURRENT_INTERP.with(|cell| cell.set(std::ptr::null()));
720 super::runtime::free_allocs();
721 return Err(RError::new(RErrorKind::Other, error_msg));
722 }
723
724 let result = unsafe { convert::sexp_to_rvalue(result_sexp) };
725 super::runtime::clear_callbacks();
726 CURRENT_INTERP.with(|cell| cell.set(std::ptr::null()));
727 super::runtime::free_allocs();
728
729 Ok(result)
730 }
731
732 pub(crate) fn dot_c(
746 &self,
747 symbol_name: &str,
748 args: &[RValue],
749 arg_names: &[Option<String>],
750 ) -> Result<RValue, RError> {
751 let fn_ptr = self.find_c_symbol(symbol_name)?;
752
753 let mut buffers: Vec<CBuffer> = Vec::with_capacity(args.len());
756 for (i, arg) in args.iter().enumerate() {
757 buffers.push(CBuffer::from_rvalue(arg).map_err(|e| {
758 RError::new(RErrorKind::Argument, format!(".C: argument {}: {e}", i + 1))
759 })?);
760 }
761
762 let mut ptrs: Vec<*mut u8> = buffers.iter_mut().map(|b| b.as_void_ptr()).collect();
763
764 CURRENT_INTERP.with(|cell| cell.set(self as *const Interpreter));
766 super::runtime::set_callbacks(super::runtime::InterpreterCallbacks {
767 find_var: Some(callback_find_var),
768 define_var: Some(callback_define_var),
769 eval_expr: Some(callback_eval_expr),
770 parse_text: Some(callback_parse_text),
771 });
772
773 extern "C" {
774 fn _minir_dotC_call_protected(fn_ptr: *const (), args: *mut *mut u8, nargs: i32)
775 -> i32;
776 fn _minir_get_error_msg() -> *const c_char;
777 fn _minir_bt_count() -> i32;
778 fn _minir_bt_frames() -> *const *const std::ffi::c_void;
779 }
780
781 let nargs = i32::try_from(ptrs.len()).unwrap_or(0);
782 let error_code = unsafe { _minir_dotC_call_protected(fn_ptr, ptrs.as_mut_ptr(), nargs) };
783
784 if error_code != 0 {
785 let error_msg = unsafe {
786 let msg_ptr = _minir_get_error_msg();
787 if msg_ptr.is_null() {
788 "unknown error in native code".to_string()
789 } else {
790 CStr::from_ptr(msg_ptr)
791 .to_str()
792 .unwrap_or("unknown error")
793 .to_string()
794 }
795 };
796
797 let bt_count = unsafe { _minir_bt_count() } as usize;
799 if bt_count > 0 {
800 let bt_raw = unsafe { std::slice::from_raw_parts(_minir_bt_frames(), bt_count) };
801 let native_bt = crate::interpreter::NativeBacktrace {
802 frames: bt_raw.iter().map(|p| *p as usize).collect(),
803 };
804 *self.pending_native_backtrace.borrow_mut() = Some(native_bt);
805 }
806
807 super::runtime::clear_callbacks();
808 CURRENT_INTERP.with(|cell| cell.set(std::ptr::null()));
809 super::runtime::free_allocs();
810
811 return Err(RError::new(RErrorKind::Other, error_msg));
812 }
813
814 let mut result_values: Vec<(Option<String>, RValue)> = Vec::with_capacity(buffers.len());
816 for (i, buf) in buffers.iter().enumerate() {
817 let name = arg_names.get(i).and_then(|n| n.clone());
818 result_values.push((name, buf.to_rvalue()));
819 }
820
821 super::runtime::clear_callbacks();
823 CURRENT_INTERP.with(|cell| cell.set(std::ptr::null()));
824 super::runtime::free_allocs();
825
826 Ok(RValue::List(RList::new(result_values)))
827 }
828
829 pub(crate) fn find_include_dir(&self) -> Option<std::path::PathBuf> {
836 if let Some(dir) = self.get_env_var("MINIR_INCLUDE") {
838 let p = std::path::PathBuf::from(dir);
839 if p.join("miniR").join("Rinternals.h").is_file() {
840 return Some(p);
841 }
842 }
843
844 if let Ok(exe) = std::env::current_exe() {
846 if let Some(exe_dir) = exe.parent() {
847 let p = exe_dir.join("../include");
848 if p.join("miniR").join("Rinternals.h").is_file() {
849 return Some(p);
850 }
851 }
852 }
853
854 let wd = self.get_working_dir();
856 let p = wd.join("include");
857 if p.join("miniR").join("Rinternals.h").is_file() {
858 return Some(p);
859 }
860
861 let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
863 let p = manifest.join("include");
864 if p.join("miniR").join("Rinternals.h").is_file() {
865 return Some(p);
866 }
867
868 None
869 }
870
871 pub(crate) fn load_package_native_code(
878 &self,
879 pkg_name: &str,
880 pkg_dir: &std::path::Path,
881 dyn_libs: &[crate::interpreter::packages::namespace::DynLibDirective],
882 ) -> Result<(), RError> {
883 if dyn_libs.is_empty() {
884 return Ok(());
885 }
886
887 let ext = if cfg!(target_os = "macos") {
888 "dylib"
889 } else {
890 "so"
891 };
892
893 for directive in dyn_libs {
894 let lib_name = &directive.library;
895
896 let precompiled = pkg_dir.join("libs").join(format!("{lib_name}.{ext}"));
898 if precompiled.is_file() {
899 self.dyn_load(&precompiled)?;
900 continue;
901 }
902
903 let src_dir = pkg_dir.join("src");
905 if !src_dir.is_dir() {
906 tracing::warn!(
907 "useDynLib({lib_name}): no precompiled library and no src/ directory in {}",
908 pkg_dir.display()
909 );
910 continue;
911 }
912
913 let include_dir = self.find_include_dir().ok_or_else(|| {
914 RError::other(format!(
915 "cannot compile native code for '{pkg_name}': \
916 miniR include directory not found (set MINIR_INCLUDE env var)"
917 ))
918 })?;
919
920 let linking_to_includes = self.resolve_linking_to_includes(pkg_dir);
922
923 let output_dir = pkg_dir.join("libs");
925 let compile = |out_dir: &std::path::Path| {
926 super::compile::compile_package_with_deps(
927 &src_dir,
928 lib_name,
929 out_dir,
930 &include_dir,
931 &linking_to_includes,
932 )
933 .map_err(|e| {
934 RError::other(format!(
935 "compilation of native code for '{pkg_name}' failed: {e}"
936 ))
937 })
938 };
939
940 if std::fs::create_dir_all(&output_dir).is_err() {
941 let output_dir = self.temp_dir.path().join(format!("native-{pkg_name}"));
943 std::fs::create_dir_all(&output_dir)
944 .map_err(|e| RError::other(format!("cannot create output directory: {e}")))?;
945 let lib_path = compile(&output_dir)?;
946 self.dyn_load(&lib_path)?;
947 continue;
948 }
949
950 let lib_path = compile(&output_dir)?;
951 self.dyn_load(&lib_path)?;
952 }
953
954 Ok(())
955 }
956
957 fn resolve_linking_to_includes(&self, pkg_dir: &std::path::Path) -> Vec<std::path::PathBuf> {
962 let desc_path = pkg_dir.join("DESCRIPTION");
963 let desc_text = match std::fs::read_to_string(&desc_path) {
964 Ok(t) => t,
965 Err(_) => return Vec::new(),
966 };
967 let desc = match crate::interpreter::packages::description::PackageDescription::parse(
968 &desc_text,
969 ) {
970 Ok(d) => d,
971 Err(_) => return Vec::new(),
972 };
973
974 let mut includes = Vec::new();
975 for dep in &desc.linking_to {
976 if let Some(dep_dir) = self.find_package_dir(&dep.package) {
978 let inst_include = dep_dir.join("inst").join("include");
980 if inst_include.is_dir() {
981 includes.push(inst_include);
982 } else {
983 let include = dep_dir.join("include");
984 if include.is_dir() {
985 includes.push(include);
986 }
987 }
988 }
989 }
990 includes
991 }
992}
993
994