fix: handle batch commands locally and require Java 21 in CI

Batch was broken because it sent a single "batch" command to the Java
bridge, which rejected it. Now the Rust CLI parses each line of the
batch file as a sub-command and dispatches them individually through
execute_via_bridge, collecting results into a JSON response.

Also updated CI to install Java 21 (required by Ghidra 12.0.1).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Kiselev
2026-02-05 13:28:08 -08:00
co-authored by Claude Opus 4.6
parent ae2d882f26
commit 57dbf62fd4
3 changed files with 26 additions and 10 deletions
+2 -2
View File
@@ -20,11 +20,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Java 17
- name: Install Java 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
java-version: '21'
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
-4
View File
@@ -359,10 +359,6 @@ impl BridgeClient {
self.send_command("script_list", None)
}
pub fn batch(&self, commands: &[serde_json::Value]) -> Result<serde_json::Value> {
self.send_command("batch", Some(json!({"commands": commands})))
}
pub fn program_close(&self) -> Result<serde_json::Value> {
self.send_command("close_program", None)
}
+24 -4
View File
@@ -803,15 +803,35 @@ fn execute_via_bridge(
}
Commands::Disasm(args) => client.disasm(&args.address, args.num_instructions),
Commands::Batch(args) => {
// Read batch file and send commands
// Read batch file and execute each command locally
let content = std::fs::read_to_string(&args.script_file)
.map_err(|e| anyhow::anyhow!("Failed to read batch file: {}", e))?;
let commands: Vec<serde_json::Value> = content
let lines: Vec<&str> = content
.lines()
.filter(|l| !l.trim().is_empty() && !l.trim().starts_with('#'))
.map(|l| serde_json::from_str(l).unwrap_or_else(|_| json!({"command": l.trim()})))
.collect();
client.batch(&commands)
let mut results = Vec::new();
for line in &lines {
let words: Vec<&str> = std::iter::once("ghidra")
.chain(line.split_whitespace())
.collect();
let sub_result = match Cli::try_parse_from(&words) {
Ok(sub_cli) => {
execute_via_bridge(client, &sub_cli.command, true, default_limit)
}
Err(e) => Err(anyhow::anyhow!("{}", e)),
};
match sub_result {
Ok(val) => results.push(json!({"command": line.trim(), "result": val})),
Err(e) => results.push(json!({"command": line.trim(), "error": e.to_string()})),
}
}
Ok(json!({
"commands_parsed": lines.len(),
"results": results
}))
}
Commands::Stats(_) => client.stats(),
_ => anyhow::bail!("Command not supported"),