From e16372aecd81e4bc8899b6b0107d5f3aa878fa1d Mon Sep 17 00:00:00 2001 From: Alok Kumar Date: Fri, 10 Jan 2025 16:53:46 +0530 Subject: [PATCH] add dependencies to rpm Added dependencies to rpm. Reference: https://github.com/aboutcode-org/scancode-toolkit/issues/649 Signed-off-by: Alok Kumar Signed-off-by: Alok Kumar --- src/packagedcode/rpm.py | 188 +++++++++++++++++- .../data/plugin/rpm-package-expected.json | 14 +- ...0-4.el6_3.x86_64.rpm-package-expected.json | 14 +- ...l6_3.x86_64.rpm-package-only-expected.json | 14 +- ...-2.4-0.b2.rhfc1.dag.i386.rpm.expected.json | 32 ++- 5 files changed, 251 insertions(+), 11 deletions(-) diff --git a/src/packagedcode/rpm.py b/src/packagedcode/rpm.py index 4cce7e8d1f3..e6aa78c5d8a 100644 --- a/src/packagedcode/rpm.py +++ b/src/packagedcode/rpm.py @@ -15,6 +15,7 @@ from collections import namedtuple from pathlib import Path +from packageurl import PackageURL from packagedcode import models from packagedcode import nevra from packagedcode.licensing import RESOURCE_TO_PACKAGE_LICENSE_FIELDS @@ -44,7 +45,6 @@ def logger_debug(*args): """ Support for RPMs, installed databases and spec files. """ -# TODO: retrieve dependencies # TODO: parse spec files see: # http://www.faqs.org/docs/artu/ch05s02.html#id2906931%29.) @@ -125,7 +125,6 @@ def to_string(self): return vr -# TODO: add dependencies!!! class BaseRpmInstalledDatabaseHandler(models.DatafileHandler): @classmethod @@ -135,6 +134,37 @@ def parse(cls, location, package_only=False): loc_path = Path(location) rpmdb_loc = str(loc_path.parent) + rpm_tags = get_rpm_tags(location, include_desc=True) + + if TRACE: logger_debug('recognize: rpm_tags', rpm_tags) + if not rpm_tags: + return + + dependencies = [] + name = rpm_tags.name + version = rpm_tags.version + is_pinned = version is not None and version != "" + + # Construct PackageURL without '@version' if version is missing + purl = PackageURL( + type="rpm", + namespace=None, # RPMs typically don't use namespaces + name=name, + version=version if is_pinned else None + ) + + # Prepare the dependent package model + dependencies.append( + models.DependentPackage( + purl=purl.to_string(), + scope="dependencies", + is_runtime=True, + is_optional=False, + is_pinned=is_pinned, + extracted_requirement=version, + ) + ) + # dump and parse the rpmdb to XMLish xmlish_loc = collect_installed_rpmdb_xmlish_from_rpmdb_loc(rpmdb_loc=rpmdb_loc) package_data = parse_rpm_xmlish( @@ -142,6 +172,7 @@ def parse(cls, location, package_only=False): datasource_id=cls.datasource_id, package_type=cls.default_package_type, package_only=package_only, + dependencies=dependencies, ) # TODO: package_data.namespace = cls.default_package_namespace return package_data @@ -225,7 +256,6 @@ def assemble(cls, package_data, resource, codebase, package_adder): yield resource -# TODO: add dependencies!!! class RpmInstalledNdbDatabaseHandler(BaseRpmInstalledDatabaseHandler): # used by recent Suse datasource_id = 'rpm_installed_database_ndb' @@ -237,8 +267,48 @@ class RpmInstalledNdbDatabaseHandler(BaseRpmInstalledDatabaseHandler): description = 'RPM installed package NDB database' documentation_url = 'https://fedoraproject.org/wiki/Changes/NewRpmDBFormat' + @classmethod + def parse(cls, location, package_only=False): + rpm_tags = get_rpm_tags(location, include_desc=True) + + if TRACE: logger_debug('recognize: rpm_tags', rpm_tags) + if not rpm_tags: + return + + dependencies = [] + name = rpm_tags.name + version = rpm_tags.version + is_pinned = version is not None and version != "" + + # Construct PackageURL without '@version' if version is missing + purl = PackageURL( + type="rpm", + namespace=None, # RPMs typically don't use namespaces + name=name, + version=version if is_pinned else None + ) + + # Prepare the dependent package model + dependencies.append( + models.DependentPackage( + purl=purl.to_string(), + scope="dependencies", + is_runtime=True, + is_optional=False, + is_pinned=is_pinned, + extracted_requirement=version, + ) + ) + + package_data = dict( + datasource_id=cls.datasource_id, + type=cls.default_package_type, + dependencies=dependencies, + ) + + yield models.PackageData.from_data(package_data, package_only) + -# TODO: add dependencies!!! class RpmInstalledSqliteDatabaseHandler(BaseRpmInstalledDatabaseHandler): # used by newer RHEL/CentOS/Fedora/CoreOS # Filetype: SQLite 3.x database, ... @@ -253,8 +323,48 @@ class RpmInstalledSqliteDatabaseHandler(BaseRpmInstalledDatabaseHandler): description = 'RPM installed package SQLite database' documentation_url = 'https://fedoraproject.org/wiki/Changes/Sqlite_Rpmdb' + @classmethod + def parse(cls, location, package_only=False): + rpm_tags = get_rpm_tags(location, include_desc=True) + + if TRACE: logger_debug('recognize: rpm_tags', rpm_tags) + if not rpm_tags: + return + + dependencies = [] + name = rpm_tags.name + version = rpm_tags.version + is_pinned = version is not None and version != "" + + # Construct PackageURL without '@version' if version is missing + purl = PackageURL( + type="rpm", + namespace=None, # RPMs typically don't use namespaces + name=name, + version=version if is_pinned else None + ) + + # Prepare the dependent package model + dependencies.append( + models.DependentPackage( + purl=purl.to_string(), + scope="dependencies", + is_runtime=True, + is_optional=False, + is_pinned=is_pinned, + extracted_requirement=version, + ) + ) + + package_data = dict( + datasource_id=cls.datasource_id, + type=cls.default_package_type, + dependencies=dependencies, + ) + + yield models.PackageData.from_data(package_data, package_only) + -# TODO: add dependencies!!! class RpmInstalledBdbDatabaseHandler(BaseRpmInstalledDatabaseHandler): # used by legacy RHEL/CentOS/Fedora/Suse datasource_id = 'rpm_installed_database_bdb' @@ -267,6 +377,47 @@ class RpmInstalledBdbDatabaseHandler(BaseRpmInstalledDatabaseHandler): description = 'RPM installed package BDB database' documentation_url = 'https://man7.org/linux/man-pages/man8/rpmdb.8.html' + @classmethod + def parse(cls, location, package_only=False): + rpm_tags = get_rpm_tags(location, include_desc=True) + + if TRACE: logger_debug('recognize: rpm_tags', rpm_tags) + if not rpm_tags: + return + + dependencies = [] + name = rpm_tags.name + version = rpm_tags.version + is_pinned = version is not None and version != "" + + # Construct PackageURL without '@version' if version is missing + purl = PackageURL( + type="rpm", + namespace=None, # RPMs typically don't use namespaces + name=name, + version=version if is_pinned else None + ) + + # Prepare the dependent package model + dependencies.append( + models.DependentPackage( + purl=purl.to_string(), + scope="dependencies", + is_runtime=True, + is_optional=False, + is_pinned=is_pinned, + extracted_requirement=version, + ) + ) + + package_data = dict( + datasource_id=cls.datasource_id, + type=cls.default_package_type, + dependencies=dependencies, + ) + + yield models.PackageData.from_data(package_data, package_only) + # TODO: implement me!!@ class RpmSpecfileHandler(models.NonAssemblableDatafileHandler): @@ -278,7 +429,6 @@ class RpmSpecfileHandler(models.NonAssemblableDatafileHandler): documentation_url = 'https://en.wikipedia.org/wiki/RPM_Package_Manager' -# TODO: add dependencies!!! class RpmArchiveHandler(models.DatafileHandler): datasource_id = 'rpm_archive' path_patterns = ('*.rpm', '*.src.rpm', '*.srpm', '*.mvl', '*.vip',) @@ -354,6 +504,30 @@ def parse(cls, location, package_only=False): description = build_description(summary=rpm_tags.summary, description=rpm_tags.description) + dependencies = [] + name = rpm_tags.name + version = rpm_tags.version + is_pinned = version is not None and version != "" + + # Construct PackageURL without '@version' if version is missing + purl = PackageURL( + type="rpm", + namespace=None, # RPMs typically don't use namespaces + name=name, + version=version if is_pinned else None + ) + + # Prepare the dependent package model + dependencies.append( + models.DependentPackage( + purl=purl.to_string(), + scope="dependencies", + is_runtime=True, + is_optional=False, + is_pinned=is_pinned, + extracted_requirement=version, + ) + ) if TRACE: data = dict( name=name, @@ -363,6 +537,7 @@ def parse(cls, location, package_only=False): parties=parties, extracted_license_statement=rpm_tags.license or None, source_packages=source_packages, + dependencies=dependencies, ) logger_debug('recognize: data to create a package:\n', data) @@ -377,6 +552,7 @@ def parse(cls, location, package_only=False): parties=parties, extracted_license_statement=rpm_tags.license or None, source_packages=source_packages, + dependencies=dependencies, ) if TRACE: diff --git a/tests/packagedcode/data/plugin/rpm-package-expected.json b/tests/packagedcode/data/plugin/rpm-package-expected.json index b8dbf0aefdb..50e81c2c4a6 100644 --- a/tests/packagedcode/data/plugin/rpm-package-expected.json +++ b/tests/packagedcode/data/plugin/rpm-package-expected.json @@ -142,7 +142,19 @@ "is_private": false, "is_virtual": false, "extra_data": {}, - "dependencies": [], + "dependencies": [ + { + "purl": "pkg:rpm/alfandega@2.0", + "extracted_requirement": "2.0", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + } + ], "repository_homepage_url": null, "repository_download_url": null, "api_data_url": null, diff --git a/tests/packagedcode/data/rpm/header/libproxy-bin-0.3.0-4.el6_3.x86_64.rpm-package-expected.json b/tests/packagedcode/data/rpm/header/libproxy-bin-0.3.0-4.el6_3.x86_64.rpm-package-expected.json index 4b38c57bc79..047448ffee9 100644 --- a/tests/packagedcode/data/rpm/header/libproxy-bin-0.3.0-4.el6_3.x86_64.rpm-package-expected.json +++ b/tests/packagedcode/data/rpm/header/libproxy-bin-0.3.0-4.el6_3.x86_64.rpm-package-expected.json @@ -69,7 +69,19 @@ "is_private": false, "is_virtual": false, "extra_data": {}, - "dependencies": [], + "dependencies": [ + { + "purl": "pkg:rpm/libproxy-bin@0.3.0", + "extracted_requirement": "0.3.0", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + } + ], "repository_homepage_url": null, "repository_download_url": null, "api_data_url": null, diff --git a/tests/packagedcode/data/rpm/header/libproxy-bin-0.3.0-4.el6_3.x86_64.rpm-package-only-expected.json b/tests/packagedcode/data/rpm/header/libproxy-bin-0.3.0-4.el6_3.x86_64.rpm-package-only-expected.json index a04e785381c..bc86faa9f2d 100644 --- a/tests/packagedcode/data/rpm/header/libproxy-bin-0.3.0-4.el6_3.x86_64.rpm-package-only-expected.json +++ b/tests/packagedcode/data/rpm/header/libproxy-bin-0.3.0-4.el6_3.x86_64.rpm-package-only-expected.json @@ -46,7 +46,19 @@ "is_private": false, "is_virtual": false, "extra_data": {}, - "dependencies": [], + "dependencies": [ + { + "purl": "pkg:rpm/libproxy-bin@0.3.0", + "extracted_requirement": "0.3.0", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + } + ], "repository_homepage_url": null, "repository_download_url": null, "api_data_url": null, diff --git a/tests/scancode/data/rpm/fping-2.4-0.b2.rhfc1.dag.i386.rpm.expected.json b/tests/scancode/data/rpm/fping-2.4-0.b2.rhfc1.dag.i386.rpm.expected.json index f84a565b80f..e0730850a0e 100644 --- a/tests/scancode/data/rpm/fping-2.4-0.b2.rhfc1.dag.i386.rpm.expected.json +++ b/tests/scancode/data/rpm/fping-2.4-0.b2.rhfc1.dag.i386.rpm.expected.json @@ -82,7 +82,23 @@ "purl": "pkg:rpm/fping@2.4-0.b2.rhfc1.dag" } ], - "dependencies": [], + "dependencies": [ + { + "purl": "pkg:rpm/fping@2.4", + "extracted_requirement": "2.4", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": {}, + "extra_data": {}, + "dependency_uid": "pkg:rpm/fping@2.4?uuid=4c055d32-6a8c-465e-9c76-f2714b7ce23f", + "for_package_uid": "pkg:rpm/fping@2.4-0.b2.rhfc1.dag?uuid=1903aca6-54da-49d6-a219-ff432eff4398", + "datafile_path": "fping-2.4-0.b2.rhfc1.dag.i386.rpm", + "datasource_id": "rpm_archive" + } + ], "files": [ { "path": "fping-2.4-0.b2.rhfc1.dag.i386.rpm", @@ -158,7 +174,19 @@ "is_private": false, "is_virtual": false, "extra_data": {}, - "dependencies": [], + "dependencies": [ + { + "purl": "pkg:rpm/fping@2.4", + "extracted_requirement": "2.4", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + } + ], "repository_homepage_url": null, "repository_download_url": null, "api_data_url": null,