1use std::collections::BTreeMap;
2
3use crate::licenses::{self, LicenseInfo};
4use codespan_reporting::term;
5use krates::Utf8PathBuf as PathBuf;
6use krates::cm::Package;
7use serde::{Serialize, Serializer};
8
9#[derive(Clone, Serialize)]
10pub struct UsedBy<'a> {
11 #[serde(rename = "crate")]
12 pub krate: &'a krates::cm::Package,
13 pub path: Option<PathBuf>,
14}
15
16#[derive(Clone, Serialize)]
17pub struct License<'a> {
18 pub name: String,
20 pub id: String,
22 pub first_of_kind: bool,
24 pub text: String,
26 pub source_path: Option<PathBuf>,
28 pub used_by: Vec<UsedBy<'a>>,
30}
31
32#[derive(Serialize)]
33pub struct LicenseSet {
34 pub count: usize,
36 pub name: String,
38 pub id: String,
40 pub indices: Vec<usize>,
42 pub text: String,
44}
45
46fn serialize_as_string<T: std::fmt::Display, S: Serializer>(
47 value: &T,
48 serializer: S,
49) -> Result<S::Ok, S::Error> {
50 serializer.collect_str(value)
51}
52
53#[derive(Serialize)]
54pub struct PackageLicense<'a> {
55 pub package: &'a Package,
57 #[serde(serialize_with = "serialize_as_string")]
59 pub license: &'a LicenseInfo,
60}
61
62#[derive(Serialize)]
63pub struct LicenseList<'a> {
64 pub overview: Vec<LicenseSet>,
66 pub licenses: Vec<License<'a>>,
69 pub crates: Vec<PackageLicense<'a>>,
71}
72
73pub fn generate<'kl>(
76 nfos: &'kl [licenses::KrateLicense<'kl>],
77 resolved: &[Option<licenses::Resolved>],
78 files: &licenses::resolution::Files,
79 stream: Option<term::termcolor::StandardStream>,
80) -> anyhow::Result<LicenseList<'kl>> {
81 use licenses::resolution::Severity;
82
83 let mut num_errors = 0;
84
85 let term_and_diag = stream.map(|s| (s, term::Config::default()));
86
87 let mut licenses = {
88 let mut licenses = BTreeMap::new();
89 for (krate_license, resolved) in nfos
90 .iter()
91 .zip(resolved.iter())
92 .filter_map(|(kl, res)| res.as_ref().map(|res| (kl, res)))
93 {
94 match &term_and_diag {
95 Some((stream, diag_cfg)) if !resolved.diagnostics.is_empty() => {
96 let mut streaml = stream.lock();
97
98 for diag in &resolved.diagnostics {
99 if diag.severity >= Severity::Error {
100 num_errors += 1;
101 }
102
103 term::emit_to_io_write(&mut streaml, diag_cfg, files, diag)?;
104 }
105 }
106 _ => {}
107 }
108
109 let license_iter = resolved.licenses.iter().flat_map(|license| {
110 let mut license_texts = Vec::new();
111 match license.license {
112 spdx::LicenseItem::Spdx { id, .. } => {
113 license_texts.extend(krate_license
117 .license_files
118 .iter()
119 .filter_map(|lf| {
120 if !lf
122 .license_expr
123 .evaluate(|ereq| ereq.license.id() == Some(id))
124 {
125 return None;
126 }
127
128 match &lf.kind {
129 licenses::LicenseFileKind::Text(text)
130 | licenses::LicenseFileKind::AddendumText(text, _) => {
131 let license = License {
132 name: id.full_name.to_owned(),
133 id: id.name.to_owned(),
134 text: text.clone(),
135 source_path: Some(lf.path.clone()),
136 used_by: Vec::new(),
137 first_of_kind: false,
138 };
139 Some(license)
140 }
141 licenses::LicenseFileKind::Header => None,
142 }
143 }));
144
145 if license_texts.is_empty() {
146 log::debug!(
147 "unable to find text for license '{license}' for crate '{}', falling back to canonical text",
148 krate_license.krate
149 );
150
151 license_texts.push(License {
154 name: id.full_name.to_owned(),
155 id: id.name.to_owned(),
156 text: id.text().to_owned(),
157 source_path: None,
158 used_by: Vec::new(),
159 first_of_kind: false,
160 });
161 }
162 }
163 spdx::LicenseItem::Other { .. } => {
164 log::warn!(
165 "{license} has no license file for crate '{}'",
166 krate_license.krate
167 );
168 }
169 }
170
171 license_texts
172 });
173
174 for license in license_iter {
175 let entry = licenses
176 .entry(license.name.clone())
177 .or_insert_with(BTreeMap::new);
178
179 let lic = entry.entry(license.text.clone()).or_insert_with(|| license);
180 lic.used_by.push(UsedBy {
181 krate: krate_license.krate,
182 path: None,
183 });
184 }
185 }
186
187 let mut licenses: Vec<_> = licenses
188 .into_iter()
189 .flat_map(|(_, v)| v.into_values())
190 .collect();
191
192 for lic in &mut licenses {
194 lic.used_by.sort_by(|a, b| a.krate.id.cmp(&b.krate.id));
195 }
196
197 licenses.sort_by(|a, b| a.id.cmp(&b.id));
198 licenses
199 };
200
201 if num_errors > 0 {
202 anyhow::bail!(
203 "encountered {num_errors} errors resolving licenses, unable to generate output"
204 );
205 }
206
207 let mut overview: BTreeMap<&str, LicenseSet> = BTreeMap::new();
208
209 for (ndx, lic) in licenses.iter_mut().enumerate() {
210 let ls = overview.entry(&lic.id).or_insert_with(|| {
211 lic.first_of_kind = true;
212 LicenseSet {
213 count: 0,
214 name: lic.name.clone(),
215 id: lic.id.clone(),
216 indices: Vec::with_capacity(10),
217 text: lic.text.clone(),
218 }
219 });
220 ls.indices.push(ndx);
221 ls.count += lic.used_by.len();
222 }
223
224 let mut overview = overview.into_values().collect::<Vec<_>>();
225 overview.sort_by(|a, b| b.count.cmp(&a.count));
227
228 let crates = nfos
229 .iter()
230 .filter(|nfo| !matches!(nfo.lic_info, LicenseInfo::Ignore))
231 .map(|nfo| PackageLicense {
232 package: &nfo.krate.0,
233 license: &nfo.lic_info,
234 })
235 .collect();
236 Ok(LicenseList {
237 overview,
238 licenses,
239 crates,
240 })
241}