diff --git a/src/distrobox_handler.rs b/src/distrobox_handler.rs index 22c6c82..96f084b 100644 --- a/src/distrobox_handler.rs +++ b/src/distrobox_handler.rs @@ -459,9 +459,26 @@ pub fn create_box_streaming( is_nvidia(), ); let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect(); + stream_distrobox(&tx, &arg_refs); + + // `distrobox create` only writes the container's configuration. The + // container is actually built the first time it is entered, which is when + // distrobox prints "Starting container", "Installing basic packages" and + // the rest of the setup. That used to scroll past in a terminal we opened + // afterwards; trigger it here with a no-op enter instead, so the same + // dialog shows the setup too. + stream_distrobox(&tx, &["enter", box_name, "--", "true"]); +} +/// Spawns one `distrobox` invocation and forwards every line of its stdout and +/// stderr to `tx` as it arrives, ending each stream with an empty line so the +/// dialog can tell a stream has finished. Returns once the process exits. +/// +/// stdout and stderr are read on their own threads and interleaved on purpose: +/// distrobox writes progress to both and the order is not meaningful. +fn stream_distrobox(tx: &Sender, args: &[&str]) { let mut child = match Command::new("distrobox") - .args(&arg_refs) + .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() @@ -479,10 +496,6 @@ pub fn create_box_streaming( let stdout = child.stdout.take(); let stderr = child.stderr.take(); - // Spawn a thread per stream. Each thread reads line-by-line and forwards - // to the same channel; this lets us stream both streams concurrently - // without ordering them on purpose (distrobox writes progress messages - // to both, and the order is not meaningful to the user). let tx_out = tx.clone(); let stdout_handle = stdout.map(|s| { std::thread::spawn(move || { @@ -511,9 +524,8 @@ pub fn create_box_streaming( }) }); - // Wait for the process. We don't need the exit status here - the - // completion of the two stream threads is enough to know the command - // has finished writing output. + // Wait for the process. We don't need the exit status here - the two + // stream threads finishing is enough to know it has stopped writing. let _ = child.wait(); if let Some(h) = stdout_handle { @@ -911,7 +923,7 @@ mod stream_tests { #[test] #[ignore] fn streaming_create_emits_lines_and_makes_a_box() { - use super::{delete_box, get_all_distroboxes}; + use super::{delete_box, get_all_distroboxes, get_command_output}; use std::sync::mpsc::channel; let name = "bb-stream-selftest"; @@ -933,7 +945,17 @@ mod stream_tests { assert!(non_empty > 0, "expected some output lines, got none"); let exists = get_all_distroboxes().iter().any(|b| b.name == name); + + // The streamed run now includes the first-enter setup, so by the time it + // returns the container must be built and usable - a plain enter should + // run a command and come straight back, without doing setup again. + let ready = get_command_output("distrobox", Some(&["enter", name, "--", "echo", "READY"])); + let _ = delete_box(name); assert!(exists, "streaming create did not produce a listable box"); + assert!( + ready.contains("READY"), + "box was not set up and ready after streaming create, got: {ready}" + ); } }