in license-scan/src/main.rs [291:413]
fn write_attribution(
name: &str,
scan_dir: &Path,
out_dir: &Path,
scanner: &ScanStrategy<'_>,
clarifications: &Clarifications,
stated_license: Option<Expression>,
) -> Result<()> {
eprintln!("{}:", name);
if let Some(stated_license) = stated_license.as_ref() {
eprintln!(" + {} (stated in metadata)", stated_license);
}
let mut files = HashMap::new();
for entry in WalkBuilder::new(scan_dir).types(TYPES.clone()).build() {
let entry = entry?;
if entry.file_type().map_or(false, |ft| ft.is_file()) {
let rel_path = entry.path().strip_prefix(scan_dir)?;
let data = fs::read_to_string(entry.path())
.with_context(|| format!("failed to read {}", entry.path().display()))?;
let file_hash = hash(&data.as_bytes());
files.insert(rel_path.to_owned(), (data, file_hash));
}
}
let file_hashes = files
.iter()
.map(|(file, (_, hash))| (file.as_path(), *hash))
.collect();
let license = if let Some(clarified) = clarifications.get(name, file_hashes)? {
let expression = clarified.expression.to_string();
eprintln!(" ! {} (clarified)", expression);
copy_files(out_dir, &files, clarified.skip_files)?;
expression
} else {
let mut licenses = Vec::new();
for (file, (data, file_hash)) in &files {
let containing = scanner
.scan(&TextData::new(data))
.map_err(|e| e.compat())?
.containing;
if containing.is_empty() {
if non_license(file) {
eprintln!(
" + {} (hash = 0x{:x}) detected as non-license file",
file.display(),
file_hash
);
} else {
if stated_license.is_some() {
// if the package states a license and we heuristically detect that this is
// a top-level "either license, at your option" file, ignore it
let trainwreck = data.split_whitespace().collect::<Vec<_>>().join(" ");
if trainwreck.contains("under the terms of either license")
|| trainwreck.contains("at your option")
{
eprintln!(
" + {} (hash = 0x{:x}) detected as non-license file",
file.display(),
file_hash
);
continue;
}
}
bail!(
"failed to detect any license from {} (hash = 0x{:x}), \
please add a clarification",
scan_dir.join(file).display(),
file_hash,
);
}
}
for result in containing {
eprintln!(
" + {} (hash = 0x{:x}) detected as {} (confidence {:.4})",
file.display(),
file_hash,
result.license.name,
result.score,
);
if let Some(stated_license) = stated_license.as_ref() {
// The license we detected should be included in the stated license string,
// otherwise we know the stated license is incomplete, in which case we should
// have had a clarification.
ensure!(
stated_license.requirements().any(|er| {
// `er` is an `ExpressionReq`; `er.req` is a `LicenseReq`.
// `er.req.license.id()` returns `Option<LicenseId>`.
er.req.license.id().is_some()
&& er.req.license.id() == spdx::license_id(result.license.name)
}),
"detected license \"{}\" from {} is not present in the license \
field \"{}\" for {}",
result.license.name,
file.display(),
stated_license,
name
);
} else {
licenses.push(result.license.name);
}
}
}
copy_files(out_dir, &files, &[])?;
if let Some(stated_license) = stated_license {
stated_license.to_string()
} else {
licenses.sort();
licenses.dedup();
let expression = licenses.join(" AND ");
eprintln!(" = {}", expression);
expression
}
};
fs::create_dir_all(out_dir)?;
fs::write(
out_dir.join("attribution.txt"),
format!("{}\nSPDX-License-Identifier: {}\n", name, license),
)?;
Ok(())
}