about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJonathan Pallant <jonathan.pallant@ferrous-systems.com>2024-11-19 11:37:30 +0000
committerJonathan Pallant <jonathan.pallant@ferrous-systems.com>2024-11-21 10:01:21 +0000
commit9dfc682834e950890b9bf883e3a1ca8c3888aaf1 (patch)
tree353966659aade72e2b6146259018415b102c2815
parente6c1e14e5d487a90abeb5ba4dccc608521bbfb54 (diff)
downloadrust-9dfc682834e950890b9bf883e3a1ca8c3888aaf1.tar.gz
rust-9dfc682834e950890b9bf883e3a1ca8c3888aaf1.zip
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.
-rw-r--r--src/bootstrap/src/core/build_steps/run.rs3
-rw-r--r--src/tools/generate-copyright/src/cargo_metadata.rs3
-rw-r--r--src/tools/generate-copyright/src/main.rs149
-rw-r--r--src/tools/generate-copyright/templates/COPYRIGHT-library.html53
4 files changed, 180 insertions, 28 deletions
diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs
index a6dff7fde80..1a0a90564e6 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 31b18c3dc10..5fadee85448 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<BTreeMap<Package, PackageMetadata>, 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 afa75d0d671..f9d96b59462 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<cargo_metadata::Package, cargo_metadata::PackageMetadata>,
-}
-
 /// 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<cargo_metadata::Package, cargo_metadata::PackageMetadata>,
+}
+
+/// The HTML template for the library copyright file
+#[derive(Template)]
+#[template(path = "COPYRIGHT-library.html")]
+struct LibraryCopyrightTemplate {
+    in_tree: Node,
+    dependencies: BTreeMap<cargo_metadata::Package, cargo_metadata::PackageMetadata>,
+}
+
 /// 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<String>, directories: Vec<String>, 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<Node> {
+        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<String>,
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 00000000000..2c1eba741db
--- /dev/null
+++ b/src/tools/generate-copyright/templates/COPYRIGHT-library.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>Copyright notices for The Rust Standard Library</title>
+</head>
+<body>
+
+<h1>Copyright notices for The Rust Standard Library</h1>
+
+<p>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.</p>
+
+<h2>Table of Contents</h2>
+<ul>
+    <li><a href="#in-tree-files">In-tree files</a></li>
+    <li><a href="#out-of-tree-dependencies">Out-of-tree dependencies</a></li>
+</ul>
+
+<h2 id="in-tree-files">In-tree files</h2>
+
+<p>The following licenses cover the in-tree source files that were used in this
+release:</p>
+
+{{ in_tree|safe }}
+
+<h2 id="out-of-tree-dependencies">Out-of-tree dependencies</h2>
+
+<p>The following licenses cover the out-of-tree crates that were used in the
+Rust Standard Library in this release:</p>
+
+{% for (key, value) in dependencies %}
+    <h3>📦 {{key.name}}-{{key.version}}</h3>
+    <p><b>URL:</b> <a href="https://crates.io/crates/{{ key.name }}/{{ key.version }}">https://crates.io/crates/{{ key.name }}/{{ key.version }}</a></p>
+    <p><b>Authors:</b> {{ value.authors|join(", ") }}</p>
+    <p><b>License:</b> {{ value.license }}</p>
+    {% let len = value.notices.len() %}
+    {% if len > 0 %}
+        <p><b>Notices:</b>
+        {% for (notice_name, notice_text) in value.notices %}
+            <details>
+                <summary><code>{{ notice_name }}</code></summary>
+                <pre>
+{{ notice_text }}
+                </pre>
+            </details>
+        {% endfor %}
+        </p>
+    {% endif %}
+{% endfor %}
+</body>
+</html>
\ No newline at end of file