io::process::Command: add fine-grained env builder
This commit changes the `io::process::Command` API to provide fine-grained control over the environment: * The `env` method now inserts/updates a key/value pair. * The `env_remove` method removes a key from the environment. * The old `env` method, which sets the entire environment in one shot, is renamed to `env_set_all`. It can be used in conjunction with the finer-grained methods. This renaming is a breaking change. To support these new methods, the internal `env` representation for `Command` has been changed to an optional `HashMap` holding owned `CString`s (to support non-utf8 data). The `HashMap` is only materialized if the environment is updated. The implementation does not try hard to avoid allocation, since the cost of launching a process will dwarf any allocation cost. This patch also adds `PartialOrd`, `Eq`, and `Hash` implementations for `CString`. [breaking-change]
This commit is contained in:
parent
f9fe251777
commit
bfa853f8ed
11 changed files with 137 additions and 72 deletions
|
@ -16,6 +16,7 @@ use prelude::*;
|
|||
|
||||
use str;
|
||||
use fmt;
|
||||
use os;
|
||||
use io::{IoResult, IoError};
|
||||
use io;
|
||||
use libc;
|
||||
|
@ -24,6 +25,7 @@ use owned::Box;
|
|||
use rt::rtio::{RtioProcess, ProcessConfig, IoFactory, LocalIo};
|
||||
use rt::rtio;
|
||||
use c_str::CString;
|
||||
use collections::HashMap;
|
||||
|
||||
/// Signal a process to exit, without forcibly killing it. Corresponds to
|
||||
/// SIGTERM on unix platforms.
|
||||
|
@ -78,6 +80,9 @@ pub struct Process {
|
|||
pub extra_io: Vec<Option<io::PipeStream>>,
|
||||
}
|
||||
|
||||
/// A HashMap representation of environment variables.
|
||||
pub type EnvMap = HashMap<CString, CString>;
|
||||
|
||||
/// The `Command` type acts as a process builder, providing fine-grained control
|
||||
/// over how a new process should be spawned. A default configuration can be
|
||||
/// generated using `Command::new(program)`, where `program` gives a path to the
|
||||
|
@ -100,7 +105,7 @@ pub struct Command {
|
|||
// methods below, and serialized into rt::rtio::ProcessConfig.
|
||||
program: CString,
|
||||
args: Vec<CString>,
|
||||
env: Option<Vec<(CString, CString)>>,
|
||||
env: Option<EnvMap>,
|
||||
cwd: Option<CString>,
|
||||
stdin: StdioContainer,
|
||||
stdout: StdioContainer,
|
||||
|
@ -147,31 +152,53 @@ impl Command {
|
|||
}
|
||||
|
||||
/// Add an argument to pass to the program.
|
||||
pub fn arg<'a, T:ToCStr>(&'a mut self, arg: T) -> &'a mut Command {
|
||||
pub fn arg<'a, T: ToCStr>(&'a mut self, arg: T) -> &'a mut Command {
|
||||
self.args.push(arg.to_c_str());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple arguments to pass to the program.
|
||||
pub fn args<'a, T:ToCStr>(&'a mut self, args: &[T]) -> &'a mut Command {
|
||||
pub fn args<'a, T: ToCStr>(&'a mut self, args: &[T]) -> &'a mut Command {
|
||||
self.args.extend(args.iter().map(|arg| arg.to_c_str()));;
|
||||
self
|
||||
}
|
||||
// Get a mutable borrow of the environment variable map for this `Command`.
|
||||
fn get_env_map<'a>(&'a mut self) -> &'a mut EnvMap {
|
||||
match self.env {
|
||||
Some(ref mut map) => map,
|
||||
None => {
|
||||
// if the env is currently just inheriting from the parent's,
|
||||
// materialize the parent's env into a hashtable.
|
||||
self.env = Some(os::env_as_bytes().move_iter()
|
||||
.map(|(k, v)| (k.as_slice().to_c_str(),
|
||||
v.as_slice().to_c_str()))
|
||||
.collect());
|
||||
self.env.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the environment for the child process (rather than inheriting it
|
||||
/// from the current process).
|
||||
/// Inserts or updates an environment variable mapping.
|
||||
pub fn env<'a, T: ToCStr, U: ToCStr>(&'a mut self, key: T, val: U)
|
||||
-> &'a mut Command {
|
||||
self.get_env_map().insert(key.to_c_str(), val.to_c_str());
|
||||
self
|
||||
}
|
||||
|
||||
// FIXME (#13851): We should change this interface to allow clients to (1)
|
||||
// build up the env vector incrementally and (2) allow both inheriting the
|
||||
// current process's environment AND overriding/adding additional
|
||||
// environment variables. The underlying syscalls assume that the
|
||||
// environment has no duplicate names, so we really want to use a hashtable
|
||||
// to compute the environment to pass down to the syscall; resolving issue
|
||||
// #13851 will make it possible to use the standard hashtable.
|
||||
pub fn env<'a, T:ToCStr>(&'a mut self, env: &[(T,T)]) -> &'a mut Command {
|
||||
self.env = Some(env.iter().map(|&(ref name, ref val)| {
|
||||
(name.to_c_str(), val.to_c_str())
|
||||
}).collect());
|
||||
/// Removes an environment variable mapping.
|
||||
pub fn env_remove<'a, T: ToCStr>(&'a mut self, key: T) -> &'a mut Command {
|
||||
self.get_env_map().remove(&key.to_c_str());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the entire environment map for the child process.
|
||||
///
|
||||
/// If the given slice contains multiple instances of an environment
|
||||
/// variable, the *rightmost* instance will determine the value.
|
||||
pub fn env_set_all<'a, T: ToCStr, U: ToCStr>(&'a mut self, env: &[(T,U)])
|
||||
-> &'a mut Command {
|
||||
self.env = Some(env.iter().map(|&(ref k, ref v)| (k.to_c_str(), v.to_c_str()))
|
||||
.collect());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -245,10 +272,15 @@ impl Command {
|
|||
let extra_io: Vec<rtio::StdioContainer> =
|
||||
self.extra_io.iter().map(|x| to_rtio(*x)).collect();
|
||||
LocalIo::maybe_raise(|io| {
|
||||
let env = match self.env {
|
||||
None => None,
|
||||
Some(ref env_map) =>
|
||||
Some(env_map.iter().collect::<Vec<_>>())
|
||||
};
|
||||
let cfg = ProcessConfig {
|
||||
program: &self.program,
|
||||
args: self.args.as_slice(),
|
||||
env: self.env.as_ref().map(|env| env.as_slice()),
|
||||
env: env.as_ref().map(|e| e.as_slice()),
|
||||
cwd: self.cwd.as_ref(),
|
||||
stdin: to_rtio(self.stdin),
|
||||
stdout: to_rtio(self.stdout),
|
||||
|
@ -872,9 +904,9 @@ mod tests {
|
|||
}
|
||||
})
|
||||
|
||||
iotest!(fn test_add_to_env() {
|
||||
iotest!(fn test_override_env() {
|
||||
let new_env = vec![("RUN_TEST_NEW_ENV", "123")];
|
||||
let prog = env_cmd().env(new_env.as_slice()).spawn().unwrap();
|
||||
let prog = env_cmd().env_set_all(new_env.as_slice()).spawn().unwrap();
|
||||
let result = prog.wait_with_output().unwrap();
|
||||
let output = str::from_utf8_lossy(result.output.as_slice()).into_string();
|
||||
|
||||
|
@ -882,6 +914,40 @@ mod tests {
|
|||
"didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output);
|
||||
})
|
||||
|
||||
iotest!(fn test_add_to_env() {
|
||||
let prog = env_cmd().env("RUN_TEST_NEW_ENV", "123").spawn().unwrap();
|
||||
let result = prog.wait_with_output().unwrap();
|
||||
let output = str::from_utf8_lossy(result.output.as_slice()).into_string();
|
||||
|
||||
assert!(output.as_slice().contains("RUN_TEST_NEW_ENV=123"),
|
||||
"didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output);
|
||||
})
|
||||
|
||||
iotest!(fn test_remove_from_env() {
|
||||
use os;
|
||||
|
||||
// save original environment
|
||||
let old_env = os::getenv("RUN_TEST_NEW_ENV");
|
||||
|
||||
os::setenv("RUN_TEST_NEW_ENV", "123");
|
||||
let prog = env_cmd().env_remove("RUN_TEST_NEW_ENV").spawn().unwrap();
|
||||
let result = prog.wait_with_output().unwrap();
|
||||
let output = str::from_utf8_lossy(result.output.as_slice()).into_string();
|
||||
|
||||
// restore original environment
|
||||
match old_env {
|
||||
None => {
|
||||
os::unsetenv("RUN_TEST_NEW_ENV");
|
||||
}
|
||||
Some(val) => {
|
||||
os::setenv("RUN_TEST_NEW_ENV", val.as_slice());
|
||||
}
|
||||
}
|
||||
|
||||
assert!(!output.as_slice().contains("RUN_TEST_NEW_ENV"),
|
||||
"found RUN_TEST_NEW_ENV inside of:\n\n{}", output);
|
||||
})
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn sleeper() -> Process {
|
||||
Command::new("sleep").arg("1000").spawn().unwrap()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue