11use std:: io;
22use std:: path:: { Path , PathBuf } ;
33
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;
398
409/// Copy a directory into another.
4110pub 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>) {
5019 if ty. is_dir ( ) {
5120 copy_dir_all_inner ( entry. path ( ) , dst. join ( entry. file_name ( ) ) ) ?;
5221 } 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:: 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+ }
5450 } else {
5551 std:: fs:: copy ( entry. path ( ) , dst. join ( entry. file_name ( ) ) ) ?;
5652 }
@@ -69,12 +65,6 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
6965 }
7066}
7167
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-
7868/// Helper for reading entries in a given directory.
7969pub fn read_dir_entries < P : AsRef < Path > , F : FnMut ( & Path ) > ( dir : P , mut callback : F ) {
8070 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
8575/// A wrapper around [`std::fs::remove_file`] which includes the file path in the panic message.
8676#[ track_caller]
8777pub 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+ }
9089}
9190
9291/// 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) {
165164 ) ) ;
166165}
167166
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.
169172#[ track_caller]
170173pub 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+ }
175193}
176194
177195/// A wrapper around [`std::fs::rename`] which includes the file path in the panic message.
0 commit comments