Auto merge of #77029 - ehuss:command-access, r=dtolnay

Add accessors to Command.

This adds some accessor methods to `Command` to provide a way to access the values set when building the `Command`. An example where this can be useful is to display the command to be executed. This is roughly based on the [`ProcessBuilder`](13b73cdaf7/src/cargo/util/process_builder.rs (L105-L134)) in Cargo.

Possible concerns about the API:
- Values with NULs on Unix will be returned as `"<string-with-nul>"`. I don't think it is practical to avoid this, since otherwise a whole separate copy of all the values would need to be kept in `Command`.
- Does not handle `arg0` on Unix. This can be awkward to support in `get_args` and is rarely used. I figure if someone really wants it, it can be added to `CommandExt` as a separate method.
- Does not offer a way to detect `env_clear`. I'm uncertain if it would be useful for anyone.
- Does not offer a way to get an environment variable by name (`get_env`). I figure this can be added later if anyone really wants it. I think the motivation for this is weak, though. Also, the API could be a little awkward (return a `Option<Option<&OsStr>>`?).
- `get_envs` could skip "cleared" entries and just return `&OsStr` values instead of `Option<&OsStr>`. I'm on the fence here. My use case is to display a shell command, and I only intend it to be roughly equivalent to the actual execution, and I probably won't display `None` entries. I erred on the side of providing extra information, but I suspect many situations will just filter out the `None`s.
- Could implement more iterator stuff (like `DoubleEndedIterator`).

I have not implemented new std items before, so I'm uncertain if the existing issue should be reused, or if a new tracking issue is needed.

cc #44434
This commit is contained in:
bors 2020-10-02 07:51:24 +00:00
commit 154f1f544d
8 changed files with 303 additions and 8 deletions

View File

@ -110,6 +110,8 @@ use crate::path::Path;
use crate::str;
use crate::sys::pipe::{read2, AnonPipe};
use crate::sys::process as imp;
#[unstable(feature = "command_access", issue = "44434")]
pub use crate::sys_common::process::CommandEnvs;
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
/// Representation of a running or exited child process.
@ -894,6 +896,98 @@ impl Command {
.map(Child::from_inner)
.and_then(|mut p| p.wait())
}
/// Returns the path to the program that was given to [`Command::new`].
///
/// # Examples
///
/// ```
/// # #![feature(command_access)]
/// use std::process::Command;
///
/// let cmd = Command::new("echo");
/// assert_eq!(cmd.get_program(), "echo");
/// ```
#[unstable(feature = "command_access", issue = "44434")]
pub fn get_program(&self) -> &OsStr {
self.inner.get_program()
}
/// Returns an iterator of the arguments that will be passed to the program.
///
/// This does not include the path to the program as the first argument;
/// it only includes the arguments specified with [`Command::arg`] and
/// [`Command::args`].
///
/// # Examples
///
/// ```
/// # #![feature(command_access)]
/// use std::ffi::OsStr;
/// use std::process::Command;
///
/// let mut cmd = Command::new("echo");
/// cmd.arg("first").arg("second");
/// let args: Vec<&OsStr> = cmd.get_args().collect();
/// assert_eq!(args, &["first", "second"]);
/// ```
#[unstable(feature = "command_access", issue = "44434")]
pub fn get_args(&self) -> CommandArgs<'_> {
CommandArgs { inner: self.inner.get_args() }
}
/// Returns an iterator of the environment variables that will be set when
/// the process is spawned.
///
/// Each element is a tuple `(&OsStr, Option<&OsStr>)`, where the first
/// value is the key, and the second is the value, which is [`None`] if
/// the environment variable is to be explicitly removed.
///
/// This only includes environment variables explicitly set with
/// [`Command::env`], [`Command::envs`], and [`Command::env_remove`]. It
/// does not include environment variables that will be inherited by the
/// child process.
///
/// # Examples
///
/// ```
/// # #![feature(command_access)]
/// use std::ffi::OsStr;
/// use std::process::Command;
///
/// let mut cmd = Command::new("ls");
/// cmd.env("TERM", "dumb").env_remove("TZ");
/// let envs: Vec<(&OsStr, Option<&OsStr>)> = cmd.get_envs().collect();
/// assert_eq!(envs, &[
/// (OsStr::new("TERM"), Some(OsStr::new("dumb"))),
/// (OsStr::new("TZ"), None)
/// ]);
/// ```
#[unstable(feature = "command_access", issue = "44434")]
pub fn get_envs(&self) -> CommandEnvs<'_> {
self.inner.get_envs()
}
/// Returns the working directory for the child process.
///
/// This returns [`None`] if the working directory will not be changed.
///
/// # Examples
///
/// ```
/// # #![feature(command_access)]
/// use std::path::Path;
/// use std::process::Command;
///
/// let mut cmd = Command::new("ls");
/// assert_eq!(cmd.get_current_dir(), None);
/// cmd.current_dir("/bin");
/// assert_eq!(cmd.get_current_dir(), Some(Path::new("/bin")));
/// ```
#[unstable(feature = "command_access", issue = "44434")]
pub fn get_current_dir(&self) -> Option<&Path> {
self.inner.get_current_dir()
}
}
#[stable(feature = "rust1", since = "1.0.0")]
@ -918,6 +1012,37 @@ impl AsInnerMut<imp::Command> for Command {
}
}
/// An iterator over the command arguments.
///
/// This struct is created by [`Command::get_args`]. See its documentation for
/// more.
#[unstable(feature = "command_access", issue = "44434")]
#[derive(Debug)]
pub struct CommandArgs<'a> {
inner: imp::CommandArgs<'a>,
}
#[unstable(feature = "command_access", issue = "44434")]
impl<'a> Iterator for CommandArgs<'a> {
type Item = &'a OsStr;
fn next(&mut self) -> Option<&'a OsStr> {
self.inner.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
#[unstable(feature = "command_access", issue = "44434")]
impl<'a> ExactSizeIterator for CommandArgs<'a> {
fn len(&self) -> usize {
self.inner.len()
}
fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
/// The output of a finished process.
///
/// This is returned in a Result by either the [`output`] method of a

View File

@ -1,6 +1,7 @@
pub use self::process_common::{Command, ExitCode, Stdio, StdioPipes};
pub use self::process_common::{Command, CommandArgs, ExitCode, Stdio, StdioPipes};
pub use self::process_inner::{ExitStatus, Process};
pub use crate::ffi::OsString as EnvKey;
pub use crate::sys_common::process::CommandEnvs;
mod process_common;
#[cfg(not(target_os = "fuchsia"))]

View File

@ -7,11 +7,12 @@ use crate::collections::BTreeMap;
use crate::ffi::{CStr, CString, OsStr, OsString};
use crate::fmt;
use crate::io;
use crate::path::Path;
use crate::ptr;
use crate::sys::fd::FileDesc;
use crate::sys::fs::File;
use crate::sys::pipe::{self, AnonPipe};
use crate::sys_common::process::CommandEnv;
use crate::sys_common::process::{CommandEnv, CommandEnvs};
#[cfg(not(target_os = "fuchsia"))]
use crate::sys::fs::OpenOptions;
@ -184,11 +185,30 @@ impl Command {
pub fn saw_nul(&self) -> bool {
self.saw_nul
}
pub fn get_program(&self) -> &OsStr {
OsStr::from_bytes(self.program.as_bytes())
}
pub fn get_args(&self) -> CommandArgs<'_> {
let mut iter = self.args.iter();
iter.next();
CommandArgs { iter }
}
pub fn get_envs(&self) -> CommandEnvs<'_> {
self.env.iter()
}
pub fn get_current_dir(&self) -> Option<&Path> {
self.cwd.as_ref().map(|cs| Path::new(OsStr::from_bytes(cs.as_bytes())))
}
pub fn get_argv(&self) -> &Vec<*const c_char> {
&self.argv.0
}
pub fn get_program(&self) -> &CStr {
pub fn get_program_cstr(&self) -> &CStr {
&*self.program
}
@ -402,3 +422,32 @@ impl ExitCode {
self.0 as i32
}
}
pub struct CommandArgs<'a> {
iter: crate::slice::Iter<'a, CString>,
}
impl<'a> Iterator for CommandArgs<'a> {
type Item = &'a OsStr;
fn next(&mut self) -> Option<&'a OsStr> {
self.iter.next().map(|cs| OsStr::from_bytes(cs.as_bytes()))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<'a> ExactSizeIterator for CommandArgs<'a> {
fn len(&self) -> usize {
self.iter.len()
}
fn is_empty(&self) -> bool {
self.iter.is_empty()
}
}
impl<'a> fmt::Debug for CommandArgs<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter.clone()).finish()
}
}

View File

@ -120,7 +120,7 @@ impl Command {
| FDIO_SPAWN_CLONE_NAMESPACE
| FDIO_SPAWN_CLONE_ENVIRON // this is ignored when envp is non-null
| FDIO_SPAWN_CLONE_UTC_CLOCK,
self.get_program().as_ptr(),
self.get_program_cstr().as_ptr(),
self.get_argv().as_ptr(),
envp,
actions.len() as size_t,

View File

@ -245,7 +245,7 @@ impl Command {
*sys::os::environ() = envp.as_ptr();
}
libc::execvp(self.get_program().as_ptr(), self.get_argv().as_ptr());
libc::execvp(self.get_program_cstr().as_ptr(), self.get_argv().as_ptr());
Err(io::Error::last_os_error())
}
@ -383,7 +383,7 @@ impl Command {
let envp = envp.map(|c| c.as_ptr()).unwrap_or_else(|| *sys::os::environ() as *const _);
let ret = libc::posix_spawnp(
&mut p.pid,
self.get_program().as_ptr(),
self.get_program_cstr().as_ptr(),
file_actions.0.as_ptr(),
attrs.0.as_ptr(),
self.get_argv().as_ptr() as *const _,

View File

@ -1,10 +1,12 @@
use crate::ffi::OsStr;
use crate::fmt;
use crate::io;
use crate::marker::PhantomData;
use crate::path::Path;
use crate::sys::fs::File;
use crate::sys::pipe::AnonPipe;
use crate::sys::{unsupported, Void};
use crate::sys_common::process::CommandEnv;
use crate::sys_common::process::{CommandEnv, CommandEnvs};
pub use crate::ffi::OsString as EnvKey;
@ -49,6 +51,22 @@ impl Command {
pub fn stderr(&mut self, _stderr: Stdio) {}
pub fn get_program(&self) -> &OsStr {
panic!("unsupported")
}
pub fn get_args(&self) -> CommandArgs<'_> {
CommandArgs { _p: PhantomData }
}
pub fn get_envs(&self) -> CommandEnvs<'_> {
self.env.iter()
}
pub fn get_current_dir(&self) -> Option<&Path> {
None
}
pub fn spawn(
&mut self,
_default: Stdio,
@ -147,3 +165,22 @@ impl Process {
match self.0 {}
}
}
pub struct CommandArgs<'a> {
_p: PhantomData<&'a ()>,
}
impl<'a> Iterator for CommandArgs<'a> {
type Item = &'a OsStr;
fn next(&mut self) -> Option<&'a OsStr> {
None
}
}
impl<'a> ExactSizeIterator for CommandArgs<'a> {}
impl<'a> fmt::Debug for CommandArgs<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().finish()
}
}

View File

@ -22,7 +22,7 @@ use crate::sys::handle::Handle;
use crate::sys::mutex::Mutex;
use crate::sys::pipe::{self, AnonPipe};
use crate::sys::stdio;
use crate::sys_common::process::CommandEnv;
use crate::sys_common::process::{CommandEnv, CommandEnvs};
use crate::sys_common::AsInner;
use libc::{c_void, EXIT_FAILURE, EXIT_SUCCESS};
@ -134,6 +134,23 @@ impl Command {
self.flags = flags;
}
pub fn get_program(&self) -> &OsStr {
&self.program
}
pub fn get_args(&self) -> CommandArgs<'_> {
let iter = self.args.iter();
CommandArgs { iter }
}
pub fn get_envs(&self) -> CommandEnvs<'_> {
self.env.iter()
}
pub fn get_current_dir(&self) -> Option<&Path> {
self.cwd.as_ref().map(|cwd| Path::new(cwd))
}
pub fn spawn(
&mut self,
default: Stdio,
@ -529,3 +546,32 @@ fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> {
None => Ok((ptr::null(), Vec::new())),
}
}
pub struct CommandArgs<'a> {
iter: crate::slice::Iter<'a, OsString>,
}
impl<'a> Iterator for CommandArgs<'a> {
type Item = &'a OsStr;
fn next(&mut self) -> Option<&'a OsStr> {
self.iter.next().map(|s| s.as_ref())
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<'a> ExactSizeIterator for CommandArgs<'a> {
fn len(&self) -> usize {
self.iter.len()
}
fn is_empty(&self) -> bool {
self.iter.is_empty()
}
}
impl<'a> fmt::Debug for CommandArgs<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter.clone()).finish()
}
}

View File

@ -92,4 +92,41 @@ impl CommandEnv {
self.saw_path = true;
}
}
pub fn iter(&self) -> CommandEnvs<'_> {
let iter = self.vars.iter();
CommandEnvs { iter }
}
}
/// An iterator over the command environment variables.
///
/// This struct is created by
/// [`Command::get_envs`][crate::process::Command::get_envs]. See its
/// documentation for more.
#[unstable(feature = "command_access", issue = "44434")]
#[derive(Debug)]
pub struct CommandEnvs<'a> {
iter: crate::collections::btree_map::Iter<'a, EnvKey, Option<OsString>>,
}
#[unstable(feature = "command_access", issue = "44434")]
impl<'a> Iterator for CommandEnvs<'a> {
type Item = (&'a OsStr, Option<&'a OsStr>);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(key, value)| (key.as_ref(), value.as_deref()))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
#[unstable(feature = "command_access", issue = "44434")]
impl<'a> ExactSizeIterator for CommandEnvs<'a> {
fn len(&self) -> usize {
self.iter.len()
}
fn is_empty(&self) -> bool {
self.iter.is_empty()
}
}