pluto_src/
lib.rs

1use std::env;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5pub struct Build {
6    out_dir: Option<PathBuf>,
7    target: Option<String>,
8    host: Option<String>,
9    // Max number of Lua stack slots
10    max_stack_size: Option<usize>,
11    // Use longjmp instead of C++ exceptions
12    use_longjmp: Option<bool>,
13    // Disable bytecode loading
14    disable_bytecode: Option<bool>,
15    // Disable filesystem access
16    disable_fs: Option<bool>,
17    // Disable os.os.execute and io.popen
18    disable_os_exec: Option<bool>,
19    // Disable loading any C modules or shared libraries
20    disable_binaries: Option<bool>,
21}
22
23pub struct Artifacts {
24    lib_dir: PathBuf,
25    libs: Vec<String>,
26    cpp_stdlib: Option<String>,
27}
28
29impl Build {
30    #[allow(clippy::new_without_default)]
31    pub fn new() -> Build {
32        Build {
33            out_dir: env::var_os("OUT_DIR").map(|s| PathBuf::from(s).join("pluto-build")),
34            target: env::var("TARGET").ok(),
35            host: env::var("HOST").ok(),
36            max_stack_size: None,
37            use_longjmp: None,
38            disable_bytecode: None,
39            disable_fs: None,
40            disable_os_exec: None,
41            disable_binaries: None,
42        }
43    }
44
45    pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
46        self.out_dir = Some(path.as_ref().to_path_buf());
47        self
48    }
49
50    pub fn target(&mut self, target: &str) -> &mut Build {
51        self.target = Some(target.to_string());
52        self
53    }
54
55    pub fn host(&mut self, host: &str) -> &mut Build {
56        self.host = Some(host.to_string());
57        self
58    }
59
60    pub fn set_max_stack_size(&mut self, size: usize) -> &mut Build {
61        self.max_stack_size = Some(size);
62        self
63    }
64
65    pub fn use_longjmp(&mut self, r#use: bool) -> &mut Build {
66        self.use_longjmp = Some(r#use);
67        self
68    }
69
70    // Controls `PLUTO_DISABLE_COMPILED` define
71    pub fn disable_bytecode(&mut self, disable: bool) -> &mut Build {
72        self.disable_bytecode = Some(disable);
73        self
74    }
75
76    // Controls `PLUTO_NO_FILESYSTEM` define
77    pub fn disable_fs(&mut self, disable: bool) -> &mut Build {
78        self.disable_fs = Some(disable);
79        self
80    }
81
82    // Controls `PLUTO_NO_OS_EXECUTE` define
83    pub fn disable_os_exec(&mut self, disable: bool) -> &mut Build {
84        self.disable_os_exec = Some(disable);
85        self
86    }
87
88    // Controls `PLUTO_NO_BINARIES` define
89    pub fn disable_binaries(&mut self, disable: bool) -> &mut Build {
90        self.disable_binaries = Some(disable);
91        self
92    }
93
94    pub fn build(&mut self) -> Artifacts {
95        let target = &self.target.as_ref().expect("TARGET not set")[..];
96        let host = &self.host.as_ref().expect("HOST not set")[..];
97        let out_dir = self.out_dir.as_ref().expect("OUT_DIR not set");
98
99        let pluto_source_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("pluto");
100        let soup_source_dir = pluto_source_dir.join("vendor").join("Soup");
101
102        // Cleanup
103        if out_dir.exists() {
104            fs::remove_dir_all(out_dir).unwrap();
105        }
106
107        // Configure C++
108        let mut config = cc::Build::new();
109        config
110            .target(target)
111            .host(host)
112            .warnings(false)
113            .cargo_metadata(false)
114            .std("c++17")
115            .flag_if_supported("-fvisibility=hidden")
116            .flag_if_supported("-fno-rtti")
117            .flag_if_supported("-Wno-multichar")
118            .cpp(true);
119
120        if cfg!(debug_assertions) {
121            config.define("LUA_USE_APICHECK", None);
122        } else {
123            config.define("NDEBUG", None);
124            config.opt_level(2);
125            // this flag allows compiler to lower sqrt() into a single CPU instruction
126            config.flag_if_supported("-fno-math-errno");
127        }
128
129        // Build Soup
130        let soup_lib_name = "soup";
131        let mut soup_config = config.clone();
132        soup_config.add_files_by_ext(&soup_source_dir.join("soup"), "cpp");
133        match target {
134            _ if target.contains("x86_64") => {
135                soup_config
136                    .define("SOUP_USE_INTRIN", None)
137                    .add_files_by_ext(&soup_source_dir.join("Intrin"), "cpp")
138                    .flag_if_supported("-maes")
139                    .flag_if_supported("-mpclmul")
140                    .flag_if_supported("-mrdrnd")
141                    .flag_if_supported("-mrdseed")
142                    .flag_if_supported("-msha")
143                    .flag_if_supported("-msse4.1");
144            }
145            _ if target.contains("aarch64") => {
146                soup_config
147                    .define("SOUP_USE_INTRIN", None)
148                    .add_files_by_ext(&soup_source_dir.join("Intrin"), "cpp")
149                    .flag_if_supported("-march=armv8-a+crypto+crc");
150            }
151            _ => {}
152        }
153        soup_config.out_dir(out_dir).compile(soup_lib_name);
154
155        if let Some(max_stack_size) = self.max_stack_size {
156            config.define("LUAI_MAXSTACK", &*max_stack_size.to_string());
157        }
158
159        if let Some(true) = self.use_longjmp {
160            config.define("LUA_USE_LONGJMP", None);
161        }
162
163        if let Some(true) = self.disable_bytecode {
164            config.define("PLUTO_DISABLE_COMPILED", None);
165        }
166
167        if let Some(true) = self.disable_fs {
168            config.define("PLUTO_NO_FILESYSTEM", None);
169        }
170
171        if let Some(true) = self.disable_os_exec {
172            config.define("PLUTO_NO_OS_EXECUTE", None);
173        }
174
175        if let Some(true) = self.disable_binaries {
176            config.define("PLUTO_NO_BINARIES", None);
177        }
178
179        // Build Pluto
180        let pluto_lib_name = "pluto";
181        config
182            .add_files_by_ext(&pluto_source_dir, "cpp")
183            .out_dir(out_dir)
184            .compile(pluto_lib_name);
185
186        Artifacts {
187            lib_dir: out_dir.to_path_buf(),
188            libs: vec![pluto_lib_name.to_string(), soup_lib_name.to_string()],
189            cpp_stdlib: Self::get_cpp_link_stdlib(target, host),
190        }
191    }
192
193    /// Returns the C++ standard library:
194    /// 1) Uses `CXXSTDLIB` environment variable if set
195    /// 2) The default `c++` for OS X and BSDs
196    /// 3) `c++_shared` for Android
197    /// 4) `None` for MSVC
198    /// 5) `stdc++` for anything else.
199    ///
200    /// Inspired by the `cc` crate.
201    fn get_cpp_link_stdlib(target: &str, host: &str) -> Option<String> {
202        // Try to get value from the `CXXSTDLIB` env variable
203        let kind = if host == target { "HOST" } else { "TARGET" };
204        let res = env::var(format!("CXXSTDLIB_{target}"))
205            .or_else(|_| env::var(format!("CXXSTDLIB_{}", target.replace('-', "_"))))
206            .or_else(|_| env::var(format!("{kind}_CXXSTDLIB")))
207            .or_else(|_| env::var("CXXSTDLIB"))
208            .ok();
209        if res.is_some() {
210            return res;
211        }
212
213        if target.contains("msvc") {
214            None
215        } else if target.contains("apple") | target.contains("freebsd") | target.contains("openbsd")
216        {
217            Some("c++".to_string())
218        } else if target.contains("android") {
219            Some("c++_shared".to_string())
220        } else {
221            Some("stdc++".to_string())
222        }
223    }
224}
225
226impl Artifacts {
227    pub fn lib_dir(&self) -> &Path {
228        &self.lib_dir
229    }
230
231    pub fn libs(&self) -> &[String] {
232        &self.libs
233    }
234
235    pub fn print_cargo_metadata(&self) {
236        println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
237        for lib in self.libs.iter() {
238            println!("cargo:rustc-link-lib=static={}", lib);
239        }
240        if let Some(ref cpp_stdlib) = self.cpp_stdlib {
241            println!("cargo:rustc-link-lib={}", cpp_stdlib);
242        }
243    }
244}
245
246trait AddFilesByExt {
247    fn add_files_by_ext(&mut self, dir: &Path, ext: &str) -> &mut Self;
248}
249
250impl AddFilesByExt for cc::Build {
251    fn add_files_by_ext(&mut self, dir: &Path, ext: &str) -> &mut Self {
252        for entry in fs::read_dir(dir)
253            .unwrap()
254            .filter_map(|e| e.ok())
255            .filter(|e| e.path().extension() == Some(ext.as_ref()))
256        {
257            self.file(entry.path());
258        }
259        self
260    }
261}