]>
Commit | Line | Data |
---|---|---|
1 | // Copyright 2017 The Rust Project Developers. See the COPYRIGHT | |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | extern crate toml; | |
12 | extern crate rustc_serialize; | |
13 | ||
14 | use std::collections::BTreeMap; | |
15 | use std::env; | |
16 | use std::fs::File; | |
17 | use std::io::{self, Read, Write}; | |
18 | use std::path::{PathBuf, Path}; | |
19 | use std::process::{Command, Stdio}; | |
20 | ||
21 | static HOSTS: &'static [&'static str] = &[ | |
22 | "aarch64-unknown-linux-gnu", | |
23 | "arm-unknown-linux-gnueabi", | |
24 | "arm-unknown-linux-gnueabihf", | |
25 | "armv7-unknown-linux-gnueabihf", | |
26 | "i686-apple-darwin", | |
27 | "i686-pc-windows-gnu", | |
28 | "i686-pc-windows-msvc", | |
29 | "i686-unknown-linux-gnu", | |
30 | "mips-unknown-linux-gnu", | |
31 | "mips64-unknown-linux-gnuabi64", | |
32 | "mips64el-unknown-linux-gnuabi64", | |
33 | "mipsel-unknown-linux-gnu", | |
34 | "powerpc-unknown-linux-gnu", | |
35 | "powerpc64-unknown-linux-gnu", | |
36 | "powerpc64le-unknown-linux-gnu", | |
37 | "s390x-unknown-linux-gnu", | |
38 | "x86_64-apple-darwin", | |
39 | "x86_64-pc-windows-gnu", | |
40 | "x86_64-pc-windows-msvc", | |
41 | "x86_64-unknown-freebsd", | |
42 | "x86_64-unknown-linux-gnu", | |
43 | "x86_64-unknown-netbsd", | |
44 | ]; | |
45 | ||
46 | static TARGETS: &'static [&'static str] = &[ | |
47 | "aarch64-apple-ios", | |
48 | "aarch64-unknown-fuchsia", | |
49 | "aarch64-linux-android", | |
50 | "aarch64-unknown-linux-gnu", | |
51 | "arm-linux-androideabi", | |
52 | "arm-unknown-linux-gnueabi", | |
53 | "arm-unknown-linux-gnueabihf", | |
54 | "arm-unknown-linux-musleabi", | |
55 | "arm-unknown-linux-musleabihf", | |
56 | "armv7-apple-ios", | |
57 | "armv7-linux-androideabi", | |
58 | "armv7-unknown-linux-gnueabihf", | |
59 | "armv7-unknown-linux-musleabihf", | |
60 | "armv7s-apple-ios", | |
61 | "asmjs-unknown-emscripten", | |
62 | "i386-apple-ios", | |
63 | "i586-pc-windows-msvc", | |
64 | "i586-unknown-linux-gnu", | |
65 | "i686-apple-darwin", | |
66 | "i686-linux-android", | |
67 | "i686-pc-windows-gnu", | |
68 | "i686-pc-windows-msvc", | |
69 | "i686-unknown-freebsd", | |
70 | "i686-unknown-linux-gnu", | |
71 | "i686-unknown-linux-musl", | |
72 | "mips-unknown-linux-gnu", | |
73 | "mips-unknown-linux-musl", | |
74 | "mips64-unknown-linux-gnuabi64", | |
75 | "mips64el-unknown-linux-gnuabi64", | |
76 | "mipsel-unknown-linux-gnu", | |
77 | "mipsel-unknown-linux-musl", | |
78 | "powerpc-unknown-linux-gnu", | |
79 | "powerpc64-unknown-linux-gnu", | |
80 | "powerpc64le-unknown-linux-gnu", | |
81 | "s390x-unknown-linux-gnu", | |
82 | "sparc64-unknown-linux-gnu", | |
83 | "wasm32-unknown-emscripten", | |
84 | "x86_64-linux-android", | |
85 | "x86_64-apple-darwin", | |
86 | "x86_64-apple-ios", | |
87 | "x86_64-pc-windows-gnu", | |
88 | "x86_64-pc-windows-msvc", | |
89 | "x86_64-rumprun-netbsd", | |
90 | "x86_64-unknown-freebsd", | |
91 | "x86_64-unknown-fuchsia", | |
92 | "x86_64-unknown-linux-gnu", | |
93 | "x86_64-unknown-linux-musl", | |
94 | "x86_64-unknown-netbsd", | |
95 | ]; | |
96 | ||
97 | static MINGW: &'static [&'static str] = &[ | |
98 | "i686-pc-windows-gnu", | |
99 | "x86_64-pc-windows-gnu", | |
100 | ]; | |
101 | ||
102 | struct Manifest { | |
103 | manifest_version: String, | |
104 | date: String, | |
105 | pkg: BTreeMap<String, Package>, | |
106 | } | |
107 | ||
108 | #[derive(RustcEncodable)] | |
109 | struct Package { | |
110 | version: String, | |
111 | target: BTreeMap<String, Target>, | |
112 | } | |
113 | ||
114 | #[derive(RustcEncodable)] | |
115 | struct Target { | |
116 | available: bool, | |
117 | url: Option<String>, | |
118 | hash: Option<String>, | |
119 | xz_url: Option<String>, | |
120 | xz_hash: Option<String>, | |
121 | components: Option<Vec<Component>>, | |
122 | extensions: Option<Vec<Component>>, | |
123 | } | |
124 | ||
125 | impl Target { | |
126 | fn unavailable() -> Target { | |
127 | Target { | |
128 | available: false, | |
129 | url: None, | |
130 | hash: None, | |
131 | xz_url: None, | |
132 | xz_hash: None, | |
133 | components: None, | |
134 | extensions: None, | |
135 | } | |
136 | } | |
137 | } | |
138 | ||
139 | #[derive(RustcEncodable)] | |
140 | struct Component { | |
141 | pkg: String, | |
142 | target: String, | |
143 | } | |
144 | ||
145 | macro_rules! t { | |
146 | ($e:expr) => (match $e { | |
147 | Ok(e) => e, | |
148 | Err(e) => panic!("{} failed with {}", stringify!($e), e), | |
149 | }) | |
150 | } | |
151 | ||
152 | struct Builder { | |
153 | rust_release: String, | |
154 | cargo_release: String, | |
155 | input: PathBuf, | |
156 | output: PathBuf, | |
157 | gpg_passphrase: String, | |
158 | digests: BTreeMap<String, String>, | |
159 | s3_address: String, | |
160 | date: String, | |
161 | rust_version: String, | |
162 | cargo_version: String, | |
163 | } | |
164 | ||
165 | fn main() { | |
166 | let mut args = env::args().skip(1); | |
167 | let input = PathBuf::from(args.next().unwrap()); | |
168 | let output = PathBuf::from(args.next().unwrap()); | |
169 | let date = args.next().unwrap(); | |
170 | let rust_release = args.next().unwrap(); | |
171 | let cargo_release = args.next().unwrap(); | |
172 | let s3_address = args.next().unwrap(); | |
173 | let mut passphrase = String::new(); | |
174 | t!(io::stdin().read_to_string(&mut passphrase)); | |
175 | ||
176 | Builder { | |
177 | rust_release: rust_release, | |
178 | cargo_release: cargo_release, | |
179 | input: input, | |
180 | output: output, | |
181 | gpg_passphrase: passphrase, | |
182 | digests: BTreeMap::new(), | |
183 | s3_address: s3_address, | |
184 | date: date, | |
185 | rust_version: String::new(), | |
186 | cargo_version: String::new(), | |
187 | }.build(); | |
188 | } | |
189 | ||
190 | impl Builder { | |
191 | fn build(&mut self) { | |
192 | self.rust_version = self.version("rust", "x86_64-unknown-linux-gnu"); | |
193 | self.cargo_version = self.version("cargo", "x86_64-unknown-linux-gnu"); | |
194 | ||
195 | self.digest_and_sign(); | |
196 | let Manifest { manifest_version, date, pkg } = self.build_manifest(); | |
197 | ||
198 | // Unfortunately we can't use derive(RustcEncodable) here because the | |
199 | // version field is called `manifest-version`, not `manifest_version`. | |
200 | // In lieu of that just create the table directly here with a `BTreeMap` | |
201 | // and wrap it up in a `Value::Table`. | |
202 | let mut manifest = BTreeMap::new(); | |
203 | manifest.insert("manifest-version".to_string(), | |
204 | toml::Value::String(manifest_version)); | |
205 | manifest.insert("date".to_string(), toml::Value::String(date.clone())); | |
206 | manifest.insert("pkg".to_string(), toml::encode(&pkg)); | |
207 | let manifest = toml::Value::Table(manifest).to_string(); | |
208 | ||
209 | let filename = format!("channel-rust-{}.toml", self.rust_release); | |
210 | self.write_manifest(&manifest, &filename); | |
211 | ||
212 | let filename = format!("channel-rust-{}-date.txt", self.rust_release); | |
213 | self.write_date_stamp(&date, &filename); | |
214 | ||
215 | if self.rust_release != "beta" && self.rust_release != "nightly" { | |
216 | self.write_manifest(&manifest, "channel-rust-stable.toml"); | |
217 | self.write_date_stamp(&date, "channel-rust-stable-date.txt"); | |
218 | } | |
219 | } | |
220 | ||
221 | fn digest_and_sign(&mut self) { | |
222 | for file in t!(self.input.read_dir()).map(|e| t!(e).path()) { | |
223 | let filename = file.file_name().unwrap().to_str().unwrap(); | |
224 | let digest = self.hash(&file); | |
225 | self.sign(&file); | |
226 | assert!(self.digests.insert(filename.to_string(), digest).is_none()); | |
227 | } | |
228 | } | |
229 | ||
230 | fn build_manifest(&mut self) -> Manifest { | |
231 | let mut manifest = Manifest { | |
232 | manifest_version: "2".to_string(), | |
233 | date: self.date.to_string(), | |
234 | pkg: BTreeMap::new(), | |
235 | }; | |
236 | ||
237 | self.package("rustc", &mut manifest.pkg, HOSTS); | |
238 | self.package("cargo", &mut manifest.pkg, HOSTS); | |
239 | self.package("rust-mingw", &mut manifest.pkg, MINGW); | |
240 | self.package("rust-std", &mut manifest.pkg, TARGETS); | |
241 | self.package("rust-docs", &mut manifest.pkg, TARGETS); | |
242 | self.package("rust-src", &mut manifest.pkg, &["*"]); | |
243 | self.package("rls", &mut manifest.pkg, HOSTS); | |
244 | self.package("rust-analysis", &mut manifest.pkg, TARGETS); | |
245 | ||
246 | let mut pkg = Package { | |
247 | version: self.cached_version("rust").to_string(), | |
248 | target: BTreeMap::new(), | |
249 | }; | |
250 | for host in HOSTS { | |
251 | let filename = self.filename("rust", host); | |
252 | let digest = match self.digests.remove(&filename) { | |
253 | Some(digest) => digest, | |
254 | None => { | |
255 | pkg.target.insert(host.to_string(), Target::unavailable()); | |
256 | continue | |
257 | } | |
258 | }; | |
259 | let xz_filename = filename.replace(".tar.gz", ".tar.xz"); | |
260 | let xz_digest = self.digests.remove(&xz_filename); | |
261 | let mut components = Vec::new(); | |
262 | let mut extensions = Vec::new(); | |
263 | ||
264 | // rustc/rust-std/cargo/docs are all required, and so is rust-mingw | |
265 | // if it's available for the target. | |
266 | components.extend(vec![ | |
267 | Component { pkg: "rustc".to_string(), target: host.to_string() }, | |
268 | Component { pkg: "rust-std".to_string(), target: host.to_string() }, | |
269 | Component { pkg: "cargo".to_string(), target: host.to_string() }, | |
270 | Component { pkg: "rust-docs".to_string(), target: host.to_string() }, | |
271 | ]); | |
272 | if host.contains("pc-windows-gnu") { | |
273 | components.push(Component { | |
274 | pkg: "rust-mingw".to_string(), | |
275 | target: host.to_string(), | |
276 | }); | |
277 | } | |
278 | ||
279 | extensions.push(Component { | |
280 | pkg: "rls".to_string(), | |
281 | target: host.to_string(), | |
282 | }); | |
283 | extensions.push(Component { | |
284 | pkg: "rust-analysis".to_string(), | |
285 | target: host.to_string(), | |
286 | }); | |
287 | for target in TARGETS { | |
288 | if target != host { | |
289 | extensions.push(Component { | |
290 | pkg: "rust-std".to_string(), | |
291 | target: target.to_string(), | |
292 | }); | |
293 | } | |
294 | } | |
295 | extensions.push(Component { | |
296 | pkg: "rust-src".to_string(), | |
297 | target: "*".to_string(), | |
298 | }); | |
299 | ||
300 | pkg.target.insert(host.to_string(), Target { | |
301 | available: true, | |
302 | url: Some(self.url(&filename)), | |
303 | hash: Some(digest), | |
304 | xz_url: xz_digest.as_ref().map(|_| self.url(&xz_filename)), | |
305 | xz_hash: xz_digest, | |
306 | components: Some(components), | |
307 | extensions: Some(extensions), | |
308 | }); | |
309 | } | |
310 | manifest.pkg.insert("rust".to_string(), pkg); | |
311 | ||
312 | return manifest | |
313 | } | |
314 | ||
315 | fn package(&mut self, | |
316 | pkgname: &str, | |
317 | dst: &mut BTreeMap<String, Package>, | |
318 | targets: &[&str]) { | |
319 | let targets = targets.iter().map(|name| { | |
320 | let filename = self.filename(pkgname, name); | |
321 | let digest = match self.digests.remove(&filename) { | |
322 | Some(digest) => digest, | |
323 | None => return (name.to_string(), Target::unavailable()), | |
324 | }; | |
325 | let xz_filename = filename.replace(".tar.gz", ".tar.xz"); | |
326 | let xz_digest = self.digests.remove(&xz_filename); | |
327 | ||
328 | (name.to_string(), Target { | |
329 | available: true, | |
330 | url: Some(self.url(&filename)), | |
331 | hash: Some(digest), | |
332 | xz_url: xz_digest.as_ref().map(|_| self.url(&xz_filename)), | |
333 | xz_hash: xz_digest, | |
334 | components: None, | |
335 | extensions: None, | |
336 | }) | |
337 | }).collect(); | |
338 | ||
339 | dst.insert(pkgname.to_string(), Package { | |
340 | version: self.cached_version(pkgname).to_string(), | |
341 | target: targets, | |
342 | }); | |
343 | } | |
344 | ||
345 | fn url(&self, filename: &str) -> String { | |
346 | format!("{}/{}/{}", | |
347 | self.s3_address, | |
348 | self.date, | |
349 | filename) | |
350 | } | |
351 | ||
352 | fn filename(&self, component: &str, target: &str) -> String { | |
353 | if component == "rust-src" { | |
354 | format!("rust-src-{}.tar.gz", self.rust_release) | |
355 | } else if component == "cargo" { | |
356 | format!("cargo-{}-{}.tar.gz", self.cargo_release, target) | |
357 | } else { | |
358 | format!("{}-{}-{}.tar.gz", component, self.rust_release, target) | |
359 | } | |
360 | } | |
361 | ||
362 | fn cached_version(&self, component: &str) -> &str { | |
363 | if component == "cargo" { | |
364 | &self.cargo_version | |
365 | } else { | |
366 | &self.rust_version | |
367 | } | |
368 | } | |
369 | ||
370 | fn version(&self, component: &str, target: &str) -> String { | |
371 | let mut cmd = Command::new("tar"); | |
372 | let filename = self.filename(component, target); | |
373 | cmd.arg("xf") | |
374 | .arg(self.input.join(&filename)) | |
375 | .arg(format!("{}/version", filename.replace(".tar.gz", ""))) | |
376 | .arg("-O"); | |
377 | let version = t!(cmd.output()); | |
378 | if !version.status.success() { | |
379 | panic!("failed to learn version:\n\n{:?}\n\n{}\n\n{}", | |
380 | cmd, | |
381 | String::from_utf8_lossy(&version.stdout), | |
382 | String::from_utf8_lossy(&version.stderr)); | |
383 | } | |
384 | String::from_utf8_lossy(&version.stdout).trim().to_string() | |
385 | } | |
386 | ||
387 | fn hash(&self, path: &Path) -> String { | |
388 | let sha = t!(Command::new("shasum") | |
389 | .arg("-a").arg("256") | |
390 | .arg(path.file_name().unwrap()) | |
391 | .current_dir(path.parent().unwrap()) | |
392 | .output()); | |
393 | assert!(sha.status.success()); | |
394 | ||
395 | let filename = path.file_name().unwrap().to_str().unwrap(); | |
396 | let sha256 = self.output.join(format!("{}.sha256", filename)); | |
397 | t!(t!(File::create(&sha256)).write_all(&sha.stdout)); | |
398 | ||
399 | let stdout = String::from_utf8_lossy(&sha.stdout); | |
400 | stdout.split_whitespace().next().unwrap().to_string() | |
401 | } | |
402 | ||
403 | fn sign(&self, path: &Path) { | |
404 | let filename = path.file_name().unwrap().to_str().unwrap(); | |
405 | let asc = self.output.join(format!("{}.asc", filename)); | |
406 | println!("signing: {:?}", path); | |
407 | let mut cmd = Command::new("gpg"); | |
408 | cmd.arg("--no-tty") | |
409 | .arg("--yes") | |
410 | .arg("--passphrase-fd").arg("0") | |
411 | .arg("--armor") | |
412 | .arg("--output").arg(&asc) | |
413 | .arg("--detach-sign").arg(path) | |
414 | .stdin(Stdio::piped()); | |
415 | let mut child = t!(cmd.spawn()); | |
416 | t!(child.stdin.take().unwrap().write_all(self.gpg_passphrase.as_bytes())); | |
417 | assert!(t!(child.wait()).success()); | |
418 | } | |
419 | ||
420 | fn write_manifest(&self, manifest: &str, name: &str) { | |
421 | let dst = self.output.join(name); | |
422 | t!(t!(File::create(&dst)).write_all(manifest.as_bytes())); | |
423 | self.hash(&dst); | |
424 | self.sign(&dst); | |
425 | } | |
426 | ||
427 | fn write_date_stamp(&self, date: &str, name: &str) { | |
428 | let dst = self.output.join(name); | |
429 | t!(t!(File::create(&dst)).write_all(date.as_bytes())); | |
430 | self.hash(&dst); | |
431 | self.sign(&dst); | |
432 | } | |
433 | } |