From 35691f8d19f9cb37c7ddf94f91460a7d085a934a Mon Sep 17 00:00:00 2001 From: ded Date: Mon, 19 Oct 2026 20:11:14 +0300 Subject: [PATCH] G5: bench builds scenarios from an args file on Windows xtask bench passed the whole layers scenario on one command line, longer than Windows' 32 767-character limit (os error 206), so no scenario past demo ran. effectcraft-cli run takes --args-file F (one argument per line) and bench writes its commands there. Co-Authored-By: Claude Opus 5.5 --- apps/effectcraft-cli/src/main.rs | 12 ++++++++++-- apps/effectcraft-cli/tests/cli.rs | 23 +++++++++++++++++++++++ xtask/src/bench.rs | 15 +++++++++------ 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/apps/effectcraft-cli/src/main.rs b/apps/effectcraft-cli/src/main.rs index c7a012c7..6e5b8d6c 100644 --- a/apps/effectcraft-cli/src/main.rs +++ b/apps/effectcraft-cli/src/main.rs @@ -6,6 +6,7 @@ //! effectcraft-cli exec [--params JSON] run one command //! effectcraft-cli exec --list [--filter TEXT] [--schemas] list engine commands (as `commands`) //! effectcraft-cli run [ …] run several commands in order +//! effectcraft-cli run --args-file F the same, one argument per line of F //! effectcraft-cli props [--flat] [--time S] a layer's property tree (with paths) //! effectcraft-cli get [--time S] read a property //! effectcraft-cli set [--time S] [--expression E] @@ -96,6 +97,7 @@ const USAGE: &str = "usage: effectcraft-cli [--params JSON] run one engine command (exec --list: list them, as `commands`) run [ ...] run several commands in order + run --args-file F the same, one argument per line of F (no command-line length limit) props [--flat] [--time S] a layer's property tree with paths get [--time S] read a property set [--time S] [--expression E] @@ -148,6 +150,7 @@ const VALUED: &[&str] = &[ "--expression", "--depth", "--eval", + "--args-file", // render "--template", "--values", @@ -515,10 +518,15 @@ fn run(cmd: &str, args: &Args, json_out: bool) -> Result<(), Failure> { emit(&with_saved(r, saved), json_out); } "run" => { - if args.pos.is_empty() { + let mut pairs = args.pos.clone(); + // Windows caps a command line at 32 767 characters: long batches come from a file. + if let Some(f) = args.opt("--args-file") { + let text = std::fs::read_to_string(f).map_err(|e| Failure::Usage(format!("--args-file {f}: {e}")))?; + pairs.extend(text.lines().map(str::trim).filter(|l| !l.is_empty()).map(String::from)); + } + if pairs.is_empty() { return usage_err("run needs [] pairs"); } - let pairs = args.pos.clone(); let mut b = backend(args, true)?; let mut results = vec![]; let mut i = 0; diff --git a/apps/effectcraft-cli/tests/cli.rs b/apps/effectcraft-cli/tests/cli.rs index 1111db40..538887d9 100644 --- a/apps/effectcraft-cli/tests/cli.rs +++ b/apps/effectcraft-cli/tests/cli.rs @@ -110,6 +110,29 @@ fn exec_set_get_with_saved_project() { assert_eq!(v[1]["result"]["time"], v[0]["result"]["time"], "{v}"); } +/// `run --args-file` reads its command / params pairs from a file, one per line: a batch longer +/// than a Windows command line (32 767 characters, os error 206) still runs. +#[test] +fn run_reads_arguments_from_a_file() { + let proj = tmp("args-file.ecproj"); + let p = proj.to_str().unwrap(); + let mut lines = vec!["comp.new".to_string(), json!({"name": "A", "width": 64, "height": 64}).to_string()]; + for i in 0..400 { + lines.push("layer.newSolid".into()); + lines.push(json!({"width": 8, "height": 8, "name": format!("Solid with a long name to pass the limit {i:04}")}).to_string()); + lines.push(String::new()); + } + let text = lines.join("\n"); + assert!(text.len() > 32_767); + let file = tmp("args-file.txt"); + std::fs::write(&file, text).unwrap(); + let v = ok_json(&["run", "--args-file", file.to_str().unwrap(), "--empty", "--save-as", p]); + assert_eq!(v["result"].as_array().map(Vec::len), Some(401), "{v}"); + let out = bin().args(["run", "--args-file", "no/such/file.txt", "--empty"]).output().unwrap(); + assert_eq!(out.status.code(), Some(2)); + assert!(String::from_utf8_lossy(&out.stderr).contains("--args-file no/such/file.txt")); +} + #[test] fn render_frame_writes_png() { let out = tmp("f.png"); diff --git a/xtask/src/bench.rs b/xtask/src/bench.rs index b368d942..6c89672a 100644 --- a/xtask/src/bench.rs +++ b/xtask/src/bench.rs @@ -427,15 +427,18 @@ fn footage_cmds(seq_first: &Path, clip: &Path, still: &Path) -> Cmds { c } -/// Run `cmds` with `effectcraft-cli --empty run … --save-as out`. +/// Run `cmds` with `effectcraft-cli --empty run --args-file … --save-as out`: a file, because the +/// `layers` scenario is longer than a Windows command line (32 767 characters). fn build_project(bin: &Path, cmds: &Cmds, out: &Path) -> Result<(), String> { - let mut cmd = Command::new(bin); - cmd.arg("--empty").arg("run"); + let mut lines = String::new(); for (id, params) in cmds { - cmd.arg(id).arg(params.to_string()); + lines.push_str(&format!("{id}\n{params}\n")); } - cmd.arg("--save-as").arg(out); - cli(&mut cmd) + let args = out.with_extension("args"); + std::fs::write(&args, lines).map_err(|e| format!("{}: {e}", args.display()))?; + let r = cli(Command::new(bin).arg("--empty").arg("run").arg("--args-file").arg(&args).arg("--save-as").arg(out)); + let _ = std::fs::remove_file(&args); + r } /// Run a CLI command quietly; on failure, the tail of its output is the error.