@@ -134,7 +134,11 @@ pub fn wait_for_parent() -> Result<()> {
134134
135135pub fn do_add_to_path ( methods : & [ PathUpdateMethod ] ) -> Result < ( ) > {
136136 assert ! ( methods. len( ) == 1 && methods[ 0 ] == PathUpdateMethod :: Windows ) ;
137+ let new_path = _with_path_cargo_home_bin ( _add_to_path) ?;
138+ _apply_new_path ( new_path)
139+ }
137140
141+ fn _apply_new_path ( new_path : Option < String > ) -> Result < ( ) > {
138142 use std:: ptr;
139143 use winapi:: shared:: minwindef:: * ;
140144 use winapi:: um:: winuser:: {
@@ -143,39 +147,29 @@ pub fn do_add_to_path(methods: &[PathUpdateMethod]) -> Result<()> {
143147 use winreg:: enums:: { RegType , HKEY_CURRENT_USER , KEY_READ , KEY_WRITE } ;
144148 use winreg:: { RegKey , RegValue } ;
145149
146- let old_path = if let Some ( s) = get_windows_path_var ( ) ? {
147- s
148- } else {
149- // Non-unicode path
150- return Ok ( ( ) ) ;
150+ let new_path = match new_path {
151+ Some ( new_path) => new_path,
152+ None => return Ok ( ( ) ) , // No need to set the path
151153 } ;
152154
153- let mut new_path = utils:: cargo_home ( ) ?
154- . join ( "bin" )
155- . to_string_lossy ( )
156- . into_owned ( ) ;
157- if old_path. contains ( & new_path) {
158- return Ok ( ( ) ) ;
159- }
160-
161- if !old_path. is_empty ( ) {
162- new_path. push_str ( ";" ) ;
163- new_path. push_str ( & old_path) ;
164- }
165-
166155 let root = RegKey :: predef ( HKEY_CURRENT_USER ) ;
167156 let environment = root
168157 . open_subkey_with_flags ( "Environment" , KEY_READ | KEY_WRITE )
169158 . chain_err ( || ErrorKind :: PermissionDenied ) ?;
170159
171- let reg_value = RegValue {
172- bytes : utils:: string_to_winreg_bytes ( & new_path) ,
173- vtype : RegType :: REG_EXPAND_SZ ,
174- } ;
175-
176- environment
177- . set_raw_value ( "PATH" , & reg_value)
178- . chain_err ( || ErrorKind :: PermissionDenied ) ?;
160+ if new_path. is_empty ( ) {
161+ environment
162+ . delete_value ( "PATH" )
163+ . chain_err ( || ErrorKind :: PermissionDenied ) ?;
164+ } else {
165+ let reg_value = RegValue {
166+ bytes : utils:: string_to_winreg_bytes ( & new_path) ,
167+ vtype : RegType :: REG_EXPAND_SZ ,
168+ } ;
169+ environment
170+ . set_raw_value ( "PATH" , & reg_value)
171+ . chain_err ( || ErrorKind :: PermissionDenied ) ?;
172+ }
179173
180174 // Tell other processes to update their environment
181175 unsafe {
@@ -222,8 +216,23 @@ fn get_windows_path_var() -> Result<Option<String>> {
222216 }
223217}
224218
219+ // Returns None if the existing old_path does not need changing, otherwise
220+ // prepends the path_str to old_path, handling empty old_path appropriately.
221+ fn _add_to_path ( old_path : & str , path_str : String ) -> Option < String > {
222+ if old_path. is_empty ( ) {
223+ Some ( path_str)
224+ } else if old_path. contains ( & path_str) {
225+ None
226+ } else {
227+ let mut new_path = path_str. clone ( ) ;
228+ new_path. push_str ( ";" ) ;
229+ new_path. push_str ( & old_path) ;
230+ Some ( new_path)
231+ }
232+ }
233+
225234// Returns None if the existing old_path does not need changing
226- fn _remove_from_path ( old_path : & str , path_str : & str ) -> Option < String > {
235+ fn _remove_from_path ( old_path : & str , path_str : String ) -> Option < String > {
227236 let idx = old_path. find ( & path_str) ?;
228237 // If there's a trailing semicolon (likely, since we probably added one
229238 // during install), include that in the substring to remove. We don't search
@@ -244,71 +253,22 @@ fn _remove_from_path(old_path: &str, path_str: &str) -> Option<String> {
244253 Some ( new_path)
245254}
246255
247- fn _path_without_cargo_home_bin ( ) -> Result < Option < String > > {
248- let old_path = if let Some ( s) = get_windows_path_var ( ) ? {
249- s
250- } else {
251- // Non-unicode path
252- return Ok ( None ) ;
253- } ;
254-
256+ fn _with_path_cargo_home_bin < F > ( f : F ) -> Result < Option < String > >
257+ where
258+ F : FnOnce ( & str , String ) -> Option < String > ,
259+ {
260+ let windows_path = get_windows_path_var ( ) ?;
255261 let path_str = utils:: cargo_home ( ) ?
256262 . join ( "bin" )
257263 . to_string_lossy ( )
258264 . into_owned ( ) ;
259-
260- Ok ( _remove_from_path ( & old_path, & path_str) )
265+ Ok ( windows_path. and_then ( |old_path| f ( & old_path, path_str) ) )
261266}
262267
263268pub fn do_remove_from_path ( methods : & [ PathUpdateMethod ] ) -> Result < ( ) > {
264269 assert ! ( methods. len( ) == 1 && methods[ 0 ] == PathUpdateMethod :: Windows ) ;
265-
266- use std:: ptr;
267- use winapi:: shared:: minwindef:: * ;
268- use winapi:: um:: winuser:: {
269- SendMessageTimeoutA , HWND_BROADCAST , SMTO_ABORTIFHUNG , WM_SETTINGCHANGE ,
270- } ;
271- use winreg:: enums:: { RegType , HKEY_CURRENT_USER , KEY_READ , KEY_WRITE } ;
272- use winreg:: { RegKey , RegValue } ;
273-
274- let new_path = match _path_without_cargo_home_bin ( ) ? {
275- Some ( new_path) => new_path,
276- None => return Ok ( ( ) ) , // No need to set the path
277- } ;
278-
279- let root = RegKey :: predef ( HKEY_CURRENT_USER ) ;
280- let environment = root
281- . open_subkey_with_flags ( "Environment" , KEY_READ | KEY_WRITE )
282- . chain_err ( || ErrorKind :: PermissionDenied ) ?;
283-
284- if new_path. is_empty ( ) {
285- environment
286- . delete_value ( "PATH" )
287- . chain_err ( || ErrorKind :: PermissionDenied ) ?;
288- } else {
289- let reg_value = RegValue {
290- bytes : utils:: string_to_winreg_bytes ( & new_path) ,
291- vtype : RegType :: REG_EXPAND_SZ ,
292- } ;
293- environment
294- . set_raw_value ( "PATH" , & reg_value)
295- . chain_err ( || ErrorKind :: PermissionDenied ) ?;
296- }
297-
298- // Tell other processes to update their environment
299- unsafe {
300- SendMessageTimeoutA (
301- HWND_BROADCAST ,
302- WM_SETTINGCHANGE ,
303- 0 as WPARAM ,
304- "Environment\0 " . as_ptr ( ) as LPARAM ,
305- SMTO_ABORTIFHUNG ,
306- 5000 ,
307- ptr:: null_mut ( ) ,
308- ) ;
309- }
310-
311- Ok ( ( ) )
270+ let new_path = _with_path_cargo_home_bin ( _remove_from_path) ?;
271+ _apply_new_path ( new_path)
312272}
313273
314274pub fn run_update ( setup_path : & Path ) -> Result < utils:: ExitCode > {
@@ -503,9 +463,26 @@ mod tests {
503463 }
504464
505465 #[ test]
506- fn windows_uninstall_doesnt_mess_with_a_non_unicode_path ( ) {
466+ fn windows_install_does_not_add_path_twice ( ) {
467+ assert_eq ! (
468+ None ,
469+ super :: _add_to_path(
470+ r"c:\users\example\.cargo\bin;foo" ,
471+ r"c:\users\example\.cargo\bin" . into( )
472+ )
473+ ) ;
474+ }
475+
476+ #[ test]
477+ fn windows_doesnt_mess_with_a_non_unicode_path ( ) {
507478 // This writes an error, so we want a sink for it.
508- let tp = Box :: new ( currentprocess:: TestProcess :: default ( ) ) ;
479+ let tp = Box :: new ( currentprocess:: TestProcess {
480+ vars : [ ( "HOME" . to_string ( ) , "/unused" . to_string ( ) ) ]
481+ . iter ( )
482+ . cloned ( )
483+ . collect ( ) ,
484+ ..Default :: default ( )
485+ } ) ;
509486 with_registry_edits ( & || {
510487 currentprocess:: with ( tp. clone ( ) , || {
511488 let root = RegKey :: predef ( HKEY_CURRENT_USER ) ;
@@ -522,7 +499,10 @@ mod tests {
522499 } ;
523500 environment. set_raw_value ( "PATH" , & reg_value) . unwrap ( ) ;
524501 // Ok(None) signals no change to the PATH setting layer
525- assert_eq ! ( None , super :: _path_without_cargo_home_bin( ) . unwrap( ) ) ;
502+ fn panic ( _: & str , _: String ) -> Option < String > {
503+ panic ! ( "called" ) ;
504+ }
505+ assert_eq ! ( None , super :: _with_path_cargo_home_bin( panic) . unwrap( ) ) ;
526506 } )
527507 } ) ;
528508 assert_eq ! (
@@ -532,13 +512,38 @@ mod tests {
532512 ) ;
533513 }
534514
515+ #[ test]
516+ fn windows_path_regkey_type ( ) {
517+ // per issue #261, setting PATH should use REG_EXPAND_SZ.
518+ let tp = Box :: new ( currentprocess:: TestProcess :: default ( ) ) ;
519+ with_registry_edits ( & || {
520+ currentprocess:: with ( tp. clone ( ) , || {
521+ let root = RegKey :: predef ( HKEY_CURRENT_USER ) ;
522+ let environment = root
523+ . open_subkey_with_flags ( "Environment" , KEY_READ | KEY_WRITE )
524+ . unwrap ( ) ;
525+ environment. delete_value ( "PATH" ) . unwrap ( ) ;
526+
527+ assert_eq ! ( ( ) , super :: _apply_new_path( Some ( "foo" . into( ) ) ) . unwrap( ) ) ;
528+
529+ let root = RegKey :: predef ( HKEY_CURRENT_USER ) ;
530+ let environment = root
531+ . open_subkey_with_flags ( "Environment" , KEY_READ | KEY_WRITE )
532+ . unwrap ( ) ;
533+ let path = environment. get_raw_value ( "PATH" ) . unwrap ( ) ;
534+ assert_eq ! ( path. vtype, RegType :: REG_EXPAND_SZ ) ;
535+ assert_eq ! ( utils:: string_to_winreg_bytes( "foo" ) , & path. bytes[ ..] ) ;
536+ } )
537+ } ) ;
538+ }
539+
535540 #[ test]
536541 fn windows_uninstall_removes_semicolon_from_path_prefix ( ) {
537542 assert_eq ! (
538543 "foo" ,
539544 super :: _remove_from_path(
540545 r"c:\users\example\.cargo\bin;foo" ,
541- r"c:\users\example\.cargo\bin"
546+ r"c:\users\example\.cargo\bin" . into ( )
542547 )
543548 . unwrap( )
544549 )
@@ -550,7 +555,7 @@ mod tests {
550555 "foo" ,
551556 super :: _remove_from_path(
552557 r"foo;c:\users\example\.cargo\bin" ,
553- r"c:\users\example\.cargo\bin"
558+ r"c:\users\example\.cargo\bin" . into ( )
554559 )
555560 . unwrap( )
556561 )
0 commit comments