diff --git a/.gitignore b/.gitignore index fcfec84e..fb2add4b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ Cargo.lock .vscode/ .DS_Store .stignore + +*.bin \ No newline at end of file diff --git a/src/chunk.rs b/src/chunk.rs index f3db5de2..0c380176 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -524,4 +524,4 @@ impl<'lua, 'a> Chunk<'lua, 'a> { buf.extend(source); buf } -} +} \ No newline at end of file diff --git a/src/lua.rs b/src/lua.rs index 13c348c0..410a73a5 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -3192,6 +3192,143 @@ impl Lua { pub(crate) fn clone(&self) -> Arc { Arc::clone(&self.0) } + + /// Compile all the files to bytecode under a directory, save as `*.bin` + /// + /// It designs for build script, so there will be no error return. + /// + /// It will automatically print cargo:rerun-if-changed=* + /// + /// In release mode, it may not save all the debug information, see also [`Function::dump()`] + /// + /// # Examples + /// + /// ``` + /// use mlua::prelude::*; + /// fn main(){ + /// let lua = Lua::new(); + /// + /// lua.compile_single("./tests/scripts/a.lua") + /// .compile_single("./tests/scripts/b.lua"); + /// } + /// ``` + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + #[track_caller] + #[inline] + pub fn compile_single(&self, path: impl AsRef) -> &Self { + use std::fs; + use std::path::PathBuf; + + let path = path.as_ref(); + + println!("cargo:rerun-if-changed={}", path.display()); + + let bytes = fs::read(path).unwrap_or_else(|err| { + panic!( + "Error caused while reading the source file\nAt Path: {}\nCaused by:\n\t{:?}", + path.display(), + err + ) + }); + + let strip; + #[cfg(debug_assertions)] + { + strip = false; + } + #[cfg(not(debug_assertions))] + { + strip = true; + } + + let bytecode = self.load(&bytes).into_function().unwrap_or_else(|err|{ + panic!( + "Error caused while converting chunk into function\nAt Path: {}\nCaused by:\n\t{:?}", + path.display(), + err + ) + }).dump(strip); + + let mut path = PathBuf::from(path); + path.set_extension("bin"); + + fs::write(&path, bytecode).unwrap_or_else(|err| { + panic!( + "Error caused while writing the bytecode\nAt Path: {}\nCaused by:\n\t{:?}", + path.display(), + err + ) + }); + + self + } + + /// Compile all the files with the extension `lua` to bytecode under a directory, save as `*.bin` + /// + /// It is designed for build script, so there will be no error return. + /// + /// It automatically print cargo:rerun-if-changed=* of each script file + /// + /// It calls [`Lua::compile_single()`] on every file + /// + /// # Examples + /// + /// ``` + /// use mlua::prelude::*; + /// fn main(){ + /// let lua = Lua::new(); + /// + /// lua.compile_directory("./tests/scripts") + /// .compile_directory("./tests/scripts"); + /// } + /// ``` + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + #[track_caller] + pub fn compile_directory(&self, path: impl AsRef) -> &Self { + use std::fs; + use std::path::{Path, PathBuf}; + #[track_caller] + fn all_files(path: impl AsRef) -> std::io::Result> { + // here use a BFS to traversal all the files under a directory + let mut stk = vec![path.as_ref().to_path_buf()]; + let mut ans = Vec::new(); + while let Some(path) = stk.pop() { + for entry in fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + stk.push(path); + } else { + if let Some(exe) = path.extension() { + if exe == "lua" { + ans.push(path); + } + } + } + } + } + + Ok(ans) + } + + let path = path.as_ref(); + + let files = all_files(path).unwrap_or_else(|err| { + panic!( + "Error caused while traversal the directory\nAt Path: {}\nCaused by:\n\t{:?}", + path.display(), + err + ) + }); + + for path in files { + self.compile_single(path); + } + + self + } } impl LuaInner { diff --git a/tests/chunk.rs b/tests/chunk.rs index 714e887b..55e37a79 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -5,6 +5,8 @@ use std::io; use mlua::{Lua, Result}; +use std::path::{Path, PathBuf}; + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_chunk_path() -> Result<()> { @@ -28,6 +30,64 @@ fn test_chunk_path() -> Result<()> { Ok(()) } +#[test] +#[cfg(not(feature = "luau"))] +fn test_compile() { + let lua = Lua::new(); + + let work_dir = Path::new("./tests/scripts/compile/"); + + let assert = || { + assert_eq!( + lua.load(fs::read(work_dir.join("a.bin")).unwrap()) + .eval::() + .unwrap(), + "Helloworld".to_string() + ); + + assert_eq!( + lua.load(fs::read(work_dir.join("b.bin")).unwrap()) + .eval::() + .unwrap(), + "Helloworld".to_string() + ); + }; + + lua.compile_single(work_dir.join("a.lua")) + .compile_single(work_dir.join("b.lua")); + + assert(); + + // remove them to make sure the code above don't influence them test below + fs::remove_file(work_dir.join("a.bin")).unwrap(); + fs::remove_file(work_dir.join("b.bin")).unwrap(); + + lua.compile_directory(work_dir); + + assert(); +} + +#[test] +#[cfg(not(feature = "luau"))] +fn multi_file_def() { + let lua = Lua::new(); + + let work_dir = Path::new("./tests/scripts/multi_file_def"); + + lua.compile_directory(work_dir); + + lua.load(fs::read(work_dir.join("c.bin")).unwrap()) + .exec() + .unwrap(); + + let value = lua + .load(fs::read(work_dir.join("d.bin")).unwrap()) + .eval::() + .unwrap(); + + assert_eq!(value.as_str(), "Hello world !"); +} + #[test] #[cfg(feature = "macros")] fn test_chunk_macro() -> Result<()> { diff --git a/tests/scripts/compile/a.lua b/tests/scripts/compile/a.lua new file mode 100644 index 00000000..50aec14d --- /dev/null +++ b/tests/scripts/compile/a.lua @@ -0,0 +1 @@ +return "Hello" .. "world" \ No newline at end of file diff --git a/tests/scripts/compile/b.lua b/tests/scripts/compile/b.lua new file mode 100644 index 00000000..50aec14d --- /dev/null +++ b/tests/scripts/compile/b.lua @@ -0,0 +1 @@ +return "Hello" .. "world" \ No newline at end of file diff --git a/tests/scripts/compile/info.md b/tests/scripts/compile/info.md new file mode 100644 index 00000000..da1b8d9b --- /dev/null +++ b/tests/scripts/compile/info.md @@ -0,0 +1 @@ +Belong to [test_compile()](../../chunk.rs) \ No newline at end of file diff --git a/tests/scripts/multi_file_def/c.lua b/tests/scripts/multi_file_def/c.lua new file mode 100644 index 00000000..2641bfbb --- /dev/null +++ b/tests/scripts/multi_file_def/c.lua @@ -0,0 +1 @@ +hello = "Hello world !" diff --git a/tests/scripts/multi_file_def/d.lua b/tests/scripts/multi_file_def/d.lua new file mode 100644 index 00000000..9aed7b7b --- /dev/null +++ b/tests/scripts/multi_file_def/d.lua @@ -0,0 +1 @@ +return hello; \ No newline at end of file diff --git a/tests/scripts/multi_file_def/info.md b/tests/scripts/multi_file_def/info.md new file mode 100644 index 00000000..640f505d --- /dev/null +++ b/tests/scripts/multi_file_def/info.md @@ -0,0 +1 @@ +Belong to [multi_file_def()](../../chunk.rs) \ No newline at end of file