blob: d6789b6e80974d7b93c6cb8699bd52e8167bd9d8 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
'''Utility that produces OSS licenses compliance materials.'''
import argparse
import csv
import os
import shutil
from sys import stderr
from typing import Set, List
import zipfile
from fuchsia.tools.licenses.classification_types import *
from fuchsia.tools.licenses.spdx_types import *
def _log(*kwargs):
print(*kwargs, file=stderr)
def _dedup(input: List[str]) -> List[str]:
return sorted(list(set(input)))
def _write_summary(
spdx_doc: SpdxDocument, index: SpdxIndex,
classification: LicensesClassifications, output_path: str):
with open(output_path, "w") as csvfile:
writer = csv.DictWriter(
csvfile,
fieldnames=[
"name",
"identifications",
"conditions",
"overriden_conditions",
"link",
"tracking_issues",
"comments",
# The following 'debugging' fields begin with _ so can be easily
# filtered/hidden/sorted once in a spreadsheet.
"_spdx_license_id",
"_dependents",
"_detailed_identifications",
"_detailed_overrides",
"_size_bytes",
"_size_lines",
"_unidentified_lines",
])
writer.writeheader()
for license in spdx_doc.extracted_licenses:
license_id = license.license_id
dependents = [
">".join([p.name
for p in path])
for path in index.dependency_chains_for_license(
license.license_id)
]
links = []
for l in license.cross_refs:
links.append(l)
for l in license.see_also:
links.append(l)
links = _dedup(links)
identifications = []
conditions = []
overriden_conditions = []
detailed_identifications = []
detailed_overrides = []
tracking_issues = []
comments = []
identification_stats = {}
if license_id in classification.classifications_by_id:
license_classification = classification.classifications_by_id[
license_id]
overriden_conditions = []
identification_stats = {
"_size_bytes":
license_classification.size_bytes,
"_size_lines":
license_classification.size_lines,
"_unidentified_lines":
license_classification.unidentified_lines,
}
for i in license_classification.identifications:
identifications.append(i.identified_as)
conditions.append(i.condition)
detailed_identifications.append(
f"{i.identified_as} at lines {i.start_line}-{i.end_line}: {i.condition}"
)
if i.overriden_conditions:
overriden_conditions.extend(i.overriden_conditions)
if i.overriding_rules:
for r in i.overriding_rules:
detailed_overrides.append(
f"{i.identified_as} ({i.condition}) at {i.start_line}-{i.end_line} overriden to ({r.override_condition_to}) by {r.rule_file_path}"
)
tracking_issues.append(r.bug)
comments.append("\n".join(r.comment))
row = {
# License review columns
"name":
license.name,
"link":
"\n".join(_dedup(links)),
"identifications":
",\n".join(_dedup(identifications)),
"conditions":
",\n".join(_dedup(conditions)),
"overriden_conditions":
",".join(_dedup(overriden_conditions)),
"tracking_issues":
"\n".join(_dedup(tracking_issues)),
"comments":
"\n=======\n".join(_dedup(comments)),
# Advanced / debugging columns
"_spdx_license_id":
license_id,
"_dependents":
",\n".join(_dedup(dependents)),
"_detailed_identifications":
",\n".join(_dedup(detailed_identifications)),
"_detailed_overrides":
",\n".join(_dedup(detailed_overrides)),
}
row.update(identification_stats)
writer.writerow(row)
def _zip_everything(output_dir_path: str, output_zip_path: str):
with zipfile.ZipFile(output_zip_path, mode="w") as archive:
for root, dirs, files in os.walk(output_dir_path):
for file in files:
archive.write(
os.path.join(root, file),
os.path.relpath(os.path.join(root, file), output_dir_path))
def main():
'''Parses arguments.'''
parser = argparse.ArgumentParser()
parser.add_argument(
'--spdx_input',
help='An SPDX json file containing all licenses to process.'
' The output of @rules_fuchsia `fuchsia_licenses_spdx`',
required=True,
)
parser.add_argument(
'--classification_input',
help='A json file containing the results of'
' @rules_fuchsia `fuchsia_licenses_classification`',
required=False,
)
parser.add_argument(
'--output_file',
help='Where to write the archive containing all the output files.',
required=True,
)
parser.add_argument(
'--output_dir',
help='Where to write all the output files.',
required=True,
)
args = parser.parse_args()
_log(f'Got these args {args}!')
spdx_input_path = args.spdx_input
output_dir = args.output_dir
classification_input_path = args.classification_input
_log(f'Reading license info from {spdx_input_path}!')
spdx_doc = SpdxDocument.from_json(spdx_input_path)
spdx_index = SpdxIndex.create(spdx_doc)
_log(f'Outputing all the files into {output_dir}!')
spdx_doc.to_json(os.path.join(output_dir, "licenses.spdx.json"))
if classification_input_path:
shutil.copy(
classification_input_path,
os.path.join(output_dir, "classification.json"))
classification = LicensesClassifications.from_json(
classification_input_path)
else:
classification = LicensesClassifications.create_empty()
extracted_licenses_dir = os.path.join(output_dir, "extracted_licenses")
os.mkdir(extracted_licenses_dir)
for license in spdx_doc.extracted_licenses:
with open(os.path.join(extracted_licenses_dir,
f"{license.license_id}.txt"),
"w") as license_file:
license_file.write(license.extracted_text)
_write_summary(
spdx_doc, spdx_index, classification,
os.path.join(output_dir, "summary.csv"))
output_file_path = args.output_file
_log(f'Saving all the files into {output_file_path}!')
_zip_everything(output_dir, output_file_path)
if __name__ == '__main__':
main()