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 luau_source_dir = source_base_dir.join("luau");
113 let common_include_dir = luau_source_dir.join("Common").join("include");
114 let vm_source_dir = luau_source_dir.join("VM").join("src");
115 let vm_include_dir = luau_source_dir.join("VM").join("include");
116
117 if build_dir.exists() {
119 fs::remove_dir_all(&build_dir).unwrap();
120 }
121
122 let mut config = cc::Build::new();
124 config
125 .warnings(false)
126 .cargo_metadata(false)
127 .std("c++17")
128 .cpp(true);
129
130 println!("cargo:rerun-if-env-changed=LUAU_CXXFLAGS");
138 println!("cargo:rerun-if-env-changed=LUAU_INCLUDE_DIRS");
139 if let Ok(flags) = env::var("LUAU_CXXFLAGS") {
140 for flag in flags.split_whitespace() {
141 config.flag(flag);
142 }
143 }
144 if let Ok(dirs) = env::var("LUAU_INCLUDE_DIRS") {
145 for dir in env::split_paths(&dirs) {
146 config.include(dir);
147 }
148 }
149
150 if target.ends_with("emscripten") {
151 config.flag_if_supported("-fexceptions");
155 config.flag_if_supported("-fwasm-exceptions");
156 }
157
158 config.define("LUAI_MAXCSTACK", &*self.max_cstack_size.to_string());
160 config.define("LUA_VECTOR_SIZE", &*self.vector_size.to_string());
161 config.define("LUA_API", "extern \"C\"");
162
163 if self.use_longjmp {
164 config.define("LUA_USE_LONGJMP", "1");
165 }
166
167 if cfg!(debug_assertions) {
168 config.define("LUAU_ENABLE_ASSERT", None);
169 } else {
170 config.flag_if_supported("-fno-math-errno");
172 }
173
174 config.include(&common_include_dir);
175
176 let ast_lib_name = "luauast";
178 let ast_source_dir = luau_source_dir.join("Ast").join("src");
179 let ast_include_dir = luau_source_dir.join("Ast").join("include");
180 config
181 .clone()
182 .include(&ast_include_dir)
183 .add_files_by_ext_sorted(&ast_source_dir, "cpp")
184 .out_dir(&build_dir)
185 .compile(ast_lib_name);
186
187 let codegen_lib_name = "luaucodegen";
189 let codegen_source_dir = luau_source_dir.join("CodeGen").join("src");
190 let codegen_include_dir = luau_source_dir.join("CodeGen").join("include");
191 if self.enable_codegen {
192 if target.ends_with("emscripten") {
193 panic!("codegen (jit) is not supported on emscripten");
194 }
195
196 config
197 .clone()
198 .include(&codegen_include_dir)
199 .include(&vm_include_dir)
200 .include(&vm_source_dir)
201 .define("LUACODEGEN_API", "extern \"C\"")
202 .add_files_by_ext_sorted(&codegen_source_dir, "cpp")
203 .out_dir(&build_dir)
204 .compile(codegen_lib_name);
205 }
206
207 let common_lib_name = "luaucommon";
209 let common_source_dir = luau_source_dir.join("Common").join("src");
210 config
211 .clone()
212 .add_files_by_ext_sorted(&common_source_dir, "cpp")
213 .out_dir(&build_dir)
214 .compile(common_lib_name);
215
216 let bytecode_lib_name = "luaubytecode";
218 let bytecode_source_dir = luau_source_dir.join("Bytecode").join("src");
219 let bytecode_include_dir = luau_source_dir.join("Bytecode").join("include");
220 config
221 .clone()
222 .include(&bytecode_include_dir)
223 .add_files_by_ext_sorted(&bytecode_source_dir, "cpp")
224 .out_dir(&build_dir)
225 .compile(bytecode_lib_name);
226
227 let inliner_lib_name = "luauinliner";
229 let inliner_source_dir = luau_source_dir.join("Inliner").join("src");
230 let inliner_include_dir = luau_source_dir.join("Inliner").join("include");
231 config
232 .clone()
233 .include(&inliner_include_dir)
234 .include(&bytecode_include_dir)
235 .include(&bytecode_source_dir)
236 .include(&vm_include_dir)
237 .include(&vm_source_dir)
238 .define("LUAJITINLINER_API", "extern \"C\"")
239 .add_files_by_ext_sorted(&inliner_source_dir, "cpp")
240 .out_dir(&build_dir)
241 .compile(inliner_lib_name);
242
243 let compiler_lib_name = "luaucompiler";
245 let compiler_source_dir = luau_source_dir.join("Compiler").join("src");
246 let compiler_include_dir = luau_source_dir.join("Compiler").join("include");
247 config
248 .clone()
249 .include(&compiler_include_dir)
250 .include(&bytecode_include_dir)
251 .include(&ast_include_dir)
252 .define("LUACODE_API", "extern \"C\"")
253 .add_files_by_ext_sorted(&compiler_source_dir, "cpp")
254 .out_dir(&build_dir)
255 .compile(compiler_lib_name);
256
257 let config_lib_name = "luauconfig";
259 let config_source_dir = luau_source_dir.join("Config").join("src");
260 let config_include_dir = luau_source_dir.join("Config").join("include");
261 config
262 .clone()
263 .include(&config_include_dir)
264 .include(&ast_include_dir)
265 .include(&compiler_include_dir)
266 .include(&vm_include_dir)
267 .add_files_by_ext_sorted(&config_source_dir, "cpp")
268 .out_dir(&build_dir)
269 .compile(config_lib_name);
270
271 let custom_lib_name = "luaucustom";
273 let custom_source_dir = luau_source_dir.join("Custom").join("src");
274 config
275 .clone()
276 .include(&vm_include_dir)
277 .include(&vm_source_dir)
278 .add_files_by_ext_sorted(&custom_source_dir, "cpp")
279 .out_dir(&build_dir)
280 .compile(custom_lib_name);
281
282 let require_lib_name = "luaurequire";
284 let require_source_dir = luau_source_dir.join("Require").join("src");
285 let require_include_dir = luau_source_dir.join("Require").join("include");
286 config
287 .clone()
288 .include(&require_include_dir)
289 .include(&ast_include_dir)
290 .include(&config_include_dir)
291 .include(&vm_include_dir)
292 .add_files_by_ext_sorted(&require_source_dir, "cpp")
293 .out_dir(&build_dir)
294 .compile(require_lib_name);
295
296 let vm_lib_name = "luauvm";
298 config
299 .clone()
300 .include(&vm_include_dir)
301 .add_files_by_ext_sorted(&vm_source_dir, "cpp")
302 .out_dir(&build_dir)
303 .compile(vm_lib_name);
304
305 let mut artifacts = Artifacts {
306 lib_dir: build_dir,
307 libs: vec![
308 inliner_lib_name.to_string(),
309 vm_lib_name.to_string(),
310 bytecode_lib_name.to_string(),
311 compiler_lib_name.to_string(),
312 ast_lib_name.to_string(),
313 common_lib_name.to_string(),
314 config_lib_name.to_string(),
315 custom_lib_name.to_string(),
316 require_lib_name.to_string(),
317 ],
318 cpp_stdlib: Self::get_cpp_link_stdlib(target, host),
319 };
320
321 if self.enable_codegen {
322 artifacts.libs.push(codegen_lib_name.to_string());
323 }
324
325 artifacts
326 }
327
328 fn get_cpp_link_stdlib(target: &str, host: &str) -> Option<String> {
337 let kind = if host == target { "HOST" } else { "TARGET" };
339 let res = env::var(format!("CXXSTDLIB_{target}"))
340 .or_else(|_| env::var(format!("CXXSTDLIB_{}", target.replace('-', "_"))))
341 .or_else(|_| env::var(format!("{kind}_CXXSTDLIB")))
342 .or_else(|_| env::var("CXXSTDLIB"))
343 .ok();
344 if res.is_some() {
345 return res;
346 }
347
348 if target.contains("msvc") {
349 None
350 } else if target.contains("apple") | target.contains("freebsd") | target.contains("openbsd")
351 {
352 Some("c++".to_string())
353 } else if target.contains("android") {
354 Some("c++_shared".to_string())
355 } else {
356 Some("stdc++".to_string())
357 }
358 }
359}
360
361impl Artifacts {
362 pub fn lib_dir(&self) -> &Path {
363 &self.lib_dir
364 }
365
366 pub fn libs(&self) -> &[String] {
367 &self.libs
368 }
369
370 pub fn print_cargo_metadata(&self) {
371 println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
372 for lib in &self.libs {
373 println!("cargo:rustc-link-lib=static={lib}");
374 }
375 if let Some(ref cpp_stdlib) = self.cpp_stdlib {
376 println!("cargo:rustc-link-lib={cpp_stdlib}");
377 }
378 if let Some(version) = self.version() {
379 println!("cargo:rustc-env=LUAU_VERSION={version}");
380 }
381 }
382
383 pub fn version(&self) -> Option<String> {
384 let pkg_version = env!("CARGO_PKG_VERSION");
385 let (_, luau_version) = pkg_version.split_once("+luau")?;
386 Some(format!("0.{luau_version}"))
387 }
388}
389
390trait AddFilesByExt {
391 fn add_files_by_ext_sorted(&mut self, dir: &Path, ext: &str) -> &mut Self;
392}
393
394impl AddFilesByExt for cc::Build {
395 fn add_files_by_ext_sorted(&mut self, dir: &Path, ext: &str) -> &mut Self {
399 let mut sources: Vec<_> = fs::read_dir(dir)
400 .unwrap()
401 .filter_map(|e| e.ok())
402 .filter(|e| e.path().extension() == Some(ext.as_ref()))
403 .map(|e| e.path())
404 .collect();
405
406 sources.sort();
408
409 for source in sources {
410 self.file(source);
411 }
412
413 self
414 }
415}