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,31 @@ 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
+ use std:: os:: windows:: fs:: FileTypeExt ;
24
+ if ty. is_symlink_dir ( ) {
25
+ std:: os:: windows:: fs:: symlink_dir ( & target_path, new_symlink_path) ?;
26
+ } else {
27
+ // Target may be a file or another symlink, in any case we can use
28
+ // `symlink_file` here.
29
+ std:: os:: windows:: fs:: symlink_file ( & target_path, new_symlink_path) ?;
30
+ }
31
+ }
32
+ #[ cfg( unix) ]
33
+ {
34
+ std:: os:: unix:: fs:: symlink ( target_path, new_symlink_path) ?;
35
+ }
36
+ #[ cfg( not( any( windows, unix) ) ) ]
37
+ {
38
+ // Technically there's also wasi, but I have no clue about wasi symlink
39
+ // semantics and which wasi targets / environment support symlinks.
40
+ unimplemented ! ( "unsupported target" ) ;
41
+ }
54
42
} else {
55
43
std:: fs:: copy ( entry. path ( ) , dst. join ( entry. file_name ( ) ) ) ?;
56
44
}
@@ -69,12 +57,6 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
69
57
}
70
58
}
71
59
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
60
/// Helper for reading entries in a given directory.
79
61
pub fn read_dir_entries < P : AsRef < Path > , F : FnMut ( & Path ) > ( dir : P , mut callback : F ) {
80
62
for entry in read_dir ( dir) {
@@ -85,8 +67,17 @@ pub fn read_dir_entries<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F
85
67
/// A wrapper around [`std::fs::remove_file`] which includes the file path in the panic message.
86
68
#[ track_caller]
87
69
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( ) ) ) ;
70
+ if let Err ( e) = std:: fs:: remove_file ( path. as_ref ( ) ) {
71
+ panic ! ( "failed to remove file at `{}`: {e}" , path. as_ref( ) . display( ) ) ;
72
+ }
73
+ }
74
+
75
+ /// A wrapper around [`std::fs::remove_dir`] which includes the directory path in the panic message.
76
+ #[ track_caller]
77
+ pub fn remove_dir < P : AsRef < Path > > ( path : P ) {
78
+ if let Err ( e) = std:: fs:: remove_dir ( path. as_ref ( ) ) {
79
+ panic ! ( "failed to remove directory at `{}`: {e}" , path. as_ref( ) . display( ) ) ;
80
+ }
90
81
}
91
82
92
83
/// A wrapper around [`std::fs::copy`] which includes the file path in the panic message.
@@ -165,13 +156,32 @@ pub fn create_dir_all<P: AsRef<Path>>(path: P) {
165
156
) ) ;
166
157
}
167
158
168
- /// A wrapper around [`std::fs::metadata`] which includes the file path in the panic message.
159
+ /// A wrapper around [`std::fs::metadata`] which includes the file path in the panic message. Note
160
+ /// that this will traverse symlinks and will return metadata about the target file. Use
161
+ /// [`symlink_metadata`] if you don't want to traverse symlinks.
162
+ ///
163
+ /// See [`std::fs::metadata`] docs for more details.
169
164
#[ track_caller]
170
165
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
- ) )
166
+ match std:: fs:: metadata ( path. as_ref ( ) ) {
167
+ Ok ( m) => m,
168
+ Err ( e) => panic ! ( "failed to read file metadata at `{}`: {e}" , path. as_ref( ) . display( ) ) ,
169
+ }
170
+ }
171
+
172
+ /// A wrapper around [`std::fs::symlink_metadata`] which includes the file path in the panic
173
+ /// message. Note that this will not traverse symlinks and will return metadata about the filesystem
174
+ /// entity itself. Use [`metadata`] if you want to traverse symlinks.
175
+ ///
176
+ /// See [`std::fs::symlink_metadata`] docs for more details.
177
+ #[ track_caller]
178
+ pub fn symlink_metadata < P : AsRef < Path > > ( path : P ) -> std:: fs:: Metadata {
179
+ match std:: fs:: symlink_metadata ( path. as_ref ( ) ) {
180
+ Ok ( m) => m,
181
+ Err ( e) => {
182
+ panic ! ( "failed to read file metadata (shallow) at `{}`: {e}" , path. as_ref( ) . display( ) )
183
+ }
184
+ }
175
185
}
176
186
177
187
/// A wrapper around [`std::fs::rename`] which includes the file path in the panic message.
@@ -205,3 +215,73 @@ pub fn shallow_find_dir_entries<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
205
215
}
206
216
output
207
217
}
218
+
219
+ /// Create a new symbolic link to a directory.
220
+ ///
221
+ /// # Removing the symlink
222
+ ///
223
+ /// - On Windows, a symlink-to-directory needs to be removed with a corresponding [`fs::remove_dir`]
224
+ /// and not [`fs::remove_file`].
225
+ /// - On Unix, remove the symlink with [`fs::remove_file`].
226
+ ///
227
+ /// [`fs::remove_dir`]: crate::fs::remove_dir
228
+ /// [`fs::remove_file`]: crate::fs::remove_file
229
+ pub fn symlink_dir < P : AsRef < Path > , Q : AsRef < Path > > ( original : P , link : Q ) {
230
+ #[ cfg( unix) ]
231
+ {
232
+ if let Err ( e) = std:: os:: unix:: fs:: symlink ( original. as_ref ( ) , link. as_ref ( ) ) {
233
+ panic ! (
234
+ "failed to create symlink: original=`{}`, link=`{}`: {e}" ,
235
+ original. as_ref( ) . display( ) ,
236
+ link. as_ref( ) . display( )
237
+ ) ;
238
+ }
239
+ }
240
+ #[ cfg( windows) ]
241
+ {
242
+ if let Err ( e) = std:: os:: windows:: fs:: symlink_dir ( original. as_ref ( ) , link. as_ref ( ) ) {
243
+ panic ! (
244
+ "failed to create symlink-to-directory: original=`{}`, link=`{}`: {e}" ,
245
+ original. as_ref( ) . display( ) ,
246
+ link. as_ref( ) . display( )
247
+ ) ;
248
+ }
249
+ }
250
+ #[ cfg( not( any( windows, unix) ) ) ]
251
+ {
252
+ unimplemented ! ( "target family not currently supported" )
253
+ }
254
+ }
255
+
256
+ /// Create a new symbolic link to a file.
257
+ ///
258
+ /// # Removing the symlink
259
+ ///
260
+ /// On both Windows and Unix, a symlink-to-file needs to be removed with a corresponding
261
+ /// [`fs::remove_file`](crate::fs::remove_file) and not [`fs::remove_dir`](crate::fs::remove_dir).
262
+ pub fn symlink_file < P : AsRef < Path > , Q : AsRef < Path > > ( original : P , link : Q ) {
263
+ #[ cfg( unix) ]
264
+ {
265
+ if let Err ( e) = std:: os:: unix:: fs:: symlink ( original. as_ref ( ) , link. as_ref ( ) ) {
266
+ panic ! (
267
+ "failed to create symlink: original=`{}`, link=`{}`: {e}" ,
268
+ original. as_ref( ) . display( ) ,
269
+ link. as_ref( ) . display( )
270
+ ) ;
271
+ }
272
+ }
273
+ #[ cfg( windows) ]
274
+ {
275
+ if let Err ( e) = std:: os:: windows:: fs:: symlink_file ( original. as_ref ( ) , link. as_ref ( ) ) {
276
+ panic ! (
277
+ "failed to create symlink-to-file: original=`{}`, link=`{}`: {e}" ,
278
+ original. as_ref( ) . display( ) ,
279
+ link. as_ref( ) . display( )
280
+ ) ;
281
+ }
282
+ }
283
+ #[ cfg( not( any( windows, unix) ) ) ]
284
+ {
285
+ unimplemented ! ( "target family not currently supported" )
286
+ }
287
+ }
0 commit comments