1 use crate::error::{Error, Result};
2 use crate::gen::fs;
3 use crate::paths;
4 use std::path::{Component, Path, PathBuf};
5 use std::{env, io};
6
write(path: impl AsRef<Path>, content: &[u8]) -> Result<()>7 pub(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
relative_symlink_file( original: impl AsRef<Path>, link: impl AsRef<Path>, ) -> Result<()>32 pub(crate) fn relative_symlink_file(
33 original: impl AsRef<Path>,
34 link: impl AsRef<Path>,
35 ) -> Result<()> {
36 let original = original.as_ref();
37 let link = link.as_ref();
38
39 let parent_directory_error = prepare_parent_directory_for_symlink(link).err();
40 let relativized = best_effort_relativize_symlink(original, link);
41
42 symlink_file(&relativized, original, link, parent_directory_error)
43 }
44
absolute_symlink_file( original: impl AsRef<Path>, link: impl AsRef<Path>, ) -> Result<()>45 pub(crate) fn absolute_symlink_file(
46 original: impl AsRef<Path>,
47 link: impl AsRef<Path>,
48 ) -> Result<()> {
49 let original = original.as_ref();
50 let link = link.as_ref();
51
52 let parent_directory_error = prepare_parent_directory_for_symlink(link).err();
53
54 symlink_file(original, original, link, parent_directory_error)
55 }
56
relative_symlink_dir( original: impl AsRef<Path>, link: impl AsRef<Path>, ) -> Result<()>57 pub(crate) fn relative_symlink_dir(
58 original: impl AsRef<Path>,
59 link: impl AsRef<Path>,
60 ) -> Result<()> {
61 let original = original.as_ref();
62 let link = link.as_ref();
63
64 let parent_directory_error = prepare_parent_directory_for_symlink(link).err();
65 let relativized = best_effort_relativize_symlink(original, link);
66
67 symlink_dir(&relativized, link, parent_directory_error)
68 }
69
prepare_parent_directory_for_symlink(link: &Path) -> fs::Result<()>70 fn prepare_parent_directory_for_symlink(link: &Path) -> fs::Result<()> {
71 if fs::exists(link) {
72 best_effort_remove(link);
73 Ok(())
74 } else {
75 let parent = link.parent().unwrap();
76 fs::create_dir_all(parent)
77 }
78 }
79
symlink_file( path_for_symlink: &Path, path_for_copy: &Path, link: &Path, parent_directory_error: Option<fs::Error>, ) -> Result<()>80 fn symlink_file(
81 path_for_symlink: &Path,
82 path_for_copy: &Path,
83 link: &Path,
84 parent_directory_error: Option<fs::Error>,
85 ) -> Result<()> {
86 match paths::symlink_or_copy(path_for_symlink, path_for_copy, link) {
87 // As long as symlink_or_copy succeeded, ignore any create_dir_all error.
88 Ok(()) => Ok(()),
89 Err(err) => {
90 if err.kind() == io::ErrorKind::AlreadyExists {
91 // This is fine, a different simultaneous build script already
92 // created the same link or copy. The cxx_build target directory
93 // is laid out such that the same path never refers to two
94 // different targets during the same multi-crate build, so if
95 // some other build script already created the same path then we
96 // know it refers to the identical target that the current build
97 // script was trying to create.
98 Ok(())
99 } else {
100 // If create_dir_all and symlink_or_copy both failed, prefer the
101 // first error.
102 Err(Error::Fs(parent_directory_error.unwrap_or(err)))
103 }
104 }
105 }
106 }
107
symlink_dir( path_for_symlink: &Path, link: &Path, parent_directory_error: Option<fs::Error>, ) -> Result<()>108 fn symlink_dir(
109 path_for_symlink: &Path,
110 link: &Path,
111 parent_directory_error: Option<fs::Error>,
112 ) -> Result<()> {
113 match fs::symlink_dir(path_for_symlink, link) {
114 // As long as symlink_dir succeeded, ignore any create_dir_all error.
115 Ok(()) => Ok(()),
116 // If create_dir_all and symlink_dir both failed, prefer the first error.
117 Err(err) => Err(Error::Fs(parent_directory_error.unwrap_or(err))),
118 }
119 }
120
best_effort_remove(path: &Path)121 fn best_effort_remove(path: &Path) {
122 use std::fs;
123
124 if cfg!(windows) {
125 // On Windows, the correct choice of remove_file vs remove_dir needs to
126 // be used according to what the symlink *points to*. Trying to use
127 // remove_file to remove a symlink which points to a directory fails
128 // with "Access is denied".
129 if let Ok(metadata) = fs::metadata(path) {
130 if metadata.is_dir() {
131 let _ = fs::remove_dir_all(path);
132 } else {
133 let _ = fs::remove_file(path);
134 }
135 } else if fs::symlink_metadata(path).is_ok() {
136 // The symlink might exist but be dangling, in which case there is
137 // no standard way to determine what "kind" of symlink it is. Try
138 // deleting both ways.
139 if fs::remove_dir_all(path).is_err() {
140 let _ = fs::remove_file(path);
141 }
142 }
143 } else {
144 // On non-Windows, we check metadata not following symlinks. All
145 // symlinks are removed using remove_file.
146 if let Ok(metadata) = fs::symlink_metadata(path) {
147 if metadata.is_dir() {
148 let _ = fs::remove_dir_all(path);
149 } else {
150 let _ = fs::remove_file(path);
151 }
152 }
153 }
154 }
155
best_effort_relativize_symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> PathBuf156 fn best_effort_relativize_symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> PathBuf {
157 let original = original.as_ref();
158 let link = link.as_ref();
159
160 let relative_path = match abstractly_relativize_symlink(original, link) {
161 Some(relative_path) => relative_path,
162 None => return original.to_path_buf(),
163 };
164
165 // Sometimes "a/b/../c" refers to a different canonical location than "a/c".
166 // This can happen if 'b' is a symlink. The '..' canonicalizes to the parent
167 // directory of the symlink's target, not back to 'a'. In cxx-build's case
168 // someone could be using `--target-dir` with a location containing such
169 // symlinks.
170 if let Ok(original_canonical) = original.canonicalize() {
171 if let Ok(relative_canonical) = link.parent().unwrap().join(&relative_path).canonicalize() {
172 if original_canonical == relative_canonical {
173 return relative_path;
174 }
175 }
176 }
177
178 original.to_path_buf()
179 }
180
abstractly_relativize_symlink( original: impl AsRef<Path>, link: impl AsRef<Path>, ) -> Option<PathBuf>181 fn abstractly_relativize_symlink(
182 original: impl AsRef<Path>,
183 link: impl AsRef<Path>,
184 ) -> Option<PathBuf> {
185 let original = original.as_ref();
186 let link = link.as_ref();
187
188 // Relativization only makes sense if there is a semantically meaningful
189 // base directory shared between the two paths.
190 //
191 // For example /Volumes/code/library/src/lib.rs
192 // and /Volumes/code/library/target/path/to/something.a
193 // have a meaningful shared base of /Volumes/code/library. The target and
194 // source directory only likely ever get relocated as one unit.
195 //
196 // On the other hand, /Volumes/code/library/src/lib.rs
197 // and /Volumes/shared_target
198 // do not, since upon moving library to a different location it should
199 // continue referring to the original location of that shared Cargo target
200 // directory.
201 let likely_no_semantic_prefix = env::var_os("CARGO_TARGET_DIR").is_some();
202
203 if likely_no_semantic_prefix
204 || original.is_relative()
205 || link.is_relative()
206 || path_contains_intermediate_components(original)
207 || path_contains_intermediate_components(link)
208 {
209 return None;
210 }
211
212 let (common_prefix, rest_of_original, rest_of_link) = split_after_common_prefix(original, link);
213
214 if common_prefix == Path::new("") {
215 return None;
216 }
217
218 let mut rest_of_link = rest_of_link.components();
219 rest_of_link
220 .next_back()
221 .expect("original can't be a subdirectory of link");
222
223 let mut path_to_common_prefix = PathBuf::new();
224 while rest_of_link.next_back().is_some() {
225 path_to_common_prefix.push(Component::ParentDir);
226 }
227
228 Some(path_to_common_prefix.join(rest_of_original))
229 }
230
path_contains_intermediate_components(path: impl AsRef<Path>) -> bool231 fn path_contains_intermediate_components(path: impl AsRef<Path>) -> bool {
232 path.as_ref()
233 .components()
234 .any(|component| component == Component::ParentDir)
235 }
236
split_after_common_prefix<'first, 'second>( first: &'first Path, second: &'second Path, ) -> (&'first Path, &'first Path, &'second Path)237 fn split_after_common_prefix<'first, 'second>(
238 first: &'first Path,
239 second: &'second Path,
240 ) -> (&'first Path, &'first Path, &'second Path) {
241 let entire_first = first;
242 let mut first = first.components();
243 let mut second = second.components();
244 loop {
245 let rest_of_first = first.as_path();
246 let rest_of_second = second.as_path();
247 match (first.next(), second.next()) {
248 (Some(first_component), Some(second_component))
249 if first_component == second_component => {}
250 _ => {
251 let mut common_prefix = entire_first;
252 for _ in rest_of_first.components().rev() {
253 if let Some(parent) = common_prefix.parent() {
254 common_prefix = parent;
255 } else {
256 common_prefix = Path::new("");
257 break;
258 }
259 }
260 return (common_prefix, rest_of_first, rest_of_second);
261 }
262 }
263 }
264 }
265
266 #[cfg(test)]
267 mod tests {
268 use crate::out::abstractly_relativize_symlink;
269 use std::path::Path;
270
271 #[cfg(not(windows))]
272 #[test]
test_relativize_symlink_unix()273 fn test_relativize_symlink_unix() {
274 assert_eq!(
275 abstractly_relativize_symlink("/foo/bar/baz", "/foo/spam/eggs").as_deref(),
276 Some(Path::new("../bar/baz")),
277 );
278 assert_eq!(
279 abstractly_relativize_symlink("/foo/bar/../baz", "/foo/spam/eggs"),
280 None,
281 );
282 assert_eq!(
283 abstractly_relativize_symlink("/foo/bar/baz", "/foo/spam/./eggs").as_deref(),
284 Some(Path::new("../bar/baz")),
285 );
286 }
287
288 #[cfg(windows)]
289 #[test]
test_relativize_symlink_windows()290 fn test_relativize_symlink_windows() {
291 use std::path::PathBuf;
292
293 let windows_target = PathBuf::from_iter(["c:\\", "windows", "foo"]);
294 let windows_link = PathBuf::from_iter(["c:\\", "users", "link"]);
295 let windows_different_volume_link = PathBuf::from_iter(["d:\\", "users", "link"]);
296
297 assert_eq!(
298 abstractly_relativize_symlink(&windows_target, windows_link).as_deref(),
299 Some(Path::new("..\\windows\\foo")),
300 );
301 assert_eq!(
302 abstractly_relativize_symlink(&windows_target, windows_different_volume_link),
303 None,
304 );
305 }
306 }
307