101101use crate :: core:: compiler:: CompileTarget ;
102102use crate :: core:: Workspace ;
103103use crate :: util:: paths;
104- use crate :: util:: { CargoResult , FileLock } ;
104+ use crate :: util:: { CargoResult , CargoResultExt , FileLock } ;
105105use std:: path:: { Path , PathBuf } ;
106106
107107/// Contains the paths of all target output locations.
@@ -125,6 +125,8 @@ pub struct Layout {
125125 examples : PathBuf ,
126126 /// The directory for rustdoc output: `$root/doc`
127127 doc : PathBuf ,
128+ /// Root in system's temporary directory
129+ temp_root : Option < PathBuf > ,
128130 /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
129131 /// struct is `drop`ped.
130132 _lock : FileLock ,
@@ -170,6 +172,7 @@ impl Layout {
170172 fingerprint : dest. join ( ".fingerprint" ) ,
171173 examples : dest. join ( "examples" ) ,
172174 doc : root. join ( "doc" ) ,
175+ temp_root : Self :: temp_root_path ( & root) ,
173176 root,
174177 dest,
175178 _lock : lock,
@@ -178,15 +181,87 @@ impl Layout {
178181
179182 /// Makes sure all directories stored in the Layout exist on the filesystem.
180183 pub fn prepare ( & mut self ) -> CargoResult < ( ) > {
181- paths:: create_dir_all ( & self . deps ) ?;
182- paths:: create_dir_all ( & self . incremental ) ?;
183- paths:: create_dir_all ( & self . fingerprint ) ?;
184+ let temp_root = self . temp_root . as_deref ( ) ;
185+ if let Some ( temp_root) = temp_root {
186+ paths:: create_dir_all ( temp_root) ?;
187+ }
188+ Self :: create_dir_or_symlink_to_temp ( temp_root, & self . deps ) ?;
189+ Self :: create_dir_or_symlink_to_temp ( temp_root, & self . incremental ) ?;
190+ Self :: create_dir_or_symlink_to_temp ( temp_root, & self . fingerprint ) ?;
191+ Self :: create_dir_or_symlink_to_temp ( temp_root, & self . build ) ?;
184192 paths:: create_dir_all ( & self . examples ) ?;
185- paths:: create_dir_all ( & self . build ) ?;
186193
187194 Ok ( ( ) )
188195 }
189196
197+ /// Create a path for a subdirectory in system's temporary directory
198+ /// that is specifically for the root path.
199+ #[ cfg( unix) ]
200+ fn temp_root_path ( target_root : & Path ) -> Option < PathBuf > {
201+ let temp_root = paths:: persistent_temp_path ( ) ?;
202+
203+ // Each target dir gets its own temp subdir based on a hash of the path
204+ // with some printable chars for friendlier paths
205+ let root_bytes = paths:: path2bytes ( target_root) . ok ( ) ?;
206+ let mut dir_name = String :: with_capacity ( 64 + 17 ) ;
207+ for ch in root_bytes[ root_bytes. len ( ) . saturating_sub ( 64 ) ..]
208+ . iter ( )
209+ . skip ( 1 ) // initial slash
210+ . copied ( )
211+ {
212+ dir_name. push ( if ch. is_ascii_alphanumeric ( ) {
213+ ch as char
214+ } else {
215+ '-'
216+ } ) ;
217+ }
218+ dir_name. push ( '-' ) ;
219+ dir_name. push_str ( & crate :: util:: short_hash ( & root_bytes) ) ;
220+
221+ Some ( temp_root. join ( dir_name) )
222+ }
223+
224+ /// On non-Unix it's not safe to assume that symlinks are reliable and efficient,
225+ /// so symlinking to temp won't be used.
226+ #[ cfg( not( unix) ) ]
227+ fn temp_root_path ( _root : & Path ) -> Option < PathBuf > {
228+ None
229+ }
230+
231+ /// Symlink `path` to inside of `temp_root`, or create the `path` as dir as a fallback
232+ #[ cfg( unix) ]
233+ fn create_dir_or_symlink_to_temp ( temp_root : Option < & Path > , path : & Path ) -> CargoResult < ( ) > {
234+ // Don't change existing target subdirectories (this also verifies that the symlink is valid)
235+ if path. exists ( ) {
236+ return Ok ( ( ) ) ;
237+ }
238+ // Clean up broken symlinks (OK to ignore failures, subsequent operations will report a failure)
239+ let _ = std:: fs:: remove_file ( path) ;
240+
241+ if let Some ( temp_root) = temp_root {
242+ let file_name = path. file_name ( ) . expect ( "/ isn't allowed" ) ;
243+ let temp_dest = temp_root. join ( file_name) ;
244+ paths:: create_dir_all ( & temp_dest) ?;
245+ std:: os:: unix:: fs:: symlink ( & temp_dest, path) . chain_err ( || {
246+ format ! (
247+ "failed to symlink `{}` to `{}`" ,
248+ path. display( ) ,
249+ temp_dest. display( )
250+ )
251+ } ) ?;
252+ Ok ( ( ) )
253+ } else {
254+ paths:: create_dir_all ( path)
255+ }
256+ }
257+
258+ /// On non-Unix it's not safe to assume that symlinks are reliable and efficient,
259+ /// so symlinking to temp won't be used.
260+ #[ cfg( not( unix) ) ]
261+ fn create_dir_or_symlink_to_temp ( _temp_root : & Path , path : & Path ) -> CargoResult < ( ) > {
262+ paths:: create_dir_all ( path)
263+ }
264+
190265 /// Fetch the destination path for final artifacts (`/…/target/debug`).
191266 pub fn dest ( & self ) -> & Path {
192267 & self . dest
0 commit comments