@@ -2,48 +2,93 @@ use crate::compile::benchmark::codegen_backend::CodegenBackend;
2
2
use crate :: compile:: benchmark:: profile:: Profile ;
3
3
use anyhow:: { anyhow, Context } ;
4
4
use log:: debug;
5
+ use reqwest:: StatusCode ;
5
6
use std:: ffi:: OsStr ;
6
- use std:: fs:: { self , File } ;
7
+ use std:: fs;
7
8
use std:: io:: { BufReader , Read } ;
8
9
use std:: path:: { Path , PathBuf } ;
9
10
use std:: process:: Command ;
10
11
use std:: { fmt, str} ;
11
12
use tar:: Archive ;
12
13
use xz2:: bufread:: XzDecoder ;
13
14
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
+
14
31
/// Sysroot downloaded from CI.
15
32
pub struct Sysroot {
16
- pub sha : String ,
33
+ sha : String ,
17
34
pub components : ToolchainComponents ,
18
- pub triple : String ,
19
- pub preserve : bool ,
35
+ triple : String ,
36
+ preserve : bool ,
20
37
}
21
38
22
39
impl Sysroot {
23
40
pub async fn install (
41
+ cache_directory : & Path ,
24
42
sha : String ,
25
43
triple : & str ,
26
44
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 ( ) ) ) ?;
31
48
32
49
let download = SysrootDownload {
33
- directory : unpack_into . into ( ) ,
34
- rust_sha : sha,
50
+ cache_directory : cache_directory . clone ( ) ,
51
+ rust_sha : sha. clone ( ) ,
35
52
triple : triple. to_owned ( ) ,
36
53
} ;
37
54
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
+ }
44
81
}
45
82
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 ) ?;
47
92
48
93
Ok ( sysroot)
49
94
}
@@ -68,9 +113,32 @@ impl Drop for Sysroot {
68
113
}
69
114
}
70
115
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
+
71
139
#[ derive( Debug , Clone ) ]
72
140
struct SysrootDownload {
73
- directory : PathBuf ,
141
+ cache_directory : PathBuf ,
74
142
rust_sha : String ,
75
143
triple : String ,
76
144
}
@@ -118,7 +186,7 @@ impl Component {
118
186
119
187
impl SysrootDownload {
120
188
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" ) ;
122
190
let sysroot_bin = |name| {
123
191
let path = sysroot_bin_dir. join ( name) ;
124
192
path. canonicalize ( ) . with_context ( || {
@@ -134,7 +202,7 @@ impl SysrootDownload {
134
202
Some ( sysroot_bin ( "rustdoc" ) ?) ,
135
203
sysroot_bin ( "clippy-driver" ) . ok ( ) ,
136
204
sysroot_bin ( "cargo" ) ?,
137
- & self . directory . join ( & self . rust_sha ) . join ( "lib" ) ,
205
+ & self . cache_directory . join ( "lib" ) ,
138
206
) ?;
139
207
140
208
Ok ( Sysroot {
@@ -145,54 +213,58 @@ impl SysrootDownload {
145
213
} )
146
214
}
147
215
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 > {
166
217
// We usually have nightlies but we want to avoid breaking down if we
167
218
// accidentally end up with a beta or stable commit.
168
219
let urls = [
169
220
component. url ( "nightly" , self , & self . triple ) ,
170
221
component. url ( "beta" , self , & self . triple ) ,
171
222
component. url ( "stable" , self , & self . triple ) ,
172
223
] ;
224
+
225
+ // Did we see any other error than 404?
226
+ let mut found_error_that_is_not_404 = false ;
173
227
for url in & urls {
174
228
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
+ }
184
248
}
185
249
}
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
+ }
186
255
}
187
256
}
188
257
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
+ }
196
268
}
197
269
198
270
fn extract < T : Read > ( & self , component : Component , reader : T ) -> anyhow:: Result < ( ) > {
@@ -203,7 +275,7 @@ impl SysrootDownload {
203
275
_ => component. to_string ( ) ,
204
276
} ;
205
277
206
- let unpack_into = self . directory . join ( & self . rust_sha ) ;
278
+ let unpack_into = & self . cache_directory ;
207
279
208
280
for entry in archive. entries ( ) ? {
209
281
let mut entry = entry?;
@@ -617,6 +689,7 @@ fn get_lib_dir_from_rustc(rustc: &Path) -> anyhow::Result<PathBuf> {
617
689
#[ cfg( test) ]
618
690
mod tests {
619
691
use super :: * ;
692
+ use std:: fs:: File ;
620
693
621
694
#[ test]
622
695
fn fill_libraries ( ) {
0 commit comments