blob: 4fd7d6665f6162cd8a46a7f72a775fdd8a226ba3 [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 crate::crates::{self, ThirdPartySource};
use crate::manifest::CargoManifest;
use crate::paths;
use crate::util::{check_exit_ok, check_output, check_spawn, check_wait_with_output};
use std::fs;
use std::io::Write;
use std::process;
use anyhow::{Context, Result};
/// 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,
) -> Result<()> {
let vendored_crate = crates::VendoredCrate { name: name.into(), version: version.clone() };
let build_path = paths.third_party.join(ThirdPartySource::build_path(&vendored_crate));
let crate_path = paths.third_party.join(ThirdPartySource::crate_path(&vendored_crate));
let url = format!(
"{dir}/{name}/{name}-{version}.{suffix}",
dir = CRATES_IO_DOWNLOAD_URL,
suffix = CRATES_IO_DOWNLOAD_SUFFIX
);
let curl_out = check_output(
&mut process::Command::new("curl").arg("--fail").arg(url.to_string()),
"curl",
)?;
check_exit_ok(&curl_out, "curl")?;
// 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 = check_spawn(
&mut 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())
.stderr(process::Stdio::piped()),
"tar",
)?;
untar
.stdin
.take()
.unwrap()
.write_all(&curl_out.stdout)
.context("Failed to pipe crate archive to tar")?;
{
let untar_output = check_wait_with_output(untar, "tar")?;
check_exit_ok(&untar_output, "tar")?;
}
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());
Ok(())
}
/// 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"),
(
"(MIT OR Apache-2.0) AND Unicode-DFS-2016",
"Apache 2.0 AND Unicode License Agreement - Data Files and Software (2016)",
),
];