luau0_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 that a C function can use
10    max_cstack_size: usize,
11    // Use longjmp instead of C++ exceptions
12    use_longjmp: bool,
13    // Enable code generator (jit)
14    enable_codegen: bool,
15    // Vector size, must be 3 (default) or 4
16    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        // Cleanup
94        if build_dir.exists() {
95            fs::remove_dir_all(&build_dir).unwrap();
96        }
97
98        // Configure C++
99        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            // Enable c++ exceptions for emscripten (it's disabled by default)
108            // Later we should switch to wasm exceptions
109            config.flag_if_supported("-fexceptions");
110        }
111
112        // Common defines
113        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            // this flag allows compiler to lower sqrt() into a single CPU instruction
125            config.flag_if_supported("-fno-math-errno");
126        }
127
128        config.include(&common_include_dir);
129
130        // Build `Ast` library
131        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        // Build `CodeGen` library
142        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        // Build `Common` library
162        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        // Build `Compiler` library
173        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        // Build `Config` library
186        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        // Build customization library
200        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        // Build `Require` library
211        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        // Build VM
225        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    /// Returns the C++ standard library:
255    /// 1) Uses `CXXSTDLIB` environment variable if set
256    /// 2) The default `c++` for OS X and BSDs
257    /// 3) `c++_shared` for Android
258    /// 4) `None` for MSVC
259    /// 5) `stdc++` for anything else.
260    ///
261    /// Inspired by the `cc` crate.
262    fn get_cpp_link_stdlib(target: &str, host: &str) -> Option<String> {
263        // Try to get value from the `CXXSTDLIB` env variable
264        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    // It's important to keep the order of the files to get consistent builds between machines
322    // if the order is not always the same, the final binary produces a different SHA256 which
323    // might cause issues if one needs to verify which binary is being executed
324    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        // Sort for determinism
333        sources.sort();
334
335        for source in sources {
336            self.file(source);
337        }
338
339        self
340    }
341}