@@ -16,6 +16,7 @@ use prelude::*;
1616
1717use str;
1818use fmt;
19+ use os;
1920use io:: { IoResult , IoError } ;
2021use io;
2122use libc;
@@ -24,6 +25,7 @@ use owned::Box;
2425use rt:: rtio:: { RtioProcess , ProcessConfig , IoFactory , LocalIo } ;
2526use rt:: rtio;
2627use 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 ( )
0 commit comments