-
Notifications
You must be signed in to change notification settings - Fork 33
/
embed.rs
113 lines (103 loc) · 2.98 KB
/
embed.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! Helpers for use when embedding Ruby in a Rust project.
use std::{
ffi::CString,
sync::atomic::{AtomicBool, Ordering},
};
#[cfg(windows)]
use rb_sys::rb_w32_sysinit;
use rb_sys::{
ruby_cleanup, ruby_exec_node, ruby_executable_node, ruby_options, ruby_set_script_name,
ruby_setup,
};
use crate::{r_string::IntoRString, ruby_handle::RubyHandle};
/// A guard value that will run the cleanup function for the Ruby VM when
/// dropped.
pub struct Cleanup();
impl Drop for Cleanup {
fn drop(&mut self) {
unsafe {
ruby_cleanup(0);
}
}
}
/// Initialises the Ruby VM.
///
/// Calling this function is only required when embedding Ruby in Rust. It is
/// not required when embedding Rust in Ruby, e.g. in a Ruby Gem.
///
/// # Safety
///
/// Must be called in `main()`, or at least a function higher up the stack than
/// any code calling Ruby. Must not drop Cleanup until the very end of the
/// process, after all Ruby execution has finished.
///
/// # Panics
///
/// Panics if called more than once.
///
/// # Examples
///
/// ```
/// let _cleanup = unsafe { magnus::embed::init() };
/// ```
#[inline(always)]
pub unsafe fn init() -> Cleanup {
init_options(&["-e", ""])
}
#[inline(always)]
unsafe fn init_options(opts: &[&str]) -> Cleanup {
static INIT: AtomicBool = AtomicBool::new(false);
match INIT.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) {
Ok(false) => {
#[cfg(windows)]
{
let mut argc = 0;
let mut argv: [*mut std::os::raw::c_char; 0] = [];
let mut argv = argv.as_mut_ptr();
rb_w32_sysinit(&mut argc, &mut argv);
}
if ruby_setup() != 0 {
panic!("Failed to setup Ruby");
};
let cleanup = Cleanup();
let mut argv = vec![CString::new("ruby").unwrap()];
argv.extend(opts.iter().map(|s| CString::new(*s).unwrap()));
let mut argv = argv
.iter()
.map(|cs| cs.as_ptr() as *mut _)
.collect::<Vec<_>>();
let node = ruby_options(argv.len() as i32, argv.as_mut_ptr());
let mut status = 0;
if ruby_executable_node(node, &mut status) == 0 {
panic!("Ruby init code not executable");
}
if ruby_exec_node(node) != 0 {
panic!("Ruby init code failed");
};
cleanup
}
Err(true) => panic!("Ruby already initialized"),
r => panic!("unexpected INIT state {:?}", r),
}
}
impl RubyHandle {
pub fn script<T>(&self, name: T)
where
T: IntoRString,
{
let name = name.into_r_string_with(self);
unsafe { ruby_set_script_name(name.as_rb_value()) };
}
}
/// Sets the current script name.
///
/// # Panics
///
/// Panics if called from a non-Ruby thread.
#[inline]
pub fn ruby_script<T>(name: T)
where
T: IntoRString,
{
get_ruby!().script(name)
}