Skip to content

Commit 8a23f50

Browse files
authored
feat: implement missing store verbose/strict in compiled binary (#172)
* feat: add embedded config section to compiled binaries with verbose and strict mode settings * style: format code * fix: clippy error
1 parent 5312990 commit 8a23f50

File tree

2 files changed

+145
-11
lines changed

2 files changed

+145
-11
lines changed

cli/src/compile.rs

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,27 @@
44

55
use crate::error::{AndromedaError, Result, read_file_with_context};
66
use libsui::{Elf, Macho, PortableExecutable};
7+
use serde::{Deserialize, Serialize};
78
use std::{env::current_exe, fs::File, path::Path};
89

910
pub static ANDROMEDA_JS_CODE_SECTION: &str = "ANDROMEDABINCODE";
11+
pub static ANDROMEDA_CONFIG_SECTION: &str = "ANDROMEDACONFIG";
12+
13+
/// Configuration embedded in compiled binaries
14+
#[derive(Debug, Clone, Serialize, Deserialize)]
15+
pub struct EmbeddedConfig {
16+
pub verbose: bool,
17+
pub no_strict: bool,
18+
}
1019

1120
#[allow(clippy::result_large_err)]
1221
#[cfg_attr(feature = "hotpath", hotpath::measure)]
13-
pub fn compile(result_name: &Path, input_file: &Path) -> Result<()> {
22+
pub fn compile(
23+
result_name: &Path,
24+
input_file: &Path,
25+
verbose: bool,
26+
no_strict: bool,
27+
) -> Result<()> {
1428
// Validate input file exists and is readable
1529
if !input_file.exists() {
1630
return Err(AndromedaError::file_not_found(
@@ -45,6 +59,16 @@ pub fn compile(result_name: &Path, input_file: &Path) -> Result<()> {
4559

4660
let js = js_content.into_bytes();
4761

62+
// Create embedded config
63+
let config = EmbeddedConfig { verbose, no_strict };
64+
let config_json = serde_json::to_vec(&config).map_err(|e| {
65+
AndromedaError::config_error(
66+
"Failed to serialize embedded config".to_string(),
67+
None,
68+
Some(Box::new(e)),
69+
)
70+
})?;
71+
4872
// Validate output directory exists or can be created
4973
if let Some(parent) = result_name.parent()
5074
&& !parent.exists()
@@ -70,6 +94,7 @@ pub fn compile(result_name: &Path, input_file: &Path) -> Result<()> {
7094

7195
match os {
7296
"macos" => {
97+
// First pass: write JS code section
7398
Macho::from(exe)
7499
.map_err(|e| {
75100
AndromedaError::compile_error(
@@ -91,16 +116,54 @@ pub fn compile(result_name: &Path, input_file: &Path) -> Result<()> {
91116
.build_and_sign(&mut out)
92117
.map_err(|e| {
93118
AndromedaError::compile_error(
94-
"Failed to build and sign macOS executable".to_string(),
119+
"Failed to build and sign macOS executable (first pass)".to_string(),
120+
input_file.to_path_buf(),
121+
result_name.to_path_buf(),
122+
Some(Box::new(e)),
123+
)
124+
})?;
125+
126+
// Second pass: re-read and add config section
127+
let exe_with_js = std::fs::read(result_name)
128+
.map_err(|e| AndromedaError::file_read_error(result_name.to_path_buf(), e))?;
129+
let mut out = File::create(result_name).map_err(|e| {
130+
AndromedaError::permission_denied(
131+
format!("creating output file {}", result_name.display()),
132+
Some(result_name.to_path_buf()),
133+
e,
134+
)
135+
})?;
136+
Macho::from(exe_with_js)
137+
.map_err(|e| {
138+
AndromedaError::compile_error(
139+
"Failed to parse macOS executable (second pass)".to_string(),
140+
input_file.to_path_buf(),
141+
result_name.to_path_buf(),
142+
Some(Box::new(e)),
143+
)
144+
})?
145+
.write_section(ANDROMEDA_CONFIG_SECTION, config_json)
146+
.map_err(|e| {
147+
AndromedaError::compile_error(
148+
"Failed to write config section to macOS executable".to_string(),
149+
input_file.to_path_buf(),
150+
result_name.to_path_buf(),
151+
Some(Box::new(e)),
152+
)
153+
})?
154+
.build_and_sign(&mut out)
155+
.map_err(|e| {
156+
AndromedaError::compile_error(
157+
"Failed to build and sign macOS executable (second pass)".to_string(),
95158
input_file.to_path_buf(),
96159
result_name.to_path_buf(),
97160
Some(Box::new(e)),
98161
)
99162
})?;
100163
}
101164
"linux" => {
102-
Elf::new(&exe)
103-
.append(ANDROMEDA_JS_CODE_SECTION, &js, &mut out)
165+
let elf = Elf::new(&exe);
166+
elf.append(ANDROMEDA_JS_CODE_SECTION, &js, &mut out)
104167
.map_err(|e| {
105168
AndromedaError::compile_error(
106169
"Failed to append JavaScript section to Linux executable".to_string(),
@@ -109,6 +172,27 @@ pub fn compile(result_name: &Path, input_file: &Path) -> Result<()> {
109172
Some(Box::new(e)),
110173
)
111174
})?;
175+
// Note: libsui's Elf doesn't support multiple appends in sequence
176+
// We need to re-read the file and append the config section
177+
let exe_with_js = std::fs::read(result_name)
178+
.map_err(|e| AndromedaError::file_read_error(result_name.to_path_buf(), e))?;
179+
let mut out = File::create(result_name).map_err(|e| {
180+
AndromedaError::permission_denied(
181+
format!("creating output file {}", result_name.display()),
182+
Some(result_name.to_path_buf()),
183+
e,
184+
)
185+
})?;
186+
Elf::new(&exe_with_js)
187+
.append(ANDROMEDA_CONFIG_SECTION, &config_json, &mut out)
188+
.map_err(|e| {
189+
AndromedaError::compile_error(
190+
"Failed to append config section to Linux executable".to_string(),
191+
input_file.to_path_buf(),
192+
result_name.to_path_buf(),
193+
Some(Box::new(e)),
194+
)
195+
})?;
112196
}
113197
"windows" => {
114198
PortableExecutable::from(&exe)
@@ -129,6 +213,15 @@ pub fn compile(result_name: &Path, input_file: &Path) -> Result<()> {
129213
Some(Box::new(e)),
130214
)
131215
})?
216+
.write_resource(ANDROMEDA_CONFIG_SECTION, config_json)
217+
.map_err(|e| {
218+
AndromedaError::compile_error(
219+
"Failed to write config resource to Windows executable".to_string(),
220+
input_file.to_path_buf(),
221+
result_name.to_path_buf(),
222+
Some(Box::new(e)),
223+
)
224+
})?
132225
.build(&mut out)
133226
.map_err(|e| {
134227
AndromedaError::compile_error(

cli/src/main.rs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::path::PathBuf;
1313
mod bundle;
1414
use bundle::bundle;
1515
mod compile;
16-
use compile::{ANDROMEDA_JS_CODE_SECTION, compile};
16+
use compile::{ANDROMEDA_CONFIG_SECTION, ANDROMEDA_JS_CODE_SECTION, EmbeddedConfig, compile};
1717
mod repl;
1818
use repl::run_repl_with_config;
1919
mod run;
@@ -76,6 +76,14 @@ enum Command {
7676
// The output binary location
7777
#[arg(required = true)]
7878
out: PathBuf,
79+
80+
/// Enable verbose output in the compiled binary
81+
#[arg(short, long)]
82+
verbose: bool,
83+
84+
/// Disable strict mode in the compiled binary
85+
#[arg(short = 's', long)]
86+
no_strict: bool,
7987
},
8088

8189
/// Start an interactive REPL (Read-Eval-Print Loop)
@@ -220,10 +228,26 @@ fn main() {
220228
fn run_main() -> Result<()> {
221229
// Check if this is currently a single-file executable
222230
if let Ok(Some(js)) = find_section(ANDROMEDA_JS_CODE_SECTION) {
223-
// TODO: Store verbose and strict settings in a config section of the resultant binary
231+
// Try to load embedded config, fall back to defaults if not found
232+
let (verbose, no_strict) = match find_section(ANDROMEDA_CONFIG_SECTION) {
233+
Ok(Some(config_bytes)) => {
234+
match serde_json::from_slice::<EmbeddedConfig>(config_bytes) {
235+
Ok(config) => (config.verbose, config.no_strict),
236+
Err(_) => {
237+
// If config is corrupted or in old format, use defaults
238+
(false, false)
239+
}
240+
}
241+
}
242+
_ => {
243+
// No config section found (old binary format), use defaults
244+
(false, false)
245+
}
246+
};
247+
224248
return run(
225-
false,
226-
false,
249+
verbose,
250+
no_strict,
227251
vec![RuntimeFile::Embedded {
228252
path: String::from("internal"),
229253
content: js,
@@ -286,16 +310,33 @@ fn run_main() -> Result<()> {
286310
.collect();
287311
run(verbose, no_strict, runtime_files)
288312
}
289-
Command::Compile { path, out } => {
290-
compile(out.as_path(), path.as_path()).map_err(|e| {
313+
Command::Compile {
314+
path,
315+
out,
316+
verbose,
317+
no_strict,
318+
} => {
319+
compile(out.as_path(), path.as_path(), verbose, no_strict).map_err(|e| {
291320
error::AndromedaError::compile_error(
292321
format!("Compilation failed: {e}"),
293322
path.clone(),
294323
out.clone(),
295324
Some(e),
296325
)
297326
})?;
298-
println!("✅ Successfully created the output binary at {out:?}");
327+
let mut config_info = Vec::new();
328+
if verbose {
329+
config_info.push("verbose mode enabled");
330+
}
331+
if no_strict {
332+
config_info.push("strict mode disabled");
333+
}
334+
let config_str = if !config_info.is_empty() {
335+
format!(" ({})", config_info.join(", "))
336+
} else {
337+
String::new()
338+
};
339+
println!("✅ Successfully created the output binary at {out:?}{config_str}");
299340
Ok(())
300341
}
301342
Command::Repl {

0 commit comments

Comments
 (0)