Skip to content

Commit a672456

Browse files
committed
auto merge of #15353 : aturon/rust/env-hashmap, r=alexcrichton
This commit adds `env_insert` and `env_remove` methods to the `Command` builder, easing updates to the environment variables for the child process. The existing method, `env`, is still available for overriding the entire environment in one shot (after which the `env_insert` and `env_remove` methods can be used to make further adjustments). 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`.
2 parents 8bbf598 + bfa853f commit a672456

File tree

11 files changed

+138
-73
lines changed

11 files changed

+138
-73
lines changed

src/compiletest/procsrv.rs

+19-16
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use std::os;
1211
use std::str;
1312
use std::io::process::{ProcessExit, Command, Process, ProcessOutput};
1413
use std::dynamic_lib::DynamicLibrary;
1514

16-
fn target_env(lib_path: &str, aux_path: Option<&str>) -> Vec<(String, String)> {
15+
fn add_target_env(cmd: &mut Command, lib_path: &str, aux_path: Option<&str>) {
1716
// Need to be sure to put both the lib_path and the aux path in the dylib
1817
// search path for the child.
1918
let mut path = DynamicLibrary::search_path();
@@ -23,19 +22,11 @@ fn target_env(lib_path: &str, aux_path: Option<&str>) -> Vec<(String, String)> {
2322
}
2423
path.insert(0, Path::new(lib_path));
2524

26-
// Remove the previous dylib search path var
27-
let var = DynamicLibrary::envvar();
28-
let mut env: Vec<(String,String)> = os::env();
29-
match env.iter().position(|&(ref k, _)| k.as_slice() == var) {
30-
Some(i) => { env.remove(i); }
31-
None => {}
32-
}
33-
3425
// Add the new dylib search path var
26+
let var = DynamicLibrary::envvar();
3527
let newpath = DynamicLibrary::create_path(path.as_slice());
3628
let newpath = str::from_utf8(newpath.as_slice()).unwrap().to_string();
37-
env.push((var.to_string(), newpath));
38-
return env;
29+
cmd.env(var.to_string(), newpath);
3930
}
4031

4132
pub struct Result {pub status: ProcessExit, pub out: String, pub err: String}
@@ -47,8 +38,14 @@ pub fn run(lib_path: &str,
4738
env: Vec<(String, String)> ,
4839
input: Option<String>) -> Option<Result> {
4940

50-
let env = env.clone().append(target_env(lib_path, aux_path).as_slice());
51-
match Command::new(prog).args(args).env(env.as_slice()).spawn() {
41+
let mut cmd = Command::new(prog);
42+
cmd.args(args);
43+
add_target_env(&mut cmd, lib_path, aux_path);
44+
for (key, val) in env.move_iter() {
45+
cmd.env(key, val);
46+
}
47+
48+
match cmd.spawn() {
5249
Ok(mut process) => {
5350
for input in input.iter() {
5451
process.stdin.get_mut_ref().write(input.as_bytes()).unwrap();
@@ -73,8 +70,14 @@ pub fn run_background(lib_path: &str,
7370
env: Vec<(String, String)> ,
7471
input: Option<String>) -> Option<Process> {
7572

76-
let env = env.clone().append(target_env(lib_path, aux_path).as_slice());
77-
match Command::new(prog).args(args).env(env.as_slice()).spawn() {
73+
let mut cmd = Command::new(prog);
74+
cmd.args(args);
75+
add_target_env(&mut cmd, lib_path, aux_path);
76+
for (key, val) in env.move_iter() {
77+
cmd.env(key, val);
78+
}
79+
80+
match cmd.spawn() {
7881
Ok(mut process) => {
7982
for input in input.iter() {
8083
process.stdin.get_mut_ref().write(input.as_bytes()).unwrap();

src/compiletest/runtest.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ fn run_debuginfo_lldb_test(config: &Config, props: &TestProps, testfile: &Path)
574574
cmd.arg("./src/etc/lldb_batchmode.py")
575575
.arg(test_executable)
576576
.arg(debugger_script)
577-
.env([("PYTHONPATH", config.lldb_python_dir.clone().unwrap().as_slice())]);
577+
.env_set_all([("PYTHONPATH", config.lldb_python_dir.clone().unwrap().as_slice())]);
578578

579579
let (status, out, err) = match cmd.spawn() {
580580
Ok(process) => {

src/libnative/io/process.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ fn with_argv<T>(prog: &CString, args: &[CString],
729729
}
730730

731731
#[cfg(unix)]
732-
fn with_envp<T>(env: Option<&[(CString, CString)]>,
732+
fn with_envp<T>(env: Option<&[(&CString, &CString)]>,
733733
cb: proc(*const c_void) -> T) -> T {
734734
// On posixy systems we can pass a char** for envp, which is a
735735
// null-terminated array of "k=v\0" strings. Since we must create
@@ -762,7 +762,7 @@ fn with_envp<T>(env: Option<&[(CString, CString)]>,
762762
}
763763

764764
#[cfg(windows)]
765-
fn with_envp<T>(env: Option<&[(CString, CString)]>, cb: |*mut c_void| -> T) -> T {
765+
fn with_envp<T>(env: Option<&[(&CString, &CString)]>, cb: |*mut c_void| -> T) -> T {
766766
// On win32 we pass an "environment block" which is not a char**, but
767767
// rather a concatenation of null-terminated k=v\0 sequences, with a final
768768
// \0 to terminate.

src/librustdoc/test.rs

+6-17
Original file line numberDiff line numberDiff line change
@@ -176,26 +176,15 @@ fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
176176
// environment to ensure that the target loads the right libraries at
177177
// runtime. It would be a sad day if the *host* libraries were loaded as a
178178
// mistake.
179-
let exe = outdir.path().join("rust_out");
180-
let env = {
179+
let mut cmd = Command::new(outdir.path().join("rust_out"));
180+
let newpath = {
181181
let mut path = DynamicLibrary::search_path();
182182
path.insert(0, libdir.clone());
183-
184-
// Remove the previous dylib search path var
185-
let var = DynamicLibrary::envvar();
186-
let mut env: Vec<(String,String)> = os::env().move_iter().collect();
187-
match env.iter().position(|&(ref k, _)| k.as_slice() == var) {
188-
Some(i) => { env.remove(i); }
189-
None => {}
190-
};
191-
192-
// Add the new dylib search path var
193-
let newpath = DynamicLibrary::create_path(path.as_slice());
194-
env.push((var.to_string(),
195-
str::from_utf8(newpath.as_slice()).unwrap().to_string()));
196-
env
183+
DynamicLibrary::create_path(path.as_slice())
197184
};
198-
match Command::new(exe).env(env.as_slice()).output() {
185+
cmd.env(DynamicLibrary::envvar(), newpath.as_slice());
186+
187+
match cmd.output() {
199188
Err(e) => fail!("couldn't run the test: {}{}", e,
200189
if e.kind == io::PermissionDenied {
201190
" - maybe your tempdir is mounted with noexec?"

src/librustrt/c_str.rs

+17
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ use core::prelude::*;
6969

7070
use alloc::libc_heap::malloc_raw;
7171
use collections::string::String;
72+
use collections::hash;
7273
use core::kinds::marker;
7374
use core::mem;
7475
use core::ptr;
@@ -116,6 +117,22 @@ impl PartialEq for CString {
116117
}
117118
}
118119

120+
impl PartialOrd for CString {
121+
#[inline]
122+
fn partial_cmp(&self, other: &CString) -> Option<Ordering> {
123+
self.as_bytes().partial_cmp(&other.as_bytes())
124+
}
125+
}
126+
127+
impl Eq for CString {}
128+
129+
impl<S: hash::Writer> hash::Hash<S> for CString {
130+
#[inline]
131+
fn hash(&self, state: &mut S) {
132+
self.as_bytes().hash(state)
133+
}
134+
}
135+
119136
impl CString {
120137
/// Create a C String from a pointer.
121138
pub unsafe fn new(buf: *const libc::c_char, owns_buffer: bool) -> CString {

src/librustrt/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
html_root_url = "http://doc.rust-lang.org/0.11.0/")]
1818

1919
#![feature(macro_rules, phase, globs, thread_local, managed_boxes, asm)]
20-
#![feature(linkage, lang_items, unsafe_destructor)]
20+
#![feature(linkage, lang_items, unsafe_destructor, default_type_params)]
2121
#![no_std]
2222
#![experimental]
2323

src/librustrt/rtio.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub struct ProcessConfig<'a> {
7575

7676
/// Optional environment to specify for the program. If this is None, then
7777
/// it will inherit the current process's environment.
78-
pub env: Option<&'a [(CString, CString)]>,
78+
pub env: Option<&'a [(&'a CString, &'a CString)]>,
7979

8080
/// Optional working directory for the new process. If this is None, then
8181
/// the current directory of the running process is inherited.

src/librustuv/process.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ fn with_argv<T>(prog: &CString, args: &[CString],
193193
}
194194

195195
/// Converts the environment to the env array expected by libuv
196-
fn with_env<T>(env: Option<&[(CString, CString)]>,
196+
fn with_env<T>(env: Option<&[(&CString, &CString)]>,
197197
cb: |*const *const libc::c_char| -> T) -> T {
198198
// We can pass a char** for envp, which is a null-terminated array
199199
// of "k=v\0" strings. Since we must create these strings locally,

src/libstd/io/process.rs

+86-20
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use prelude::*;
1616

1717
use str;
1818
use fmt;
19+
use os;
1920
use io::{IoResult, IoError};
2021
use io;
2122
use libc;
@@ -24,6 +25,7 @@ use owned::Box;
2425
use rt::rtio::{RtioProcess, ProcessConfig, IoFactory, LocalIo};
2526
use rt::rtio;
2627
use c_str::CString;
28+
use collections::HashMap;
2729

2830
/// Signal a process to exit, without forcibly killing it. Corresponds to
2931
/// SIGTERM on unix platforms.
@@ -78,6 +80,9 @@ pub struct Process {
7880
pub extra_io: Vec<Option<io::PipeStream>>,
7981
}
8082

83+
/// A HashMap representation of environment variables.
84+
pub type EnvMap = HashMap<CString, CString>;
85+
8186
/// The `Command` type acts as a process builder, providing fine-grained control
8287
/// over how a new process should be spawned. A default configuration can be
8388
/// generated using `Command::new(program)`, where `program` gives a path to the
@@ -100,7 +105,7 @@ pub struct Command {
100105
// methods below, and serialized into rt::rtio::ProcessConfig.
101106
program: CString,
102107
args: Vec<CString>,
103-
env: Option<Vec<(CString, CString)>>,
108+
env: Option<EnvMap>,
104109
cwd: Option<CString>,
105110
stdin: StdioContainer,
106111
stdout: StdioContainer,
@@ -147,31 +152,53 @@ impl Command {
147152
}
148153

149154
/// Add an argument to pass to the program.
150-
pub fn arg<'a, T:ToCStr>(&'a mut self, arg: T) -> &'a mut Command {
155+
pub fn arg<'a, T: ToCStr>(&'a mut self, arg: T) -> &'a mut Command {
151156
self.args.push(arg.to_c_str());
152157
self
153158
}
154159

155160
/// Add multiple arguments to pass to the program.
156-
pub fn args<'a, T:ToCStr>(&'a mut self, args: &[T]) -> &'a mut Command {
161+
pub fn args<'a, T: ToCStr>(&'a mut self, args: &[T]) -> &'a mut Command {
157162
self.args.extend(args.iter().map(|arg| arg.to_c_str()));;
158163
self
159164
}
165+
// Get a mutable borrow of the environment variable map for this `Command`.
166+
fn get_env_map<'a>(&'a mut self) -> &'a mut EnvMap {
167+
match self.env {
168+
Some(ref mut map) => map,
169+
None => {
170+
// if the env is currently just inheriting from the parent's,
171+
// materialize the parent's env into a hashtable.
172+
self.env = Some(os::env_as_bytes().move_iter()
173+
.map(|(k, v)| (k.as_slice().to_c_str(),
174+
v.as_slice().to_c_str()))
175+
.collect());
176+
self.env.as_mut().unwrap()
177+
}
178+
}
179+
}
160180

161-
/// Sets the environment for the child process (rather than inheriting it
162-
/// from the current process).
163-
164-
// FIXME (#13851): We should change this interface to allow clients to (1)
165-
// build up the env vector incrementally and (2) allow both inheriting the
166-
// current process's environment AND overriding/adding additional
167-
// environment variables. The underlying syscalls assume that the
168-
// environment has no duplicate names, so we really want to use a hashtable
169-
// to compute the environment to pass down to the syscall; resolving issue
170-
// #13851 will make it possible to use the standard hashtable.
171-
pub fn env<'a, T:ToCStr>(&'a mut self, env: &[(T,T)]) -> &'a mut Command {
172-
self.env = Some(env.iter().map(|&(ref name, ref val)| {
173-
(name.to_c_str(), val.to_c_str())
174-
}).collect());
181+
/// Inserts or updates an environment variable mapping.
182+
pub fn env<'a, T: ToCStr, U: ToCStr>(&'a mut self, key: T, val: U)
183+
-> &'a mut Command {
184+
self.get_env_map().insert(key.to_c_str(), val.to_c_str());
185+
self
186+
}
187+
188+
/// Removes an environment variable mapping.
189+
pub fn env_remove<'a, T: ToCStr>(&'a mut self, key: T) -> &'a mut Command {
190+
self.get_env_map().remove(&key.to_c_str());
191+
self
192+
}
193+
194+
/// Sets the entire environment map for the child process.
195+
///
196+
/// If the given slice contains multiple instances of an environment
197+
/// variable, the *rightmost* instance will determine the value.
198+
pub fn env_set_all<'a, T: ToCStr, U: ToCStr>(&'a mut self, env: &[(T,U)])
199+
-> &'a mut Command {
200+
self.env = Some(env.iter().map(|&(ref k, ref v)| (k.to_c_str(), v.to_c_str()))
201+
.collect());
175202
self
176203
}
177204

@@ -245,10 +272,15 @@ impl Command {
245272
let extra_io: Vec<rtio::StdioContainer> =
246273
self.extra_io.iter().map(|x| to_rtio(*x)).collect();
247274
LocalIo::maybe_raise(|io| {
275+
let env = match self.env {
276+
None => None,
277+
Some(ref env_map) =>
278+
Some(env_map.iter().collect::<Vec<_>>())
279+
};
248280
let cfg = ProcessConfig {
249281
program: &self.program,
250282
args: self.args.as_slice(),
251-
env: self.env.as_ref().map(|env| env.as_slice()),
283+
env: env.as_ref().map(|e| e.as_slice()),
252284
cwd: self.cwd.as_ref(),
253285
stdin: to_rtio(self.stdin),
254286
stdout: to_rtio(self.stdout),
@@ -872,16 +904,50 @@ mod tests {
872904
}
873905
})
874906

875-
iotest!(fn test_add_to_env() {
907+
iotest!(fn test_override_env() {
876908
let new_env = vec![("RUN_TEST_NEW_ENV", "123")];
877-
let prog = env_cmd().env(new_env.as_slice()).spawn().unwrap();
909+
let prog = env_cmd().env_set_all(new_env.as_slice()).spawn().unwrap();
878910
let result = prog.wait_with_output().unwrap();
879911
let output = str::from_utf8_lossy(result.output.as_slice()).into_string();
880912

881913
assert!(output.as_slice().contains("RUN_TEST_NEW_ENV=123"),
882914
"didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output);
883915
})
884916

917+
iotest!(fn test_add_to_env() {
918+
let prog = env_cmd().env("RUN_TEST_NEW_ENV", "123").spawn().unwrap();
919+
let result = prog.wait_with_output().unwrap();
920+
let output = str::from_utf8_lossy(result.output.as_slice()).into_string();
921+
922+
assert!(output.as_slice().contains("RUN_TEST_NEW_ENV=123"),
923+
"didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output);
924+
})
925+
926+
iotest!(fn test_remove_from_env() {
927+
use os;
928+
929+
// save original environment
930+
let old_env = os::getenv("RUN_TEST_NEW_ENV");
931+
932+
os::setenv("RUN_TEST_NEW_ENV", "123");
933+
let prog = env_cmd().env_remove("RUN_TEST_NEW_ENV").spawn().unwrap();
934+
let result = prog.wait_with_output().unwrap();
935+
let output = str::from_utf8_lossy(result.output.as_slice()).into_string();
936+
937+
// restore original environment
938+
match old_env {
939+
None => {
940+
os::unsetenv("RUN_TEST_NEW_ENV");
941+
}
942+
Some(val) => {
943+
os::setenv("RUN_TEST_NEW_ENV", val.as_slice());
944+
}
945+
}
946+
947+
assert!(!output.as_slice().contains("RUN_TEST_NEW_ENV"),
948+
"found RUN_TEST_NEW_ENV inside of:\n\n{}", output);
949+
})
950+
885951
#[cfg(unix)]
886952
pub fn sleeper() -> Process {
887953
Command::new("sleep").arg("1000").spawn().unwrap()

src/test/run-pass/backtrace.rs

+3-13
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,8 @@ fn double() {
3737
}
3838

3939
fn runtest(me: &str) {
40-
let mut env = os::env().move_iter()
41-
.map(|(ref k, ref v)| {
42-
(k.to_string(), v.to_string())
43-
}).collect::<Vec<(String,String)>>();
44-
match env.iter()
45-
.position(|&(ref s, _)| "RUST_BACKTRACE" == s.as_slice()) {
46-
Some(i) => { env.remove(i); }
47-
None => {}
48-
}
49-
env.push(("RUST_BACKTRACE".to_string(), "1".to_string()));
50-
5140
// Make sure that the stack trace is printed
52-
let mut p = Command::new(me).arg("fail").env(env.as_slice()).spawn().unwrap();
41+
let mut p = Command::new(me).arg("fail").env("RUST_BACKTRACE", "1").spawn().unwrap();
5342
let out = p.wait_with_output().unwrap();
5443
assert!(!out.status.success());
5544
let s = str::from_utf8(out.error.as_slice()).unwrap();
@@ -73,7 +62,8 @@ fn runtest(me: &str) {
7362
"bad output3: {}", s);
7463

7564
// Make sure a stack trace isn't printed too many times
76-
let mut p = Command::new(me).arg("double-fail").env(env.as_slice()).spawn().unwrap();
65+
let mut p = Command::new(me).arg("double-fail")
66+
.env("RUST_BACKTRACE", "1").spawn().unwrap();
7767
let out = p.wait_with_output().unwrap();
7868
assert!(!out.status.success());
7969
let s = str::from_utf8(out.error.as_slice()).unwrap();

0 commit comments

Comments
 (0)