Skip to content

Commit

Permalink
generate-copyright: Produce HTML, not Markdown
Browse files Browse the repository at this point in the history
This format works better with large amounts of structured data.
  • Loading branch information
jonathanpallant committed Jul 15, 2024
1 parent 6320c3d commit cf04752
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 66 deletions.
5 changes: 2 additions & 3 deletions src/bootstrap/src/core/build_steps/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,10 @@ impl Step for GenerateCopyright {
}

fn run(self, builder: &Builder<'_>) -> Self::Output {
// let license_metadata = builder.ensure(CollectLicenseMetadata);
let license_metadata = builder.out.join("license-metadata.json");
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.md");
let dest = builder.out.join("COPYRIGHT.html");

let mut cmd = builder.tool_cmd(Tool::GenerateCopyright);
cmd.env("LICENSE_METADATA", &license_metadata);
Expand Down
130 changes: 67 additions & 63 deletions src/tools/generate-copyright/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@ use std::path::{Path, PathBuf};

mod cargo_metadata;

static TOP_BOILERPLATE: &'static str = r##"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Copyright notices for The Rust Toolchain</title>
</head>
<body>
<h1>Copyright notices for The Rust Toolchain</h1>
<p>This file describes the copyright and licensing information for the source
code within The Rust Project git tree, and the third-party dependencies used
when building the Rust toolchain (including 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>
"##;

static BOTTOM_BOILERPLATE: &'static str = r#"
</body>
</html>
"#;

/// The entry point to the binary.
///
/// You should probably let `bootstrap` execute this program instead of running it directly.
Expand Down Expand Up @@ -32,40 +59,22 @@ fn main() -> Result<(), Error> {

let mut buffer = Vec::new();

writeln!(buffer, "# COPYRIGHT for Rust")?;
writeln!(buffer)?;
writeln!(
buffer,
"This file describes the copyright and licensing information for the source code within The Rust Project git tree, and the third-party dependencies used when building the Rust toolchain (including the Rust Standard Library)"
)?;
writeln!(buffer)?;
writeln!(buffer, "## Table of Contents")?;
writeln!(buffer)?;
writeln!(buffer, "* [In-tree files](#in-tree-files)")?;
writeln!(buffer, "* [Out-of-tree files](#out-of-tree-files)")?;
// writeln!(buffer, "* [License Texts](#license-texts)")?;
writeln!(buffer)?;

writeln!(buffer, "## In-tree files")?;
writeln!(buffer)?;
writeln!(buffer, "{}", TOP_BOILERPLATE)?;

writeln!(
buffer,
"The following licenses cover the in-tree source files that were used in this release:"
r#"<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>"#
)?;
writeln!(buffer)?;
render_tree_recursive(&collected_tree_metadata.files, &mut buffer, 0, &mut license_set)?;

writeln!(buffer)?;

writeln!(buffer, "## Out-of-tree files")?;
writeln!(buffer)?;
writeln!(
buffer,
"The following licenses cover the out-of-tree crates that were used in this release:"
r#"<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 this release:</p>"#
)?;
writeln!(buffer)?;
render_deps(collected_cargo_metadata.iter(), &mut buffer, &mut license_set)?;

writeln!(buffer, "{}", BOTTOM_BOILERPLATE)?;

std::fs::write(&dest_file, &buffer)?;

Ok(())
Expand All @@ -79,43 +88,27 @@ fn render_tree_recursive(
depth: usize,
license_set: &mut BTreeSet<String>,
) -> Result<(), Error> {
let prefix = std::iter::repeat("> ").take(depth + 1).collect::<String>();

writeln!(buffer, r#"<div style="border:1px solid black; padding: 5px;">"#)?;
match node {
Node::Root { children } => {
for child in children {
render_tree_recursive(child, buffer, depth, license_set)?;
}
}
Node::Directory { name, children, license } => {
render_tree_license(
&prefix,
std::iter::once(name),
license.iter(),
buffer,
license_set,
)?;
render_tree_license(std::iter::once(name), license.iter(), buffer, license_set)?;
if !children.is_empty() {
writeln!(buffer, "{prefix}")?;
writeln!(buffer, "{prefix}*Exceptions:*")?;
writeln!(buffer, "<p><b>Exceptions:</b></p>")?;
for child in children {
writeln!(buffer, "{prefix}")?;
render_tree_recursive(child, buffer, depth + 1, license_set)?;
}
}
}
Node::CondensedDirectory { name, licenses } => {
render_tree_license(
&prefix,
std::iter::once(name),
licenses.iter(),
buffer,
license_set,
)?;
render_tree_license(std::iter::once(name), licenses.iter(), buffer, license_set)?;
}
Node::Group { files, directories, license } => {
render_tree_license(
&prefix,
directories.iter().chain(files.iter()),
std::iter::once(license),
buffer,
Expand All @@ -124,26 +117,26 @@ fn render_tree_recursive(
}
Node::File { name, license } => {
render_tree_license(
&prefix,
std::iter::once(name),
std::iter::once(license),
buffer,
license_set,
)?;
}
}
writeln!(buffer, "</div>")?;

Ok(())
}

/// Draw a series of sibling files/folders, as markdown, into the given Vec.
fn render_tree_license<'a>(
prefix: &str,
names: impl Iterator<Item = &'a String>,
licenses: impl Iterator<Item = &'a License>,
buffer: &mut Vec<u8>,
license_set: &mut BTreeSet<String>,
) -> Result<(), Error> {
// de-duplicate and sort SPDX and Copyright strings
let mut spdxs = BTreeSet::new();
let mut copyrights = BTreeSet::new();
for license in licenses {
Expand All @@ -154,15 +147,21 @@ fn render_tree_license<'a>(
}
}

writeln!(buffer, "<p><b>File/Directory:</b> ")?;
for name in names {
writeln!(buffer, "{prefix}**`{name}`** ")?;
writeln!(buffer, "<code>{name}</code>")?;
}
for spdx in spdxs.iter() {
writeln!(buffer, "{prefix}License: `{spdx}` ")?;
writeln!(buffer, "</p>")?;

writeln!(buffer, "<p><b>License:</b> ")?;
for (i, spdx) in spdxs.iter().enumerate() {
let suffix = if i == spdxs.len() - 1 { "" } else { ", " };
writeln!(buffer, "{spdx}{suffix}")?;
}
for (i, copyright) in copyrights.iter().enumerate() {
let suffix = if i == copyrights.len() - 1 { "" } else { " " };
writeln!(buffer, "{prefix}Copyright: {copyright}{suffix}")?;
writeln!(buffer, "</p>")?;

for copyright in copyrights.iter() {
writeln!(buffer, "<p><b>Copyright:</b> {copyright}</p>")?;
}

Ok(())
Expand All @@ -175,30 +174,25 @@ fn render_deps<'a, 'b>(
license_set: &mut BTreeSet<String>,
) -> Result<(), Error> {
for dep in deps {
let authors_list = dep.authors.join(", ").replace("<", "\\<").replace(">", "\\>");
let authors_list = dep.authors.join(", ");
let url = format!("https://crates.io/crates/{}/{}", dep.name, dep.version);
writeln!(buffer)?;
writeln!(
buffer,
"### [{name} {version}]({url})",
r#"<h3><a href="{url}">{name} {version}</a></h3>"#,
name = dep.name,
version = dep.version,
url = url,
)?;
writeln!(buffer)?;
writeln!(buffer, "* Authors: {}", authors_list)?;
writeln!(buffer, "* License: {}", dep.license)?;
writeln!(buffer, "<h4>Authors</h4><p>{}</p>", escape_html(&authors_list))?;
writeln!(buffer, "<h4>License</h4><p>{}</p>", escape_html(&dep.license))?;
license_set.insert(dep.license.clone());
for (name, contents) in &dep.notices {
writeln!(buffer)?;
writeln!(buffer, "#### {}", name.to_string_lossy())?;
writeln!(buffer, "<h4>{}</h3>", name.to_string_lossy())?;
writeln!(buffer)?;
writeln!(buffer, "<details><summary>Click to expand</summary>")?;
writeln!(buffer)?;
writeln!(buffer, "```")?;
writeln!(buffer, "{}", contents)?;
writeln!(buffer, "```")?;
writeln!(buffer)?;
writeln!(buffer, "<pre>\n{}\n</pre>", contents)?;
writeln!(buffer, "</details>")?;
}
}
Expand Down Expand Up @@ -236,3 +230,13 @@ fn env_path(var: &str) -> Result<PathBuf, Error> {
anyhow::bail!("missing environment variable {var}")
}
}

/// Escapes any invalid HTML characters
fn escape_html(input: &str) -> String {
static MAPPING: [(char, &'static str); 3] = [('&', "&amp;"), ('<', "&lt;"), ('>', "&gt;")];
let mut output = input.to_owned();
for (ch, s) in &MAPPING {
output = output.replace(*ch, s);
}
output
}

0 comments on commit cf04752

Please sign in to comment.