Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions src/distrobox_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>, args: &[&str]) {
let mut child = match Command::new("distrobox")
.args(&arg_refs)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
Expand All @@ -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 || {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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";
Expand All @@ -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}"
);
}
}