run_make_support/
fs.rs

1use std::fs::FileType;
2use std::io;
3use std::path::{Path, PathBuf};
4
5/// 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>) {
8    let src = src.as_ref();
9    let dst = dst.as_ref();
10    let metadata = symlink_metadata(src);
11    if let Err(e) = copy_symlink_raw(metadata.file_type(), src, dst) {
12        panic!("failed to copy symlink from `{}` to `{}`: {e}", src.display(), dst.display(),);
13    }
14}
15
16fn 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.
18    let target_path = std::fs::read_link(src)?;
19
20    let new_symlink_path = dst.as_ref();
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        let _ = ty;
35        std::os::unix::fs::symlink(target_path, new_symlink_path)?;
36    }
37    #[cfg(not(any(windows, unix)))]
38    {
39        let _ = ty;
40        // Technically there's also wasi, but I have no clue about wasi symlink
41        // semantics and which wasi targets / environment support symlinks.
42        unimplemented!("unsupported target");
43    }
44    Ok(())
45}
46
47/// 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>) {
50    fn copy_dir_all_inner(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
51        let dst = dst.as_ref();
52        if !dst.is_dir() {
53            std::fs::create_dir_all(&dst)?;
54        }
55        for entry in std::fs::read_dir(src)? {
56            let entry = entry?;
57            let ty = entry.file_type()?;
58            if 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        }
66        Ok(())
67    }
68
69    if let Err(e) = copy_dir_all_inner(&src, &dst) {
70        // Trying to give more context about what exactly caused the failure
71        panic!(
72            "failed to copy `{}` to `{}`: {:?}",
73            src.as_ref().display(),
74            dst.as_ref().display(),
75            e
76        );
77    }
78}
79
80/// Helper for reading entries in a given directory.
81pub fn read_dir_entries<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F) {
82    for entry in read_dir(dir) {
83        callback(&entry.unwrap().path());
84    }
85}
86
87/// 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) {
94    if let Err(e) = build_helper::fs::recursive_remove(path.as_ref()) {
95        panic!(
96            "failed to recursive remove filesystem entities at `{}`: {e}",
97            path.as_ref().display()
98        );
99    }
100}
101
102/// 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) {
105    if let Err(e) = std::fs::remove_file(path.as_ref()) {
106        panic!("failed to remove file at `{}`: {e}", path.as_ref().display());
107    }
108}
109
110/// 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) {
113    if let Err(e) = std::fs::remove_dir(path.as_ref()) {
114        panic!("failed to remove directory at `{}`: {e}", path.as_ref().display());
115    }
116}
117
118/// 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}
127
128/// 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}
134
135/// 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}
141
142/// 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}
150
151/// 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}
157
158/// 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}
166
167/// 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}
175
176/// 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}
184
185/// 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}
193
194/// 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 {
201    match std::fs::metadata(path.as_ref()) {
202        Ok(m) => m,
203        Err(e) => panic!("failed to read file metadata at `{}`: {e}", path.as_ref().display()),
204    }
205}
206
207/// 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 {
214    match std::fs::symlink_metadata(path.as_ref()) {
215        Ok(m) => m,
216        Err(e) => {
217            panic!("failed to read file metadata (shallow) at `{}`: {e}", path.as_ref().display())
218        }
219    }
220}
221
222/// 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}
231
232/// 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}
240
241/// List directory entries immediately under the given `dir`.
242#[track_caller]
243pub fn shallow_find_dir_entries<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
244    let paths = read_dir(dir);
245    let mut output = Vec::new();
246    for path in paths {
247        output.push(path.unwrap().path());
248    }
249    output
250}
251
252/// 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    {
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_dir(original.as_ref(), link.as_ref()) {
276            panic!(
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    {
285        unimplemented!("target family not currently supported")
286    }
287}
288
289/// 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    {
298        if let Err(e) = std::os::unix::fs::symlink(original.as_ref(), link.as_ref()) {
299            panic!(
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    {
308        if let Err(e) = std::os::windows::fs::symlink_file(original.as_ref(), link.as_ref()) {
309            panic!(
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    {
318        unimplemented!("target family not currently supported")
319    }
320}