blob: e42853b3124202983bec93dbaef587b8ce7ffca6 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fs;
use std::io::{Read, Write};
use std::process::{self, ExitCode};
use crate::crates;
use crate::manifest::CargoManifest;
use crate::paths;
/// Runs the download subcommand, which downloads a crate from crates.io and
/// unpacks it into the Chromium tree.
pub fn download(
name: &str,
version: semver::Version,
security: bool,
paths: &paths::ChromiumPaths,
) -> ExitCode {
let vendored_crate = crates::ChromiumVendoredCrate {
name: name.to_string(),
epoch: crates::Epoch::from_version(&version),
};
let build_path = paths.third_party.join(vendored_crate.build_path());
let crate_path = paths.third_party.join(vendored_crate.crate_path());
let url = format!(
"{dir}/{name}/{name}-{version}.{suffix}",
dir = CRATES_IO_DOWNLOAD_URL,
suffix = CRATES_IO_DOWNLOAD_SUFFIX
);
let curl_out = process::Command::new("curl")
.arg("--fail")
.arg(url.to_string())
.output()
.expect("Failed to run curl");
if !curl_out.status.success() {
eprintln!("gnrt: {}", String::from_utf8(curl_out.stderr).unwrap());
return ExitCode::FAILURE;
}
// Makes the directory where the build file will go. The crate's source code
// will go below it. This directory and its parents are allowed to exist
// already.
std::fs::create_dir_all(&build_path)
.expect("Could not make the third-party directory '{build_path}' for the crate");
// Makes the directory where the source code will be unzipped. It should not
// exist or we'd be clobbering existing files.
std::fs::create_dir(&crate_path).expect("Crate directory '{crate_path}' already exists");
let mut untar = process::Command::new("tar")
// Extract and unzip from stdin.
.arg("xzf")
.arg("-")
// Drop the first path component, which is the crate's name-version.
.arg("--strip-components=1")
// Unzip into the crate's directory in third_party/rust.
.arg(format!("--directory={}", crate_path.display()))
// The input is the downloaded file.
.stdin(process::Stdio::piped())
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.spawn()
.expect("Failed to run tar");
if untar.stdin.take().unwrap().write_all(&curl_out.stdout).is_err() {
eprintln!("gnrt: Failed to pipe input to tar, it exited early");
}
if !untar.wait().expect("Failed to wait for tar").success() {
let mut stderr_buf = Vec::new();
untar.stderr.unwrap().read_to_end(&mut stderr_buf).expect("Failed to read stderr from tar");
eprintln!("gnrt: {}", String::from_utf8(stderr_buf).unwrap());
return ExitCode::FAILURE;
}
let cargo: CargoManifest = {
let str = std::fs::read_to_string(crate_path.join("Cargo.toml"))
.expect("Unable to open downloaded Cargo.toml");
toml::de::from_str(&str).expect("Unable to parse downloaded Cargo.toml")
};
let (_, readme_license) = ALLOWED_LICENSES
.iter()
.find(|(allowed_license, _)| &cargo.package.license == *allowed_license)
.expect("License in downloaded Cargo.toml is not in ALLOWED_LICENSES");
let vcs_path = crate_path.join(".cargo_vcs_info.json");
let vcs_contents = match fs::read_to_string(&vcs_path) {
Ok(s) => serde_json::from_str(&s).unwrap(),
Err(_) => None,
};
let githash: Option<&str> = vcs_contents.as_ref().and_then(|v| {
use serde_json::Value::*;
match v {
Object(map) => match map.get("git") {
Some(Object(map)) => match map.get("sha1") {
Some(String(s)) => Some(&s[..]),
_ => None,
},
_ => None,
},
_ => None,
}
});
let readme = gen_readme_chromium_text(&cargo, readme_license, githash, security);
std::fs::write(build_path.join("README.chromium"), readme)
.expect("Failed to write README.chromium");
println!("gnrt: Downloaded {name} {version} to {path}", path = crate_path.display());
ExitCode::SUCCESS
}
/// Generate the contents of the README.chromium file.
fn gen_readme_chromium_text(
manifest: &CargoManifest,
license: &str,
githash: Option<&str>,
security: bool,
) -> String {
let security = if security { "yes" } else { "no" };
let revision = githash.map_or_else(String::new, |s| format!("Revision: {s}\n"));
format!(
"Name: {crate_name}\n\
URL: {url}\n\
Description: {description}\n\
Version: {version}\n\
Security Critical: {security}\n\
License: {license}\n\
{revision}",
crate_name = manifest.package.name,
url = format!("{}/{}", CRATES_IO_VIEW_URL, manifest.package.name),
description = manifest.package.description.as_ref().unwrap_or(&"".to_string()),
version = manifest.package.version,
)
}
static CRATES_IO_DOWNLOAD_URL: &str = "https://static.crates.io/crates";
static CRATES_IO_DOWNLOAD_SUFFIX: &str = "crate";
static CRATES_IO_VIEW_URL: &str = "https://crates.io/crates";
// Allowed licenses, in the format they are specified in Cargo.toml files from
// crates.io, and the format to write to README.chromium.
static ALLOWED_LICENSES: [(&str, &str); 15] = [
// ("Cargo.toml string", "License for README.chromium")
("Apache-2.0", "Apache 2.0"),
("MIT OR Apache-2.0", "Apache 2.0"),
("MIT/Apache-2.0", "Apache 2.0"),
("Apache-2.0 / MIT", "Apache 2.0"),
("Apache-2.0 OR MIT", "Apache 2.0"),
("Apache-2.0/MIT", "Apache 2.0"),
("MIT", "MIT"),
("Unlicense OR MIT", "MIT"),
("Unlicense/MIT", "MIT"),
("Apache-2.0 OR BSL-1.0", "Apache 2.0"),
("BSD-3-Clause", "BSD 3-Clause"),
("ISC", "ISC"),
("MIT OR Zlib OR Apache-2.0", "Apache 2.0"),
("0BSD OR MIT OR Apache-2.0", "Apache 2.0"),
("Unicode-DFS-2016", "Unicode License Agreement - Data Files and Software (2016)"),
];