lune/src/cli/build.rs

84 lines
2.7 KiB
Rust

use console::Style;
use std::{
env,
path::{Path, PathBuf},
process::ExitCode,
};
use tokio::{
fs::{self, OpenOptions},
io::AsyncWriteExt,
};
use anyhow::Result;
use mlua::Compiler as LuaCompiler;
/**
Compiles and embeds the bytecode of a requested lua file to form a standalone binary,
then writes it to an output file, with the required permissions.
*/
#[allow(clippy::similar_names)]
pub async fn build_standalone<T: AsRef<Path> + Into<PathBuf>>(
script_path: String,
output_path: T,
code: impl AsRef<[u8]>,
) -> Result<ExitCode> {
let log_output_path = output_path.as_ref().display();
let prefix_style = Style::new().green().bold();
let compile_prefix = prefix_style.apply_to("Compile");
let bytecode_prefix = prefix_style.apply_to("Bytecode");
let write_prefix = prefix_style.apply_to("Write");
let compiled_prefix = prefix_style.apply_to("Compiled");
println!("{compile_prefix} {script_path}");
// First, we read the contents of the lune interpreter as our starting point
let mut patched_bin = fs::read(env::current_exe()?).await?;
let base_bin_offset = u64::try_from(patched_bin.len())?;
// The signature which separates indicates the presence of bytecode to execute
// If a binary contains this signature, that must mean it is a standalone binary
let signature: Vec<u8> = vec![0x4f, 0x3e, 0xf8, 0x41, 0xc3, 0x3a, 0x52, 0x16];
// Compile luau input into bytecode
let bytecode = LuaCompiler::new()
.set_optimization_level(2)
.set_coverage_level(0)
.set_debug_level(0)
.compile(code);
println!(" {bytecode_prefix} {script_path}");
patched_bin.append(&mut bytecode.clone());
let mut meta = base_bin_offset.to_ne_bytes().to_vec();
// Include metadata in the META chunk, each field is 8 bytes
meta.append(&mut (bytecode.len() as u64).to_ne_bytes().to_vec()); // Size of bytecode, used to calculate end offset at runtime
meta.append(&mut 1_u64.to_ne_bytes().to_vec()); // Number of files, padded with null bytes - for future use
patched_bin.append(&mut meta);
// Append the signature to the base binary
patched_bin.append(&mut signature.clone());
// Write the compiled binary to file
#[cfg(target_family = "unix")]
OpenOptions::new()
.write(true)
.create(true)
.mode(0o770) // read, write and execute permissions for user and group
.open(&output_path)
.await?
.write_all(&patched_bin)
.await?;
#[cfg(target_family = "windows")]
fs::write(&output_path, &patched_bin).await?;
println!(" {write_prefix} {log_output_path}");
println!("{compiled_prefix} {log_output_path}");
Ok(ExitCode::SUCCESS)
}