Skip to main content

pistonite_cu/process/
builder.rs

1use std::ffi::OsStr;
2use std::path::{Path, PathBuf};
3use std::process::ExitStatus;
4
5use tokio::process::{Child as TokioChild, Command as TokioCommand};
6
7use super::{Child, ChildIo, Config, Preset};
8
9use crate::{Context as _, co, pio, str::PathExtension as _};
10
11/// A [`Command`] to be built
12pub type CommandBuilder = Command<(), (), ()>;
13
14/// Builder for spawning a child process.
15///
16/// You can use the `command()` function on `Path` or `PathBuf`
17/// to create a `CommandBuilder`.
18///
19/// ```rust,no_run
20/// # use pistonite_cu as cu;
21/// use std::path::Path;
22///
23/// use cu::pre::*;
24///
25/// # fn main() -> cu::Result<()> {
26/// let path = Path::new("ls");
27/// path.command().all_null().wait_nz()?;
28/// # Ok(()) }
29/// ```
30///
31/// # Configuration
32/// Configuring a `cu::Command` is much like configuring `Command`
33/// from `std` or `tokio`. Notable features are:
34/// - Use [`cu::args`](crate::args) and [`cu::envs`](crate::envs) macro
35///   to enable adding multiple args or envs of different type in the same call.
36///   Without the macro, `args`/`envs` only accept iterators that yield the same types.
37/// - [`current_dir()`](Self::current_dir) will be normalized based on current process, before the child is spawned.
38///   `std` recommends this, but not enforced. Here it's always done if set
39/// - [`name()`](Self::name) sets an identifier for the process to be passed to IO handlers.
40///   See below for more info about IO.
41///
42/// # Input-Output (IO)
43/// IO is the most important part of dealing with child processes. After all, you would
44/// want some information out of the process that you spawned.
45///
46/// Unlike `Command` in the standard library or `tokio`, `CommandBuilder` does not have
47/// "default" IO behavior. You cannot spawn the child until you configured how you'd like
48/// `stdout`, `stderr`, and `stdin` to behave.
49///
50/// See [`pio`] (Process IO) for configuration IO.
51///
52/// # Spawning
53/// See [`spawn`](Self::spawn) and [`co_spawn`](Self::co_spawn).
54///
55pub struct Command<Out, Err, In> {
56    /// Inner command
57    command: TokioCommand,
58    /// buffered current_dir to be set on the command before spawning
59    current_dir: Option<PathBuf>,
60    /// Name which maybe used by IO to print
61    name: Option<String>,
62    stdout: Out,
63    stderr: Err,
64    stdin: In,
65}
66
67impl CommandBuilder {
68    /// Start building a command. If the arg is a `Path` or `PathBuf`,
69    /// you can also call `.command()` on it (remember to import prelude)
70    ///
71    /// ```rust
72    /// # use pistonite_cu as cu;
73    /// use cu::pre::*;
74    ///
75    /// # fn main() -> cu::Result<()> {
76    /// let command = cu::CommandBuilder::new("git");
77    /// // or:
78    /// let command = std::path::Path::new("git").command();
79    /// // even better, find the executable in PATH using the crate's
80    /// // binary registry
81    /// let command = cu::which("git")?.command();
82    /// # Ok(()) }
83    /// ```
84    pub fn new(bin: impl AsRef<OsStr>) -> Self {
85        Self {
86            command: TokioCommand::new(bin),
87            name: None,
88            current_dir: None,
89            stdout: (),
90            stderr: (),
91            stdin: (),
92        }
93    }
94}
95
96// expose functions from tokio/std
97#[rustfmt::skip]
98impl<Out, Err, In> Command<Out, Err, In> {
99    /// Add one argument
100    ///
101    /// Use [`Self::args`] or [`args`](crate::args) macro to add more than one arguments
102    /// in one call.
103    #[inline(always)]
104    pub fn arg(mut self, arg: impl AsRef<OsStr>) -> Self { self.command.arg(arg); self }
105
106    /// Add multiple arguments
107    ///
108    /// This only accepts iterators of a single type, which forces
109    /// you to call `.as_ref()` if the input has multiple types.
110    /// To workaround this, use the [`args`](crate::args) shorthand.
111    #[inline(always)]
112    pub fn args<I: IntoIterator<Item=S>, S:AsRef<OsStr>>(mut self, args: I) -> Self { self.command.args(args); self }
113
114
115    /// Clear the environment variables, which will prevent
116    /// inheriting environment variables from the parent (this process)
117    #[inline(always)]
118    pub fn env_clear(mut self) -> Self { self.command.env_clear(); self }
119
120    /// Remove an environment variable
121    #[inline(always)]
122    pub fn env_remove(mut self, env: impl AsRef<OsStr>) -> Self { self.command.env_remove(env); self }
123
124    /// Set a single environment variable
125    #[inline(always)]
126    pub fn env(mut self, k: impl AsRef<OsStr>, v: impl AsRef<OsStr>) -> Self { self.command.env(k, v); self }
127
128    /// Set multiple environment variables
129    ///
130    /// This only accepts iterators of a single type, which forces
131    /// you to call `.as_ref()` if the input has multiple types.
132    /// To workaround this, use the [`envs`](crate::envs) macro shorthand.
133    #[inline(always)]
134    pub fn envs<I: IntoIterator<Item=(K,V)>,K:AsRef<OsStr>,V:AsRef<OsStr>>(mut self, envs: I) -> Self { self.command.envs(envs); self }
135
136    /// Add more configuration. See [`args!`](crate::args) and [`envs!`](crate::envs).
137    #[inline(always)]
138    #[allow(clippy::should_implement_trait)]
139    pub fn add(mut self, config: impl Config) -> Self {
140        config.configure(&mut self.command);
141        self
142    }
143
144    /// Set the current working directory for the child process.
145    ///
146    /// Unlike `std`/`tokio` implementation, where canonicalizing the current
147    /// dir is recommended, we always canonicalize the input here based on this
148    /// process before spawning the child.
149    #[inline(always)]
150    pub fn current_dir(mut self, dir: impl Into<PathBuf>) -> Self {
151        self.current_dir = Some(dir.into());
152        self
153    }
154
155    /// Set the name of this command, which maybe used by output config
156    /// to print in the terminal
157    #[inline(always)]
158    pub fn name(mut self, name: impl Into<String>) -> Self {
159        self.name = Some(name.into());
160        self
161    }
162
163    /// Configure child's standard input stream
164    #[inline(always)]
165    pub fn stdin<T: pio::ChildInConfig>(self, config:T) -> Command<Out, Err, T> {
166        Command {
167            command: self.command,
168            current_dir: self.current_dir,
169            name: self.name,
170            stdout: self.stdout,
171            stderr: self.stderr,
172            stdin: config,
173        }
174    }
175
176    /// Configure child's standard input stream as [`pio::null()`]
177    #[inline(always)]
178    pub fn stdin_null(self) -> Command<Out, Err, pio::Null> {
179        self.stdin(pio::null())
180    }
181
182    /// Configure child's standard input stream to inherit from parent ([`pio::inherit()`])
183    #[inline(always)]
184    pub fn stdin_inherit(self) -> Command<Out, Err, pio::Inherit> {
185        self.stdin(pio::inherit())
186    }
187
188    /// Configure child's standard output stream
189    #[inline(always)]
190    pub fn stdout<T: pio::ChildOutConfig>(self, config: T) -> Command<T, Err, In> {
191        Command {
192            command: self.command,
193            current_dir: self.current_dir,
194            name: self.name,
195            stdout: config,
196            stderr: self.stderr,
197            stdin: self.stdin
198        }
199    }
200
201    /// Configure child's standard output stream to inherit from parent ([`pio::inherit()`])
202    #[inline(always)]
203    pub fn stdout_inherit(self) -> Command<pio::Inherit, Err, In> {
204        self.stdout(pio::inherit())
205    }
206
207    /// Configure child's standard error stream
208    #[inline(always)]
209    pub fn stderr<T: pio::ChildOutConfig>(self, config: T) -> Command<Out, T, In> {
210        Command {
211            command: self.command,
212            current_dir: self.current_dir,
213            name: self.name,
214            stdout: self.stdout,
215            stderr: config,
216            stdin: self.stdin
217        }
218    }
219
220    /// Configure child's standard output stream to inherit from parent ([`pio::inherit()`])
221    #[inline(always)]
222    pub fn stderr_inherit(self) -> Command<Out, pio::Inherit, In> {
223        self.stderr(pio::inherit())
224    }
225
226    /// Configure child's both standard output and standard error with the same config
227    #[inline(always)]
228    pub fn stdoe<T: pio::ChildOutConfig + Clone>(self, config: T) -> Command<T, T, In> {
229        Command {
230            command: self.command,
231            current_dir: self.current_dir,
232            name: self.name,
233            stdout: config.clone(),
234            stderr: config,
235            stdin: self.stdin
236        }
237    }
238
239    /// Configure child's standard output stream as [`pio::null()`]
240    #[inline(always)]
241    pub fn stdout_null(self) -> Command<pio::Null, Err, In> {
242        self.stdout(pio::null())
243    }
244
245    /// Configure child's standard error stream as [`pio::null()`]
246    #[inline(always)]
247    pub fn stderr_null(self) -> Command<Out, pio::Null, In> {
248        self.stderr(pio::null())
249    }
250
251    /// Configure both child's standard output and error streams as [`pio::null()`]
252    #[inline(always)]
253    pub fn stdoe_null(self) -> Command<pio::Null, pio::Null, In> {
254        self.stdoe(pio::null())
255    }
256
257    /// Configure both child's standard input and error streams as [`pio::null()`]
258    #[inline(always)]
259    pub fn stdie_null(self) -> Command<Out, pio::Null, pio::Null> {
260        self.stdin_null().stderr_null()
261    }
262
263    /// Configure both child's standard input and output streams as [`pio::null()`]
264    #[inline(always)]
265    pub fn stdio_null(self) -> Command<pio::Null, Err, pio::Null> {
266        self.stdin_null().stdout_null()
267    }
268
269    /// Configure all standard input, output and error streams as [`pio::null()`]
270    #[inline(always)]
271    pub fn all_null(self) -> Command<pio::Null, pio::Null, pio::Null> {
272        self.stdin_null().stdout_null().stderr_null()
273    }
274
275    /// Configure all standard input, output and error streams as [`pio::inherit()`]
276    #[inline(always)]
277    pub fn all_inherit(self) -> Command<pio::Inherit, pio::Inherit, pio::Inherit> {
278        self.stdin_inherit().stdout_inherit().stderr_inherit()
279    }
280
281    /// Apply a preset for the child command's input and output.
282    ///
283    /// See [`pio`] for available IO presets.
284    #[inline(always)]
285    pub fn preset<P: Preset>(self, preset: P) -> P::Output {
286        preset.configure(self)
287    }
288
289    /// Spawn the child and run the child's IO tasks in the background. **Note that
290    /// you have to configure all 3 of `stdin`, `stdout` and `stderr`, before you can spawn the
291    /// child. Otherwise, you will get a compile error that `spawn` is not defined**
292    ///
293    /// # Blocking
294    /// Use [`co_spawn`](Self::co_spawn) if you are within an async context.
295    ///
296    /// # Return Value
297    /// The return type shown above is a placeholder. The actual return
298    /// value depends on the configuration for `stdout` and `stderr`,
299    /// specifically, whether they have a handle to access the output.
300    /// For example [`pio::inherit()`] or [`pio::spinner()`] don't
301    /// have handles, because the output is directly printed,
302    /// whereas [`pio::pipe()`] or [`pio::string()`] has handles for reading the output.
303    ///
304    /// - If both `stdout` and `stderr` don't have handle, then the output is [`Child`]
305    /// ```rust
306    /// # use pistonite_cu as cu;
307    /// use cu::pre::*; // The prelude import is required for `spawn`
308    ///
309    /// # fn main() -> cu::Result<()> {
310    /// let child = cu::which("echo")?.command()
311    ///    .arg("Hello, world!")
312    ///    .stdout(cu::lv::I) // print stdout as info level log messages
313    ///    .stdie_null()
314    ///    .spawn()?;
315    /// # Ok(()) }
316    /// ```
317    /// - If only `stdout` has a handle but not `stderr`, then the output is a 2-tuple ([`Child`],
318    ///   `Stdout), where `Stdout` is the respective handle type for the configured output.
319    /// ```rust
320    /// # use pistonite_cu as cu;
321    /// use cu::pre::*; // The prelude import is required for `spawn`
322    ///
323    /// # fn main() -> cu::Result<()> {
324    /// let (child, lines) = cu::which("bash")?.command()
325    ///    .args(["-c", r#"for i in {1..5}; do echo "Line $i"; sleep 1; done"# ])
326    ///    .stdout(cu::pio::lines())
327    ///    .stdie_null()
328    ///    .spawn()?;
329    /// # Ok(()) }
330    /// ```
331    /// - Otherwise, the output is a 3-tuple ([`Child`], `Stdout`, `Stderr`),
332    ///   where `Stdout` and `Stderr` are the respective handle type for the output configured,
333    ///   and `Stdout` might be `()` if it does not have a handle
334    /// ```rust
335    /// # use pistonite_cu as cu;
336    /// use cu::pre::*; // The prelude import is required for `spawn`
337    ///
338    /// # fn main() -> cu::Result<()> {
339    /// let (child, lines, err) = cu::which("bash")?.command()
340    ///    .args(["-c", r#"for i in {1..5}; do echo "Line $i"; sleep 1; done"# ])
341    ///    .stdout(cu::pio::lines())
342    ///    .stderr(cu::pio::string())
343    ///    .stdin_null()
344    ///    .spawn()?;
345    /// # Ok(()) }
346    /// ```
347    #[cfg(doc)]
348    pub fn spawn(self) -> crate::Result<EitherOf<Child, (Child, Out, Err)>> {
349        panic!("this is a placeholder for documetnation, see below for implementation")
350    }
351
352
353    /// Spawn the child and run the child's IO tasks in the background. **Note that
354    /// you have to configure all 3 of `stdin`, `stdout` and `stderr`, before you can spawn the
355    /// child. Otherwise, you will get a compile error that `co_spawn` is not defined**
356    ///
357    /// Note that the child's IO are still drived by the background context.
358    /// This is to prevent errors where blocking the current-thread runtime unexpectedly
359    /// block the child's IO, even when the child's IO tasks are running on the background
360    /// thread.
361    ///
362    /// # Return value
363    /// See [`spawn`](Self::spawn)
364    #[cfg(doc)]
365    pub async fn co_spawn(self) -> crate::Result<EitherOf<Child, (Child, Out, Err)>> {
366        panic!("this is a placeholder for documetnation, see below for implementation")
367    }
368}
369#[cfg(doc)]
370struct EitherOf<A, B>(A, B);
371
372/// **Implementation only applies if the child has no output handles (see [`spawn`](Command::spawn) for more
373/// details)**
374impl<
375    Out: pio::ChildOutConfig<__Null = pio::__OCNull>,
376    Err: pio::ChildOutConfig<__Null = pio::__OCNull>,
377    In: pio::ChildInConfig,
378> Command<Out, Err, In>
379{
380    /// Spawn and wait for child to exit with non-zero status code.
381    ///
382    /// This is equivalent to calling [`spawn()`](Self::spawn), then
383    /// [`wait_nz()`](Child::wait_nz) on the [`Child`].
384    #[inline(always)]
385    pub fn wait_nz(self) -> crate::Result<()> {
386        self.spawn()?.wait_nz()
387    }
388    /// Spawn and wait for child to exit, returning its [`ExitStatus`] code
389    ///
390    /// This is equivalent to calling [`spawn()`](Self::spawn), then
391    /// [`wait()`](Child::wait) on the [`Child`].
392    #[inline(always)]
393    pub fn wait(self) -> crate::Result<ExitStatus> {
394        self.spawn()?.wait()
395    }
396
397    /// Spawn and wait for child to exit with non-zero status code, using
398    /// the current tokio runtime context.
399    ///
400    /// This is equivalent to calling [`co_spawn()`](Self::co_spawn), then
401    /// [`co_wait_nz()`](Child::co_wait_nz) on the [`Child`].
402    #[inline(always)]
403    pub async fn co_wait_nz(self) -> crate::Result<()> {
404        self.co_spawn().await?.co_wait_nz().await
405    }
406    /// Spawn and wait for child to exit, returning its [`ExitStatus`] code,
407    /// using the current tokio runtime context.
408    ///
409    /// This is equivalent to calling [`co_spawn()`](Self::co_spawn), then
410    /// [`co_wait()`](Child::co_wait) on the [`Child`].
411    #[inline(always)]
412    pub async fn co_wait(self) -> crate::Result<ExitStatus> {
413        self.co_spawn().await?.co_wait().await
414    }
415}
416
417/// This trait allows implementing different return types for [`Command::spawn`] based on the configured IO.
418pub trait Spawn<Target>
419where
420    Target: Send + 'static,
421{
422    fn spawn(self) -> crate::Result<Target>;
423    fn co_spawn(self) -> crate::BoxedFuture<crate::Result<Target>>;
424}
425
426#[cfg(not(doc))]
427macro_rules! Spawned {
428    () => {
429        $crate::Child
430    };
431    (Out) => {
432        (
433            $crate::Child,
434            <Out::Task as $crate::process::pio::ChildOutTask>::Output,
435        )
436    };
437    ($A:ident, $B:ident) => {
438        (
439            $crate::Child,
440            <$A::Task as $crate::process::pio::ChildOutTask>::Output,
441            <$B::Task as $crate::process::pio::ChildOutTask>::Output,
442        )
443    };
444}
445
446#[rustfmt::skip]
447#[cfg(not(doc))]
448impl< Out: pio::ChildOutConfig<__Null=pio::__OCNull>, Err: pio::ChildOutConfig<__Null=pio::__OCNull>, In: pio::ChildInConfig> Spawn<Spawned![]> for Command<Out, Err, In> {
449    #[inline(always)]
450    fn spawn(self) -> crate::Result<Spawned![]> {
451        spawn_internal(self).map(|x| x.0)
452    }
453    #[inline(always)]
454    fn co_spawn(self) -> crate::BoxedFuture<crate::Result<Spawned![]>> {
455        Box::pin(async move {
456            co_spawn_internal(self).await.map(|x| x.0)
457        })
458    }
459}
460
461#[rustfmt::skip]
462#[cfg(not(doc))]
463impl< Out: pio::ChildOutConfig<__Null=pio::__OCNonNull>, Err: pio::ChildOutConfig<__Null=pio::__OCNull>, In: pio::ChildInConfig> Spawn<Spawned![Out]> for Command<Out, Err, In> {
464    #[inline(always)]
465    fn spawn(self) -> crate::Result<Spawned![Out]> {
466        spawn_internal(self).map(|(c,o,_)| (c,o))
467    }
468    #[inline(always)]
469    fn co_spawn(self) -> crate::BoxedFuture<crate::Result<Spawned![Out]>> {
470        Box::pin(async move {
471            co_spawn_internal(self).await.map(|(c,o,_)| (c,o))
472        })
473    }
474}
475
476#[rustfmt::skip]
477#[cfg(not(doc))]
478impl< Out: pio::ChildOutConfig, Err: pio::ChildOutConfig<__Null=pio::__OCNonNull>, In: pio::ChildInConfig> Spawn<Spawned![Out, Err]> for Command<Out, Err, In> {
479    #[inline(always)]
480    fn spawn(self) -> crate::Result<Spawned![Out, Err]> {
481        spawn_internal(self)
482    }
483    #[inline(always)]
484    fn co_spawn(self) -> crate::BoxedFuture<crate::Result<Spawned![Out, Err]>> {
485        Box::pin(co_spawn_internal(self))
486    }
487}
488
489/// handle the actual spawning
490#[allow(clippy::type_complexity)]
491fn spawn_internal<Out: pio::ChildOutConfig, Err: pio::ChildOutConfig, In: pio::ChildInConfig>(
492    mut self_: Command<Out, Err, In>,
493) -> crate::Result<(
494    Child,
495    <Out::Task as pio::ChildOutTask>::Output,
496    <Err::Task as pio::ChildOutTask>::Output,
497)> {
498    pre_spawn(&mut self_)?;
499
500    // self.command.spawn() must be called on the background runtime,
501    // because the IO will be attached to the active runtime context
502    // if we call .spawn() on the current-thread runtime, then blocking
503    // the current-thread runtime will also block the child's IO
504    co::spawn(async move {
505        let child = self_.command.spawn().context("failed to spawn command")?;
506        post_spawn(self_, child)
507    })
508    .join()?
509}
510/// handle the actual spawning
511#[allow(clippy::type_complexity)]
512async fn co_spawn_internal<
513    Out: pio::ChildOutConfig,
514    Err: pio::ChildOutConfig,
515    In: pio::ChildInConfig,
516>(
517    mut self_: Command<Out, Err, In>,
518) -> crate::Result<(
519    Child,
520    <Out::Task as pio::ChildOutTask>::Output,
521    <Err::Task as pio::ChildOutTask>::Output,
522)> {
523    pre_spawn(&mut self_)?;
524
525    // self.command.spawn() must be called on the background runtime,
526    // because the IO will be attached to the active runtime context
527    // if we call .spawn() on the current-thread runtime, then blocking
528    // the current-thread runtime will also block the child's IO
529    co::spawn(async move {
530        let child = self_.command.spawn().context("failed to spawn command")?;
531        post_spawn(self_, child)
532    })
533    .co_join()
534    .await?
535}
536
537fn pre_spawn<Out: pio::ChildOutConfig, Err: pio::ChildOutConfig, In: pio::ChildInConfig>(
538    self_: &mut Command<Out, Err, In>,
539) -> crate::Result<()> {
540    use std::fmt::Write as _;
541    let mut trace = String::new();
542
543    // build the trace message
544    let log_enabled = crate::lv::T.enabled();
545    if log_enabled {
546        let command = self_.command.as_std();
547        let _ = write!(
548            &mut trace,
549            "spawning '{}', args: [",
550            command.get_program().display()
551        );
552        let mut args = command.get_args();
553        if let Some(a) = args.next() {
554            let arg = a.display().to_string().replace('\'', "\\'");
555            let _ = write!(&mut trace, "'{arg}'");
556        }
557        for arg in args {
558            let arg = arg.display().to_string().replace('\'', "\\'");
559            let _ = write!(&mut trace, ", '{arg}'");
560        }
561        let _ = write!(&mut trace, "]");
562    }
563
564    // configure final things on the Command
565    if let Some(cd) = &self_.current_dir {
566        let cd = cd.normalize_exists().with_context(|| {
567            if log_enabled {
568                crate::trace!("error while {trace}");
569            }
570            crate::error!("cannot canonicalize current_dir: {}", cd.display());
571            "cannot canonicalize current_dir while spawning child"
572        })?;
573        if log_enabled {
574            let _ = write!(&mut trace, ", current_dir: '{}'", cd.display());
575        }
576        self_.command.current_dir(cd);
577    }
578
579    if log_enabled {
580        match &self_.name {
581            Some(name) => crate::trace!("[{name}] {trace}"),
582            _ => crate::trace!("{trace}"),
583        }
584    }
585    // configure IO
586    self_.stdout.configure_stdout(&mut self_.command);
587    self_.stderr.configure_stderr(&mut self_.command);
588    self_
589        .stdin
590        .configure_stdin(&mut self_.command)
591        .context("failed to configure child stdin")?;
592    Ok(())
593}
594
595#[allow(clippy::type_complexity)]
596fn post_spawn<Out: pio::ChildOutConfig, Err: pio::ChildOutConfig, In: pio::ChildInConfig>(
597    self_: Command<Out, Err, In>,
598    mut child: TokioChild,
599) -> crate::Result<(
600    Child,
601    <Out::Task as pio::ChildOutTask>::Output,
602    <Err::Task as pio::ChildOutTask>::Output,
603)> {
604    let name = self_.name;
605
606    let stdout = self_
607        .stdout
608        .take(&mut child, name.as_deref(), true)
609        .context("failed to take child stdout")?;
610    let stderr = self_
611        .stderr
612        .take(&mut child, name.as_deref(), false)
613        .context("failed to take child stderr")?;
614    let stdin = self_
615        .stdin
616        .take(&mut child)
617        .context("failed to take child stdin")?;
618
619    let name = match name {
620        Some(x) => format!("[{x}]"),
621        None => {
622            let command = self_.command.as_std();
623            let program = Path::new(command.get_program())
624                .file_name()
625                .map(|x| x.display().to_string());
626            match program {
627                Some(x) => format!("program '{x}'"),
628                None => "child process".to_string(),
629            }
630        }
631    };
632
633    // get the IO tasks
634    use pio::ChildInTask as _;
635    use pio::ChildOutTask as _;
636    let (stdout_future, stdout) = stdout.run();
637    let (stderr_future, stderr) = stderr.run();
638    let stdin_future = stdin.run();
639
640    let io = ChildIo::start(stdin_future, stdout_future, stderr_future);
641
642    Ok((
643        Child {
644            name,
645            inner: child,
646            io,
647        },
648        stdout,
649        stderr,
650    ))
651}