157 lines
4.9 KiB
Python
157 lines
4.9 KiB
Python
# (c) 2025 by Stephan Menzel
|
|
# Licensed under the Apache License, Version 2.0.
|
|
# See attached file LICENSE for full details
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from common.errors import DependencyInfoError
|
|
import common.settings
|
|
from common.tags import sanitize_tag
|
|
|
|
# will be populated by build_package_tree()
|
|
packages = {}
|
|
|
|
class PackageInfo:
|
|
"""Parsed from packages.json, wraps info about each dependency"""
|
|
version = ""
|
|
repo = ""
|
|
tag = ""
|
|
dependencies = {}
|
|
|
|
# sbom relevant info
|
|
name = ""
|
|
description = None
|
|
license_id = None
|
|
license_name = None
|
|
license_url = ""
|
|
|
|
def __init__(self, package_name: str):
|
|
|
|
pckjson = {}
|
|
|
|
with open("packages.json", "r") as jsonfile:
|
|
pckjson = json.load(jsonfile)
|
|
|
|
if package_name not in pckjson:
|
|
raise DependencyInfoError(f"Cannot find version info for {package_name} in packages.json")
|
|
|
|
pckdict = pckjson[package_name]
|
|
|
|
self.repo = pckdict["repo"]
|
|
self.tag = pckdict["tag"]
|
|
self.version = pckdict["version"]
|
|
self.dependencies = {}
|
|
|
|
if "depends" in pckdict:
|
|
for dependency in pckdict["depends"]:
|
|
if dependency not in packages:
|
|
packages[dependency] = PackageInfo(dependency)
|
|
self.dependencies[dependency] = packages[dependency]
|
|
|
|
# Save some data to later generate the SBOM with
|
|
self.name = package_name
|
|
self.description = pckdict["description"] if "description" in pckdict else None
|
|
if "license_id" in pckdict:
|
|
self.license_id = pckdict["license_id"]
|
|
else:
|
|
self.license_name = pckdict["license_name"]
|
|
self.license_url = pckdict["license_url"]
|
|
|
|
def dependency_path(self, package_name: str) -> Path:
|
|
"""Give the path underneath which the package, according to its version, is installed"""
|
|
if package_name not in self.dependencies:
|
|
raise DependencyInfoError(f"Cannot find dependency {package_name} in {self.name}.")
|
|
return self.dependencies[package_name].install_location()
|
|
|
|
def bom_ref(self) -> str:
|
|
"""Return the CycloneDX bom-ref for this package"""
|
|
return f"{self.name}=={self.version}"
|
|
|
|
def purl(self) -> str:
|
|
"""Return the CycloneDX purl for this package"""
|
|
return f"pkg:cmake/{self.name}@{self.version}"
|
|
|
|
def add_to_sbom(self, sbom: dict) -> None:
|
|
"""Add all information we have about this package to a CycloneDX SBOM"""
|
|
|
|
if "components" not in sbom:
|
|
sbom["components"] = []
|
|
|
|
license = {
|
|
"acknowledgement": "concluded",
|
|
"url": self.license_url
|
|
}
|
|
|
|
if self.license_id:
|
|
license["id"] = self.license_id
|
|
else:
|
|
license["name"] = self.license_name
|
|
|
|
sbom["components"].append({
|
|
"name": self.name,
|
|
"purl": self.purl(),
|
|
"type": "library",
|
|
"version": self.version,
|
|
"bom-ref": self.bom_ref(),
|
|
"description": self.description,
|
|
"externalReferences": [
|
|
{
|
|
"comment": "from packaging metadata Project-URL: Source Code",
|
|
"type": "other",
|
|
"url": self.repo,
|
|
},
|
|
],
|
|
"licenses": [
|
|
{
|
|
"license": license
|
|
},
|
|
]
|
|
})
|
|
|
|
if "dependencies" not in sbom:
|
|
sbom["dependencies"] = []
|
|
|
|
dependencies_entry = {"ref": self.purl()}
|
|
for name, dep_info in self.dependencies.items():
|
|
if "dependsOn" not in dependencies_entry:
|
|
dependencies_entry["dependsOn"] = []
|
|
dependencies_entry["dependsOn"].append(dep_info.purl())
|
|
sbom["dependencies"].append(dependencies_entry)
|
|
|
|
def install_location(self) -> Path:
|
|
"""Get the directory where this package out to be installed in
|
|
"""
|
|
return common.settings.install_prefix / Path(f"{self.name}-{self.version}")
|
|
|
|
def src_dir(self) -> Path:
|
|
"""Get us the directory where the source is going to be checked out in (a subdir under "raw" normally)
|
|
"""
|
|
return common.settings.build_dir / Path(f"{self.name}-{sanitize_tag(self.tag)}")
|
|
|
|
|
|
def build_package_tree() -> None:
|
|
"""Assemble the global dependency tree of packages known to that system
|
|
"""
|
|
global packages
|
|
pckjson = {}
|
|
with open("packages.json", "r") as jsonfile:
|
|
pckjson = json.load(jsonfile)
|
|
|
|
for package_name in pckjson.keys():
|
|
if package_name not in packages:
|
|
packages[package_name] = PackageInfo(package_name)
|
|
|
|
|
|
def get_package_info(name: str) -> PackageInfo:
|
|
"""Get the PackageInfo struct of that name or
|
|
|
|
:throws DependencyInfoError
|
|
"""
|
|
global packages
|
|
|
|
if name not in packages:
|
|
raise DependencyInfoError(f"No build info for {name}")
|
|
|
|
return packages[name]
|