diff --git a/liccheck/command_line.py b/liccheck/command_line.py index 3dc2e4a..b0d6abc 100644 --- a/liccheck/command_line.py +++ b/liccheck/command_line.py @@ -2,13 +2,14 @@ import collections import os.path -from liccheck.requirements import parse_requirements, resolve, resolve_without_deps +from liccheck.requirements import parse_requirements, resolve from configparser import ConfigParser, NoOptionError import enum import functools import re import textwrap +from packaging.requirements import Requirement import sys import semantic_version import toml @@ -138,10 +139,7 @@ class Reason(enum.Enum): def get_packages_info(requirement_file, no_deps=False): - regex_license = re.compile(r"License(?:-Expression)?: (?P.*)?$", re.M) - regex_classifier = re.compile( - r"Classifier: License(?: :: OSI Approved)?(?: :: (?P.*))?$", re.M - ) + regex_classifier = re.compile(r"^License(?: :: OSI Approved)?(?: :: (?P.*))?$") requirements = parse_requirements(requirement_file) @@ -153,31 +151,27 @@ def transform(dist): licenses = list(set([strip_license(l) for l in licenses])) return { - "name": dist.project_name, + "name": dist.name, "version": dist.version, - "location": dist.location, - "dependencies": [dependency.project_name for dependency in dist.requires()], + "dependencies": [Requirement(r).name for r in dist.requires] if dist.requires is not None else [], "licenses": licenses, } def get_license(dist): - if dist.has_metadata(dist.PKG_INFO): - metadata = dist.get_metadata(dist.PKG_INFO) - match = regex_license.search(metadata) - if match: - license = match.group("license") - if license != "UNKNOWN": # Value when license not specified. - return [license] + if dist.metadata is not None: + license = dist.metadata.get('License-Expression') or dist.metadata.get('License') + if license is not None: + return [license] return [] def get_licenses_from_classifiers(dist): - if dist.has_metadata(dist.PKG_INFO): - metadata = dist.get_metadata(dist.PKG_INFO) - - # match might be found, but None if using the classifier: - # License :: OSI Approved - return [m for m in regex_classifier.findall(metadata) if m] + if dist.metadata is not None: + classifiers = dist.metadata.get_all('Classifier') + if classifiers is not None: + # match might be found, but None if using the classifier: + # License :: OSI Approved + return [l.group(1) for l in [regex_classifier.match(m) for m in classifiers] if l] return [] @@ -191,11 +185,12 @@ def strip_license(license): return license[: -len(" license")] return license - resolve_func = resolve_without_deps if no_deps else resolve - packages = [transform(dist) for dist in resolve_func(requirements)] + packages = [transform(dist) for dist in resolve(requirements, without_deps=no_deps)] # keep only unique values as there are maybe some duplicates unique = [] - [unique.append(item) for item in packages if item not in unique] + for item in packages: + if item not in unique: + unique.append(item) return sorted(unique, key=(lambda item: item["name"].lower())) diff --git a/liccheck/requirements.py b/liccheck/requirements.py index 8c8e106..fc4d5e2 100644 --- a/liccheck/requirements.py +++ b/liccheck/requirements.py @@ -1,4 +1,6 @@ -import pkg_resources +import importlib.metadata as ilm +from packaging.markers import Marker, default_environment +from packaging.requirements import Requirement try: from pip._internal.network.session import PipSession @@ -24,29 +26,21 @@ def parse_requirements(requirement_file): requirements = [] for req in pip_parse_requirements(requirement_file, session=PipSession()): install_req = install_req_from_parsed_requirement(req) - if install_req.markers and not pkg_resources.evaluate_marker(str(install_req.markers)): + if install_req.markers is not None and not install_req.markers.evaluate(): # req should not installed due to env markers continue elif install_req.editable: # skip editable req as they are failing in the resolve phase continue - requirements.append(pkg_resources.Requirement.parse(str(install_req.req))) + requirements.append(install_req.req) return requirements -def resolve_without_deps(requirements): - working_set = pkg_resources.working_set +def resolve(requirements, without_deps=False): for req in requirements: - env = pkg_resources.Environment(working_set.entries) - dist = env.best_match( - req=req, - working_set=working_set, - installer=None, - replace_conflicting=False, - ) - yield dist - - -def resolve(requirements): - for dist in pkg_resources.working_set.resolve(requirements): + dist = ilm.distribution(req.name) yield dist + if not without_deps and dist.requires is not None: + requires = [Requirement(r) for r in dist.requires] + requires = [r for r in requires if r.marker is None or r.marker.evaluate()] + yield from resolve(requires) diff --git a/tests/test_get_packages_info.py b/tests/test_get_packages_info.py index 5df46f7..d97ecf5 100644 --- a/tests/test_get_packages_info.py +++ b/tests/test_get_packages_info.py @@ -1,6 +1,6 @@ import sys -import pkg_resources +import importlib.metadata as ilm import pytest from liccheck.command_line import get_packages_info @@ -24,10 +24,7 @@ def test_license_strip_with_return_carriage(tmp_path, mocker): tmpfh.write(b"Name: pip\r\n") tmpfh.write(b"Version: 23.3.1\r\n") tmpfh.write(b"Classifier: License :: OSI Approved :: MIT License\r\n") - metadata = pkg_resources.PathMetadata(tmp_path, tmp_path) - resolve.return_value = [ - pkg_resources.Distribution(project_name="pip", metadata=metadata) - ] + resolve.return_value = [ilm.Distribution.at(tmp_path)] assert get_packages_info(req_path)[0]["licenses"] == ["MIT"] @@ -86,8 +83,5 @@ def test_license_expression(tmp_path, mocker): tmpfh.write("Name: Twisted\n") tmpfh.write("Version: 23.8.0\n") tmpfh.write("License-Expression: MIT\n") - metadata = pkg_resources.FileMetadata(pkg_info_path) - resolve.return_value = [ - pkg_resources.Distribution(project_name="Twisted", metadata=metadata) - ] + resolve.return_value = [ilm.Distribution.at(tmp_path)] assert get_packages_info(req_path)[0]["licenses"] == ["MIT"]