@@ -2,48 +2,93 @@ use crate::compile::benchmark::codegen_backend::CodegenBackend;
22use crate :: compile:: benchmark:: profile:: Profile ;
33use anyhow:: { anyhow, Context } ;
44use log:: debug;
5+ use reqwest:: StatusCode ;
56use std:: ffi:: OsStr ;
6- use std:: fs:: { self , File } ;
7+ use std:: fs;
78use std:: io:: { BufReader , Read } ;
89use std:: path:: { Path , PathBuf } ;
910use std:: process:: Command ;
1011use std:: { fmt, str} ;
1112use tar:: Archive ;
1213use xz2:: bufread:: XzDecoder ;
1314
15+ pub enum SysrootDownloadError {
16+ SysrootShaNotFound ,
17+ IO ( anyhow:: Error ) ,
18+ }
19+
20+ impl SysrootDownloadError {
21+ pub fn as_anyhow_error ( self ) -> anyhow:: Error {
22+ match self {
23+ SysrootDownloadError :: SysrootShaNotFound => {
24+ anyhow:: anyhow!( "Sysroot was not found on CI" )
25+ }
26+ SysrootDownloadError :: IO ( error) => error,
27+ }
28+ }
29+ }
30+
1431/// Sysroot downloaded from CI.
1532pub struct Sysroot {
16- pub sha : String ,
33+ sha : String ,
1734 pub components : ToolchainComponents ,
18- pub triple : String ,
19- pub preserve : bool ,
35+ triple : String ,
36+ preserve : bool ,
2037}
2138
2239impl Sysroot {
2340 pub async fn install (
41+ cache_directory : & Path ,
2442 sha : String ,
2543 triple : & str ,
2644 backends : & [ CodegenBackend ] ,
27- ) -> anyhow:: Result < Self > {
28- let unpack_into = "cache" ;
29-
30- fs:: create_dir_all ( unpack_into) ?;
45+ ) -> Result < Self , SysrootDownloadError > {
46+ let cache_directory = cache_directory. join ( triple) . join ( & sha) ;
47+ fs:: create_dir_all ( & cache_directory) . map_err ( |e| SysrootDownloadError :: IO ( e. into ( ) ) ) ?;
3148
3249 let download = SysrootDownload {
33- directory : unpack_into . into ( ) ,
34- rust_sha : sha,
50+ cache_directory : cache_directory . clone ( ) ,
51+ rust_sha : sha. clone ( ) ,
3552 triple : triple. to_owned ( ) ,
3653 } ;
3754
38- download. get_and_extract ( Component :: Rustc ) . await ?;
39- download. get_and_extract ( Component :: Std ) . await ?;
40- download. get_and_extract ( Component :: Cargo ) . await ?;
41- download. get_and_extract ( Component :: RustSrc ) . await ?;
42- if backends. contains ( & CodegenBackend :: Cranelift ) {
43- download. get_and_extract ( Component :: Cranelift ) . await ?;
55+ let requires_cranelift = backends. contains ( & CodegenBackend :: Cranelift ) ;
56+
57+ let stamp = SysrootStamp :: load_from_dir ( & cache_directory) ;
58+ match stamp {
59+ Ok ( stamp) => {
60+ log:: debug!( "Found existing stamp for {sha}/{triple}: {stamp:?}" ) ;
61+ // We should already have a complete sysroot present on disk, check if we need to
62+ // download optional components
63+ if requires_cranelift && !stamp. cranelift {
64+ download. get_and_extract ( Component :: Cranelift ) . await ?;
65+ }
66+ }
67+ Err ( _) => {
68+ log:: debug!(
69+ "No existing stamp found for {sha}/{triple}, downloading a fresh sysroot"
70+ ) ;
71+
72+ // There is no stamp, download everything
73+ download. get_and_extract ( Component :: Rustc ) . await ?;
74+ download. get_and_extract ( Component :: Std ) . await ?;
75+ download. get_and_extract ( Component :: Cargo ) . await ?;
76+ download. get_and_extract ( Component :: RustSrc ) . await ?;
77+ if requires_cranelift {
78+ download. get_and_extract ( Component :: Cranelift ) . await ?;
79+ }
80+ }
4481 }
4582
46- let sysroot = download. into_sysroot ( ) ?;
83+ // Update the stamp
84+ let stamp = SysrootStamp {
85+ cranelift : requires_cranelift,
86+ } ;
87+ stamp
88+ . store_to_dir ( & cache_directory)
89+ . map_err ( SysrootDownloadError :: IO ) ?;
90+
91+ let sysroot = download. into_sysroot ( ) . map_err ( SysrootDownloadError :: IO ) ?;
4792
4893 Ok ( sysroot)
4994 }
@@ -68,9 +113,32 @@ impl Drop for Sysroot {
68113 }
69114}
70115
116+ const SYSROOT_STAMP_FILENAME : & str = ".sysroot-stamp.json" ;
117+
118+ /// Stores a proof on disk that we have downloaded a complete sysroot.
119+ /// It is used to avoid redownloading a sysroot if it already exists on disk.
120+ #[ derive( serde:: Serialize , serde:: Deserialize , Debug ) ]
121+ struct SysrootStamp {
122+ /// Was Cranelift downloaded as a part of the sysroot?
123+ cranelift : bool ,
124+ }
125+
126+ impl SysrootStamp {
127+ fn load_from_dir ( dir : & Path ) -> anyhow:: Result < Self > {
128+ let data = std:: fs:: read ( dir. join ( SYSROOT_STAMP_FILENAME ) ) ?;
129+ let stamp: SysrootStamp = serde_json:: from_slice ( & data) ?;
130+ Ok ( stamp)
131+ }
132+
133+ fn store_to_dir ( & self , dir : & Path ) -> anyhow:: Result < ( ) > {
134+ let file = std:: fs:: File :: create ( dir. join ( SYSROOT_STAMP_FILENAME ) ) ?;
135+ Ok ( serde_json:: to_writer ( file, self ) ?)
136+ }
137+ }
138+
71139#[ derive( Debug , Clone ) ]
72140struct SysrootDownload {
73- directory : PathBuf ,
141+ cache_directory : PathBuf ,
74142 rust_sha : String ,
75143 triple : String ,
76144}
@@ -118,7 +186,7 @@ impl Component {
118186
119187impl SysrootDownload {
120188 fn into_sysroot ( self ) -> anyhow:: Result < Sysroot > {
121- let sysroot_bin_dir = self . directory . join ( & self . rust_sha ) . join ( "bin" ) ;
189+ let sysroot_bin_dir = self . cache_directory . join ( "bin" ) ;
122190 let sysroot_bin = |name| {
123191 let path = sysroot_bin_dir. join ( name) ;
124192 path. canonicalize ( ) . with_context ( || {
@@ -134,7 +202,7 @@ impl SysrootDownload {
134202 Some ( sysroot_bin ( "rustdoc" ) ?) ,
135203 sysroot_bin ( "clippy-driver" ) . ok ( ) ,
136204 sysroot_bin ( "cargo" ) ?,
137- & self . directory . join ( & self . rust_sha ) . join ( "lib" ) ,
205+ & self . cache_directory . join ( "lib" ) ,
138206 ) ?;
139207
140208 Ok ( Sysroot {
@@ -145,54 +213,58 @@ impl SysrootDownload {
145213 } )
146214 }
147215
148- async fn get_and_extract ( & self , component : Component ) -> anyhow:: Result < ( ) > {
149- let archive_path = self . directory . join ( format ! (
150- "{}-{}-{}.tar.xz" ,
151- self . rust_sha, self . triple, component,
152- ) ) ;
153- if archive_path. exists ( ) {
154- let reader = BufReader :: new ( File :: open ( & archive_path) ?) ;
155- let decompress = XzDecoder :: new ( reader) ;
156- let extract = self . extract ( component, decompress) ;
157- match extract {
158- Ok ( ( ) ) => return Ok ( ( ) ) ,
159- Err ( err) => {
160- log:: warn!( "extracting {} failed: {:?}" , archive_path. display( ) , err) ;
161- fs:: remove_file ( & archive_path) . context ( "removing archive_path" ) ?;
162- }
163- }
164- }
165-
216+ async fn get_and_extract ( & self , component : Component ) -> Result < ( ) , SysrootDownloadError > {
166217 // We usually have nightlies but we want to avoid breaking down if we
167218 // accidentally end up with a beta or stable commit.
168219 let urls = [
169220 component. url ( "nightly" , self , & self . triple ) ,
170221 component. url ( "beta" , self , & self . triple ) ,
171222 component. url ( "stable" , self , & self . triple ) ,
172223 ] ;
224+
225+ // Did we see any other error than 404?
226+ let mut found_error_that_is_not_404 = false ;
173227 for url in & urls {
174228 log:: debug!( "requesting: {}" , url) ;
175- let resp = reqwest:: get ( url) . await ?;
176- log:: debug!( "{}" , resp. status( ) ) ;
177- if resp. status ( ) . is_success ( ) {
178- let bytes: Vec < u8 > = resp. bytes ( ) . await ?. into ( ) ;
179- let reader = XzDecoder :: new ( BufReader :: new ( bytes. as_slice ( ) ) ) ;
180- match self . extract ( component, reader) {
181- Ok ( ( ) ) => return Ok ( ( ) ) ,
182- Err ( err) => {
183- log:: warn!( "extracting {} failed: {:?}" , url, err) ;
229+ let resp = reqwest:: get ( url)
230+ . await
231+ . map_err ( |e| SysrootDownloadError :: IO ( e. into ( ) ) ) ?;
232+ log:: debug!( "response status: {}" , resp. status( ) ) ;
233+
234+ match resp. status ( ) {
235+ s if s. is_success ( ) => {
236+ let bytes: Vec < u8 > = resp
237+ . bytes ( )
238+ . await
239+ . map_err ( |e| SysrootDownloadError :: IO ( e. into ( ) ) ) ?
240+ . into ( ) ;
241+ let reader = XzDecoder :: new ( BufReader :: new ( bytes. as_slice ( ) ) ) ;
242+ match self . extract ( component, reader) {
243+ Ok ( ( ) ) => return Ok ( ( ) ) ,
244+ Err ( err) => {
245+ log:: warn!( "extracting {url} failed: {err:?}" ) ;
246+ found_error_that_is_not_404 = true ;
247+ }
184248 }
185249 }
250+ StatusCode :: NOT_FOUND => { }
251+ _ => {
252+ log:: error!( "response body: {}" , resp. text( ) . await . unwrap_or_default( ) ) ;
253+ found_error_that_is_not_404 = true
254+ }
186255 }
187256 }
188257
189- Err ( anyhow ! (
190- "unable to download sha {} triple {} module {} from any of {:?}" ,
191- self . rust_sha,
192- self . triple,
193- component,
194- urls
195- ) )
258+ if !found_error_that_is_not_404 {
259+ // The only errors we saw were 404, so we assume that the toolchain is simply not on CI
260+ Err ( SysrootDownloadError :: SysrootShaNotFound )
261+ } else {
262+ Err ( SysrootDownloadError :: IO ( anyhow ! (
263+ "unable to download sha {} triple {} module {component} from any of {urls:?}" ,
264+ self . rust_sha,
265+ self . triple,
266+ ) ) )
267+ }
196268 }
197269
198270 fn extract < T : Read > ( & self , component : Component , reader : T ) -> anyhow:: Result < ( ) > {
@@ -203,7 +275,7 @@ impl SysrootDownload {
203275 _ => component. to_string ( ) ,
204276 } ;
205277
206- let unpack_into = self . directory . join ( & self . rust_sha ) ;
278+ let unpack_into = & self . cache_directory ;
207279
208280 for entry in archive. entries ( ) ? {
209281 let mut entry = entry?;
@@ -617,6 +689,7 @@ fn get_lib_dir_from_rustc(rustc: &Path) -> anyhow::Result<PathBuf> {
617689#[ cfg( test) ]
618690mod tests {
619691 use super :: * ;
692+ use std:: fs:: File ;
620693
621694 #[ test]
622695 fn fill_libraries ( ) {
0 commit comments