@@ -134,7 +134,11 @@ pub fn wait_for_parent() -> Result<()> {
134
134
135
135
pub fn do_add_to_path ( methods : & [ PathUpdateMethod ] ) -> Result < ( ) > {
136
136
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
+ }
137
140
141
+ fn _apply_new_path ( new_path : Option < String > ) -> Result < ( ) > {
138
142
use std:: ptr;
139
143
use winapi:: shared:: minwindef:: * ;
140
144
use winapi:: um:: winuser:: {
@@ -143,39 +147,29 @@ pub fn do_add_to_path(methods: &[PathUpdateMethod]) -> Result<()> {
143
147
use winreg:: enums:: { RegType , HKEY_CURRENT_USER , KEY_READ , KEY_WRITE } ;
144
148
use winreg:: { RegKey , RegValue } ;
145
149
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
151
153
} ;
152
154
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
-
166
155
let root = RegKey :: predef ( HKEY_CURRENT_USER ) ;
167
156
let environment = root
168
157
. open_subkey_with_flags ( "Environment" , KEY_READ | KEY_WRITE )
169
158
. chain_err ( || ErrorKind :: PermissionDenied ) ?;
170
159
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
+ }
179
173
180
174
// Tell other processes to update their environment
181
175
unsafe {
@@ -222,8 +216,23 @@ fn get_windows_path_var() -> Result<Option<String>> {
222
216
}
223
217
}
224
218
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
+
225
234
// 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 > {
227
236
let idx = old_path. find ( & path_str) ?;
228
237
// If there's a trailing semicolon (likely, since we probably added one
229
238
// 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> {
244
253
Some ( new_path)
245
254
}
246
255
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 ( ) ?;
255
261
let path_str = utils:: cargo_home ( ) ?
256
262
. join ( "bin" )
257
263
. to_string_lossy ( )
258
264
. 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) ) )
261
266
}
262
267
263
268
pub fn do_remove_from_path ( methods : & [ PathUpdateMethod ] ) -> Result < ( ) > {
264
269
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)
312
272
}
313
273
314
274
pub fn run_update ( setup_path : & Path ) -> Result < utils:: ExitCode > {
@@ -503,9 +463,26 @@ mod tests {
503
463
}
504
464
505
465
#[ 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 ( ) {
507
478
// 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
+ } ) ;
509
486
with_registry_edits ( & || {
510
487
currentprocess:: with ( tp. clone ( ) , || {
511
488
let root = RegKey :: predef ( HKEY_CURRENT_USER ) ;
@@ -522,7 +499,10 @@ mod tests {
522
499
} ;
523
500
environment. set_raw_value ( "PATH" , & reg_value) . unwrap ( ) ;
524
501
// 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( ) ) ;
526
506
} )
527
507
} ) ;
528
508
assert_eq ! (
@@ -532,13 +512,38 @@ mod tests {
532
512
) ;
533
513
}
534
514
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
+
535
540
#[ test]
536
541
fn windows_uninstall_removes_semicolon_from_path_prefix ( ) {
537
542
assert_eq ! (
538
543
"foo" ,
539
544
super :: _remove_from_path(
540
545
r"c:\users\example\.cargo\bin;foo" ,
541
- r"c:\users\example\.cargo\bin"
546
+ r"c:\users\example\.cargo\bin" . into ( )
542
547
)
543
548
. unwrap( )
544
549
)
@@ -550,7 +555,7 @@ mod tests {
550
555
"foo" ,
551
556
super :: _remove_from_path(
552
557
r"foo;c:\users\example\.cargo\bin" ,
553
- r"c:\users\example\.cargo\bin"
558
+ r"c:\users\example\.cargo\bin" . into ( )
554
559
)
555
560
. unwrap( )
556
561
)
0 commit comments