From 9dfc682834e950890b9bf883e3a1ca8c3888aaf1 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Tue, 19 Nov 2024 11:37:30 +0000 Subject: [PATCH] generate-copyright: Now generates a library file too. We only run reuse once, so the output has to be filtered to find only the files that are relevant to the library tree. Outputs build/COPYRIGHT.html and build/COPYRIGHT-library.html. --- src/bootstrap/src/core/build_steps/run.rs | 3 +- .../generate-copyright/src/cargo_metadata.rs | 3 +- src/tools/generate-copyright/src/main.rs | 149 +++++++++++++++--- .../templates/COPYRIGHT-library.html | 53 +++++++ 4 files changed, 180 insertions(+), 28 deletions(-) create mode 100644 src/tools/generate-copyright/templates/COPYRIGHT-library.html diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index a6dff7fde8054..1a0a90564e6ad 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -211,12 +211,13 @@ impl Step for GenerateCopyright { fn run(self, builder: &Builder<'_>) -> Self::Output { let license_metadata = builder.ensure(CollectLicenseMetadata); - // Temporary location, it will be moved to the proper one once it's accurate. let dest = builder.out.join("COPYRIGHT.html"); + let dest_libstd = builder.out.join("COPYRIGHT-library.html"); let mut cmd = builder.tool_cmd(Tool::GenerateCopyright); cmd.env("LICENSE_METADATA", &license_metadata); cmd.env("DEST", &dest); + cmd.env("DEST_LIBSTD", &dest_libstd); cmd.env("OUT_DIR", &builder.out); cmd.env("CARGO", &builder.initial_cargo); cmd.run(builder); diff --git a/src/tools/generate-copyright/src/cargo_metadata.rs b/src/tools/generate-copyright/src/cargo_metadata.rs index 31b18c3dc1035..5fadee8544895 100644 --- a/src/tools/generate-copyright/src/cargo_metadata.rs +++ b/src/tools/generate-copyright/src/cargo_metadata.rs @@ -52,14 +52,13 @@ pub struct PackageMetadata { /// assume `reuse` has covered it already. pub fn get_metadata_and_notices( cargo: &Path, - dest: &Path, + vendor_path: &Path, root_path: &Path, manifest_paths: &[&Path], ) -> Result, Error> { let mut output = get_metadata(cargo, root_path, manifest_paths)?; // Now do a cargo-vendor and grab everything - let vendor_path = dest.join("vendor"); println!("Vendoring deps into {}...", vendor_path.display()); run_cargo_vendor(cargo, &vendor_path, manifest_paths)?; diff --git a/src/tools/generate-copyright/src/main.rs b/src/tools/generate-copyright/src/main.rs index afa75d0d67140..f9d96b594626d 100644 --- a/src/tools/generate-copyright/src/main.rs +++ b/src/tools/generate-copyright/src/main.rs @@ -6,13 +6,6 @@ use rinja::Template; mod cargo_metadata; -#[derive(Template)] -#[template(path = "COPYRIGHT.html")] -struct CopyrightTemplate { - in_tree: Node, - dependencies: BTreeMap, -} - /// The entry point to the binary. /// /// You should probably let `bootstrap` execute this program instead of running it directly. @@ -20,49 +13,89 @@ struct CopyrightTemplate { /// Run `x.py run generate-copyright` fn main() -> Result<(), Error> { let dest_file = env_path("DEST")?; + let libstd_dest_file = env_path("DEST_LIBSTD")?; let out_dir = env_path("OUT_DIR")?; let cargo = env_path("CARGO")?; let license_metadata = env_path("LICENSE_METADATA")?; - let collected_tree_metadata: Metadata = - serde_json::from_slice(&std::fs::read(&license_metadata)?)?; - let root_path = std::path::absolute(".")?; - let workspace_paths = [ - Path::new("./Cargo.toml"), - Path::new("./src/tools/cargo/Cargo.toml"), - Path::new("./library/Cargo.toml"), - ]; - let mut collected_cargo_metadata = - cargo_metadata::get_metadata_and_notices(&cargo, &out_dir, &root_path, &workspace_paths)?; - let stdlib_set = - cargo_metadata::get_metadata(&cargo, &root_path, &[Path::new("./library/std/Cargo.toml")])?; + // Scan Cargo dependencies + let mut collected_cargo_metadata = + cargo_metadata::get_metadata_and_notices(&cargo, &out_dir.join("vendor"), &root_path, &[ + Path::new("./Cargo.toml"), + Path::new("./src/tools/cargo/Cargo.toml"), + Path::new("./library/Cargo.toml"), + ])?; + + let library_collected_cargo_metadata = cargo_metadata::get_metadata_and_notices( + &cargo, + &out_dir.join("library-vendor"), + &root_path, + &[Path::new("./library/Cargo.toml")], + )?; for (key, value) in collected_cargo_metadata.iter_mut() { - value.is_in_libstd = Some(stdlib_set.contains_key(key)); + value.is_in_libstd = Some(library_collected_cargo_metadata.contains_key(key)); } + // Load JSON output by reuse + let collected_tree_metadata: Metadata = + serde_json::from_slice(&std::fs::read(&license_metadata)?)?; + + // Find libstd sub-set + let library_collected_tree_metadata = Metadata { + files: collected_tree_metadata + .files + .trim_clone(&Path::new("./library"), &Path::new(".")) + .unwrap(), + }; + + // Output main file let template = CopyrightTemplate { in_tree: collected_tree_metadata.files, dependencies: collected_cargo_metadata, }; - let output = template.render()?; - std::fs::write(&dest_file, output)?; + // Output libstd subset file + let template = LibraryCopyrightTemplate { + in_tree: library_collected_tree_metadata.files, + dependencies: library_collected_cargo_metadata, + }; + let output = template.render()?; + std::fs::write(&libstd_dest_file, output)?; + Ok(()) } +/// The HTML template for the toolchain copyright file +#[derive(Template)] +#[template(path = "COPYRIGHT.html")] +struct CopyrightTemplate { + in_tree: Node, + dependencies: BTreeMap, +} + +/// The HTML template for the library copyright file +#[derive(Template)] +#[template(path = "COPYRIGHT-library.html")] +struct LibraryCopyrightTemplate { + in_tree: Node, + dependencies: BTreeMap, +} + /// Describes a tree of metadata for our filesystem tree -#[derive(serde::Deserialize)] +/// +/// Must match the JSON emitted by the `CollectLicenseMetadata` bootstrap tool. +#[derive(serde::Deserialize, Clone, Debug, PartialEq, Eq)] struct Metadata { files: Node, } /// Describes one node in our metadata tree -#[derive(serde::Deserialize, rinja::Template)] +#[derive(serde::Deserialize, rinja::Template, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "kebab-case", tag = "type")] #[template(path = "Node.html")] pub(crate) enum Node { @@ -72,8 +105,74 @@ pub(crate) enum Node { Group { files: Vec, directories: Vec, license: License }, } +impl Node { + /// Clone, this node, but only if the path to the item is within the match path + fn trim_clone(&self, match_path: &Path, parent_path: &Path) -> Option { + match self { + Node::Root { children } => { + let mut filtered_children = Vec::new(); + for node in children { + if let Some(child_node) = node.trim_clone(match_path, parent_path) { + filtered_children.push(child_node); + } + } + if filtered_children.is_empty() { + None + } else { + Some(Node::Root { children: filtered_children }) + } + } + Node::Directory { name, children, license } => { + let child_name = parent_path.join(name); + if !(child_name.starts_with(match_path) || match_path.starts_with(&child_name)) { + return None; + } + let mut filtered_children = Vec::new(); + for node in children { + if let Some(child_node) = node.trim_clone(match_path, &child_name) { + filtered_children.push(child_node); + } + } + Some(Node::Directory { + name: name.clone(), + children: filtered_children, + license: license.clone(), + }) + } + Node::File { name, license } => { + let child_name = parent_path.join(name); + if !(child_name.starts_with(match_path) || match_path.starts_with(&child_name)) { + return None; + } + Some(Node::File { name: name.clone(), license: license.clone() }) + } + Node::Group { files, directories, license } => { + let mut filtered_child_files = Vec::new(); + for child in files { + let child_name = parent_path.join(child); + if child_name.starts_with(match_path) || match_path.starts_with(&child_name) { + filtered_child_files.push(child.clone()); + } + } + let mut filtered_child_dirs = Vec::new(); + for child in directories { + let child_name = parent_path.join(child); + if child_name.starts_with(match_path) || match_path.starts_with(&child_name) { + filtered_child_dirs.push(child.clone()); + } + } + Some(Node::Group { + files: filtered_child_files, + directories: filtered_child_dirs, + license: license.clone(), + }) + } + } + } +} + /// A License has an SPDX license name and a list of copyright holders. -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Clone, Debug, PartialEq, Eq)] struct License { spdx: String, copyright: Vec, diff --git a/src/tools/generate-copyright/templates/COPYRIGHT-library.html b/src/tools/generate-copyright/templates/COPYRIGHT-library.html new file mode 100644 index 0000000000000..2c1eba741db0a --- /dev/null +++ b/src/tools/generate-copyright/templates/COPYRIGHT-library.html @@ -0,0 +1,53 @@ + + + + + Copyright notices for The Rust Standard Library + + + +

Copyright notices for The Rust Standard Library

+ +

This file describes the copyright and licensing information for the Rust +Standard Library source code within The Rust Project git tree, and the +third-party dependencies used when building the Rust Standard Library.

+ +

Table of Contents

+ + +

In-tree files

+ +

The following licenses cover the in-tree source files that were used in this +release:

+ +{{ in_tree|safe }} + +

Out-of-tree dependencies

+ +

The following licenses cover the out-of-tree crates that were used in the +Rust Standard Library in this release:

+ +{% for (key, value) in dependencies %} +

📦 {{key.name}}-{{key.version}}

+

URL: https://crates.io/crates/{{ key.name }}/{{ key.version }}

+

Authors: {{ value.authors|join(", ") }}

+

License: {{ value.license }}

+ {% let len = value.notices.len() %} + {% if len > 0 %} +

Notices: + {% for (notice_name, notice_text) in value.notices %} +

+ {{ notice_name }} +
+{{ notice_text }}
+                
+
+ {% endfor %} +

+ {% endif %} +{% endfor %} + + \ No newline at end of file