xref: /third_party/rust/crates/cxx/gen/build/src/out.rs (revision 33d722a9)
1use crate::error::{Error, Result};
2use crate::gen::fs;
3use crate::paths;
4use std::io;
5use std::path::Path;
6
7pub(crate) fn write(path: impl AsRef<Path>, content: &[u8]) -> Result<()> {
8    let path = path.as_ref();
9
10    let mut create_dir_error = None;
11    if fs::exists(path) {
12        if let Ok(existing) = fs::read(path) {
13            if existing == content {
14                // Avoid bumping modified time with unchanged contents.
15                return Ok(());
16            }
17        }
18        best_effort_remove(path);
19    } else {
20        let parent = path.parent().unwrap();
21        create_dir_error = fs::create_dir_all(parent).err();
22    }
23
24    match fs::write(path, content) {
25        // As long as write succeeded, ignore any create_dir_all error.
26        Ok(()) => Ok(()),
27        // If create_dir_all and write both failed, prefer the first error.
28        Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))),
29    }
30}
31
32pub(crate) fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> {
33    let original = original.as_ref();
34    let link = link.as_ref();
35
36    let mut create_dir_error = None;
37    if fs::exists(link) {
38        best_effort_remove(link);
39    } else {
40        let parent = link.parent().unwrap();
41        create_dir_error = fs::create_dir_all(parent).err();
42    }
43
44    match paths::symlink_or_copy(original, link) {
45        // As long as symlink_or_copy succeeded, ignore any create_dir_all error.
46        Ok(()) => Ok(()),
47        Err(err) => {
48            if err.kind() == io::ErrorKind::AlreadyExists {
49                // This is fine, a different simultaneous build script already
50                // created the same link or copy. The cxx_build target directory
51                // is laid out such that the same path never refers to two
52                // different targets during the same multi-crate build, so if
53                // some other build script already created the same path then we
54                // know it refers to the identical target that the current build
55                // script was trying to create.
56                Ok(())
57            } else {
58                // If create_dir_all and symlink_or_copy both failed, prefer the
59                // first error.
60                Err(Error::Fs(create_dir_error.unwrap_or(err)))
61            }
62        }
63    }
64}
65
66pub(crate) fn symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> {
67    let original = original.as_ref();
68    let link = link.as_ref();
69
70    let mut create_dir_error = None;
71    if fs::exists(link) {
72        best_effort_remove(link);
73    } else {
74        let parent = link.parent().unwrap();
75        create_dir_error = fs::create_dir_all(parent).err();
76    }
77
78    match fs::symlink_dir(original, link) {
79        // As long as symlink_dir succeeded, ignore any create_dir_all error.
80        Ok(()) => Ok(()),
81        // If create_dir_all and symlink_dir both failed, prefer the first error.
82        Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))),
83    }
84}
85
86fn best_effort_remove(path: &Path) {
87    use std::fs;
88
89    if cfg!(windows) {
90        // On Windows, the correct choice of remove_file vs remove_dir needs to
91        // be used according to what the symlink *points to*. Trying to use
92        // remove_file to remove a symlink which points to a directory fails
93        // with "Access is denied".
94        if let Ok(metadata) = fs::metadata(path) {
95            if metadata.is_dir() {
96                let _ = fs::remove_dir_all(path);
97            } else {
98                let _ = fs::remove_file(path);
99            }
100        } else if fs::symlink_metadata(path).is_ok() {
101            // The symlink might exist but be dangling, in which case there is
102            // no standard way to determine what "kind" of symlink it is. Try
103            // deleting both ways.
104            if fs::remove_dir_all(path).is_err() {
105                let _ = fs::remove_file(path);
106            }
107        }
108    } else {
109        // On non-Windows, we check metadata not following symlinks. All
110        // symlinks are removed using remove_file.
111        if let Ok(metadata) = fs::symlink_metadata(path) {
112            if metadata.is_dir() {
113                let _ = fs::remove_dir_all(path);
114            } else {
115                let _ = fs::remove_file(path);
116            }
117        }
118    }
119}
120