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
- }
4
+ #[ cfg( unix) ]
5
+ pub mod unix;
6
+ #[ cfg( windows) ]
7
+ pub mod windows;
39
8
40
9
/// Copy a directory into another.
41
10
pub fn copy_dir_all ( src : impl AsRef < Path > , dst : impl AsRef < Path > ) {
@@ -50,7 +19,34 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
50
19
if ty. is_dir ( ) {
51
20
copy_dir_all_inner ( entry. path ( ) , dst. join ( entry. file_name ( ) ) ) ?;
52
21
} else if ty. is_symlink ( ) {
53
- copy_symlink ( entry. path ( ) , dst. join ( entry. file_name ( ) ) ) ?;
22
+ // Traverse symlink once to find path of target entity.
23
+ let target_path = std:: fs:: read_link ( entry. path ( ) ) ?;
24
+
25
+ let new_symlink_path = dst. join ( entry. file_name ( ) ) ;
26
+ #[ cfg( windows) ]
27
+ {
28
+ // Find metadata about target entity (shallow, no further symlink traversal in
29
+ // case target is also a symlink).
30
+ let target_metadata = std:: fs:: symlink_metadata ( & target_path) ?;
31
+ let target_file_type = target_metadata. file_type ( ) ;
32
+ if target_file_type. is_dir ( ) {
33
+ std:: os:: windows:: fs:: symlink_dir ( & target_path, new_symlink_path) ?;
34
+ } else {
35
+ // Target may be a file or another symlink, in any case we can use
36
+ // `symlink_file` here.
37
+ std:: os:: windows:: fs:: symlink_file ( & target_path, new_symlink_path) ?;
38
+ }
39
+ }
40
+ #[ cfg( unix) ]
41
+ {
42
+ std:: os:: unix:: fs:: symlink ( target_path, new_symlink_path) ?;
43
+ }
44
+ #[ cfg( not( any( windows, unix) ) ) ]
45
+ {
46
+ // Technically there's also wasi, but I have no clue about wasi symlink
47
+ // semantics and which wasi targets / environment support symlinks.
48
+ unimplemented ! ( "unsupported target" ) ;
49
+ }
54
50
} else {
55
51
std:: fs:: copy ( entry. path ( ) , dst. join ( entry. file_name ( ) ) ) ?;
56
52
}
@@ -69,12 +65,6 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
69
65
}
70
66
}
71
67
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
68
/// Helper for reading entries in a given directory.
79
69
pub fn read_dir_entries < P : AsRef < Path > , F : FnMut ( & Path ) > ( dir : P , mut callback : F ) {
80
70
for entry in read_dir ( dir) {
@@ -85,8 +75,17 @@ pub fn read_dir_entries<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F
85
75
/// A wrapper around [`std::fs::remove_file`] which includes the file path in the panic message.
86
76
#[ track_caller]
87
77
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( ) ) ) ;
78
+ if let Err ( e) = std:: fs:: remove_file ( path. as_ref ( ) ) {
79
+ panic ! ( "failed to remove file at `{}`: {e}" , path. as_ref( ) . display( ) ) ;
80
+ }
81
+ }
82
+
83
+ /// A wrapper around [`std::fs::remove_dir`] which includes the directory path in the panic message.
84
+ #[ track_caller]
85
+ pub fn remove_dir < P : AsRef < Path > > ( path : P ) {
86
+ if let Err ( e) = std:: fs:: remove_dir ( path. as_ref ( ) ) {
87
+ panic ! ( "failed to remove directory at `{}`: {e}" , path. as_ref( ) . display( ) ) ;
88
+ }
90
89
}
91
90
92
91
/// A wrapper around [`std::fs::copy`] which includes the file path in the panic message.
@@ -165,13 +164,32 @@ pub fn create_dir_all<P: AsRef<Path>>(path: P) {
165
164
) ) ;
166
165
}
167
166
168
- /// A wrapper around [`std::fs::metadata`] which includes the file path in the panic message.
167
+ /// A wrapper around [`std::fs::metadata`] which includes the file path in the panic message. Note
168
+ /// that this will traverse symlinks and will return metadata about the target file. Use
169
+ /// [`symlink_metadata`] if you don't want to traverse symlinks.
170
+ ///
171
+ /// See [`std::fs::metadata`] docs for more details.
169
172
#[ track_caller]
170
173
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
- ) )
174
+ match std:: fs:: metadata ( path. as_ref ( ) ) {
175
+ Ok ( m) => m,
176
+ Err ( e) => panic ! ( "failed to read file metadata at `{}`: {e}" , path. as_ref( ) . display( ) ) ,
177
+ }
178
+ }
179
+
180
+ /// A wrapper around [`std::fs::symlink_metadata`] which includes the file path in the panic
181
+ /// message. Note that this will not traverse symlinks and will return metadata about the filesystem
182
+ /// entity itself. Use [`metadata`] if you want to traverse symlinks.
183
+ ///
184
+ /// See [`std::fs::symlink_metadata`] docs for more details.
185
+ #[ track_caller]
186
+ pub fn symlink_metadata < P : AsRef < Path > > ( path : P ) -> std:: fs:: Metadata {
187
+ match std:: fs:: symlink_metadata ( path. as_ref ( ) ) {
188
+ Ok ( m) => m,
189
+ Err ( e) => {
190
+ panic ! ( "failed to read file metadata (shallow) at `{}`: {e}" , path. as_ref( ) . display( ) )
191
+ }
192
+ }
175
193
}
176
194
177
195
/// A wrapper around [`std::fs::rename`] which includes the file path in the panic message.
0 commit comments