mirror of
https://github.com/lune-org/lune.git
synced 2025-04-02 01:20:56 +01:00
Give standalone compilation its own dir, leave some future notes
This commit is contained in:
parent
e5e83d1ad6
commit
0392c163a0
6 changed files with 165 additions and 105 deletions
|
@ -1,5 +1,5 @@
|
|||
use std::{
|
||||
env::{self, consts::EXE_EXTENSION},
|
||||
env::consts::EXE_EXTENSION,
|
||||
path::{Path, PathBuf},
|
||||
process::ExitCode,
|
||||
};
|
||||
|
@ -7,10 +7,9 @@ use std::{
|
|||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
use mlua::Compiler as LuaCompiler;
|
||||
use tokio::{fs, io::AsyncWriteExt as _};
|
||||
|
||||
use crate::executor::MetaChunk;
|
||||
use crate::standalone::metadata::Metadata;
|
||||
|
||||
/// Build a standalone executable
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
|
@ -43,18 +42,9 @@ impl BuildCommand {
|
|||
"Creating standalone binary using {}",
|
||||
style(input_path_displayed).green()
|
||||
);
|
||||
let mut patched_bin = fs::read(env::current_exe()?).await?;
|
||||
|
||||
// Compile luau input into bytecode
|
||||
let bytecode = LuaCompiler::new()
|
||||
.set_optimization_level(2)
|
||||
.set_coverage_level(0)
|
||||
.set_debug_level(1)
|
||||
.compile(source_code);
|
||||
|
||||
// Append the bytecode / metadata to the end
|
||||
let meta = MetaChunk { bytecode };
|
||||
patched_bin.extend_from_slice(&meta.to_bytes());
|
||||
let patched_bin = Metadata::create_env_patched_bin(source_code.clone())
|
||||
.await
|
||||
.context("failed to create patched binary")?;
|
||||
|
||||
// And finally write the patched binary to the output file
|
||||
println!(
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
use std::{env, process::ExitCode};
|
||||
|
||||
use lune::Runtime;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use tokio::fs;
|
||||
|
||||
const MAGIC: &[u8; 8] = b"cr3sc3nt";
|
||||
|
||||
/**
|
||||
Metadata for a standalone Lune executable. Can be used to
|
||||
discover and load the bytecode contained in a standalone binary.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MetaChunk {
|
||||
pub bytecode: Vec<u8>,
|
||||
}
|
||||
|
||||
impl MetaChunk {
|
||||
/**
|
||||
Tries to read a standalone binary from the given bytes.
|
||||
*/
|
||||
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
|
||||
let bytes = bytes.as_ref();
|
||||
if bytes.len() < 16 || !bytes.ends_with(MAGIC) {
|
||||
bail!("not a standalone binary")
|
||||
}
|
||||
|
||||
// Extract bytecode size
|
||||
let bytecode_size_bytes = &bytes[bytes.len() - 16..bytes.len() - 8];
|
||||
let bytecode_size =
|
||||
usize::try_from(u64::from_be_bytes(bytecode_size_bytes.try_into().unwrap()))?;
|
||||
|
||||
// Extract bytecode
|
||||
let bytecode = bytes[bytes.len() - 16 - bytecode_size..].to_vec();
|
||||
|
||||
Ok(Self { bytecode })
|
||||
}
|
||||
|
||||
/**
|
||||
Writes the metadata chunk to a byte vector, to later bet read using `from_bytes`.
|
||||
*/
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(&self.bytecode);
|
||||
bytes.extend_from_slice(&(self.bytecode.len() as u64).to_be_bytes());
|
||||
bytes.extend_from_slice(MAGIC);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns whether or not the currently executing Lune binary
|
||||
is a standalone binary, and if so, the bytes of the binary.
|
||||
*/
|
||||
pub async fn check_env() -> (bool, Vec<u8>) {
|
||||
let path = env::current_exe().expect("failed to get path to current running lune executable");
|
||||
let contents = fs::read(path).await.unwrap_or_default();
|
||||
let is_standalone = contents.ends_with(MAGIC);
|
||||
(is_standalone, contents)
|
||||
}
|
||||
|
||||
/**
|
||||
Discovers, loads and executes the bytecode contained in a standalone binary.
|
||||
*/
|
||||
pub async fn run_standalone(patched_bin: impl AsRef<[u8]>) -> Result<ExitCode> {
|
||||
// The first argument is the path to the current executable
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let meta = MetaChunk::from_bytes(patched_bin).expect("must be a standalone binary");
|
||||
|
||||
let result = Runtime::new()
|
||||
.with_args(args)
|
||||
.run("STANDALONE", meta.bytecode)
|
||||
.await;
|
||||
|
||||
Ok(match result {
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
Ok(code) => code,
|
||||
})
|
||||
}
|
10
src/main.rs
10
src/main.rs
|
@ -11,7 +11,7 @@
|
|||
use std::process::ExitCode;
|
||||
|
||||
pub(crate) mod cli;
|
||||
pub(crate) mod executor;
|
||||
pub(crate) mod standalone;
|
||||
|
||||
use cli::Cli;
|
||||
use console::style;
|
||||
|
@ -26,12 +26,8 @@ async fn main() -> ExitCode {
|
|||
.with_level(true)
|
||||
.init();
|
||||
|
||||
let (is_standalone, bin) = executor::check_env().await;
|
||||
|
||||
if is_standalone {
|
||||
// It's fine to unwrap here since we don't want to continue
|
||||
// if something fails
|
||||
return executor::run_standalone(bin).await.unwrap();
|
||||
if let Some(bin) = standalone::check().await {
|
||||
return standalone::run(bin).await.unwrap();
|
||||
}
|
||||
|
||||
match Cli::new().run().await {
|
||||
|
|
99
src/standalone/metadata.rs
Normal file
99
src/standalone/metadata.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use std::{env, path::PathBuf};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use mlua::Compiler as LuaCompiler;
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::fs;
|
||||
|
||||
const MAGIC: &[u8; 8] = b"cr3sc3nt";
|
||||
|
||||
static CURRENT_EXE: Lazy<PathBuf> =
|
||||
Lazy::new(|| env::current_exe().expect("failed to get current exe"));
|
||||
|
||||
/*
|
||||
TODO: Right now all we do is append the bytecode to the end
|
||||
of the binary, but we will need a more flexible solution in
|
||||
the future to store many files as well as their metadata.
|
||||
|
||||
The best solution here is most likely to use a well-supported
|
||||
and rust-native binary serialization format with a stable
|
||||
specification, one that also supports byte arrays well without
|
||||
overhead, so the best solution seems to currently be Postcard:
|
||||
|
||||
https://github.com/jamesmunns/postcard
|
||||
https://crates.io/crates/postcard
|
||||
*/
|
||||
|
||||
/**
|
||||
Metadata for a standalone Lune executable. Can be used to
|
||||
discover and load the bytecode contained in a standalone binary.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Metadata {
|
||||
pub bytecode: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/**
|
||||
Returns whether or not the currently executing Lune binary
|
||||
is a standalone binary, and if so, the bytes of the binary.
|
||||
*/
|
||||
pub async fn check_env() -> (bool, Vec<u8>) {
|
||||
let contents = fs::read(CURRENT_EXE.to_path_buf())
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let is_standalone = contents.ends_with(MAGIC);
|
||||
(is_standalone, contents)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a patched standalone binary from the given script contents.
|
||||
*/
|
||||
pub async fn create_env_patched_bin(script_contents: impl Into<Vec<u8>>) -> Result<Vec<u8>> {
|
||||
let mut patched_bin = fs::read(CURRENT_EXE.to_path_buf()).await?;
|
||||
|
||||
// Compile luau input into bytecode
|
||||
let bytecode = LuaCompiler::new()
|
||||
.set_optimization_level(2)
|
||||
.set_coverage_level(0)
|
||||
.set_debug_level(1)
|
||||
.compile(script_contents.into());
|
||||
|
||||
// Append the bytecode / metadata to the end
|
||||
let meta = Self { bytecode };
|
||||
patched_bin.extend_from_slice(&meta.to_bytes());
|
||||
|
||||
Ok(patched_bin)
|
||||
}
|
||||
|
||||
/**
|
||||
Tries to read a standalone binary from the given bytes.
|
||||
*/
|
||||
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
|
||||
let bytes = bytes.as_ref();
|
||||
if bytes.len() < 16 || !bytes.ends_with(MAGIC) {
|
||||
bail!("not a standalone binary")
|
||||
}
|
||||
|
||||
// Extract bytecode size
|
||||
let bytecode_size_bytes = &bytes[bytes.len() - 16..bytes.len() - 8];
|
||||
let bytecode_size =
|
||||
usize::try_from(u64::from_be_bytes(bytecode_size_bytes.try_into().unwrap()))?;
|
||||
|
||||
// Extract bytecode
|
||||
let bytecode = bytes[bytes.len() - 16 - bytecode_size..].to_vec();
|
||||
|
||||
Ok(Self { bytecode })
|
||||
}
|
||||
|
||||
/**
|
||||
Writes the metadata chunk to a byte vector, to later bet read using `from_bytes`.
|
||||
*/
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(&self.bytecode);
|
||||
bytes.extend_from_slice(&(self.bytecode.len() as u64).to_be_bytes());
|
||||
bytes.extend_from_slice(MAGIC);
|
||||
bytes
|
||||
}
|
||||
}
|
44
src/standalone/mod.rs
Normal file
44
src/standalone/mod.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use std::{env, process::ExitCode};
|
||||
|
||||
use anyhow::Result;
|
||||
use lune::Runtime;
|
||||
|
||||
pub(crate) mod metadata;
|
||||
pub(crate) mod tracer;
|
||||
|
||||
use self::metadata::Metadata;
|
||||
|
||||
/**
|
||||
Returns whether or not the currently executing Lune binary
|
||||
is a standalone binary, and if so, the bytes of the binary.
|
||||
*/
|
||||
pub async fn check() -> Option<Vec<u8>> {
|
||||
let (is_standalone, patched_bin) = Metadata::check_env().await;
|
||||
if is_standalone {
|
||||
Some(patched_bin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Discovers, loads and executes the bytecode contained in a standalone binary.
|
||||
*/
|
||||
pub async fn run(patched_bin: impl AsRef<[u8]>) -> Result<ExitCode> {
|
||||
// The first argument is the path to the current executable
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let meta = Metadata::from_bytes(patched_bin).expect("must be a standalone binary");
|
||||
|
||||
let result = Runtime::new()
|
||||
.with_args(args)
|
||||
.run("STANDALONE", meta.bytecode)
|
||||
.await;
|
||||
|
||||
Ok(match result {
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
Ok(code) => code,
|
||||
})
|
||||
}
|
14
src/standalone/tracer.rs
Normal file
14
src/standalone/tracer.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
TODO: Implement tracing of requires here
|
||||
|
||||
Rough steps / outline:
|
||||
|
||||
1. Create a new tracer struct using a main entrypoint script path
|
||||
2. Some kind of discovery mechanism that goes through all require chains (failing on recursive ones)
|
||||
2a. Conversion of script-relative paths to cwd-relative paths + normalization
|
||||
2b. Cache all found files in a map of file path -> file contents
|
||||
2c. Prepend some kind of symbol to paths that can tell our runtime `require` function that it
|
||||
should look up a bundled/standalone script, a good symbol here is probably a dollar sign ($)
|
||||
3. ???
|
||||
4. Profit
|
||||
*/
|
Loading…
Add table
Reference in a new issue