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