Skip to main content

luau0_src/
lib.rs

1use std::env;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5/// Represents the configuration for building Luau artifacts.
6pub struct Build {
7    out_dir: Option<PathBuf>,
8    target: Option<String>,
9    host: Option<String>,
10    // Max number of Lua stack slots that a C function can use
11    max_cstack_size: usize,
12    // Use longjmp instead of C++ exceptions
13    use_longjmp: bool,
14    // Enable code generator (jit)
15    enable_codegen: bool,
16    // Vector size, must be 3 (default) or 4
17    vector_size: usize,
18}
19
20/// Represents the artifacts produced by the build process.
21pub 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    /// Creates a new `Build` instance with default settings.
43    pub fn new() -> Build {
44        Build::default()
45    }
46
47    /// Sets the output directory for the build artifacts.
48    ///
49    /// Default is the environment variable `OUT_DIR`.
50    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    /// Sets the target architecture for the build.
56    ///
57    /// Default is the environment variable `TARGET`.
58    pub fn target(&mut self, target: &str) -> &mut Build {
59        self.target = Some(target.to_string());
60        self
61    }
62
63    /// Sets the host architecture for the build.
64    ///
65    /// Default is the environment variable `HOST`.
66    pub fn host(&mut self, host: &str) -> &mut Build {
67        self.host = Some(host.to_string());
68        self
69    }
70
71    /// Sets the maximum number of Lua stack slots that a C function can use.
72    ///
73    /// Default is 1,000,000.
74    pub fn set_max_cstack_size(&mut self, size: usize) -> &mut Build {
75        self.max_cstack_size = size;
76        self
77    }
78
79    /// Sets whether to use longjmp instead of C++ exceptions.
80    ///
81    /// Default is false.
82    pub fn use_longjmp(&mut self, r#use: bool) -> &mut Build {
83        self.use_longjmp = r#use;
84        self
85    }
86
87    /// Sets whether to enable the code generator (JIT).
88    ///
89    /// Default is false.
90    pub fn enable_codegen(&mut self, enable: bool) -> &mut Build {
91        self.enable_codegen = enable;
92        self
93    }
94
95    /// Sets the vector size (3 or 4).
96    ///
97    /// Default is 3.
98    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    /// Builds the Lua artifacts for the specified version.
105    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        // Cleanup
118        if build_dir.exists() {
119            fs::remove_dir_all(&build_dir).unwrap();
120        }
121
122        // Configure C++
123        let mut config = cc::Build::new();
124        config
125            .warnings(false)
126            .cargo_metadata(false)
127            .std("c++17")
128            .cpp(true);
129
130        // Extra compiler flags and include directories for the vendored C++.
131        //
132        // `LUAU_CXXFLAGS` is split on whitespace and appended to the compiler
133        // command; `LUAU_INCLUDE_DIRS` uses the platform path separator. Both
134        // are declared with `rerun-if-env-changed`, so changing either forces a
135        // rebuild instead of silently reusing an artifact built with the old
136        // settings.
137        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            // cc-rs unconditionally adds `-fno-exceptions` for wasm32.
152            // We need to re-enable exceptions and switch to the
153            // wasm-native EH ABI that Rust's emscripten target uses.
154            config.flag_if_supported("-fexceptions");
155            config.flag_if_supported("-fwasm-exceptions");
156        }
157
158        // Common defines
159        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            // this flag allows compiler to lower sqrt() into a single CPU instruction
171            config.flag_if_supported("-fno-math-errno");
172        }
173
174        config.include(&common_include_dir);
175
176        // Build `Ast` library
177        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        // Build `CodeGen` library
188        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        // Build `Common` library
208        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        // Build `Bytecode` library
217        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        // Build `Inliner` library
228        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        // Build `Compiler` library
244        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        // Build `Config` library
258        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        // Build customization library
272        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        // Build `Require` library
283        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        // Build VM
297        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    /// Returns the C++ standard library:
329    /// 1) Uses `CXXSTDLIB` environment variable if set
330    /// 2) The default `c++` for OS X and BSDs
331    /// 3) `c++_shared` for Android
332    /// 4) `None` for MSVC
333    /// 5) `stdc++` for anything else.
334    ///
335    /// Inspired by the `cc` crate.
336    fn get_cpp_link_stdlib(target: &str, host: &str) -> Option<String> {
337        // Try to get value from the `CXXSTDLIB` env variable
338        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    // It's important to keep the order of the files to get consistent builds between machines
396    // if the order is not always the same, the final binary produces a different SHA256 which
397    // might cause issues if one needs to verify which binary is being executed
398    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        // Sort for determinism
407        sources.sort();
408
409        for source in sources {
410            self.file(source);
411        }
412
413        self
414    }
415}