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- } 
39- 
404/// Copy a directory into another. 
415pub  fn  copy_dir_all ( src :  impl  AsRef < Path > ,  dst :  impl  AsRef < Path > )  { 
426    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>) {
5014            if  ty. is_dir ( )  { 
5115                copy_dir_all_inner ( entry. path ( ) ,  dst. join ( entry. file_name ( ) ) ) ?; 
5216            }  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+                 } 
5445            }  else  { 
5546                std:: fs:: copy ( entry. path ( ) ,  dst. join ( entry. file_name ( ) ) ) ?; 
5647            } 
@@ -69,12 +60,6 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
6960    } 
7061} 
7162
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- 
7863/// Helper for reading entries in a given directory. 
7964pub  fn  read_dir_entries < P :  AsRef < Path > ,  F :  FnMut ( & Path ) > ( dir :  P ,  mut  callback :  F )  { 
8065    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
8570/// A wrapper around [`std::fs::remove_file`] which includes the file path in the panic message. 
8671#[ track_caller]  
8772pub  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+     } 
9084} 
9185
9286/// 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) {
165159    ) ) ; 
166160} 
167161
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. 
169167#[ track_caller]  
170168pub  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+     } 
175188} 
176189
177190/// 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> {
205218    } 
206219    output
207220} 
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