Skip to content

Commit eeb4601

Browse files
generate-copyright now scans for cargo dependencies.
1 parent 7fdefb8 commit eeb4601

File tree

5 files changed

+161
-1
lines changed

5 files changed

+161
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,7 @@ dependencies = [
15521552
"anyhow",
15531553
"serde",
15541554
"serde_json",
1555+
"thiserror",
15551556
]
15561557

15571558
[[package]]

src/bootstrap/src/core/build_steps/run.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ impl Step for GenerateCopyright {
217217
let mut cmd = builder.tool_cmd(Tool::GenerateCopyright);
218218
cmd.env("LICENSE_METADATA", &license_metadata);
219219
cmd.env("DEST", &dest);
220+
cmd.env("CARGO", &builder.initial_cargo);
220221
cmd.run(builder);
221222

222223
dest

src/tools/generate-copyright/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
name = "generate-copyright"
33
version = "0.1.0"
44
edition = "2021"
5+
description = "Produces a manifest of all the copyrighted materials in the Rust Toolchain"
56

67
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
78

89
[dependencies]
910
anyhow = "1.0.65"
1011
serde = { version = "1.0.147", features = ["derive"] }
1112
serde_json = "1.0.85"
13+
thiserror = "1"
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! Gets metadata about a workspace from Cargo
2+
3+
/// Describes how this module can fail
4+
#[derive(Debug, thiserror::Error)]
5+
pub enum Error {
6+
#[error("Failed to run cargo metadata: {0:?}")]
7+
Launching(#[from] std::io::Error),
8+
#[error("Failed get output from cargo metadata: {0:?}")]
9+
GettingMetadata(String),
10+
#[error("Failed parse JSON output from cargo metadata: {0:?}")]
11+
ParsingJson(#[from] serde_json::Error),
12+
#[error("Failed find expected JSON element {0} in output from cargo metadata")]
13+
MissingJsonElement(&'static str),
14+
#[error("Failed find expected JSON element {0} in output from cargo metadata for package {1}")]
15+
MissingJsonElementForPackage(String, String),
16+
}
17+
18+
/// Describes one of our dependencies
19+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
20+
pub struct Dependency {
21+
/// The name of the package
22+
pub name: String,
23+
/// The version number
24+
pub version: String,
25+
/// The license it is under
26+
pub license: String,
27+
/// The list of authors from the package metadata
28+
pub authors: Vec<String>,
29+
}
30+
31+
/// Use `cargo` to get a list of dependencies and their license data
32+
///
33+
/// Any dependency with a path beginning with `root_path` is ignored, as we
34+
/// assume `reuse` has covered it already.
35+
pub fn get(
36+
cargo: &std::path::Path,
37+
manifest_path: &std::path::Path,
38+
root_path: &std::path::Path,
39+
) -> Result<Vec<Dependency>, Error> {
40+
if manifest_path.file_name() != Some(std::ffi::OsStr::new("Cargo.toml")) {
41+
panic!("cargo_manifest::get requires a path to a Cargo.toml file");
42+
}
43+
let metadata_output = std::process::Command::new(cargo)
44+
.arg("metadata")
45+
.arg("--format-version=1")
46+
.arg("--all-features")
47+
.arg("--manifest-path")
48+
.arg(manifest_path)
49+
.env("RUSTC_BOOTSTRAP", "1")
50+
.output()
51+
.map_err(|e| Error::Launching(e))?;
52+
if !metadata_output.status.success() {
53+
return Err(Error::GettingMetadata(
54+
String::from_utf8(metadata_output.stderr).expect("UTF-8 output from cargo"),
55+
));
56+
}
57+
let metadata_json: serde_json::Value = serde_json::from_slice(&metadata_output.stdout)?;
58+
let packages = metadata_json["packages"]
59+
.as_array()
60+
.ok_or_else(|| Error::MissingJsonElement("packages array"))?;
61+
let mut v = Vec::new();
62+
for package in packages {
63+
let package =
64+
package.as_object().ok_or_else(|| Error::MissingJsonElement("package object"))?;
65+
// println!("Package: {}", serde_json::to_string_pretty(package).expect("JSON encoding"));
66+
let manifest_path = package
67+
.get("manifest_path")
68+
.and_then(|v| v.as_str())
69+
.map(std::path::Path::new)
70+
.ok_or_else(|| Error::MissingJsonElement("package.manifest_path"))?;
71+
if manifest_path.starts_with(&root_path) {
72+
// it's an in-tree dependency and reuse covers it
73+
continue;
74+
}
75+
// otherwise it's an out-of-tree dependency
76+
let get_string = |field_name: &str, package_name: &str| {
77+
package.get(field_name).and_then(|v| v.as_str()).ok_or_else(|| {
78+
Error::MissingJsonElementForPackage(
79+
format!("package.{field_name}"),
80+
package_name.to_owned(),
81+
)
82+
})
83+
};
84+
85+
let name = get_string("name", "unknown")?;
86+
let license = get_string("license", name)?;
87+
let version = get_string("version", name)?;
88+
let authors_list = package
89+
.get("authors")
90+
.and_then(|v| v.as_array())
91+
.ok_or_else(|| Error::MissingJsonElement("package.authors"))?;
92+
let authors: Vec<String> =
93+
authors_list.iter().filter_map(|v| v.as_str()).map(|s| s.to_owned()).collect();
94+
95+
v.push(Dependency {
96+
name: name.to_owned(),
97+
version: version.to_owned(),
98+
license: license.to_owned(),
99+
authors,
100+
})
101+
}
102+
103+
Ok(v)
104+
}

src/tools/generate-copyright/src/main.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,56 @@
11
use anyhow::Error;
22
use std::collections::BTreeSet;
33
use std::io::Write;
4-
use std::path::PathBuf;
4+
use std::path::{Path, PathBuf};
55

6+
mod cargo_metadata;
7+
8+
/// The entry point to the binary.
9+
///
10+
/// You should probably let `bootstrap` execute this program instead of running it directly.
11+
///
12+
/// Run `x.py run generate-metadata`
613
fn main() -> Result<(), Error> {
714
let dest = env_path("DEST")?;
15+
let cargo = env_path("CARGO")?;
816
let license_metadata = env_path("LICENSE_METADATA")?;
917

1018
let metadata: Metadata = serde_json::from_slice(&std::fs::read(&license_metadata)?)?;
1119

20+
let mut deps_set = BTreeSet::new();
21+
22+
let root_path = std::path::absolute(".")?;
23+
for dep in cargo_metadata::get(&cargo, Path::new("./Cargo.toml"), &root_path)? {
24+
deps_set.insert(dep);
25+
}
26+
for dep in cargo_metadata::get(&cargo, Path::new("./src/tools/cargo/Cargo.toml"), &root_path)? {
27+
deps_set.insert(dep);
28+
}
29+
for dep in cargo_metadata::get(&cargo, Path::new("./library/std/Cargo.toml"), &root_path)? {
30+
deps_set.insert(dep);
31+
}
32+
1233
let mut buffer = Vec::new();
34+
35+
write!(
36+
buffer,
37+
"# In-tree files\n\nThe following licenses cover the in-tree source files that were used in this release:\n\n"
38+
)?;
1339
render_recursive(&metadata.files, &mut buffer, 0)?;
1440

41+
write!(
42+
buffer,
43+
"\n# Out-of-tree files\n\nThe following licenses cover the out-of-tree crates that were used in this release:\n\n"
44+
)?;
45+
render_deps(deps_set.iter(), &mut buffer)?;
46+
1547
std::fs::write(&dest, &buffer)?;
1648

1749
Ok(())
1850
}
1951

52+
/// Recursively draw the tree of files/folders we found on disk and their licences, as
53+
/// markdown, into the given Vec.
2054
fn render_recursive(node: &Node, buffer: &mut Vec<u8>, depth: usize) -> Result<(), Error> {
2155
let prefix = std::iter::repeat("> ").take(depth + 1).collect::<String>();
2256

@@ -56,6 +90,7 @@ fn render_recursive(node: &Node, buffer: &mut Vec<u8>, depth: usize) -> Result<(
5690
Ok(())
5791
}
5892

93+
/// Draw a series of sibling files/folders, as markdown, into the given Vec.
5994
fn render_license<'a>(
6095
prefix: &str,
6196
names: impl Iterator<Item = &'a String>,
@@ -85,6 +120,23 @@ fn render_license<'a>(
85120
Ok(())
86121
}
87122

123+
/// Render a list of out-of-tree dependencies as markdown into the given Vec.
124+
fn render_deps<'a, 'b>(
125+
deps: impl Iterator<Item = &'a cargo_metadata::Dependency>,
126+
buffer: &'b mut Vec<u8>,
127+
) -> Result<(), Error> {
128+
for dep in deps {
129+
let authors_list = dep.authors.join(", ");
130+
let url = format!("https://crates.io/crates/{}/{}", dep.name, dep.version);
131+
writeln!(
132+
buffer,
133+
"* [{} {}]({}) ({}), by {}",
134+
dep.name, dep.version, url, dep.license, authors_list
135+
)?;
136+
}
137+
Ok(())
138+
}
139+
88140
#[derive(serde::Deserialize)]
89141
struct Metadata {
90142
files: Node,

0 commit comments

Comments
 (0)