]> git.lizzy.rs Git - rust.git/blob - src/tools/build-manifest/src/main.rs
Rollup merge of #63055 - Mark-Simulacrum:save-analysis-clean-2, r=Xanewok
[rust.git] / src / tools / build-manifest / src / main.rs
1 use toml;
2 use serde::Serialize;
3
4 use std::collections::BTreeMap;
5 use std::env;
6 use std::fs;
7 use std::io::{self, Read, Write};
8 use std::path::{PathBuf, Path};
9 use std::process::{Command, Stdio};
10
11 static HOSTS: &[&str] = &[
12     "aarch64-unknown-linux-gnu",
13     "arm-unknown-linux-gnueabi",
14     "arm-unknown-linux-gnueabihf",
15     "armv7-unknown-linux-gnueabihf",
16     "i686-apple-darwin",
17     "i686-pc-windows-gnu",
18     "i686-pc-windows-msvc",
19     "i686-unknown-linux-gnu",
20     "mips-unknown-linux-gnu",
21     "mips64-unknown-linux-gnuabi64",
22     "mips64el-unknown-linux-gnuabi64",
23     "mipsel-unknown-linux-gnu",
24     "mipsisa32r6-unknown-linux-gnu",
25     "mipsisa32r6el-unknown-linux-gnu",
26     "mipsisa64r6-unknown-linux-gnuabi64",
27     "mipsisa64r6el-unknown-linux-gnuabi64",
28     "powerpc-unknown-linux-gnu",
29     "powerpc64-unknown-linux-gnu",
30     "powerpc64le-unknown-linux-gnu",
31     "s390x-unknown-linux-gnu",
32     "x86_64-apple-darwin",
33     "x86_64-pc-windows-gnu",
34     "x86_64-pc-windows-msvc",
35     "x86_64-unknown-freebsd",
36     "x86_64-unknown-linux-gnu",
37     "x86_64-unknown-linux-musl",
38     "x86_64-unknown-netbsd",
39 ];
40
41 static TARGETS: &[&str] = &[
42     "aarch64-apple-ios",
43     "aarch64-fuchsia",
44     "aarch64-linux-android",
45     "aarch64-pc-windows-msvc",
46     "aarch64-unknown-cloudabi",
47     "aarch64-unknown-linux-gnu",
48     "aarch64-unknown-linux-musl",
49     "arm-linux-androideabi",
50     "arm-unknown-linux-gnueabi",
51     "arm-unknown-linux-gnueabihf",
52     "arm-unknown-linux-musleabi",
53     "arm-unknown-linux-musleabihf",
54     "armv5te-unknown-linux-gnueabi",
55     "armv5te-unknown-linux-musleabi",
56     "armv7-apple-ios",
57     "armv7-linux-androideabi",
58     "thumbv7neon-linux-androideabi",
59     "armv7-unknown-linux-gnueabihf",
60     "thumbv7neon-unknown-linux-gnueabihf",
61     "armv7-unknown-linux-musleabihf",
62     "armebv7r-none-eabi",
63     "armebv7r-none-eabihf",
64     "armv7r-none-eabi",
65     "armv7r-none-eabihf",
66     "armv7s-apple-ios",
67     "asmjs-unknown-emscripten",
68     "i386-apple-ios",
69     "i586-pc-windows-msvc",
70     "i586-unknown-linux-gnu",
71     "i586-unknown-linux-musl",
72     "i686-apple-darwin",
73     "i686-linux-android",
74     "i686-pc-windows-gnu",
75     "i686-pc-windows-msvc",
76     "i686-unknown-freebsd",
77     "i686-unknown-linux-gnu",
78     "i686-unknown-linux-musl",
79     "mips-unknown-linux-gnu",
80     "mips-unknown-linux-musl",
81     "mips64-unknown-linux-gnuabi64",
82     "mips64el-unknown-linux-gnuabi64",
83     "mipsisa32r6-unknown-linux-gnu",
84     "mipsisa32r6el-unknown-linux-gnu",
85     "mipsisa64r6-unknown-linux-gnuabi64",
86     "mipsisa64r6el-unknown-linux-gnuabi64",
87     "mipsel-unknown-linux-gnu",
88     "mipsel-unknown-linux-musl",
89     "nvptx64-nvidia-cuda",
90     "powerpc-unknown-linux-gnu",
91     "powerpc64-unknown-linux-gnu",
92     "powerpc64le-unknown-linux-gnu",
93     "riscv32i-unknown-none-elf",
94     "riscv32imc-unknown-none-elf",
95     "riscv32imac-unknown-none-elf",
96     "riscv64imac-unknown-none-elf",
97     "riscv64gc-unknown-none-elf",
98     "s390x-unknown-linux-gnu",
99     "sparc64-unknown-linux-gnu",
100     "sparcv9-sun-solaris",
101     "thumbv6m-none-eabi",
102     "thumbv7em-none-eabi",
103     "thumbv7em-none-eabihf",
104     "thumbv7m-none-eabi",
105     "thumbv8m.base-none-eabi",
106     "thumbv8m.main-none-eabi",
107     "thumbv8m.main-none-eabihf",
108     "wasm32-unknown-emscripten",
109     "wasm32-unknown-unknown",
110     "wasm32-wasi",
111     "x86_64-apple-darwin",
112     "x86_64-apple-ios",
113     "x86_64-fortanix-unknown-sgx",
114     "x86_64-fuchsia",
115     "x86_64-linux-android",
116     "x86_64-pc-windows-gnu",
117     "x86_64-pc-windows-msvc",
118     "x86_64-rumprun-netbsd",
119     "x86_64-sun-solaris",
120     "x86_64-pc-solaris",
121     "x86_64-unknown-cloudabi",
122     "x86_64-unknown-freebsd",
123     "x86_64-unknown-linux-gnu",
124     "x86_64-unknown-linux-gnux32",
125     "x86_64-unknown-linux-musl",
126     "x86_64-unknown-netbsd",
127     "x86_64-unknown-redox",
128 ];
129
130 static DOCS_TARGETS: &[&str] = &[
131     "i686-apple-darwin",
132     "i686-pc-windows-gnu",
133     "i686-pc-windows-msvc",
134     "i686-unknown-linux-gnu",
135     "x86_64-apple-darwin",
136     "x86_64-pc-windows-gnu",
137     "x86_64-pc-windows-msvc",
138     "x86_64-unknown-linux-gnu",
139 ];
140
141 static MINGW: &[&str] = &[
142     "i686-pc-windows-gnu",
143     "x86_64-pc-windows-gnu",
144 ];
145
146 #[derive(Serialize)]
147 #[serde(rename_all = "kebab-case")]
148 struct Manifest {
149     manifest_version: String,
150     date: String,
151     pkg: BTreeMap<String, Package>,
152     renames: BTreeMap<String, Rename>,
153     profiles: BTreeMap<String, Vec<String>>,
154 }
155
156 #[derive(Serialize)]
157 struct Package {
158     version: String,
159     git_commit_hash: Option<String>,
160     target: BTreeMap<String, Target>,
161 }
162
163 #[derive(Serialize)]
164 struct Rename {
165     to: String,
166 }
167
168 #[derive(Serialize, Default)]
169 struct Target {
170     available: bool,
171     url: Option<String>,
172     hash: Option<String>,
173     xz_url: Option<String>,
174     xz_hash: Option<String>,
175     components: Option<Vec<Component>>,
176     extensions: Option<Vec<Component>>,
177 }
178
179 impl Target {
180     fn unavailable() -> Self { Self::default() }
181 }
182
183 #[derive(Serialize)]
184 struct Component {
185     pkg: String,
186     target: String,
187 }
188
189 impl Component {
190     fn from_str(pkg: &str, target: &str) -> Self {
191         Self { pkg: pkg.to_string(), target: target.to_string() }
192     }
193 }
194
195 macro_rules! t {
196     ($e:expr) => (match $e {
197         Ok(e) => e,
198         Err(e) => panic!("{} failed with {}", stringify!($e), e),
199     })
200 }
201
202 struct Builder {
203     rust_release: String,
204     cargo_release: String,
205     rls_release: String,
206     clippy_release: String,
207     rustfmt_release: String,
208     llvm_tools_release: String,
209     lldb_release: String,
210     miri_release: String,
211
212     input: PathBuf,
213     output: PathBuf,
214     gpg_passphrase: String,
215     digests: BTreeMap<String, String>,
216     s3_address: String,
217     date: String,
218
219     rust_version: Option<String>,
220     cargo_version: Option<String>,
221     rls_version: Option<String>,
222     clippy_version: Option<String>,
223     rustfmt_version: Option<String>,
224     llvm_tools_version: Option<String>,
225     lldb_version: Option<String>,
226     miri_version: Option<String>,
227
228     rust_git_commit_hash: Option<String>,
229     cargo_git_commit_hash: Option<String>,
230     rls_git_commit_hash: Option<String>,
231     clippy_git_commit_hash: Option<String>,
232     rustfmt_git_commit_hash: Option<String>,
233     llvm_tools_git_commit_hash: Option<String>,
234     lldb_git_commit_hash: Option<String>,
235     miri_git_commit_hash: Option<String>,
236
237     should_sign: bool,
238 }
239
240 fn main() {
241     // Avoid signing packages while manually testing
242     // Do NOT set this envvar in CI
243     let should_sign = env::var("BUILD_MANIFEST_DISABLE_SIGNING").is_err();
244
245     // Safety check to ensure signing is always enabled on CI
246     // The CI environment variable is set by both Travis and AppVeyor
247     if !should_sign && env::var("CI").is_ok() {
248         println!("The 'BUILD_MANIFEST_DISABLE_SIGNING' env var can't be enabled on CI.");
249         println!("If you're not running this on CI, unset the 'CI' env var.");
250         panic!();
251     }
252
253     let mut args = env::args().skip(1);
254     let input = PathBuf::from(args.next().unwrap());
255     let output = PathBuf::from(args.next().unwrap());
256     let date = args.next().unwrap();
257     let rust_release = args.next().unwrap();
258     let s3_address = args.next().unwrap();
259     let cargo_release = args.next().unwrap();
260     let rls_release = args.next().unwrap();
261     let clippy_release = args.next().unwrap();
262     let miri_release = args.next().unwrap();
263     let rustfmt_release = args.next().unwrap();
264     let llvm_tools_release = args.next().unwrap();
265     let lldb_release = args.next().unwrap();
266
267     // Do not ask for a passphrase while manually testing
268     let mut passphrase = String::new();
269     if should_sign {
270         t!(io::stdin().read_to_string(&mut passphrase));
271     }
272
273     Builder {
274         rust_release,
275         cargo_release,
276         rls_release,
277         clippy_release,
278         rustfmt_release,
279         llvm_tools_release,
280         lldb_release,
281         miri_release,
282
283         input,
284         output,
285         gpg_passphrase: passphrase,
286         digests: BTreeMap::new(),
287         s3_address,
288         date,
289
290         rust_version: None,
291         cargo_version: None,
292         rls_version: None,
293         clippy_version: None,
294         rustfmt_version: None,
295         llvm_tools_version: None,
296         lldb_version: None,
297         miri_version: None,
298
299         rust_git_commit_hash: None,
300         cargo_git_commit_hash: None,
301         rls_git_commit_hash: None,
302         clippy_git_commit_hash: None,
303         rustfmt_git_commit_hash: None,
304         llvm_tools_git_commit_hash: None,
305         lldb_git_commit_hash: None,
306         miri_git_commit_hash: None,
307
308         should_sign,
309     }.build();
310 }
311
312 enum PkgType { RustSrc, Cargo, Rls, Clippy, Rustfmt, LlvmTools, Lldb, Miri, Other }
313
314 impl PkgType {
315     fn from_component(component: &str) -> Self {
316         use PkgType::*;
317         match component {
318             "rust-src" => RustSrc,
319             "cargo" => Cargo,
320             "rls" | "rls-preview" => Rls,
321             "clippy" | "clippy-preview" => Clippy,
322             "rustfmt" | "rustfmt-preview" => Rustfmt,
323             "llvm-tools" | "llvm-tools-preview" => LlvmTools,
324             "lldb" | "lldb-preview" => Lldb,
325             "miri" | "miri-preview" => Miri,
326             _ => Other,
327         }
328     }
329 }
330
331 impl Builder {
332     fn build(&mut self) {
333         self.rust_version = self.version("rust", "x86_64-unknown-linux-gnu");
334         self.cargo_version = self.version("cargo", "x86_64-unknown-linux-gnu");
335         self.rls_version = self.version("rls", "x86_64-unknown-linux-gnu");
336         self.clippy_version = self.version("clippy", "x86_64-unknown-linux-gnu");
337         self.rustfmt_version = self.version("rustfmt", "x86_64-unknown-linux-gnu");
338         self.llvm_tools_version = self.version("llvm-tools", "x86_64-unknown-linux-gnu");
339         // lldb is only built for macOS.
340         self.lldb_version = self.version("lldb", "x86_64-apple-darwin");
341         self.miri_version = self.version("miri", "x86_64-unknown-linux-gnu");
342
343         self.rust_git_commit_hash = self.git_commit_hash("rust", "x86_64-unknown-linux-gnu");
344         self.cargo_git_commit_hash = self.git_commit_hash("cargo", "x86_64-unknown-linux-gnu");
345         self.rls_git_commit_hash = self.git_commit_hash("rls", "x86_64-unknown-linux-gnu");
346         self.clippy_git_commit_hash = self.git_commit_hash("clippy", "x86_64-unknown-linux-gnu");
347         self.rustfmt_git_commit_hash = self.git_commit_hash("rustfmt", "x86_64-unknown-linux-gnu");
348         self.llvm_tools_git_commit_hash = self.git_commit_hash("llvm-tools",
349                                                                "x86_64-unknown-linux-gnu");
350         self.lldb_git_commit_hash = self.git_commit_hash("lldb", "x86_64-unknown-linux-gnu");
351         self.miri_git_commit_hash = self.git_commit_hash("miri", "x86_64-unknown-linux-gnu");
352
353         self.digest_and_sign();
354         let manifest = self.build_manifest();
355         self.write_channel_files(&self.rust_release, &manifest);
356
357         if self.rust_release != "beta" && self.rust_release != "nightly" {
358             self.write_channel_files("stable", &manifest);
359         }
360     }
361
362     fn digest_and_sign(&mut self) {
363         for file in t!(self.input.read_dir()).map(|e| t!(e).path()) {
364             let filename = file.file_name().unwrap().to_str().unwrap();
365             let digest = self.hash(&file);
366             self.sign(&file);
367             assert!(self.digests.insert(filename.to_string(), digest).is_none());
368         }
369     }
370
371     fn build_manifest(&mut self) -> Manifest {
372         let mut manifest = Manifest {
373             manifest_version: "2".to_string(),
374             date: self.date.to_string(),
375             pkg: BTreeMap::new(),
376             renames: BTreeMap::new(),
377             profiles: BTreeMap::new(),
378         };
379         self.add_packages_to(&mut manifest);
380         self.add_profiles_to(&mut manifest);
381         self.add_renames_to(&mut manifest);
382         manifest.pkg.insert("rust".to_string(), self.rust_package(&manifest));
383         manifest
384     }
385
386     fn add_packages_to(&mut self, manifest: &mut Manifest) {
387         let mut package = |name, targets| self.package(name, &mut manifest.pkg, targets);
388         package("rustc", HOSTS);
389         package("cargo", HOSTS);
390         package("rust-mingw", MINGW);
391         package("rust-std", TARGETS);
392         package("rust-docs", DOCS_TARGETS);
393         package("rust-src", &["*"]);
394         package("rls-preview", HOSTS);
395         package("clippy-preview", HOSTS);
396         package("miri-preview", HOSTS);
397         package("rustfmt-preview", HOSTS);
398         package("rust-analysis", TARGETS);
399         package("llvm-tools-preview", TARGETS);
400         package("lldb-preview", TARGETS);
401     }
402
403     fn add_profiles_to(&mut self, manifest: &mut Manifest) {
404         let mut profile = |name, pkgs| self.profile(name, &mut manifest.profiles, pkgs);
405         profile("minimal", &["rustc", "cargo", "rust-std", "rust-mingw"]);
406         profile("default", &[
407             "rustc", "cargo", "rust-std", "rust-mingw",
408             "rust-docs", "rustfmt-preview", "clippy-preview"
409         ]);
410         profile("complete", &[
411             "rustc", "cargo", "rust-std", "rust-mingw",
412             "rust-docs", "rustfmt-preview", "clippy-preview",
413             "rls-preview", "rust-src", "llvm-tools-preview",
414             "lldb-preview", "rust-analysis", "miri-preview"
415         ]);
416     }
417
418     fn add_renames_to(&self, manifest: &mut Manifest) {
419         let mut rename = |from: &str, to: &str| manifest.renames.insert(
420             from.to_owned(),
421             Rename { to: to.to_owned() }
422         );
423         rename("rls", "rls-preview");
424         rename("rustfmt", "rustfmt-preview");
425         rename("clippy", "clippy-preview");
426         rename("miri", "miri-preview");
427     }
428
429     fn rust_package(&mut self, manifest: &Manifest) -> Package {
430         let mut pkg = Package {
431             version: self.cached_version("rust")
432                          .as_ref()
433                          .expect("Couldn't find Rust version")
434                          .clone(),
435             git_commit_hash: self.cached_git_commit_hash("rust").clone(),
436             target: BTreeMap::new(),
437         };
438         for host in HOSTS {
439             if let Some(target) = self.target_host_combination(host, &manifest) {
440                 pkg.target.insert(host.to_string(), target);
441             } else {
442                 pkg.target.insert(host.to_string(), Target::unavailable());
443                 continue
444             }
445         }
446         pkg
447     }
448
449     fn target_host_combination(&mut self, host: &str, manifest: &Manifest) -> Option<Target> {
450         let filename = self.filename("rust", host);
451         let digest = self.digests.remove(&filename)?;
452         let xz_filename = filename.replace(".tar.gz", ".tar.xz");
453         let xz_digest = self.digests.remove(&xz_filename);
454         let mut components = Vec::new();
455         let mut extensions = Vec::new();
456
457         let host_component = |pkg| Component::from_str(pkg, host);
458
459         // rustc/rust-std/cargo/docs are all required,
460         // and so is rust-mingw if it's available for the target.
461         components.extend(vec![
462             host_component("rustc"),
463             host_component("rust-std"),
464             host_component("cargo"),
465             host_component("rust-docs"),
466         ]);
467         if host.contains("pc-windows-gnu") {
468             components.push(host_component("rust-mingw"));
469         }
470
471         // Tools are always present in the manifest,
472         // but might be marked as unavailable if they weren't built.
473         extensions.extend(vec![
474             host_component("clippy-preview"),
475             host_component("miri-preview"),
476             host_component("rls-preview"),
477             host_component("rustfmt-preview"),
478             host_component("llvm-tools-preview"),
479             host_component("lldb-preview"),
480             host_component("rust-analysis"),
481         ]);
482
483         extensions.extend(
484             TARGETS.iter()
485                 .filter(|&&target| target != host)
486                 .map(|target| Component::from_str("rust-std", target))
487         );
488         extensions.push(Component::from_str("rust-src", "*"));
489
490         // If the components/extensions don't actually exist for this
491         // particular host/target combination then nix it entirely from our
492         // lists.
493         let has_component = |c: &Component| {
494             if c.target == "*" {
495                 return true
496             }
497             let pkg = match manifest.pkg.get(&c.pkg) {
498                 Some(p) => p,
499                 None => return false,
500             };
501             pkg.target.get(&c.target).is_some()
502         };
503         extensions.retain(&has_component);
504         components.retain(&has_component);
505
506         Some(Target {
507             available: true,
508             url: Some(self.url(&filename)),
509             hash: Some(digest),
510             xz_url: xz_digest.as_ref().map(|_| self.url(&xz_filename)),
511             xz_hash: xz_digest,
512             components: Some(components),
513             extensions: Some(extensions),
514         })
515     }
516
517     fn profile(&mut self,
518                profile_name: &str,
519                dst: &mut BTreeMap<String, Vec<String>>,
520                pkgs: &[&str]) {
521         dst.insert(profile_name.to_owned(), pkgs.iter().map(|s| (*s).to_owned()).collect());
522     }
523
524     fn package(&mut self,
525                pkgname: &str,
526                dst: &mut BTreeMap<String, Package>,
527                targets: &[&str]) {
528         let (version, mut is_present) = self.cached_version(pkgname)
529             .as_ref()
530             .cloned()
531             .map(|version| (version, true))
532             .unwrap_or_default();
533
534         // miri needs to build std with xargo, which doesn't allow stable/beta:
535         // <https://github.com/japaric/xargo/pull/204#issuecomment-374888868>
536         if pkgname == "miri-preview" && self.rust_release != "nightly" {
537             is_present = false; // ignore it
538         }
539
540         let targets = targets.iter().map(|name| {
541             if is_present {
542                 let filename = self.filename(pkgname, name);
543                 let digest = match self.digests.remove(&filename) {
544                     Some(digest) => digest,
545                     None => return (name.to_string(), Target::unavailable()),
546                 };
547                 let xz_filename = filename.replace(".tar.gz", ".tar.xz");
548                 let xz_digest = self.digests.remove(&xz_filename);
549
550                 (name.to_string(), Target {
551                     available: true,
552                     url: Some(self.url(&filename)),
553                     hash: Some(digest),
554                     xz_url: xz_digest.as_ref().map(|_| self.url(&xz_filename)),
555                     xz_hash: xz_digest,
556                     components: None,
557                     extensions: None,
558                 })
559             } else {
560                 // If the component is not present for this build add it anyway but mark it as
561                 // unavailable -- this way rustup won't allow upgrades without --force
562                 (name.to_string(), Target::unavailable())
563             }
564         }).collect();
565
566         dst.insert(pkgname.to_string(), Package {
567             version,
568             git_commit_hash: self.cached_git_commit_hash(pkgname).clone(),
569             target: targets,
570         });
571     }
572
573     fn url(&self, filename: &str) -> String {
574         format!("{}/{}/{}",
575                 self.s3_address,
576                 self.date,
577                 filename)
578     }
579
580     fn filename(&self, component: &str, target: &str) -> String {
581         use PkgType::*;
582         match PkgType::from_component(component) {
583             RustSrc => format!("rust-src-{}.tar.gz", self.rust_release),
584             Cargo => format!("cargo-{}-{}.tar.gz", self.cargo_release, target),
585             Rls => format!("rls-{}-{}.tar.gz", self.rls_release, target),
586             Clippy => format!("clippy-{}-{}.tar.gz", self.clippy_release, target),
587             Rustfmt => format!("rustfmt-{}-{}.tar.gz", self.rustfmt_release, target),
588             LlvmTools => format!("llvm-tools-{}-{}.tar.gz", self.llvm_tools_release, target),
589             Lldb => format!("lldb-{}-{}.tar.gz", self.lldb_release, target),
590             Miri => format!("miri-{}-{}.tar.gz", self.miri_release, target),
591             Other => format!("{}-{}-{}.tar.gz", component, self.rust_release, target),
592         }
593     }
594
595     fn cached_version(&self, component: &str) -> &Option<String> {
596         use PkgType::*;
597         match PkgType::from_component(component) {
598             Cargo => &self.cargo_version,
599             Rls => &self.rls_version,
600             Clippy => &self.clippy_version,
601             Rustfmt => &self.rustfmt_version,
602             LlvmTools => &self.llvm_tools_version,
603             Lldb => &self.lldb_version,
604             Miri => &self.miri_version,
605             _ => &self.rust_version,
606         }
607     }
608
609     fn cached_git_commit_hash(&self, component: &str) -> &Option<String> {
610         use PkgType::*;
611         match PkgType::from_component(component) {
612             Cargo => &self.cargo_git_commit_hash,
613             Rls => &self.rls_git_commit_hash,
614             Clippy => &self.clippy_git_commit_hash,
615             Rustfmt => &self.rustfmt_git_commit_hash,
616             LlvmTools => &self.llvm_tools_git_commit_hash,
617             Lldb => &self.lldb_git_commit_hash,
618             Miri => &self.miri_git_commit_hash,
619             _ => &self.rust_git_commit_hash,
620         }
621     }
622
623     fn version(&self, component: &str, target: &str) -> Option<String> {
624         self.untar(component, target, |filename| format!("{}/version", filename))
625     }
626
627     fn git_commit_hash(&self, component: &str, target: &str) -> Option<String> {
628         self.untar(component, target, |filename| format!("{}/git-commit-hash", filename))
629     }
630
631     fn untar<F>(&self, component: &str, target: &str, dir: F) -> Option<String>
632     where
633         F: FnOnce(String) -> String
634     {
635         let mut cmd = Command::new("tar");
636         let filename = self.filename(component, target);
637         cmd.arg("xf")
638            .arg(self.input.join(&filename))
639            .arg(dir(filename.replace(".tar.gz", "")))
640            .arg("-O");
641         let output = t!(cmd.output());
642         if output.status.success() {
643             Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
644         } else {
645             None
646         }
647     }
648
649     fn hash(&self, path: &Path) -> String {
650         let sha = t!(Command::new("shasum")
651                         .arg("-a").arg("256")
652                         .arg(path.file_name().unwrap())
653                         .current_dir(path.parent().unwrap())
654                         .output());
655         assert!(sha.status.success());
656
657         let filename = path.file_name().unwrap().to_str().unwrap();
658         let sha256 = self.output.join(format!("{}.sha256", filename));
659         t!(fs::write(&sha256, &sha.stdout));
660
661         let stdout = String::from_utf8_lossy(&sha.stdout);
662         stdout.split_whitespace().next().unwrap().to_string()
663     }
664
665     fn sign(&self, path: &Path) {
666         if !self.should_sign {
667             return;
668         }
669
670         let filename = path.file_name().unwrap().to_str().unwrap();
671         let asc = self.output.join(format!("{}.asc", filename));
672         println!("signing: {:?}", path);
673         let mut cmd = Command::new("gpg");
674         cmd.arg("--pinentry-mode=loopback")
675             .arg("--no-tty")
676             .arg("--yes")
677             .arg("--batch")
678             .arg("--passphrase-fd").arg("0")
679             .arg("--personal-digest-preferences").arg("SHA512")
680             .arg("--armor")
681             .arg("--output").arg(&asc)
682             .arg("--detach-sign").arg(path)
683             .stdin(Stdio::piped());
684         let mut child = t!(cmd.spawn());
685         t!(child.stdin.take().unwrap().write_all(self.gpg_passphrase.as_bytes()));
686         assert!(t!(child.wait()).success());
687     }
688
689     fn write_channel_files(&self, channel_name: &str, manifest: &Manifest) {
690         self.write(&toml::to_string(&manifest).unwrap(), channel_name, ".toml");
691         self.write(&manifest.date, channel_name, "-date.txt");
692         self.write(manifest.pkg["rust"].git_commit_hash.as_ref().unwrap(),
693                    channel_name, "-git-commit-hash.txt");
694     }
695
696     fn write(&self, contents: &str, channel_name: &str, suffix: &str) {
697         let dst = self.output.join(format!("channel-rust-{}{}", channel_name, suffix));
698         t!(fs::write(&dst, contents));
699         self.hash(&dst);
700         self.sign(&dst);
701     }
702 }