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_stack_size: Option<usize>,
11 use_longjmp: Option<bool>,
13 disable_bytecode: Option<bool>,
15 disable_fs: Option<bool>,
17 disable_os_exec: Option<bool>,
19 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 pub fn disable_bytecode(&mut self, disable: bool) -> &mut Build {
72 self.disable_bytecode = Some(disable);
73 self
74 }
75
76 pub fn disable_fs(&mut self, disable: bool) -> &mut Build {
78 self.disable_fs = Some(disable);
79 self
80 }
81
82 pub fn disable_os_exec(&mut self, disable: bool) -> &mut Build {
84 self.disable_os_exec = Some(disable);
85 self
86 }
87
88 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 if out_dir.exists() {
104 fs::remove_dir_all(out_dir).unwrap();
105 }
106
107 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 config.flag_if_supported("-fno-math-errno");
127 }
128
129 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 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 fn get_cpp_link_stdlib(target: &str, host: &str) -> Option<String> {
202 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}