Skip to content

Commit d2e06b7

Browse files
authored
Rollup merge of rust-lang#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`](https://github.com/rust-lang/cargo/blob/13b73cdaf76b2d9182515c9cf26a8f68342d08ef/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 rust-lang#44434
2 parents 4ded681 + c297e20 commit d2e06b7

File tree

8 files changed

+303
-8
lines changed

8 files changed

+303
-8
lines changed

library/std/src/process.rs

+125
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ use crate::path::Path;
110110
use crate::str;
111111
use crate::sys::pipe::{read2, AnonPipe};
112112
use crate::sys::process as imp;
113+
#[unstable(feature = "command_access", issue = "44434")]
114+
pub use crate::sys_common::process::CommandEnvs;
113115
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
114116

115117
/// Representation of a running or exited child process.
@@ -894,6 +896,98 @@ impl Command {
894896
.map(Child::from_inner)
895897
.and_then(|mut p| p.wait())
896898
}
899+
900+
/// Returns the path to the program that was given to [`Command::new`].
901+
///
902+
/// # Examples
903+
///
904+
/// ```
905+
/// # #![feature(command_access)]
906+
/// use std::process::Command;
907+
///
908+
/// let cmd = Command::new("echo");
909+
/// assert_eq!(cmd.get_program(), "echo");
910+
/// ```
911+
#[unstable(feature = "command_access", issue = "44434")]
912+
pub fn get_program(&self) -> &OsStr {
913+
self.inner.get_program()
914+
}
915+
916+
/// Returns an iterator of the arguments that will be passed to the program.
917+
///
918+
/// This does not include the path to the program as the first argument;
919+
/// it only includes the arguments specified with [`Command::arg`] and
920+
/// [`Command::args`].
921+
///
922+
/// # Examples
923+
///
924+
/// ```
925+
/// # #![feature(command_access)]
926+
/// use std::ffi::OsStr;
927+
/// use std::process::Command;
928+
///
929+
/// let mut cmd = Command::new("echo");
930+
/// cmd.arg("first").arg("second");
931+
/// let args: Vec<&OsStr> = cmd.get_args().collect();
932+
/// assert_eq!(args, &["first", "second"]);
933+
/// ```
934+
#[unstable(feature = "command_access", issue = "44434")]
935+
pub fn get_args(&self) -> CommandArgs<'_> {
936+
CommandArgs { inner: self.inner.get_args() }
937+
}
938+
939+
/// Returns an iterator of the environment variables that will be set when
940+
/// the process is spawned.
941+
///
942+
/// Each element is a tuple `(&OsStr, Option<&OsStr>)`, where the first
943+
/// value is the key, and the second is the value, which is [`None`] if
944+
/// the environment variable is to be explicitly removed.
945+
///
946+
/// This only includes environment variables explicitly set with
947+
/// [`Command::env`], [`Command::envs`], and [`Command::env_remove`]. It
948+
/// does not include environment variables that will be inherited by the
949+
/// child process.
950+
///
951+
/// # Examples
952+
///
953+
/// ```
954+
/// # #![feature(command_access)]
955+
/// use std::ffi::OsStr;
956+
/// use std::process::Command;
957+
///
958+
/// let mut cmd = Command::new("ls");
959+
/// cmd.env("TERM", "dumb").env_remove("TZ");
960+
/// let envs: Vec<(&OsStr, Option<&OsStr>)> = cmd.get_envs().collect();
961+
/// assert_eq!(envs, &[
962+
/// (OsStr::new("TERM"), Some(OsStr::new("dumb"))),
963+
/// (OsStr::new("TZ"), None)
964+
/// ]);
965+
/// ```
966+
#[unstable(feature = "command_access", issue = "44434")]
967+
pub fn get_envs(&self) -> CommandEnvs<'_> {
968+
self.inner.get_envs()
969+
}
970+
971+
/// Returns the working directory for the child process.
972+
///
973+
/// This returns [`None`] if the working directory will not be changed.
974+
///
975+
/// # Examples
976+
///
977+
/// ```
978+
/// # #![feature(command_access)]
979+
/// use std::path::Path;
980+
/// use std::process::Command;
981+
///
982+
/// let mut cmd = Command::new("ls");
983+
/// assert_eq!(cmd.get_current_dir(), None);
984+
/// cmd.current_dir("/bin");
985+
/// assert_eq!(cmd.get_current_dir(), Some(Path::new("/bin")));
986+
/// ```
987+
#[unstable(feature = "command_access", issue = "44434")]
988+
pub fn get_current_dir(&self) -> Option<&Path> {
989+
self.inner.get_current_dir()
990+
}
897991
}
898992

899993
#[stable(feature = "rust1", since = "1.0.0")]
@@ -918,6 +1012,37 @@ impl AsInnerMut<imp::Command> for Command {
9181012
}
9191013
}
9201014

1015+
/// An iterator over the command arguments.
1016+
///
1017+
/// This struct is created by [`Command::get_args`]. See its documentation for
1018+
/// more.
1019+
#[unstable(feature = "command_access", issue = "44434")]
1020+
#[derive(Debug)]
1021+
pub struct CommandArgs<'a> {
1022+
inner: imp::CommandArgs<'a>,
1023+
}
1024+
1025+
#[unstable(feature = "command_access", issue = "44434")]
1026+
impl<'a> Iterator for CommandArgs<'a> {
1027+
type Item = &'a OsStr;
1028+
fn next(&mut self) -> Option<&'a OsStr> {
1029+
self.inner.next()
1030+
}
1031+
fn size_hint(&self) -> (usize, Option<usize>) {
1032+
self.inner.size_hint()
1033+
}
1034+
}
1035+
1036+
#[unstable(feature = "command_access", issue = "44434")]
1037+
impl<'a> ExactSizeIterator for CommandArgs<'a> {
1038+
fn len(&self) -> usize {
1039+
self.inner.len()
1040+
}
1041+
fn is_empty(&self) -> bool {
1042+
self.inner.is_empty()
1043+
}
1044+
}
1045+
9211046
/// The output of a finished process.
9221047
///
9231048
/// This is returned in a Result by either the [`output`] method of a

library/std/src/sys/unix/process/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
pub use self::process_common::{Command, ExitCode, Stdio, StdioPipes};
1+
pub use self::process_common::{Command, CommandArgs, ExitCode, Stdio, StdioPipes};
22
pub use self::process_inner::{ExitStatus, Process};
33
pub use crate::ffi::OsString as EnvKey;
4+
pub use crate::sys_common::process::CommandEnvs;
45

56
mod process_common;
67
#[cfg(not(target_os = "fuchsia"))]

library/std/src/sys/unix/process/process_common.rs

+51-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ use crate::collections::BTreeMap;
77
use crate::ffi::{CStr, CString, OsStr, OsString};
88
use crate::fmt;
99
use crate::io;
10+
use crate::path::Path;
1011
use crate::ptr;
1112
use crate::sys::fd::FileDesc;
1213
use crate::sys::fs::File;
1314
use crate::sys::pipe::{self, AnonPipe};
14-
use crate::sys_common::process::CommandEnv;
15+
use crate::sys_common::process::{CommandEnv, CommandEnvs};
1516

1617
#[cfg(not(target_os = "fuchsia"))]
1718
use crate::sys::fs::OpenOptions;
@@ -184,11 +185,30 @@ impl Command {
184185
pub fn saw_nul(&self) -> bool {
185186
self.saw_nul
186187
}
188+
189+
pub fn get_program(&self) -> &OsStr {
190+
OsStr::from_bytes(self.program.as_bytes())
191+
}
192+
193+
pub fn get_args(&self) -> CommandArgs<'_> {
194+
let mut iter = self.args.iter();
195+
iter.next();
196+
CommandArgs { iter }
197+
}
198+
199+
pub fn get_envs(&self) -> CommandEnvs<'_> {
200+
self.env.iter()
201+
}
202+
203+
pub fn get_current_dir(&self) -> Option<&Path> {
204+
self.cwd.as_ref().map(|cs| Path::new(OsStr::from_bytes(cs.as_bytes())))
205+
}
206+
187207
pub fn get_argv(&self) -> &Vec<*const c_char> {
188208
&self.argv.0
189209
}
190210

191-
pub fn get_program(&self) -> &CStr {
211+
pub fn get_program_cstr(&self) -> &CStr {
192212
&*self.program
193213
}
194214

@@ -402,3 +422,32 @@ impl ExitCode {
402422
self.0 as i32
403423
}
404424
}
425+
426+
pub struct CommandArgs<'a> {
427+
iter: crate::slice::Iter<'a, CString>,
428+
}
429+
430+
impl<'a> Iterator for CommandArgs<'a> {
431+
type Item = &'a OsStr;
432+
fn next(&mut self) -> Option<&'a OsStr> {
433+
self.iter.next().map(|cs| OsStr::from_bytes(cs.as_bytes()))
434+
}
435+
fn size_hint(&self) -> (usize, Option<usize>) {
436+
self.iter.size_hint()
437+
}
438+
}
439+
440+
impl<'a> ExactSizeIterator for CommandArgs<'a> {
441+
fn len(&self) -> usize {
442+
self.iter.len()
443+
}
444+
fn is_empty(&self) -> bool {
445+
self.iter.is_empty()
446+
}
447+
}
448+
449+
impl<'a> fmt::Debug for CommandArgs<'a> {
450+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451+
f.debug_list().entries(self.iter.clone()).finish()
452+
}
453+
}

library/std/src/sys/unix/process/process_fuchsia.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ impl Command {
120120
| FDIO_SPAWN_CLONE_NAMESPACE
121121
| FDIO_SPAWN_CLONE_ENVIRON // this is ignored when envp is non-null
122122
| FDIO_SPAWN_CLONE_UTC_CLOCK,
123-
self.get_program().as_ptr(),
123+
self.get_program_cstr().as_ptr(),
124124
self.get_argv().as_ptr(),
125125
envp,
126126
actions.len() as size_t,

library/std/src/sys/unix/process/process_unix.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ impl Command {
245245
*sys::os::environ() = envp.as_ptr();
246246
}
247247

248-
libc::execvp(self.get_program().as_ptr(), self.get_argv().as_ptr());
248+
libc::execvp(self.get_program_cstr().as_ptr(), self.get_argv().as_ptr());
249249
Err(io::Error::last_os_error())
250250
}
251251

@@ -383,7 +383,7 @@ impl Command {
383383
let envp = envp.map(|c| c.as_ptr()).unwrap_or_else(|| *sys::os::environ() as *const _);
384384
let ret = libc::posix_spawnp(
385385
&mut p.pid,
386-
self.get_program().as_ptr(),
386+
self.get_program_cstr().as_ptr(),
387387
file_actions.0.as_ptr(),
388388
attrs.0.as_ptr(),
389389
self.get_argv().as_ptr() as *const _,

library/std/src/sys/unsupported/process.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use crate::ffi::OsStr;
22
use crate::fmt;
33
use crate::io;
4+
use crate::marker::PhantomData;
5+
use crate::path::Path;
46
use crate::sys::fs::File;
57
use crate::sys::pipe::AnonPipe;
68
use crate::sys::{unsupported, Void};
7-
use crate::sys_common::process::CommandEnv;
9+
use crate::sys_common::process::{CommandEnv, CommandEnvs};
810

911
pub use crate::ffi::OsString as EnvKey;
1012

@@ -49,6 +51,22 @@ impl Command {
4951

5052
pub fn stderr(&mut self, _stderr: Stdio) {}
5153

54+
pub fn get_program(&self) -> &OsStr {
55+
panic!("unsupported")
56+
}
57+
58+
pub fn get_args(&self) -> CommandArgs<'_> {
59+
CommandArgs { _p: PhantomData }
60+
}
61+
62+
pub fn get_envs(&self) -> CommandEnvs<'_> {
63+
self.env.iter()
64+
}
65+
66+
pub fn get_current_dir(&self) -> Option<&Path> {
67+
None
68+
}
69+
5270
pub fn spawn(
5371
&mut self,
5472
_default: Stdio,
@@ -147,3 +165,22 @@ impl Process {
147165
match self.0 {}
148166
}
149167
}
168+
169+
pub struct CommandArgs<'a> {
170+
_p: PhantomData<&'a ()>,
171+
}
172+
173+
impl<'a> Iterator for CommandArgs<'a> {
174+
type Item = &'a OsStr;
175+
fn next(&mut self) -> Option<&'a OsStr> {
176+
None
177+
}
178+
}
179+
180+
impl<'a> ExactSizeIterator for CommandArgs<'a> {}
181+
182+
impl<'a> fmt::Debug for CommandArgs<'a> {
183+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184+
f.debug_list().finish()
185+
}
186+
}

library/std/src/sys/windows/process.rs

+47-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::sys::handle::Handle;
2222
use crate::sys::mutex::Mutex;
2323
use crate::sys::pipe::{self, AnonPipe};
2424
use crate::sys::stdio;
25-
use crate::sys_common::process::CommandEnv;
25+
use crate::sys_common::process::{CommandEnv, CommandEnvs};
2626
use crate::sys_common::AsInner;
2727

2828
use libc::{c_void, EXIT_FAILURE, EXIT_SUCCESS};
@@ -134,6 +134,23 @@ impl Command {
134134
self.flags = flags;
135135
}
136136

137+
pub fn get_program(&self) -> &OsStr {
138+
&self.program
139+
}
140+
141+
pub fn get_args(&self) -> CommandArgs<'_> {
142+
let iter = self.args.iter();
143+
CommandArgs { iter }
144+
}
145+
146+
pub fn get_envs(&self) -> CommandEnvs<'_> {
147+
self.env.iter()
148+
}
149+
150+
pub fn get_current_dir(&self) -> Option<&Path> {
151+
self.cwd.as_ref().map(|cwd| Path::new(cwd))
152+
}
153+
137154
pub fn spawn(
138155
&mut self,
139156
default: Stdio,
@@ -529,3 +546,32 @@ fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> {
529546
None => Ok((ptr::null(), Vec::new())),
530547
}
531548
}
549+
550+
pub struct CommandArgs<'a> {
551+
iter: crate::slice::Iter<'a, OsString>,
552+
}
553+
554+
impl<'a> Iterator for CommandArgs<'a> {
555+
type Item = &'a OsStr;
556+
fn next(&mut self) -> Option<&'a OsStr> {
557+
self.iter.next().map(|s| s.as_ref())
558+
}
559+
fn size_hint(&self) -> (usize, Option<usize>) {
560+
self.iter.size_hint()
561+
}
562+
}
563+
564+
impl<'a> ExactSizeIterator for CommandArgs<'a> {
565+
fn len(&self) -> usize {
566+
self.iter.len()
567+
}
568+
fn is_empty(&self) -> bool {
569+
self.iter.is_empty()
570+
}
571+
}
572+
573+
impl<'a> fmt::Debug for CommandArgs<'a> {
574+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575+
f.debug_list().entries(self.iter.clone()).finish()
576+
}
577+
}

0 commit comments

Comments
 (0)