@@ -36,9 +36,10 @@ pub(crate) fn relative_symlink_file(
3636 let original = original. as_ref ( ) ;
3737 let link = link. as_ref ( ) ;
3838
39+ let parent_directory_error = prepare_parent_directory_for_symlink ( link) . err ( ) ;
3940 let relativized = best_effort_relativize_symlink ( original, link) ;
4041
41- symlink_file ( & relativized, original, link)
42+ symlink_file ( & relativized, original, link, parent_directory_error )
4243}
4344
4445pub ( crate ) fn absolute_symlink_file (
@@ -48,7 +49,9 @@ pub(crate) fn absolute_symlink_file(
4849 let original = original. as_ref ( ) ;
4950 let link = link. as_ref ( ) ;
5051
51- symlink_file ( original, original, link)
52+ let parent_directory_error = prepare_parent_directory_for_symlink ( link) . err ( ) ;
53+
54+ symlink_file ( original, original, link, parent_directory_error)
5255}
5356
5457pub ( crate ) fn relative_symlink_dir (
@@ -58,20 +61,28 @@ pub(crate) fn relative_symlink_dir(
5861 let original = original. as_ref ( ) ;
5962 let link = link. as_ref ( ) ;
6063
64+ let parent_directory_error = prepare_parent_directory_for_symlink ( link) . err ( ) ;
6165 let relativized = best_effort_relativize_symlink ( original, link) ;
6266
63- symlink_dir ( & relativized, link)
67+ symlink_dir ( & relativized, link, parent_directory_error )
6468}
6569
66- fn symlink_file ( path_for_symlink : & Path , path_for_copy : & Path , link : & Path ) -> Result < ( ) > {
67- let mut create_dir_error = None ;
70+ fn prepare_parent_directory_for_symlink ( link : & Path ) -> fs:: Result < ( ) > {
6871 if fs:: exists ( link) {
6972 best_effort_remove ( link) ;
73+ Ok ( ( ) )
7074 } else {
7175 let parent = link. parent ( ) . unwrap ( ) ;
72- create_dir_error = fs:: create_dir_all ( parent) . err ( ) ;
76+ fs:: create_dir_all ( parent)
7377 }
78+ }
7479
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 < ( ) > {
7586 match paths:: symlink_or_copy ( path_for_symlink, path_for_copy, link) {
7687 // As long as symlink_or_copy succeeded, ignore any create_dir_all error.
7788 Ok ( ( ) ) => Ok ( ( ) ) ,
@@ -88,26 +99,22 @@ fn symlink_file(path_for_symlink: &Path, path_for_copy: &Path, link: &Path) -> R
8899 } else {
89100 // If create_dir_all and symlink_or_copy both failed, prefer the
90101 // first error.
91- Err ( Error :: Fs ( create_dir_error . unwrap_or ( err) ) )
102+ Err ( Error :: Fs ( parent_directory_error . unwrap_or ( err) ) )
92103 }
93104 }
94105 }
95106}
96107
97- fn symlink_dir ( path_for_symlink : & Path , link : & Path ) -> Result < ( ) > {
98- let mut create_dir_error = None ;
99- if fs:: exists ( link) {
100- best_effort_remove ( link) ;
101- } else {
102- let parent = link. parent ( ) . unwrap ( ) ;
103- create_dir_error = fs:: create_dir_all ( parent) . err ( ) ;
104- }
105-
108+ fn symlink_dir (
109+ path_for_symlink : & Path ,
110+ link : & Path ,
111+ parent_directory_error : Option < fs:: Error > ,
112+ ) -> Result < ( ) > {
106113 match fs:: symlink_dir ( path_for_symlink, link) {
107114 // As long as symlink_dir succeeded, ignore any create_dir_all error.
108115 Ok ( ( ) ) => Ok ( ( ) ) ,
109116 // If create_dir_all and symlink_dir both failed, prefer the first error.
110- Err ( err) => Err ( Error :: Fs ( create_dir_error . unwrap_or ( err) ) ) ,
117+ Err ( err) => Err ( Error :: Fs ( parent_directory_error . unwrap_or ( err) ) ) ,
111118 }
112119}
113120
@@ -150,6 +157,34 @@ fn best_effort_relativize_symlink(original: impl AsRef<Path>, link: impl AsRef<P
150157 let original = original. as_ref ( ) ;
151158 let link = link. as_ref ( ) ;
152159
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+
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+
153188 // Relativization only makes sense if there is a semantically meaningful
154189 // base directory shared between the two paths.
155190 //
@@ -171,13 +206,13 @@ fn best_effort_relativize_symlink(original: impl AsRef<Path>, link: impl AsRef<P
171206 || path_contains_intermediate_components ( original)
172207 || path_contains_intermediate_components ( link)
173208 {
174- return original . to_path_buf ( ) ;
209+ return None ;
175210 }
176211
177212 let ( common_prefix, rest_of_original, rest_of_link) = split_after_common_prefix ( original, link) ;
178213
179214 if common_prefix == Path :: new ( "" ) {
180- return original . to_path_buf ( ) ;
215+ return None ;
181216 }
182217
183218 let mut rest_of_link = rest_of_link. components ( ) ;
@@ -190,7 +225,7 @@ fn best_effort_relativize_symlink(original: impl AsRef<Path>, link: impl AsRef<P
190225 path_to_common_prefix. push ( Component :: ParentDir ) ;
191226 }
192227
193- path_to_common_prefix. join ( rest_of_original)
228+ Some ( path_to_common_prefix. join ( rest_of_original) )
194229}
195230
196231fn path_contains_intermediate_components ( path : impl AsRef < Path > ) -> bool {
@@ -230,23 +265,23 @@ fn split_after_common_prefix<'first, 'second>(
230265
231266#[ cfg( test) ]
232267mod tests {
233- use crate :: out:: best_effort_relativize_symlink ;
268+ use crate :: out:: abstractly_relativize_symlink ;
234269 use std:: path:: Path ;
235270
236271 #[ cfg( not( windows) ) ]
237272 #[ test]
238273 fn test_relativize_symlink_unix ( ) {
239274 assert_eq ! (
240- best_effort_relativize_symlink ( "/foo/bar/baz" , "/foo/spam/eggs" ) ,
241- Path :: new( "../bar/baz" ) ,
275+ abstractly_relativize_symlink ( "/foo/bar/baz" , "/foo/spam/eggs" ) . as_deref ( ) ,
276+ Some ( Path :: new( "../bar/baz" ) ) ,
242277 ) ;
243278 assert_eq ! (
244- best_effort_relativize_symlink ( "/foo/bar/../baz" , "/foo/spam/eggs" ) ,
245- Path :: new ( "/foo/bar/../baz" ) ,
279+ abstractly_relativize_symlink ( "/foo/bar/../baz" , "/foo/spam/eggs" ) ,
280+ None ,
246281 ) ;
247282 assert_eq ! (
248- best_effort_relativize_symlink ( "/foo/bar/baz" , "/foo/spam/./eggs" ) ,
249- Path :: new( "../bar/baz" ) ,
283+ abstractly_relativize_symlink ( "/foo/bar/baz" , "/foo/spam/./eggs" ) . as_deref ( ) ,
284+ Some ( Path :: new( "../bar/baz" ) ) ,
250285 ) ;
251286 }
252287
@@ -260,12 +295,12 @@ mod tests {
260295 let windows_different_volume_link = PathBuf :: from_iter ( [ "d:\\ " , "users" , "link" ] ) ;
261296
262297 assert_eq ! (
263- best_effort_relativize_symlink ( & windows_target, windows_link) ,
264- Path :: new( "..\\ windows\\ foo" ) ,
298+ abstractly_relativize_symlink ( & windows_target, windows_link) . as_deref ( ) ,
299+ Some ( Path :: new( "..\\ windows\\ foo" ) ) ,
265300 ) ;
266301 assert_eq ! (
267- best_effort_relativize_symlink ( & windows_target, windows_different_volume_link) ,
268- windows_target ,
302+ abstractly_relativize_symlink ( & windows_target, windows_different_volume_link) ,
303+ None ,
269304 ) ;
270305 }
271306}
0 commit comments