feat: proper args support for standalone binaries

This commit is contained in:
Erica Marigold 2023-11-23 12:25:33 +05:30
parent cf2f93d480
commit 2af8ed3b9f
No known key found for this signature in database
GPG key ID: 2768CC0C23D245D1
3 changed files with 97 additions and 65 deletions

View file

@ -60,4 +60,4 @@ for rowIndex, row in csvTable do
print(string.format("┣%s┫", thiccLine)) print(string.format("┣%s┫", thiccLine))
end end
end end
print(string.format("┗%s┛", thiccLine)) print(string.format("┗%s┛", thiccLine))

View file

@ -44,17 +44,17 @@ pub async fn build_standalone<T: AsRef<Path> + Into<PathBuf>>(
patched_bin.append(&mut signature.clone()); patched_bin.append(&mut signature.clone());
// Write the compiled binary to file // Write the compiled binary to file
#[cfg(not(target_os = "windows"))] #[cfg(target_family = "unix")]
OpenOptions::new() OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.mode(0o770) .mode(0o770) // read, write and execute permissions for user and group
.open(&output_path) .open(&output_path)
.await? .await?
.write_all(&patched_bin) .write_all(&patched_bin)
.await?; .await?;
#[cfg(target_os = "windows")] #[cfg(target_family = "windows")]
fs::write(&output_path, &patched_bin).await?; fs::write(&output_path, &patched_bin).await?;
Ok(ExitCode::SUCCESS) Ok(ExitCode::SUCCESS)

View file

@ -92,6 +92,99 @@ impl Cli {
let is_standalone = bin[bin.len() - signature.len()..bin.len()] == signature; let is_standalone = bin[bin.len() - signature.len()..bin.len()] == signature;
if is_standalone {
let mut bytecode_offset = 0;
let mut bytecode_size = 0;
// standalone binary structure (reversed, 8 bytes per field)
// [0] => signature
// ----------------
// -- META Chunk --
// [1] => file count
// [2] => bytecode size
// [3] => bytecode offset
// ----------------
// -- MISC Chunk --
// [4..n] => bytecode (variable size)
// ----------------
// NOTE: All integers are 8 byte unsigned 64 bit (u64's).
// The rchunks will have unequally sized sections in the beginning
// but that doesn't matter to us because we don't need anything past the
// middle chunks where the bytecode is stored
for (idx, chunk) in bin.rchunks(signature.len()).enumerate() {
if idx == 0 && chunk != signature {
// Binary is guaranteed to be standalone, we've confirmed this before
unreachable!()
}
if idx == 3 {
bytecode_offset = u64::from_ne_bytes(chunk.try_into()?);
}
if idx == 2 {
bytecode_size = u64::from_ne_bytes(chunk.try_into()?);
}
}
// If we were able to retrieve the required metadata, we load
// and execute the bytecode
if bytecode_offset != 0 && bytecode_size != 0 {
// FIXME: Passing arguments does not work like it should, because the first
// argument provided is treated as the script path. We should probably also not
// allow any runner functionality within standalone binaries
let mut reserved_args = Vec::new();
macro_rules! include_reserved_args {
($($arg_bool:expr=> $mapping:literal),*) => {
$(
if $arg_bool {
reserved_args.push($mapping.to_string())
}
)*
};
}
let mut real_args = Vec::new();
if let Some(first_arg) = self.script_path {
println!("{first_arg}");
real_args.push(first_arg);
}
include_reserved_args! {
self.setup => "--setup",
self.generate_docs_file => "--generate-docs-file",
self.generate_selene_types => "--generate-selene-types",
self.generate_luau_types => "--generate-luau-types",
self.list => "--list",
self.build => "--build"
}
real_args.append(&mut reserved_args);
real_args.append(&mut self.script_args.clone());
let result = Lune::new()
.with_args(real_args) // TODO: args should also include lune reserved ones
.run(
"STANDALONE",
&bin[usize::try_from(bytecode_offset)?
..usize::try_from(bytecode_offset + bytecode_size)?],
)
.await;
return Ok(match result {
Err(err) => {
eprintln!("{err}");
ExitCode::FAILURE
}
Ok(code) => code,
});
}
}
// List files in `lune` and `.lune` directories, if wanted // List files in `lune` and `.lune` directories, if wanted
// This will also exit early and not run anything else // This will also exit early and not run anything else
if self.list && !is_standalone { if self.list && !is_standalone {
@ -157,67 +250,6 @@ impl Cli {
return Ok(ExitCode::SUCCESS); return Ok(ExitCode::SUCCESS);
} }
if is_standalone {
let mut bytecode_offset = 0;
let mut bytecode_size = 0;
// standalone binary structure (reversed, 8 bytes per field)
// [0] => signature
// ----------------
// -- META Chunk --
// [1] => file count
// [2] => bytecode size
// [3] => bytecode offset
// ----------------
// -- MISC Chunk --
// [4..n] => bytecode (variable size)
// ----------------
// NOTE: All integers are 8 byte unsigned 64 bit (u64's).
// The rchunks will have unequally sized sections in the beginning
// but that doesn't matter to us because we don't need anything past the
// middle chunks where the bytecode is stored
for (idx, chunk) in bin.rchunks(signature.len()).enumerate() {
if idx == 0 && chunk != signature {
// We don't have a standalone binary
break;
}
if idx == 3 {
bytecode_offset = u64::from_ne_bytes(chunk.try_into()?);
}
if idx == 2 {
bytecode_size = u64::from_ne_bytes(chunk.try_into()?);
}
}
// If we were able to retrieve the required metadata, we load
// and execute the bytecode
if bytecode_offset != 0 && bytecode_size != 0 {
// FIXME: Passing arguments does not work like it should, because the first
// argument provided is treated as the script path. We should probably also not
// allow any runner functionality within standalone binaries
let result = Lune::new()
.with_args(self.script_args.clone()) // TODO: args should also include lune reserved ones
.run(
"STANDALONE",
&bin[usize::try_from(bytecode_offset).unwrap()
..usize::try_from(bytecode_offset + bytecode_size).unwrap()],
)
.await;
return Ok(match result {
Err(err) => {
eprintln!("{err}");
ExitCode::FAILURE
}
Ok(code) => code,
});
}
}
// If not in a standalone context and we don't have any arguments // If not in a standalone context and we don't have any arguments
// display the interactive REPL interface // display the interactive REPL interface
return repl::show_interface().await; return repl::show_interface().await;