1
1
use std:: io;
2
2
use std:: path:: { Path , PathBuf } ;
3
3
4
- // FIXME(jieyouxu): modify create_symlink to panic on windows.
5
-
6
- /// Creates a new symlink to a path on the filesystem, adjusting for Windows or Unix.
7
- #[ cfg( target_family = "windows" ) ]
8
- pub fn create_symlink < P : AsRef < Path > , Q : AsRef < Path > > ( original : P , link : Q ) {
9
- if link. as_ref ( ) . exists ( ) {
10
- std:: fs:: remove_dir ( link. as_ref ( ) ) . unwrap ( ) ;
11
- }
12
- if original. as_ref ( ) . is_file ( ) {
13
- std:: os:: windows:: fs:: symlink_file ( original. as_ref ( ) , link. as_ref ( ) ) . expect ( & format ! (
14
- "failed to create symlink {:?} for {:?}" ,
15
- link. as_ref( ) . display( ) ,
16
- original. as_ref( ) . display( ) ,
17
- ) ) ;
18
- } else {
19
- std:: os:: windows:: fs:: symlink_dir ( original. as_ref ( ) , link. as_ref ( ) ) . expect ( & format ! (
20
- "failed to create symlink {:?} for {:?}" ,
21
- link. as_ref( ) . display( ) ,
22
- original. as_ref( ) . display( ) ,
23
- ) ) ;
24
- }
25
- }
26
-
27
- /// Creates a new symlink to a path on the filesystem, adjusting for Windows or Unix.
28
- #[ cfg( target_family = "unix" ) ]
29
- pub fn create_symlink < P : AsRef < Path > , Q : AsRef < Path > > ( original : P , link : Q ) {
30
- if link. as_ref ( ) . exists ( ) {
31
- std:: fs:: remove_dir ( link. as_ref ( ) ) . unwrap ( ) ;
32
- }
33
- std:: os:: unix:: fs:: symlink ( original. as_ref ( ) , link. as_ref ( ) ) . expect ( & format ! (
34
- "failed to create symlink {:?} for {:?}" ,
35
- link. as_ref( ) . display( ) ,
36
- original. as_ref( ) . display( ) ,
37
- ) ) ;
38
- }
39
-
40
4
/// Copy a directory into another.
41
5
pub fn copy_dir_all ( src : impl AsRef < Path > , dst : impl AsRef < Path > ) {
42
6
fn copy_dir_all_inner ( src : impl AsRef < Path > , dst : impl AsRef < Path > ) -> io:: Result < ( ) > {
@@ -50,7 +14,34 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
50
14
if ty. is_dir ( ) {
51
15
copy_dir_all_inner ( entry. path ( ) , dst. join ( entry. file_name ( ) ) ) ?;
52
16
} else if ty. is_symlink ( ) {
53
- copy_symlink ( entry. path ( ) , dst. join ( entry. file_name ( ) ) ) ?;
17
+ // Traverse symlink once to find path of target entity.
18
+ let target_path = std:: fs:: read_link ( entry. path ( ) ) ?;
19
+
20
+ let new_symlink_path = dst. join ( entry. file_name ( ) ) ;
21
+ #[ cfg( windows) ]
22
+ {
23
+ // Find metadata about target entity (shallow, no further symlink traversal in
24
+ // case target is also a symlink).
25
+ let target_metadata = std:: fs:: symlink_metadata ( & target_path) ?;
26
+ let target_file_type = target_metadata. file_type ( ) ;
27
+ if target_file_type. is_dir ( ) {
28
+ std:: os:: windows:: fs:: symlink_dir ( & target_path, new_symlink_path) ?;
29
+ } else {
30
+ // Target may be a file or another symlink, in any case we can use
31
+ // `symlink_file` here.
32
+ std:: os:: windows:: fs:: symlink_file ( & target_path, new_symlink_path) ?;
33
+ }
34
+ }
35
+ #[ cfg( unix) ]
36
+ {
37
+ std:: os:: unix:: fs:: symlink ( target_path, new_symlink_path) ?;
38
+ }
39
+ #[ cfg( not( any( windows, unix) ) ) ]
40
+ {
41
+ // Technically there's also wasi, but I have no clue about wasi symlink
42
+ // semantics and which wasi targets / environment support symlinks.
43
+ unimplemented ! ( "unsupported target" ) ;
44
+ }
54
45
} else {
55
46
std:: fs:: copy ( entry. path ( ) , dst. join ( entry. file_name ( ) ) ) ?;
56
47
}
@@ -69,12 +60,6 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
69
60
}
70
61
}
71
62
72
- fn copy_symlink < P : AsRef < Path > , Q : AsRef < Path > > ( from : P , to : Q ) -> io:: Result < ( ) > {
73
- let target_path = std:: fs:: read_link ( from) . unwrap ( ) ;
74
- create_symlink ( target_path, to) ;
75
- Ok ( ( ) )
76
- }
77
-
78
63
/// Helper for reading entries in a given directory.
79
64
pub fn read_dir_entries < P : AsRef < Path > , F : FnMut ( & Path ) > ( dir : P , mut callback : F ) {
80
65
for entry in read_dir ( dir) {
@@ -85,8 +70,17 @@ pub fn read_dir_entries<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F
85
70
/// A wrapper around [`std::fs::remove_file`] which includes the file path in the panic message.
86
71
#[ track_caller]
87
72
pub fn remove_file < P : AsRef < Path > > ( path : P ) {
88
- std:: fs:: remove_file ( path. as_ref ( ) )
89
- . expect ( & format ! ( "the file in path \" {}\" could not be removed" , path. as_ref( ) . display( ) ) ) ;
73
+ if let Err ( e) = std:: fs:: remove_file ( path. as_ref ( ) ) {
74
+ panic ! ( "failed to remove file at `{}`: {e}" , path. as_ref( ) . display( ) ) ;
75
+ }
76
+ }
77
+
78
+ /// A wrapper around [`std::fs::remove_dir`] which includes the directory path in the panic message.
79
+ #[ track_caller]
80
+ pub fn remove_dir < P : AsRef < Path > > ( path : P ) {
81
+ if let Err ( e) = std:: fs:: remove_dir ( path. as_ref ( ) ) {
82
+ panic ! ( "failed to remove directory at `{}`: {e}" , path. as_ref( ) . display( ) ) ;
83
+ }
90
84
}
91
85
92
86
/// A wrapper around [`std::fs::copy`] which includes the file path in the panic message.
@@ -165,13 +159,32 @@ pub fn create_dir_all<P: AsRef<Path>>(path: P) {
165
159
) ) ;
166
160
}
167
161
168
- /// A wrapper around [`std::fs::metadata`] which includes the file path in the panic message.
162
+ /// A wrapper around [`std::fs::metadata`] which includes the file path in the panic message. Note
163
+ /// that this will traverse symlinks and will return metadata about the target file. Use
164
+ /// [`symlink_metadata`] if you don't want to traverse symlinks.
165
+ ///
166
+ /// See [`std::fs::metadata`] docs for more details.
169
167
#[ track_caller]
170
168
pub fn metadata < P : AsRef < Path > > ( path : P ) -> std:: fs:: Metadata {
171
- std:: fs:: metadata ( path. as_ref ( ) ) . expect ( & format ! (
172
- "the file's metadata in path \" {}\" could not be read" ,
173
- path. as_ref( ) . display( )
174
- ) )
169
+ match std:: fs:: metadata ( path. as_ref ( ) ) {
170
+ Ok ( m) => m,
171
+ Err ( e) => panic ! ( "failed to read file metadata at `{}`: {e}" , path. as_ref( ) . display( ) ) ,
172
+ }
173
+ }
174
+
175
+ /// A wrapper around [`std::fs::symlink_metadata`] which includes the file path in the panic
176
+ /// message. Note that this will not traverse symlinks and will return metadata about the filesystem
177
+ /// entity itself. Use [`metadata`] if you want to traverse symlinks.
178
+ ///
179
+ /// See [`std::fs::symlink_metadata`] docs for more details.
180
+ #[ track_caller]
181
+ pub fn symlink_metadata < P : AsRef < Path > > ( path : P ) -> std:: fs:: Metadata {
182
+ match std:: fs:: symlink_metadata ( path. as_ref ( ) ) {
183
+ Ok ( m) => m,
184
+ Err ( e) => {
185
+ panic ! ( "failed to read file metadata (shallow) at `{}`: {e}" , path. as_ref( ) . display( ) )
186
+ }
187
+ }
175
188
}
176
189
177
190
/// A wrapper around [`std::fs::rename`] which includes the file path in the panic message.
@@ -205,3 +218,73 @@ pub fn shallow_find_dir_entries<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
205
218
}
206
219
output
207
220
}
221
+
222
+ /// Create a new symbolic link to a directory.
223
+ ///
224
+ /// # Removing the symlink
225
+ ///
226
+ /// - On Windows, a symlink-to-directory needs to be removed with a corresponding [`fs::remove_dir`]
227
+ /// and not [`fs::remove_file`].
228
+ /// - On Unix, remove the symlink with [`fs::remove_file`].
229
+ ///
230
+ /// [`fs::remove_dir`]: crate::fs::remove_dir
231
+ /// [`fs::remove_file`]: crate::fs::remove_file
232
+ pub fn symlink_dir < P : AsRef < Path > , Q : AsRef < Path > > ( original : P , link : Q ) {
233
+ #[ cfg( unix) ]
234
+ {
235
+ if let Err ( e) = std:: os:: unix:: fs:: symlink ( original. as_ref ( ) , link. as_ref ( ) ) {
236
+ panic ! (
237
+ "failed to create symlink: original=`{}`, link=`{}`: {e}" ,
238
+ original. as_ref( ) . display( ) ,
239
+ link. as_ref( ) . display( )
240
+ ) ;
241
+ }
242
+ }
243
+ #[ cfg( windows) ]
244
+ {
245
+ if let Err ( e) = std:: os:: windows:: fs:: symlink_dir ( original. as_ref ( ) , link. as_ref ( ) ) {
246
+ panic ! (
247
+ "failed to create symlink-to-directory: original=`{}`, link=`{}`: {e}" ,
248
+ original. as_ref( ) . display( ) ,
249
+ link. as_ref( ) . display( )
250
+ ) ;
251
+ }
252
+ }
253
+ #[ cfg( not( any( windows, unix) ) ) ]
254
+ {
255
+ unimplemented ! ( "target family not currently supported" )
256
+ }
257
+ }
258
+
259
+ /// Create a new symbolic link to a file.
260
+ ///
261
+ /// # Removing the symlink
262
+ ///
263
+ /// On both Windows and Unix, a symlink-to-file needs to be removed with a corresponding
264
+ /// [`fs::remove_file`](crate::fs::remove_file) and not [`fs::remove_dir`](crate::fs::remove_dir).
265
+ pub fn symlink_file < P : AsRef < Path > , Q : AsRef < Path > > ( original : P , link : Q ) {
266
+ #[ cfg( unix) ]
267
+ {
268
+ if let Err ( e) = std:: os:: unix:: fs:: symlink ( original. as_ref ( ) , link. as_ref ( ) ) {
269
+ panic ! (
270
+ "failed to create symlink: original=`{}`, link=`{}`: {e}" ,
271
+ original. as_ref( ) . display( ) ,
272
+ link. as_ref( ) . display( )
273
+ ) ;
274
+ }
275
+ }
276
+ #[ cfg( windows) ]
277
+ {
278
+ if let Err ( e) = std:: os:: windows:: fs:: symlink_file ( original. as_ref ( ) , link. as_ref ( ) ) {
279
+ panic ! (
280
+ "failed to create symlink-to-file: original=`{}`, link=`{}`: {e}" ,
281
+ original. as_ref( ) . display( ) ,
282
+ link. as_ref( ) . display( )
283
+ ) ;
284
+ }
285
+ }
286
+ #[ cfg( not( any( windows, unix) ) ) ]
287
+ {
288
+ unimplemented ! ( "target family not currently supported" )
289
+ }
290
+ }
0 commit comments