Skip to content

Commit

Permalink
feat: initial standalone executable builder impl
Browse files Browse the repository at this point in the history
  • Loading branch information
CompeyDev committed Nov 20, 2023
1 parent 507d88e commit 5d58ba7
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .lune/csv_printer.luau
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ for rowIndex, row in csvTable do
print(string.format("┣%s┫", thiccLine))
end
end
print(string.format("┗%s┛", thiccLine))
print(string.format("┗%s┛", thiccLine))
40 changes: 40 additions & 0 deletions src/cli/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::{
env,
path::{Path, PathBuf},
};
use tokio::fs;

use anyhow::Result;
use mlua::Compiler as LuaCompiler;

pub async fn build_standalone<T: AsRef<Path> + Into<PathBuf>>(
output_path: T,
code: impl AsRef<[u8]>,
) -> Result<()> {
// First, we read the contents of the lune interpreter as our starting point
let mut patched_bin = fs::read(env::current_exe()?).await?;

// The signature which separates indicates the presence of bytecode to execute
// If a binary contains this signature, that must mean it is a standalone binar
let signature: Vec<u8> = vec![0x12, 0xed, 0x93, 0x14, 0x28];

// Append the signature to the base binary
for byte in signature {
patched_bin.push(byte);
}

// Compile luau input into bytecode
let mut bytecode = LuaCompiler::new()
.set_optimization_level(2)
.set_coverage_level(0)
.set_debug_level(0)
.compile(code);

// Append compiled bytecode to binary and finalize
patched_bin.append(&mut bytecode);

// Write the compiled binary to file
fs::write(output_path, patched_bin).await?;

Ok(())
}
55 changes: 51 additions & 4 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{fmt::Write as _, process::ExitCode};
use std::{env, fmt::Write as _, path::PathBuf, process::ExitCode};

use anyhow::{Context, Result};
use clap::Parser;
Expand All @@ -9,6 +9,7 @@ use tokio::{
io::{stdin, AsyncReadExt},
};

pub(crate) mod build;
pub(crate) mod gen;
pub(crate) mod repl;
pub(crate) mod setup;
Expand All @@ -20,6 +21,8 @@ use utils::{
listing::{find_lune_scripts, sort_lune_scripts, write_lune_scripts_list},
};

use self::build::build_standalone;

/// A Luau script runner
#[derive(Parser, Debug, Default, Clone)]
#[command(version, long_about = None)]
Expand All @@ -44,6 +47,8 @@ pub struct Cli {
/// Generate a Lune documentation file for Luau LSP
#[clap(long, hide = true)]
generate_docs_file: bool,
#[clap(long, hide = true)]
build: bool,
}

#[allow(dead_code)]
Expand Down Expand Up @@ -116,6 +121,7 @@ impl Cli {

return Ok(ExitCode::SUCCESS);
}

// Generate (save) definition files, if wanted
let generate_file_requested = self.setup
|| self.generate_luau_types
Expand Down Expand Up @@ -143,14 +149,35 @@ impl Cli {
if generate_file_requested {
return Ok(ExitCode::SUCCESS);
}
// If we did not generate any typedefs we know that the user did not
// provide any other options, and in that case we should enter the REPL
return repl::show_interface().await;

// Signature which is only present in standalone lune binaries
let signature: Vec<u8> = vec![0x12, 0xed, 0x93, 0x14, 0x28];

// Read the current lune binary to memory
let bin = read_to_vec(env::current_exe()?).await?;

// Check to see if the lune executable includes the signature
return match bin
.windows(signature.len())
.position(|block| block == signature)
{
// If we find the signature, all bytes after the 5 signature bytes must be bytecode
Some(offset) => Ok(Lune::new()
.with_args(self.script_args)
.run("STANDALONE", &bin[offset + signature.len()..bin.len()])
.await?),

// If we did not generate any typedefs, know we're not a precompiled bin and
// we know that the user did not provide any other options, and in that
// case we should enter the REPL
None => repl::show_interface().await,
};
}
// Figure out if we should read from stdin or from a file,
// reading from stdin is marked by passing a single "-"
// (dash) as the script name to run to the cli
let script_path = self.script_path.unwrap();

let (script_display_name, script_contents) = if script_path == "-" {
let mut stdin_contents = Vec::new();
stdin()
Expand All @@ -165,6 +192,26 @@ impl Cli {
let file_display_name = file_path.with_extension("").display().to_string();
(file_display_name, file_contents)
};

if self.build {
let output_path =
PathBuf::from(script_path.clone()).with_extension(env::consts::EXE_EXTENSION);
println!(
"Building {script_path} to {}",
output_path.to_string_lossy()
);

return Ok(
match build_standalone(output_path, strip_shebang(script_contents.clone())).await {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("{err}");
ExitCode::FAILURE
}
},
);
}

// Create a new lune object with all globals & run the script
let result = Lune::new()
.with_args(self.script_args)
Expand Down

0 comments on commit 5d58ba7

Please sign in to comment.