Skip to content

Commit fffeaa7

Browse files
authoredOct 5, 2020
Rollup merge of #77407 - pietroalbini:less-build-manifest, r=Mark-Simulacrum
Improve build-manifest to work with the improved promote-release This PR makes some changes to build-manifest to have it work better with the other improvements I'm making to [promote-release](https://github.com/rust-lang/promote-release). A new way to invoke the tool was added: `./x.py run src/tools/build-manifest`. The new invocation disables the generation of `.sha256` files and the generation of GPG signatures, as those steps are not tied to the Rust version we're building the manifest of: handling them in `promote-release` will improve the maintenability of our release process. Invocations through the old command (`./x.py dist hash-and-sign`) are referred inside the source code as "legacy". The new invocation also enables internal parallelism, disabled on legacy to avoid overloading our old server. Improvements were also made on how the checksums included in the manifest are generated: * The manifest is first generated with placeholder checksums, and then a function walks through the manifes and calculates only the needed hashes. Before this PR, all the hashes were calculated beforehand, including the hashes of unused files. * Calculating the hashes is now done in parallel with rayon, to better utilize all the available disk bandwidth. * The `sha2` crate is now used instead of the `sha256sum` CLI tool: this avoids the overhead of calling another process, but more importantly enables hardware acceleration whenever available (the `sha256sum` CLI tool doesn't support it at all). r? @Mark-Simulacrum This PR is best reviewed commit-by-commit.
2 parents fe087ec + 9352062 commit fffeaa7

File tree

8 files changed

+334
-130
lines changed

8 files changed

+334
-130
lines changed
 

Diff for: ‎Cargo.lock

+64-8
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,16 @@ dependencies = [
183183
"block-padding",
184184
"byte-tools",
185185
"byteorder",
186-
"generic-array",
186+
"generic-array 0.12.3",
187+
]
188+
189+
[[package]]
190+
name = "block-buffer"
191+
version = "0.9.0"
192+
source = "registry+https://github.com/rust-lang/crates.io-index"
193+
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
194+
dependencies = [
195+
"generic-array 0.14.4",
187196
]
188197

189198
[[package]]
@@ -233,8 +242,11 @@ version = "0.1.0"
233242
dependencies = [
234243
"anyhow",
235244
"flate2",
245+
"hex 0.4.2",
246+
"rayon",
236247
"serde",
237248
"serde_json",
249+
"sha2",
238250
"tar",
239251
"toml",
240252
]
@@ -687,6 +699,12 @@ version = "0.8.0"
687699
source = "registry+https://github.com/rust-lang/crates.io-index"
688700
checksum = "9a21fa21941700a3cd8fcb4091f361a6a712fac632f85d9f487cc892045d55c6"
689701

702+
[[package]]
703+
name = "cpuid-bool"
704+
version = "0.1.2"
705+
source = "registry+https://github.com/rust-lang/crates.io-index"
706+
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
707+
690708
[[package]]
691709
name = "crates-io"
692710
version = "0.31.1"
@@ -884,7 +902,16 @@ version = "0.8.1"
884902
source = "registry+https://github.com/rust-lang/crates.io-index"
885903
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
886904
dependencies = [
887-
"generic-array",
905+
"generic-array 0.12.3",
906+
]
907+
908+
[[package]]
909+
name = "digest"
910+
version = "0.9.0"
911+
source = "registry+https://github.com/rust-lang/crates.io-index"
912+
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
913+
dependencies = [
914+
"generic-array 0.14.4",
888915
]
889916

890917
[[package]]
@@ -1166,6 +1193,16 @@ dependencies = [
11661193
"typenum",
11671194
]
11681195

1196+
[[package]]
1197+
name = "generic-array"
1198+
version = "0.14.4"
1199+
source = "registry+https://github.com/rust-lang/crates.io-index"
1200+
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
1201+
dependencies = [
1202+
"typenum",
1203+
"version_check",
1204+
]
1205+
11691206
[[package]]
11701207
name = "getopts"
11711208
version = "0.2.21"
@@ -1835,9 +1872,9 @@ version = "0.8.0"
18351872
source = "registry+https://github.com/rust-lang/crates.io-index"
18361873
checksum = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8"
18371874
dependencies = [
1838-
"block-buffer",
1839-
"digest",
1840-
"opaque-debug",
1875+
"block-buffer 0.7.3",
1876+
"digest 0.8.1",
1877+
"opaque-debug 0.2.3",
18411878
]
18421879

18431880
[[package]]
@@ -2097,6 +2134,12 @@ version = "0.2.3"
20972134
source = "registry+https://github.com/rust-lang/crates.io-index"
20982135
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
20992136

2137+
[[package]]
2138+
name = "opaque-debug"
2139+
version = "0.3.0"
2140+
source = "registry+https://github.com/rust-lang/crates.io-index"
2141+
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
2142+
21002143
[[package]]
21012144
name = "open"
21022145
version = "1.4.0"
@@ -4362,10 +4405,23 @@ version = "0.8.2"
43624405
source = "registry+https://github.com/rust-lang/crates.io-index"
43634406
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
43644407
dependencies = [
4365-
"block-buffer",
4366-
"digest",
4408+
"block-buffer 0.7.3",
4409+
"digest 0.8.1",
43674410
"fake-simd",
4368-
"opaque-debug",
4411+
"opaque-debug 0.2.3",
4412+
]
4413+
4414+
[[package]]
4415+
name = "sha2"
4416+
version = "0.9.1"
4417+
source = "registry+https://github.com/rust-lang/crates.io-index"
4418+
checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1"
4419+
dependencies = [
4420+
"block-buffer 0.9.0",
4421+
"cfg-if",
4422+
"cpuid-bool",
4423+
"digest 0.9.0",
4424+
"opaque-debug 0.3.0",
43694425
]
43704426

43714427
[[package]]

Diff for: ‎src/bootstrap/builder.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ impl<'a> Builder<'a> {
477477
install::Src,
478478
install::Rustc
479479
),
480-
Kind::Run => describe!(run::ExpandYamlAnchors,),
480+
Kind::Run => describe!(run::ExpandYamlAnchors, run::BuildManifest,),
481481
}
482482
}
483483

Diff for: ‎src/bootstrap/dist.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub fn pkgname(builder: &Builder<'_>, component: &str) -> String {
4646
}
4747
}
4848

49-
fn distdir(builder: &Builder<'_>) -> PathBuf {
49+
pub(crate) fn distdir(builder: &Builder<'_>) -> PathBuf {
5050
builder.out.join("dist")
5151
}
5252

@@ -2371,6 +2371,7 @@ impl Step for HashSign {
23712371
cmd.arg(addr);
23722372
cmd.arg(&builder.config.channel);
23732373
cmd.arg(&builder.src);
2374+
cmd.env("BUILD_MANIFEST_LEGACY", "1");
23742375

23752376
builder.create_dir(&distdir(builder));
23762377

Diff for: ‎src/bootstrap/run.rs

+42
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::builder::{Builder, RunConfig, ShouldRun, Step};
2+
use crate::dist::distdir;
23
use crate::tool::Tool;
4+
use build_helper::output;
35
use std::process::Command;
46

57
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -41,3 +43,43 @@ fn try_run(builder: &Builder<'_>, cmd: &mut Command) -> bool {
4143
}
4244
true
4345
}
46+
47+
#[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
48+
pub struct BuildManifest;
49+
50+
impl Step for BuildManifest {
51+
type Output = ();
52+
const ONLY_HOSTS: bool = true;
53+
54+
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
55+
run.path("src/tools/build-manifest")
56+
}
57+
58+
fn make_run(run: RunConfig<'_>) {
59+
run.builder.ensure(BuildManifest);
60+
}
61+
62+
fn run(self, builder: &Builder<'_>) {
63+
// This gets called by `promote-release`
64+
// (https://github.com/rust-lang/promote-release).
65+
let mut cmd = builder.tool_cmd(Tool::BuildManifest);
66+
let sign = builder.config.dist_sign_folder.as_ref().unwrap_or_else(|| {
67+
panic!("\n\nfailed to specify `dist.sign-folder` in `config.toml`\n\n")
68+
});
69+
let addr = builder.config.dist_upload_addr.as_ref().unwrap_or_else(|| {
70+
panic!("\n\nfailed to specify `dist.upload-addr` in `config.toml`\n\n")
71+
});
72+
73+
let today = output(Command::new("date").arg("+%Y-%m-%d"));
74+
75+
cmd.arg(sign);
76+
cmd.arg(distdir(builder));
77+
cmd.arg(today.trim());
78+
cmd.arg(addr);
79+
cmd.arg(&builder.config.channel);
80+
cmd.arg(&builder.src);
81+
82+
builder.create_dir(&distdir(builder));
83+
builder.run(&mut cmd);
84+
}
85+
}

Diff for: ‎src/tools/build-manifest/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ serde_json = "1.0"
1111
anyhow = "1.0.32"
1212
flate2 = "1.0.16"
1313
tar = "0.4.29"
14+
sha2 = "0.9.1"
15+
rayon = "1.3.1"
16+
hex = "0.4.2"

Diff for: ‎src/tools/build-manifest/README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ Then, you can generate the manifest and all the packages from `path/to/dist` to
2020
`path/to/output` with:
2121

2222
```
23-
$ BUILD_MANIFEST_DISABLE_SIGNING=1 cargo +nightly run \
24-
path/to/dist path/to/output 1970-01-01 http://example.com \
23+
$ cargo +nightly run path/to/dist path/to/output 1970-01-01 http://example.com \
2524
CHANNEL path/to/rust/repo
2625
```
2726

Diff for: ‎src/tools/build-manifest/src/main.rs

+107-118
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,22 @@
44
//! via `x.py dist hash-and-sign`; the cmdline arguments are set up
55
//! by rustbuild (in `src/bootstrap/dist.rs`).
66
7+
mod manifest;
78
mod versions;
89

10+
use crate::manifest::{Component, FileHash, Manifest, Package, Rename, Target};
911
use crate::versions::{PkgType, Versions};
10-
use serde::Serialize;
11-
use std::collections::BTreeMap;
12-
use std::collections::HashMap;
12+
use rayon::prelude::*;
13+
use sha2::Digest;
14+
use std::collections::{BTreeMap, HashMap, HashSet};
1315
use std::env;
16+
use std::error::Error;
1417
use std::fs::{self, File};
15-
use std::io::{self, Read, Write};
18+
use std::io::{self, BufReader, Read, Write};
1619
use std::path::{Path, PathBuf};
1720
use std::process::{Command, Stdio};
21+
use std::sync::Mutex;
22+
use std::time::Instant;
1823

1924
static HOSTS: &[&str] = &[
2025
"aarch64-unknown-linux-gnu",
@@ -167,57 +172,6 @@ static MINGW: &[&str] = &["i686-pc-windows-gnu", "x86_64-pc-windows-gnu"];
167172

168173
static NIGHTLY_ONLY_COMPONENTS: &[&str] = &["miri-preview", "rust-analyzer-preview"];
169174

170-
#[derive(Serialize)]
171-
#[serde(rename_all = "kebab-case")]
172-
struct Manifest {
173-
manifest_version: String,
174-
date: String,
175-
pkg: BTreeMap<String, Package>,
176-
renames: BTreeMap<String, Rename>,
177-
profiles: BTreeMap<String, Vec<String>>,
178-
}
179-
180-
#[derive(Serialize)]
181-
struct Package {
182-
version: String,
183-
git_commit_hash: Option<String>,
184-
target: BTreeMap<String, Target>,
185-
}
186-
187-
#[derive(Serialize)]
188-
struct Rename {
189-
to: String,
190-
}
191-
192-
#[derive(Serialize, Default)]
193-
struct Target {
194-
available: bool,
195-
url: Option<String>,
196-
hash: Option<String>,
197-
xz_url: Option<String>,
198-
xz_hash: Option<String>,
199-
components: Option<Vec<Component>>,
200-
extensions: Option<Vec<Component>>,
201-
}
202-
203-
impl Target {
204-
fn unavailable() -> Self {
205-
Self::default()
206-
}
207-
}
208-
209-
#[derive(Serialize)]
210-
struct Component {
211-
pkg: String,
212-
target: String,
213-
}
214-
215-
impl Component {
216-
fn from_str(pkg: &str, target: &str) -> Self {
217-
Self { pkg: pkg.to_string(), target: target.to_string() }
218-
}
219-
}
220-
221175
macro_rules! t {
222176
($e:expr) => {
223177
match $e {
@@ -232,25 +186,33 @@ struct Builder {
232186

233187
input: PathBuf,
234188
output: PathBuf,
235-
gpg_passphrase: String,
236-
digests: BTreeMap<String, String>,
237189
s3_address: String,
238190
date: String,
239191

240-
should_sign: bool,
192+
legacy: bool,
193+
legacy_gpg_passphrase: String,
241194
}
242195

243196
fn main() {
244-
// Avoid signing packages while manually testing
245-
// Do NOT set this envvar in CI
246-
let should_sign = env::var("BUILD_MANIFEST_DISABLE_SIGNING").is_err();
247-
248-
// Safety check to ensure signing is always enabled on CI
249-
// The CI environment variable is set by both Travis and AppVeyor
250-
if !should_sign && env::var("CI").is_ok() {
251-
println!("The 'BUILD_MANIFEST_DISABLE_SIGNING' env var can't be enabled on CI.");
252-
println!("If you're not running this on CI, unset the 'CI' env var.");
253-
panic!();
197+
// Up until Rust 1.48 the release process relied on build-manifest to create the SHA256
198+
// checksums of released files and to sign the tarballs. That was moved over to promote-release
199+
// in time for the branching of Rust 1.48, but the old release process still had to work the
200+
// old way.
201+
//
202+
// When running build-manifest through the old ./x.py dist hash-and-sign the environment
203+
// variable will be set, enabling the legacy behavior of generating the .sha256 files and
204+
// signing the tarballs.
205+
//
206+
// Once the old release process is fully decommissioned, the environment variable, all the
207+
// related code in this tool and ./x.py dist hash-and-sign can be removed.
208+
let legacy = env::var("BUILD_MANIFEST_LEGACY").is_ok();
209+
210+
// Avoid overloading the old server in legacy mode.
211+
if legacy {
212+
rayon::ThreadPoolBuilder::new()
213+
.num_threads(1)
214+
.build_global()
215+
.expect("failed to initialize Rayon");
254216
}
255217

256218
let mut args = env::args().skip(1);
@@ -263,7 +225,7 @@ fn main() {
263225

264226
// Do not ask for a passphrase while manually testing
265227
let mut passphrase = String::new();
266-
if should_sign {
228+
if legacy {
267229
// `x.py` passes the passphrase via stdin.
268230
t!(io::stdin().read_to_string(&mut passphrase));
269231
}
@@ -273,20 +235,21 @@ fn main() {
273235

274236
input,
275237
output,
276-
gpg_passphrase: passphrase,
277-
digests: BTreeMap::new(),
278238
s3_address,
279239
date,
280240

281-
should_sign,
241+
legacy,
242+
legacy_gpg_passphrase: passphrase,
282243
}
283244
.build();
284245
}
285246

286247
impl Builder {
287248
fn build(&mut self) {
288249
self.check_toolstate();
289-
self.digest_and_sign();
250+
if self.legacy {
251+
self.digest_and_sign();
252+
}
290253
let manifest = self.build_manifest();
291254

292255
let rust_version = self.versions.package_version(&PkgType::Rust).unwrap();
@@ -324,10 +287,9 @@ impl Builder {
324287
/// Hash all files, compute their signatures, and collect the hashes in `self.digests`.
325288
fn digest_and_sign(&mut self) {
326289
for file in t!(self.input.read_dir()).map(|e| t!(e).path()) {
327-
let filename = file.file_name().unwrap().to_str().unwrap();
328-
let digest = self.hash(&file);
290+
file.file_name().unwrap().to_str().unwrap();
291+
self.hash(&file);
329292
self.sign(&file);
330-
assert!(self.digests.insert(filename.to_string(), digest).is_none());
331293
}
332294
}
333295

@@ -343,6 +305,9 @@ impl Builder {
343305
self.add_profiles_to(&mut manifest);
344306
self.add_renames_to(&mut manifest);
345307
manifest.pkg.insert("rust".to_string(), self.rust_package(&manifest));
308+
309+
self.fill_missing_hashes(&mut manifest);
310+
346311
manifest
347312
}
348313

@@ -438,9 +403,12 @@ impl Builder {
438403

439404
fn target_host_combination(&mut self, host: &str, manifest: &Manifest) -> Option<Target> {
440405
let filename = self.versions.tarball_name(&PkgType::Rust, host).unwrap();
441-
let digest = self.digests.remove(&filename)?;
442-
let xz_filename = filename.replace(".tar.gz", ".tar.xz");
443-
let xz_digest = self.digests.remove(&xz_filename);
406+
407+
let mut target = Target::from_compressed_tar(self, &filename);
408+
if !target.available {
409+
return None;
410+
}
411+
444412
let mut components = Vec::new();
445413
let mut extensions = Vec::new();
446414

@@ -496,15 +464,9 @@ impl Builder {
496464
extensions.retain(&has_component);
497465
components.retain(&has_component);
498466

499-
Some(Target {
500-
available: true,
501-
url: Some(self.url(&filename)),
502-
hash: Some(digest),
503-
xz_url: xz_digest.as_ref().map(|_| self.url(&xz_filename)),
504-
xz_hash: xz_digest,
505-
components: Some(components),
506-
extensions: Some(extensions),
507-
})
467+
target.components = Some(components);
468+
target.extensions = Some(extensions);
469+
Some(target)
508470
}
509471

510472
fn profile(
@@ -542,37 +504,19 @@ impl Builder {
542504
let targets = targets
543505
.iter()
544506
.map(|name| {
545-
if is_present {
546-
// The component generally exists, but it might still be missing for this target.
507+
let target = if is_present {
547508
let filename = self
548509
.versions
549510
.tarball_name(&PkgType::from_component(pkgname), name)
550511
.unwrap();
551-
let digest = match self.digests.remove(&filename) {
552-
Some(digest) => digest,
553-
// This component does not exist for this target -- skip it.
554-
None => return (name.to_string(), Target::unavailable()),
555-
};
556-
let xz_filename = filename.replace(".tar.gz", ".tar.xz");
557-
let xz_digest = self.digests.remove(&xz_filename);
558-
559-
(
560-
name.to_string(),
561-
Target {
562-
available: true,
563-
url: Some(self.url(&filename)),
564-
hash: Some(digest),
565-
xz_url: xz_digest.as_ref().map(|_| self.url(&xz_filename)),
566-
xz_hash: xz_digest,
567-
components: None,
568-
extensions: None,
569-
},
570-
)
512+
513+
Target::from_compressed_tar(self, &filename)
571514
} else {
572515
// If the component is not present for this build add it anyway but mark it as
573516
// unavailable -- this way rustup won't allow upgrades without --force
574-
(name.to_string(), Target::unavailable())
575-
}
517+
Target::unavailable()
518+
};
519+
(name.to_string(), target)
576520
})
577521
.collect();
578522

@@ -586,8 +530,9 @@ impl Builder {
586530
);
587531
}
588532

589-
fn url(&self, filename: &str) -> String {
590-
format!("{}/{}/{}", self.s3_address, self.date, filename)
533+
fn url(&self, path: &Path) -> String {
534+
let file_name = path.file_name().unwrap().to_str().unwrap();
535+
format!("{}/{}/{}", self.s3_address, self.date, file_name)
591536
}
592537

593538
fn hash(&self, path: &Path) -> String {
@@ -608,7 +553,7 @@ impl Builder {
608553
}
609554

610555
fn sign(&self, path: &Path) {
611-
if !self.should_sign {
556+
if !self.legacy {
612557
return;
613558
}
614559

@@ -631,10 +576,45 @@ impl Builder {
631576
.arg(path)
632577
.stdin(Stdio::piped());
633578
let mut child = t!(cmd.spawn());
634-
t!(child.stdin.take().unwrap().write_all(self.gpg_passphrase.as_bytes()));
579+
t!(child.stdin.take().unwrap().write_all(self.legacy_gpg_passphrase.as_bytes()));
635580
assert!(t!(child.wait()).success());
636581
}
637582

583+
fn fill_missing_hashes(&self, manifest: &mut Manifest) {
584+
// First collect all files that need hashes
585+
let mut need_hashes = HashSet::new();
586+
crate::manifest::visit_file_hashes(manifest, |file_hash| {
587+
if let FileHash::Missing(path) = file_hash {
588+
need_hashes.insert(path.clone());
589+
}
590+
});
591+
592+
let collected = Mutex::new(HashMap::new());
593+
let collection_start = Instant::now();
594+
println!(
595+
"collecting hashes for {} tarballs across {} threads",
596+
need_hashes.len(),
597+
rayon::current_num_threads().min(need_hashes.len()),
598+
);
599+
need_hashes.par_iter().for_each(|path| match fetch_hash(path) {
600+
Ok(hash) => {
601+
collected.lock().unwrap().insert(path, hash);
602+
}
603+
Err(err) => eprintln!("error while fetching the hash for {}: {}", path.display(), err),
604+
});
605+
let collected = collected.into_inner().unwrap();
606+
println!("collected {} hashes in {:.2?}", collected.len(), collection_start.elapsed());
607+
608+
crate::manifest::visit_file_hashes(manifest, |file_hash| {
609+
if let FileHash::Missing(path) = file_hash {
610+
match collected.get(path) {
611+
Some(hash) => *file_hash = FileHash::Present(hash.clone()),
612+
None => panic!("missing hash for file {}", path.display()),
613+
}
614+
}
615+
})
616+
}
617+
638618
fn write_channel_files(&self, channel_name: &str, manifest: &Manifest) {
639619
self.write(&toml::to_string(&manifest).unwrap(), channel_name, ".toml");
640620
self.write(&manifest.date, channel_name, "-date.txt");
@@ -648,7 +628,16 @@ impl Builder {
648628
fn write(&self, contents: &str, channel_name: &str, suffix: &str) {
649629
let dst = self.output.join(format!("channel-rust-{}{}", channel_name, suffix));
650630
t!(fs::write(&dst, contents));
651-
self.hash(&dst);
652-
self.sign(&dst);
631+
if self.legacy {
632+
self.hash(&dst);
633+
self.sign(&dst);
634+
}
653635
}
654636
}
637+
638+
fn fetch_hash(path: &Path) -> Result<String, Box<dyn Error>> {
639+
let mut file = BufReader::new(File::open(path)?);
640+
let mut sha256 = sha2::Sha256::default();
641+
std::io::copy(&mut file, &mut sha256)?;
642+
Ok(hex::encode(sha256.finalize()))
643+
}

Diff for: ‎src/tools/build-manifest/src/manifest.rs

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use crate::Builder;
2+
use serde::{Serialize, Serializer};
3+
use std::collections::BTreeMap;
4+
use std::path::{Path, PathBuf};
5+
6+
#[derive(Serialize)]
7+
#[serde(rename_all = "kebab-case")]
8+
pub(crate) struct Manifest {
9+
pub(crate) manifest_version: String,
10+
pub(crate) date: String,
11+
pub(crate) pkg: BTreeMap<String, Package>,
12+
pub(crate) renames: BTreeMap<String, Rename>,
13+
pub(crate) profiles: BTreeMap<String, Vec<String>>,
14+
}
15+
16+
#[derive(Serialize)]
17+
pub(crate) struct Package {
18+
pub(crate) version: String,
19+
pub(crate) git_commit_hash: Option<String>,
20+
pub(crate) target: BTreeMap<String, Target>,
21+
}
22+
23+
#[derive(Serialize)]
24+
pub(crate) struct Rename {
25+
pub(crate) to: String,
26+
}
27+
28+
#[derive(Serialize, Default)]
29+
pub(crate) struct Target {
30+
pub(crate) available: bool,
31+
pub(crate) url: Option<String>,
32+
pub(crate) hash: Option<FileHash>,
33+
pub(crate) xz_url: Option<String>,
34+
pub(crate) xz_hash: Option<FileHash>,
35+
pub(crate) components: Option<Vec<Component>>,
36+
pub(crate) extensions: Option<Vec<Component>>,
37+
}
38+
39+
impl Target {
40+
pub(crate) fn from_compressed_tar(builder: &Builder, base_path: &str) -> Self {
41+
let base_path = builder.input.join(base_path);
42+
let gz = Self::tarball_variant(&base_path, "gz");
43+
let xz = Self::tarball_variant(&base_path, "xz");
44+
45+
if gz.is_none() {
46+
return Self::unavailable();
47+
}
48+
49+
Self {
50+
available: true,
51+
components: None,
52+
extensions: None,
53+
// .gz
54+
url: gz.as_ref().map(|path| builder.url(path)),
55+
hash: gz.map(FileHash::Missing),
56+
// .xz
57+
xz_url: xz.as_ref().map(|path| builder.url(path)),
58+
xz_hash: xz.map(FileHash::Missing),
59+
}
60+
}
61+
62+
fn tarball_variant(base: &Path, ext: &str) -> Option<PathBuf> {
63+
let mut path = base.to_path_buf();
64+
path.set_extension(ext);
65+
if path.is_file() { Some(path) } else { None }
66+
}
67+
68+
pub(crate) fn unavailable() -> Self {
69+
Self::default()
70+
}
71+
}
72+
73+
#[derive(Serialize)]
74+
pub(crate) struct Component {
75+
pub(crate) pkg: String,
76+
pub(crate) target: String,
77+
}
78+
79+
impl Component {
80+
pub(crate) fn from_str(pkg: &str, target: &str) -> Self {
81+
Self { pkg: pkg.to_string(), target: target.to_string() }
82+
}
83+
}
84+
85+
#[allow(unused)]
86+
pub(crate) enum FileHash {
87+
Missing(PathBuf),
88+
Present(String),
89+
}
90+
91+
impl Serialize for FileHash {
92+
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
93+
match self {
94+
FileHash::Missing(path) => Err(serde::ser::Error::custom(format!(
95+
"can't serialize a missing hash for file {}",
96+
path.display()
97+
))),
98+
FileHash::Present(inner) => inner.serialize(serializer),
99+
}
100+
}
101+
}
102+
103+
pub(crate) fn visit_file_hashes(manifest: &mut Manifest, mut f: impl FnMut(&mut FileHash)) {
104+
for pkg in manifest.pkg.values_mut() {
105+
for target in pkg.target.values_mut() {
106+
if let Some(hash) = &mut target.hash {
107+
f(hash);
108+
}
109+
if let Some(hash) = &mut target.xz_hash {
110+
f(hash);
111+
}
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)
Please sign in to comment.