1use std::env;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5pub struct Build {
7 out_dir: Option<PathBuf>,
8 target: Option<String>,
9 host: Option<String>,
10 max_cstack_size: usize,
12 use_longjmp: bool,
14 enable_codegen: bool,
16 vector_size: usize,
18}
19
20pub struct Artifacts {
22 lib_dir: PathBuf,
23 libs: Vec<String>,
24 cpp_stdlib: Option<String>,
25}
26
27impl Default for Build {
28 fn default() -> Self {
29 Build {
30 out_dir: env::var_os("OUT_DIR").map(PathBuf::from),
31 target: env::var("TARGET").ok(),
32 host: env::var("HOST").ok(),
33 max_cstack_size: 1000000,
34 use_longjmp: false,
35 enable_codegen: false,
36 vector_size: 3,
37 }
38 }
39}
40
41impl Build {
42 pub fn new() -> Build {
44 Build::default()
45 }
46
47 pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
51 self.out_dir = Some(path.as_ref().to_path_buf());
52 self
53 }
54
55 pub fn target(&mut self, target: &str) -> &mut Build {
59 self.target = Some(target.to_string());
60 self
61 }
62
63 pub fn host(&mut self, host: &str) -> &mut Build {
67 self.host = Some(host.to_string());
68 self
69 }
70
71 pub fn set_max_cstack_size(&mut self, size: usize) -> &mut Build {
75 self.max_cstack_size = size;
76 self
77 }
78
79 pub fn use_longjmp(&mut self, r#use: bool) -> &mut Build {
83 self.use_longjmp = r#use;
84 self
85 }
86
87 pub fn enable_codegen(&mut self, enable: bool) -> &mut Build {
91 self.enable_codegen = enable;
92 self
93 }
94
95 pub fn set_vector_size(&mut self, size: usize) -> &mut Build {
99 assert!(size == 3 || size == 4, "vector size must be 3 or 4");
100 self.vector_size = size;
101 self
102 }
103
104 pub fn build(&mut self) -> Artifacts {
106 let target = &self.target.as_ref().expect("TARGET is not set")[..];
107 let host = &self.host.as_ref().expect("HOST is not set")[..];
108 let out_dir = self.out_dir.as_ref().expect("OUT_DIR is not set");
109 let build_dir = out_dir.join("luau-build");
110
111 let source_base_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
112 let common_include_dir = source_base_dir.join("luau").join("Common").join("include");
113 let vm_source_dir = source_base_dir.join("luau").join("VM").join("src");
114 let vm_include_dir = source_base_dir.join("luau").join("VM").join("include");
115
116 if build_dir.exists() {
118 fs::remove_dir_all(&build_dir).unwrap();
119 }
120
121 let mut config = cc::Build::new();
123 config
124 .warnings(false)
125 .cargo_metadata(false)
126 .std("c++17")
127 .cpp(true);
128
129 if target.ends_with("emscripten") {
130 config.flag_if_supported("-fexceptions");
134 config.flag_if_supported("-fwasm-exceptions");
135 }
136
137 config.define("LUAI_MAXCSTACK", &*self.max_cstack_size.to_string());
139 config.define("LUA_VECTOR_SIZE", &*self.vector_size.to_string());
140 config.define("LUA_API", "extern \"C\"");
141
142 if self.use_longjmp {
143 config.define("LUA_USE_LONGJMP", "1");
144 }
145
146 if cfg!(debug_assertions) {
147 config.define("LUAU_ENABLE_ASSERT", None);
148 } else {
149 config.flag_if_supported("-fno-math-errno");
151 }
152
153 config.include(&common_include_dir);
154
155 let ast_lib_name = "luauast";
157 let ast_source_dir = source_base_dir.join("luau").join("Ast").join("src");
158 let ast_include_dir = source_base_dir.join("luau").join("Ast").join("include");
159 config
160 .clone()
161 .include(&ast_include_dir)
162 .add_files_by_ext_sorted(&ast_source_dir, "cpp")
163 .out_dir(&build_dir)
164 .compile(ast_lib_name);
165
166 let codegen_lib_name = "luaucodegen";
168 let codegen_source_dir = source_base_dir.join("luau").join("CodeGen").join("src");
169 let codegen_include_dir = source_base_dir.join("luau").join("CodeGen").join("include");
170 if self.enable_codegen {
171 if target.ends_with("emscripten") {
172 panic!("codegen (jit) is not supported on emscripten");
173 }
174
175 config
176 .clone()
177 .include(&codegen_include_dir)
178 .include(&vm_include_dir)
179 .include(&vm_source_dir)
180 .define("LUACODEGEN_API", "extern \"C\"")
181 .add_files_by_ext_sorted(&codegen_source_dir, "cpp")
182 .out_dir(&build_dir)
183 .compile(codegen_lib_name);
184 }
185
186 let common_lib_name = "luaucommon";
188 let common_source_dir = source_base_dir.join("luau").join("Common").join("src");
189 let common_include_dir = (source_base_dir.join("luau").join("Common")).join("include");
190 config
191 .clone()
192 .include(&common_include_dir)
193 .add_files_by_ext_sorted(&common_source_dir, "cpp")
194 .out_dir(&build_dir)
195 .compile(common_lib_name);
196
197 let compiler_lib_name = "luaucompiler";
199 let compiler_source_dir = source_base_dir.join("luau").join("Compiler").join("src");
200 let compiler_include_dir = (source_base_dir.join("luau").join("Compiler")).join("include");
201 config
202 .clone()
203 .include(&compiler_include_dir)
204 .include(&ast_include_dir)
205 .define("LUACODE_API", "extern \"C\"")
206 .add_files_by_ext_sorted(&compiler_source_dir, "cpp")
207 .out_dir(&build_dir)
208 .compile(compiler_lib_name);
209
210 let config_lib_name = "luauconfig";
212 let config_source_dir = source_base_dir.join("luau").join("Config").join("src");
213 let config_include_dir = source_base_dir.join("luau").join("Config").join("include");
214 config
215 .clone()
216 .include(&config_include_dir)
217 .include(&ast_include_dir)
218 .include(&compiler_include_dir)
219 .include(&vm_include_dir)
220 .add_files_by_ext_sorted(&config_source_dir, "cpp")
221 .out_dir(&build_dir)
222 .compile(config_lib_name);
223
224 let custom_lib_name = "luaucustom";
226 let custom_source_dir = source_base_dir.join("luau").join("Custom").join("src");
227 config
228 .clone()
229 .include(&vm_include_dir)
230 .include(&vm_source_dir)
231 .add_files_by_ext_sorted(&custom_source_dir, "cpp")
232 .out_dir(&build_dir)
233 .compile(custom_lib_name);
234
235 let require_lib_name = "luaurequire";
237 let require_source_dir = source_base_dir.join("luau").join("Require").join("src");
238 let require_include_dir = source_base_dir.join("luau").join("Require").join("include");
239 config
240 .clone()
241 .include(&require_include_dir)
242 .include(&ast_include_dir)
243 .include(&config_include_dir)
244 .include(&vm_include_dir)
245 .add_files_by_ext_sorted(&require_source_dir, "cpp")
246 .out_dir(&build_dir)
247 .compile(require_lib_name);
248
249 let vm_lib_name = "luauvm";
251 config
252 .clone()
253 .include(&vm_include_dir)
254 .add_files_by_ext_sorted(&vm_source_dir, "cpp")
255 .out_dir(&build_dir)
256 .compile(vm_lib_name);
257
258 let mut artifacts = Artifacts {
259 lib_dir: build_dir,
260 libs: vec![
261 vm_lib_name.to_string(),
262 compiler_lib_name.to_string(),
263 ast_lib_name.to_string(),
264 common_lib_name.to_string(),
265 config_lib_name.to_string(),
266 custom_lib_name.to_string(),
267 require_lib_name.to_string(),
268 ],
269 cpp_stdlib: Self::get_cpp_link_stdlib(target, host),
270 };
271
272 if self.enable_codegen {
273 artifacts.libs.push(codegen_lib_name.to_string());
274 }
275
276 artifacts
277 }
278
279 fn get_cpp_link_stdlib(target: &str, host: &str) -> Option<String> {
288 let kind = if host == target { "HOST" } else { "TARGET" };
290 let res = env::var(format!("CXXSTDLIB_{target}"))
291 .or_else(|_| env::var(format!("CXXSTDLIB_{}", target.replace('-', "_"))))
292 .or_else(|_| env::var(format!("{kind}_CXXSTDLIB")))
293 .or_else(|_| env::var("CXXSTDLIB"))
294 .ok();
295 if res.is_some() {
296 return res;
297 }
298
299 if target.contains("msvc") {
300 None
301 } else if target.contains("apple") | target.contains("freebsd") | target.contains("openbsd")
302 {
303 Some("c++".to_string())
304 } else if target.contains("android") {
305 Some("c++_shared".to_string())
306 } else {
307 Some("stdc++".to_string())
308 }
309 }
310}
311
312impl Artifacts {
313 pub fn lib_dir(&self) -> &Path {
314 &self.lib_dir
315 }
316
317 pub fn libs(&self) -> &[String] {
318 &self.libs
319 }
320
321 pub fn print_cargo_metadata(&self) {
322 println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
323 for lib in &self.libs {
324 println!("cargo:rustc-link-lib=static={lib}");
325 }
326 if let Some(ref cpp_stdlib) = self.cpp_stdlib {
327 println!("cargo:rustc-link-lib={cpp_stdlib}");
328 }
329 if let Some(version) = self.version() {
330 println!("cargo:rustc-env=LUAU_VERSION={version}");
331 }
332 }
333
334 pub fn version(&self) -> Option<String> {
335 let pkg_version = env!("CARGO_PKG_VERSION");
336 let (_, luau_version) = pkg_version.split_once("+luau")?;
337 Some(format!("0.{luau_version}"))
338 }
339}
340
341trait AddFilesByExt {
342 fn add_files_by_ext_sorted(&mut self, dir: &Path, ext: &str) -> &mut Self;
343}
344
345impl AddFilesByExt for cc::Build {
346 fn add_files_by_ext_sorted(&mut self, dir: &Path, ext: &str) -> &mut Self {
350 let mut sources: Vec<_> = fs::read_dir(dir)
351 .unwrap()
352 .filter_map(|e| e.ok())
353 .filter(|e| e.path().extension() == Some(ext.as_ref()))
354 .map(|e| e.path())
355 .collect();
356
357 sources.sort();
359
360 for source in sources {
361 self.file(source);
362 }
363
364 self
365 }
366}