Module continuous_delivery_scripts.spdx_report.spdx_project
Definition of an SPDX report for a Python project.
Expand source code
#
# Copyright (C) 2020-2025 Arm Limited or its affiliates and Contributors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""Definition of an SPDX report for a Python project."""
from pathlib import Path
import os
from spdx.writers.tagvalue import write_document
from typing import Optional, List, cast, Tuple, Dict
from continuous_delivery_scripts.spdx_report.spdx_dependency import DependencySpdxDocumentRef
from continuous_delivery_scripts.spdx_report.spdx_document import SpdxDocument
from continuous_delivery_scripts.utils.hash_helpers import determine_sha1_hash_of_file
from continuous_delivery_scripts.utils.python.package_helpers import ProjectMetadataFetcher
from continuous_delivery_scripts.spdx_report.spdx_helpers import is_package_licence_manually_checked
from continuous_delivery_scripts.spdx_report.spdx_summary import SummaryGenerator
class SpdxProject:
"""SPDX for a project.
SPDX information about a project so that it complies with OpenChain
See https://certification.openchainproject.or
"""
def __init__(self, parser: ProjectMetadataFetcher) -> None:
"""Constructor."""
self._parser = parser
self._main_document: Optional[SpdxDocument] = None
self._dependency_documents: Optional[List[SpdxDocument]] = None
def _generate_documents(self) -> None:
if self._main_document:
return
self._dependency_documents = list()
project_metadata = self._parser.project_metadata
dependencies = project_metadata.dependencies_metadata
for dependency in dependencies:
self._dependency_documents.append(SpdxDocument(dependency, is_dependency=True))
self._main_document = SpdxDocument(package_metadata=project_metadata.project_metadata)
@property
def main_document(self) -> SpdxDocument:
"""Gets project's main SPDX document."""
self._generate_documents()
return cast(SpdxDocument, self._main_document)
@property
def dependency_documents(self) -> List[SpdxDocument]:
"""Gets the list of project's dependencies SPDX documents."""
self._generate_documents()
return self._dependency_documents if self._dependency_documents else list()
@staticmethod
def generate_tag_value_file(dir: Path, spdx_doc: SpdxDocument, filename: str = "LICENSE.spdx") -> str:
"""Generates the Tag file into the directory.
See https://github.com/david-a-wheeler/spdx-tutorial#spdx-files
Args:
dir: output directory
filename: file name of the document
spdx_doc: SPDX document to write down
Returns:
file checksum
"""
if not dir.exists():
raise ValueError(f"Undefined directory: {str(dir)}")
if not dir.is_dir():
raise NotADirectoryError(str(dir))
path = dir.joinpath(filename)
with open(str(path), mode="w", encoding="utf-8") as out:
write_document(spdx_doc.generate_spdx_document(), out)
return determine_sha1_hash_of_file(path)
def generate_licensing_summary(self, dir: Path) -> None:
"""Generates licensing summary into the specified directory.
Args:
dir: output directory
"""
SummaryGenerator(
self.main_document.generate_spdx_package(), [d.generate_spdx_package() for d in self.dependency_documents]
).generate_summary(dir)
def generate_tag_value_files(self, dir: Path) -> None:
"""Generates SPDX tag-value files into the specified directory.
See https://github.com/david-a-wheeler/spdx-tutorial#spdx-files
There will be a file for the current project as well as a file
per third-party dependencies
Args:
dir: output directory
"""
if not dir.exists():
raise ValueError(f"Undefined directory: {str(dir)}")
if not dir.is_dir():
raise NotADirectoryError(str(dir))
externalRefs = list()
for spdx_dependency in self.dependency_documents:
file_name = f"{spdx_dependency.name}.spdx"
checksum = SpdxProject.generate_tag_value_file(dir, spdx_dependency, file_name)
externalRefs.append(
DependencySpdxDocumentRef(
name=spdx_dependency.document_name, namespace=spdx_dependency.document_namespace, checksum=checksum
)
)
self.main_document.external_refs = externalRefs
SpdxProject.generate_tag_value_file(dir, self.main_document, f"{self.main_document.name}.spdx")
def _report_issues(self, issues: Dict[str, str]) -> None:
if issues:
raise ValueError(
f",{os.linesep}".join(
[
f"Package [{package_name}] has a non-compliant licence ({package_licence}) for this project"
for package_name, package_licence in issues.items()
]
)
)
def _check_one_licence_compliance(self, spdx_document: SpdxDocument, issues: Dict[str, str]) -> None:
main_valid, actual_valid, name, main_licence, actual_licence = _check_package_licence(spdx_document)
if not ((main_valid and actual_valid) or is_package_licence_manually_checked(name)):
issues[name] = actual_licence if main_valid else main_licence
def _check_package_dependencies_licence_compliance(self, issues: Dict[str, str]) -> None:
for dependency in self.dependency_documents:
self._check_one_licence_compliance(dependency, issues)
def _check_package_licence_compliance(self, issues: Dict[str, str]) -> None:
self._check_one_licence_compliance(self.main_document, issues)
def check_licence_compliance(self) -> None:
"""Checks whether the licences of the package as well as all its dependencies are compliant.
By compliant, it is meant that all the licences are in the list of accepted licences set for the given project.
"""
issues: Dict[str, str] = dict()
self._check_package_licence_compliance(issues)
self._check_package_dependencies_licence_compliance(issues)
self._report_issues(issues)
def _check_package_licence(package_document: SpdxDocument) -> Tuple[bool, bool, str, str, str]:
package = package_document.generate_spdx_package()
return (
package.is_main_licence_accepted,
package.is_licence_accepted,
package.name,
package.main_licence,
package.licence,
)
Classes
class SpdxProject (parser: ProjectMetadataFetcher)
-
SPDX for a project.
SPDX information about a project so that it complies with OpenChain See https://certification.openchainproject.or
Constructor.
Expand source code
class SpdxProject: """SPDX for a project. SPDX information about a project so that it complies with OpenChain See https://certification.openchainproject.or """ def __init__(self, parser: ProjectMetadataFetcher) -> None: """Constructor.""" self._parser = parser self._main_document: Optional[SpdxDocument] = None self._dependency_documents: Optional[List[SpdxDocument]] = None def _generate_documents(self) -> None: if self._main_document: return self._dependency_documents = list() project_metadata = self._parser.project_metadata dependencies = project_metadata.dependencies_metadata for dependency in dependencies: self._dependency_documents.append(SpdxDocument(dependency, is_dependency=True)) self._main_document = SpdxDocument(package_metadata=project_metadata.project_metadata) @property def main_document(self) -> SpdxDocument: """Gets project's main SPDX document.""" self._generate_documents() return cast(SpdxDocument, self._main_document) @property def dependency_documents(self) -> List[SpdxDocument]: """Gets the list of project's dependencies SPDX documents.""" self._generate_documents() return self._dependency_documents if self._dependency_documents else list() @staticmethod def generate_tag_value_file(dir: Path, spdx_doc: SpdxDocument, filename: str = "LICENSE.spdx") -> str: """Generates the Tag file into the directory. See https://github.com/david-a-wheeler/spdx-tutorial#spdx-files Args: dir: output directory filename: file name of the document spdx_doc: SPDX document to write down Returns: file checksum """ if not dir.exists(): raise ValueError(f"Undefined directory: {str(dir)}") if not dir.is_dir(): raise NotADirectoryError(str(dir)) path = dir.joinpath(filename) with open(str(path), mode="w", encoding="utf-8") as out: write_document(spdx_doc.generate_spdx_document(), out) return determine_sha1_hash_of_file(path) def generate_licensing_summary(self, dir: Path) -> None: """Generates licensing summary into the specified directory. Args: dir: output directory """ SummaryGenerator( self.main_document.generate_spdx_package(), [d.generate_spdx_package() for d in self.dependency_documents] ).generate_summary(dir) def generate_tag_value_files(self, dir: Path) -> None: """Generates SPDX tag-value files into the specified directory. See https://github.com/david-a-wheeler/spdx-tutorial#spdx-files There will be a file for the current project as well as a file per third-party dependencies Args: dir: output directory """ if not dir.exists(): raise ValueError(f"Undefined directory: {str(dir)}") if not dir.is_dir(): raise NotADirectoryError(str(dir)) externalRefs = list() for spdx_dependency in self.dependency_documents: file_name = f"{spdx_dependency.name}.spdx" checksum = SpdxProject.generate_tag_value_file(dir, spdx_dependency, file_name) externalRefs.append( DependencySpdxDocumentRef( name=spdx_dependency.document_name, namespace=spdx_dependency.document_namespace, checksum=checksum ) ) self.main_document.external_refs = externalRefs SpdxProject.generate_tag_value_file(dir, self.main_document, f"{self.main_document.name}.spdx") def _report_issues(self, issues: Dict[str, str]) -> None: if issues: raise ValueError( f",{os.linesep}".join( [ f"Package [{package_name}] has a non-compliant licence ({package_licence}) for this project" for package_name, package_licence in issues.items() ] ) ) def _check_one_licence_compliance(self, spdx_document: SpdxDocument, issues: Dict[str, str]) -> None: main_valid, actual_valid, name, main_licence, actual_licence = _check_package_licence(spdx_document) if not ((main_valid and actual_valid) or is_package_licence_manually_checked(name)): issues[name] = actual_licence if main_valid else main_licence def _check_package_dependencies_licence_compliance(self, issues: Dict[str, str]) -> None: for dependency in self.dependency_documents: self._check_one_licence_compliance(dependency, issues) def _check_package_licence_compliance(self, issues: Dict[str, str]) -> None: self._check_one_licence_compliance(self.main_document, issues) def check_licence_compliance(self) -> None: """Checks whether the licences of the package as well as all its dependencies are compliant. By compliant, it is meant that all the licences are in the list of accepted licences set for the given project. """ issues: Dict[str, str] = dict() self._check_package_licence_compliance(issues) self._check_package_dependencies_licence_compliance(issues) self._report_issues(issues)
Static methods
def generate_tag_value_file(dir: pathlib.Path, spdx_doc: SpdxDocument, filename: str = 'LICENSE.spdx') ‑> str
-
Generates the Tag file into the directory.
See https://github.com/david-a-wheeler/spdx-tutorial#spdx-files
Args
dir
- output directory
filename
- file name of the document
spdx_doc
- SPDX document to write down
Returns
file checksum
Expand source code
@staticmethod def generate_tag_value_file(dir: Path, spdx_doc: SpdxDocument, filename: str = "LICENSE.spdx") -> str: """Generates the Tag file into the directory. See https://github.com/david-a-wheeler/spdx-tutorial#spdx-files Args: dir: output directory filename: file name of the document spdx_doc: SPDX document to write down Returns: file checksum """ if not dir.exists(): raise ValueError(f"Undefined directory: {str(dir)}") if not dir.is_dir(): raise NotADirectoryError(str(dir)) path = dir.joinpath(filename) with open(str(path), mode="w", encoding="utf-8") as out: write_document(spdx_doc.generate_spdx_document(), out) return determine_sha1_hash_of_file(path)
Instance variables
var dependency_documents : List[SpdxDocument]
-
Gets the list of project's dependencies SPDX documents.
Expand source code
@property def dependency_documents(self) -> List[SpdxDocument]: """Gets the list of project's dependencies SPDX documents.""" self._generate_documents() return self._dependency_documents if self._dependency_documents else list()
var main_document : SpdxDocument
-
Gets project's main SPDX document.
Expand source code
@property def main_document(self) -> SpdxDocument: """Gets project's main SPDX document.""" self._generate_documents() return cast(SpdxDocument, self._main_document)
Methods
def check_licence_compliance(self) ‑> None
-
Checks whether the licences of the package as well as all its dependencies are compliant.
By compliant, it is meant that all the licences are in the list of accepted licences set for the given project.
Expand source code
def check_licence_compliance(self) -> None: """Checks whether the licences of the package as well as all its dependencies are compliant. By compliant, it is meant that all the licences are in the list of accepted licences set for the given project. """ issues: Dict[str, str] = dict() self._check_package_licence_compliance(issues) self._check_package_dependencies_licence_compliance(issues) self._report_issues(issues)
def generate_licensing_summary(self, dir: pathlib.Path) ‑> None
-
Generates licensing summary into the specified directory.
Args
dir
- output directory
Expand source code
def generate_licensing_summary(self, dir: Path) -> None: """Generates licensing summary into the specified directory. Args: dir: output directory """ SummaryGenerator( self.main_document.generate_spdx_package(), [d.generate_spdx_package() for d in self.dependency_documents] ).generate_summary(dir)
def generate_tag_value_files(self, dir: pathlib.Path) ‑> None
-
Generates SPDX tag-value files into the specified directory.
See https://github.com/david-a-wheeler/spdx-tutorial#spdx-files There will be a file for the current project as well as a file per third-party dependencies
Args
dir
- output directory
Expand source code
def generate_tag_value_files(self, dir: Path) -> None: """Generates SPDX tag-value files into the specified directory. See https://github.com/david-a-wheeler/spdx-tutorial#spdx-files There will be a file for the current project as well as a file per third-party dependencies Args: dir: output directory """ if not dir.exists(): raise ValueError(f"Undefined directory: {str(dir)}") if not dir.is_dir(): raise NotADirectoryError(str(dir)) externalRefs = list() for spdx_dependency in self.dependency_documents: file_name = f"{spdx_dependency.name}.spdx" checksum = SpdxProject.generate_tag_value_file(dir, spdx_dependency, file_name) externalRefs.append( DependencySpdxDocumentRef( name=spdx_dependency.document_name, namespace=spdx_dependency.document_namespace, checksum=checksum ) ) self.main_document.external_refs = externalRefs SpdxProject.generate_tag_value_file(dir, self.main_document, f"{self.main_document.name}.spdx")