@@ -16,6 +16,7 @@ use prelude::*;
16
16
17
17
use str;
18
18
use fmt;
19
+ use os;
19
20
use io:: { IoResult , IoError } ;
20
21
use io;
21
22
use libc;
@@ -24,6 +25,7 @@ use owned::Box;
24
25
use rt:: rtio:: { RtioProcess , ProcessConfig , IoFactory , LocalIo } ;
25
26
use rt:: rtio;
26
27
use c_str:: CString ;
28
+ use collections:: HashMap ;
27
29
28
30
/// Signal a process to exit, without forcibly killing it. Corresponds to
29
31
/// SIGTERM on unix platforms.
@@ -78,6 +80,9 @@ pub struct Process {
78
80
pub extra_io : Vec < Option < io:: PipeStream > > ,
79
81
}
80
82
83
+ /// A HashMap representation of environment variables.
84
+ pub type EnvMap = HashMap < CString , CString > ;
85
+
81
86
/// The `Command` type acts as a process builder, providing fine-grained control
82
87
/// over how a new process should be spawned. A default configuration can be
83
88
/// generated using `Command::new(program)`, where `program` gives a path to the
@@ -100,7 +105,7 @@ pub struct Command {
100
105
// methods below, and serialized into rt::rtio::ProcessConfig.
101
106
program : CString ,
102
107
args : Vec < CString > ,
103
- env : Option < Vec < ( CString , CString ) > > ,
108
+ env : Option < EnvMap > ,
104
109
cwd : Option < CString > ,
105
110
stdin : StdioContainer ,
106
111
stdout : StdioContainer ,
@@ -147,31 +152,53 @@ impl Command {
147
152
}
148
153
149
154
/// 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 {
151
156
self . args . push ( arg. to_c_str ( ) ) ;
152
157
self
153
158
}
154
159
155
160
/// 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 {
157
162
self . args . extend ( args. iter ( ) . map ( |arg| arg. to_c_str ( ) ) ) ; ;
158
163
self
159
164
}
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
+ }
160
180
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 ( ) ) ;
175
202
self
176
203
}
177
204
@@ -245,10 +272,15 @@ impl Command {
245
272
let extra_io: Vec < rtio:: StdioContainer > =
246
273
self . extra_io . iter ( ) . map ( |x| to_rtio ( * x) ) . collect ( ) ;
247
274
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
+ } ;
248
280
let cfg = ProcessConfig {
249
281
program : & self . program ,
250
282
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 ( ) ) ,
252
284
cwd : self . cwd . as_ref ( ) ,
253
285
stdin : to_rtio ( self . stdin ) ,
254
286
stdout : to_rtio ( self . stdout ) ,
@@ -872,16 +904,50 @@ mod tests {
872
904
}
873
905
} )
874
906
875
- iotest ! ( fn test_add_to_env ( ) {
907
+ iotest ! ( fn test_override_env ( ) {
876
908
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( ) ;
878
910
let result = prog. wait_with_output( ) . unwrap( ) ;
879
911
let output = str :: from_utf8_lossy( result. output. as_slice( ) ) . into_string( ) ;
880
912
881
913
assert!( output. as_slice( ) . contains( "RUN_TEST_NEW_ENV=123" ) ,
882
914
"didn't find RUN_TEST_NEW_ENV inside of:\n \n {}" , output) ;
883
915
} )
884
916
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
+
885
951
#[ cfg( unix) ]
886
952
pub fn sleeper ( ) -> Process {
887
953
Command :: new ( "sleep" ) . arg ( "1000" ) . spawn ( ) . unwrap ( )
0 commit comments