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