1use std::fs::FileType;
2use std::io;
3use std::path::{Path, PathBuf};
45/// Given a symlink at `src`, read its target, then create a new symlink at `dst` also pointing to
6/// target.
7pub fn copy_symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
8let src = src.as_ref();
9let dst = dst.as_ref();
10let metadata = symlink_metadata(src);
11if let Err(e) = copy_symlink_raw(metadata.file_type(), src, dst) {
12panic!("failed to copy symlink from `{}` to `{}`: {e}", src.display(), dst.display(),);
13 }
14}
1516fn copy_symlink_raw(ty: FileType, src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
17// Traverse symlink once to find path of target entity.
18let target_path = std::fs::read_link(src)?;
1920let new_symlink_path = dst.as_ref();
21#[cfg(windows)]
22{
23use std::os::windows::fs::FileTypeExt;
24if 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.
29std::os::windows::fs::symlink_file(&target_path, new_symlink_path)?;
30 }
31 }
32#[cfg(unix)]
33{
34let _ = ty;
35 std::os::unix::fs::symlink(target_path, new_symlink_path)?;
36 }
37#[cfg(not(any(windows, unix)))]
38{
39let _ = ty;
40// Technically there's also wasi, but I have no clue about wasi symlink
41 // semantics and which wasi targets / environment support symlinks.
42unimplemented!("unsupported target");
43 }
44Ok(())
45}
4647/// Copy a directory into another. This will not traverse symlinks; instead, it will create new
48/// symlinks pointing at target paths that symlinks in the original directory points to.
49pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
50fn copy_dir_all_inner(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
51let dst = dst.as_ref();
52if !dst.is_dir() {
53 std::fs::create_dir_all(&dst)?;
54 }
55for entry in std::fs::read_dir(src)? {
56let entry = entry?;
57let ty = entry.file_type()?;
58if ty.is_dir() {
59 copy_dir_all_inner(entry.path(), dst.join(entry.file_name()))?;
60 } else if ty.is_symlink() {
61 copy_symlink_raw(ty, entry.path(), dst.join(entry.file_name()))?;
62 } else {
63 std::fs::copy(entry.path(), dst.join(entry.file_name()))?;
64 }
65 }
66Ok(())
67 }
6869if let Err(e) = copy_dir_all_inner(&src, &dst) {
70// Trying to give more context about what exactly caused the failure
71panic!(
72"failed to copy `{}` to `{}`: {:?}",
73 src.as_ref().display(),
74 dst.as_ref().display(),
75 e
76 );
77 }
78}
7980/// Helper for reading entries in a given directory.
81pub fn read_dir_entries<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F) {
82for entry in read_dir(dir) {
83 callback(&entry.unwrap().path());
84 }
85}
8687/// A wrapper around [`build_helper::fs::recursive_remove`] which includes the file path in the
88/// panic message.
89///
90/// This handles removing symlinks on Windows (e.g. symlink-to-file will be removed via
91/// [`std::fs::remove_file`] while symlink-to-dir will be removed via [`std::fs::remove_dir`]).
92#[track_caller]
93pub fn recursive_remove<P: AsRef<Path>>(path: P) {
94if let Err(e) = build_helper::fs::recursive_remove(path.as_ref()) {
95panic!(
96"failed to recursive remove filesystem entities at `{}`: {e}",
97 path.as_ref().display()
98 );
99 }
100}
101102/// A wrapper around [`std::fs::remove_file`] which includes the file path in the panic message.
103#[track_caller]
104pub fn remove_file<P: AsRef<Path>>(path: P) {
105if let Err(e) = std::fs::remove_file(path.as_ref()) {
106panic!("failed to remove file at `{}`: {e}", path.as_ref().display());
107 }
108}
109110/// A wrapper around [`std::fs::remove_dir`] which includes the directory path in the panic message.
111#[track_caller]
112pub fn remove_dir<P: AsRef<Path>>(path: P) {
113if let Err(e) = std::fs::remove_dir(path.as_ref()) {
114panic!("failed to remove directory at `{}`: {e}", path.as_ref().display());
115 }
116}
117118/// A wrapper around [`std::fs::copy`] which includes the file path in the panic message.
119#[track_caller]
120pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) {
121 std::fs::copy(from.as_ref(), to.as_ref()).expect(&format!(
122"the file \"{}\" could not be copied over to \"{}\"",
123 from.as_ref().display(),
124 to.as_ref().display(),
125 ));
126}
127128/// A wrapper around [`std::fs::File::create`] which includes the file path in the panic message.
129#[track_caller]
130pub fn create_file<P: AsRef<Path>>(path: P) {
131 std::fs::File::create(path.as_ref())
132 .expect(&format!("the file in path \"{}\" could not be created", path.as_ref().display()));
133}
134135/// A wrapper around [`std::fs::read`] which includes the file path in the panic message.
136#[track_caller]
137pub fn read<P: AsRef<Path>>(path: P) -> Vec<u8> {
138 std::fs::read(path.as_ref())
139 .expect(&format!("the file in path \"{}\" could not be read", path.as_ref().display()))
140}
141142/// A wrapper around [`std::fs::read_to_string`] which includes the file path in the panic message.
143#[track_caller]
144pub fn read_to_string<P: AsRef<Path>>(path: P) -> String {
145 std::fs::read_to_string(path.as_ref()).expect(&format!(
146"the file in path \"{}\" could not be read into a String",
147 path.as_ref().display()
148 ))
149}
150151/// A wrapper around [`std::fs::read_dir`] which includes the file path in the panic message.
152#[track_caller]
153pub fn read_dir<P: AsRef<Path>>(path: P) -> std::fs::ReadDir {
154 std::fs::read_dir(path.as_ref())
155 .expect(&format!("the directory in path \"{}\" could not be read", path.as_ref().display()))
156}
157158/// A wrapper around [`std::fs::write`] which includes the file path in the panic message.
159#[track_caller]
160pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) {
161 std::fs::write(path.as_ref(), contents.as_ref()).expect(&format!(
162"the file in path \"{}\" could not be written to",
163 path.as_ref().display()
164 ));
165}
166167/// A wrapper around [`std::fs::remove_dir_all`] which includes the file path in the panic message.
168#[track_caller]
169pub fn remove_dir_all<P: AsRef<Path>>(path: P) {
170 std::fs::remove_dir_all(path.as_ref()).expect(&format!(
171"the directory in path \"{}\" could not be removed alongside all its contents",
172 path.as_ref().display(),
173 ));
174}
175176/// A wrapper around [`std::fs::create_dir`] which includes the file path in the panic message.
177#[track_caller]
178pub fn create_dir<P: AsRef<Path>>(path: P) {
179 std::fs::create_dir(path.as_ref()).expect(&format!(
180"the directory in path \"{}\" could not be created",
181 path.as_ref().display()
182 ));
183}
184185/// A wrapper around [`std::fs::create_dir_all`] which includes the file path in the panic message.
186#[track_caller]
187pub fn create_dir_all<P: AsRef<Path>>(path: P) {
188 std::fs::create_dir_all(path.as_ref()).expect(&format!(
189"the directory (and all its parents) in path \"{}\" could not be created",
190 path.as_ref().display()
191 ));
192}
193194/// A wrapper around [`std::fs::metadata`] which includes the file path in the panic message. Note
195/// that this will traverse symlinks and will return metadata about the target file. Use
196/// [`symlink_metadata`] if you don't want to traverse symlinks.
197///
198/// See [`std::fs::metadata`] docs for more details.
199#[track_caller]
200pub fn metadata<P: AsRef<Path>>(path: P) -> std::fs::Metadata {
201match std::fs::metadata(path.as_ref()) {
202Ok(m) => m,
203Err(e) => panic!("failed to read file metadata at `{}`: {e}", path.as_ref().display()),
204 }
205}
206207/// A wrapper around [`std::fs::symlink_metadata`] which includes the file path in the panic
208/// message. Note that this will not traverse symlinks and will return metadata about the filesystem
209/// entity itself. Use [`metadata`] if you want to traverse symlinks.
210///
211/// See [`std::fs::symlink_metadata`] docs for more details.
212#[track_caller]
213pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> std::fs::Metadata {
214match std::fs::symlink_metadata(path.as_ref()) {
215Ok(m) => m,
216Err(e) => {
217panic!("failed to read file metadata (shallow) at `{}`: {e}", path.as_ref().display())
218 }
219 }
220}
221222/// A wrapper around [`std::fs::rename`] which includes the file path in the panic message.
223#[track_caller]
224pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) {
225 std::fs::rename(from.as_ref(), to.as_ref()).expect(&format!(
226"the file \"{}\" could not be moved over to \"{}\"",
227 from.as_ref().display(),
228 to.as_ref().display(),
229 ));
230}
231232/// A wrapper around [`std::fs::set_permissions`] which includes the file path in the panic message.
233#[track_caller]
234pub fn set_permissions<P: AsRef<Path>>(path: P, perm: std::fs::Permissions) {
235 std::fs::set_permissions(path.as_ref(), perm).expect(&format!(
236"the file's permissions in path \"{}\" could not be changed",
237 path.as_ref().display()
238 ));
239}
240241/// List directory entries immediately under the given `dir`.
242#[track_caller]
243pub fn shallow_find_dir_entries<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
244let paths = read_dir(dir);
245let mut output = Vec::new();
246for path in paths {
247 output.push(path.unwrap().path());
248 }
249 output
250}
251252/// Create a new symbolic link to a directory.
253///
254/// # Removing the symlink
255///
256/// - On Windows, a symlink-to-directory needs to be removed with a corresponding [`fs::remove_dir`]
257/// and not [`fs::remove_file`].
258/// - On Unix, remove the symlink with [`fs::remove_file`].
259///
260/// [`fs::remove_dir`]: crate::fs::remove_dir
261/// [`fs::remove_file`]: crate::fs::remove_file
262pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) {
263#[cfg(unix)]
264{
265if let Err(e) = std::os::unix::fs::symlink(original.as_ref(), link.as_ref()) {
266panic!(
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{
275if let Err(e) = std::os::windows::fs::symlink_dir(original.as_ref(), link.as_ref()) {
276panic!(
277"failed to create symlink-to-directory: original=`{}`, link=`{}`: {e}",
278 original.as_ref().display(),
279 link.as_ref().display()
280 );
281 }
282 }
283#[cfg(not(any(windows, unix)))]
284{
285unimplemented!("target family not currently supported")
286 }
287}
288289/// Create a new symbolic link to a file.
290///
291/// # Removing the symlink
292///
293/// On both Windows and Unix, a symlink-to-file needs to be removed with a corresponding
294/// [`fs::remove_file`](crate::fs::remove_file) and not [`fs::remove_dir`](crate::fs::remove_dir).
295pub fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) {
296#[cfg(unix)]
297{
298if let Err(e) = std::os::unix::fs::symlink(original.as_ref(), link.as_ref()) {
299panic!(
300"failed to create symlink: original=`{}`, link=`{}`: {e}",
301 original.as_ref().display(),
302 link.as_ref().display()
303 );
304 }
305 }
306#[cfg(windows)]
307{
308if let Err(e) = std::os::windows::fs::symlink_file(original.as_ref(), link.as_ref()) {
309panic!(
310"failed to create symlink-to-file: original=`{}`, link=`{}`: {e}",
311 original.as_ref().display(),
312 link.as_ref().display()
313 );
314 }
315 }
316#[cfg(not(any(windows, unix)))]
317{
318unimplemented!("target family not currently supported")
319 }
320}