234 lines
10 KiB
Python
234 lines
10 KiB
Python
# Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
import argparse
|
|
import logging
|
|
import pkg_resources
|
|
import re
|
|
|
|
from typing import Union
|
|
|
|
|
|
class LicenseType:
|
|
@staticmethod
|
|
def _short_name(name: str) -> Union[str,None]:
|
|
match = re.match('\((.+)\)', name)
|
|
return match.group(1) if match is not None else None
|
|
|
|
def __init__(self, name: str, group: bool = False, alt_names: list[str] = []) -> None:
|
|
self.name = name
|
|
self.short_name = LicenseType._short_name(name)
|
|
self.alt_names = [x.casefold() for x in [self.name,self.short_name,*alt_names] if x is not None]
|
|
self.group = group
|
|
|
|
def __eq__(self, __value: 'LicenseType') -> bool:
|
|
return isinstance(__value, LicenseType) and (self.name == __value.name)
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
def classifier_str(self) -> str:
|
|
return ' :: '.join(['License', self.name])
|
|
|
|
class OSILicenseType(LicenseType):
|
|
def __init__(self, name: str, alt_names: list[str] = list[str]()) -> None:
|
|
super(OSILicenseType, self).__init__(name, group=False, alt_names=alt_names)
|
|
|
|
def classifier_str(self) -> str:
|
|
return ' :: '.join(['License', 'OSI Approved', self.name])
|
|
|
|
|
|
class LicenseClassifierMap:
|
|
_licenses: list[LicenseType] = [
|
|
LicenseType('Aladdin Free Public License (AFPL)'),
|
|
LicenseType('CC0 1.0 Universal (CC0 1.0) Public Domain Dedication'),
|
|
LicenseType('CeCILL-B Free Software License Agreement (CECILL-B)'),
|
|
LicenseType('CeCILL-C Free Software License Agreement (CECILL-C)'),
|
|
LicenseType('DFSG approved'),
|
|
LicenseType('Eiffel Forum License (EFL)'),
|
|
LicenseType('Free For Educational Use'),
|
|
LicenseType('Free For Home Use'),
|
|
LicenseType('Free To Use But Restricted'),
|
|
LicenseType('Free for non-commercial use'),
|
|
LicenseType('Freely Distributable'),
|
|
LicenseType('Freeware'),
|
|
LicenseType('GUST Font License 1.0'),
|
|
LicenseType('GUST Font License 2006-09-30'),
|
|
LicenseType('Netscape Public License (NPL)'),
|
|
LicenseType('Nokia Open Source License (NOKOS)'),
|
|
LicenseType('OSI Approved', group=True),
|
|
OSILicenseType('Academic Free License (AFL)'),
|
|
OSILicenseType('Apache Software License', alt_names=['Apache','Apache License v2.0']),
|
|
OSILicenseType('Apple Public Source License'),
|
|
OSILicenseType('Artistic License'),
|
|
OSILicenseType('Attribution Assurance License'),
|
|
OSILicenseType('BSD License', alt_names=['BSD', 'new BSD']),
|
|
OSILicenseType('Boost Software License 1.0 (BSL-1.0)', alt_names=['BSL']),
|
|
OSILicenseType('CEA CNRS Inria Logiciel Libre License, version 2.1 (CeCILL-2.1)'),
|
|
OSILicenseType('Common Development and Distribution License 1.0 (CDDL-1.0)'),
|
|
OSILicenseType('Common Public License'),
|
|
OSILicenseType('Eclipse Public License 1.0 (EPL-1.0)'),
|
|
OSILicenseType('Eclipse Public License 2.0 (EPL-2.0)'),
|
|
OSILicenseType('Eiffel Forum License'),
|
|
OSILicenseType('European Union Public Licence 1.0 (EUPL 1.0)'),
|
|
OSILicenseType('European Union Public Licence 1.1 (EUPL 1.1)'),
|
|
OSILicenseType('European Union Public Licence 1.2 (EUPL 1.2)'),
|
|
OSILicenseType('GNU Affero General Public License v3', alt_names=['AGPL','AGPLv3']),
|
|
OSILicenseType('GNU Affero General Public License v3 or later (AGPLv3+)'),
|
|
OSILicenseType('GNU Free Documentation License (FDL)'),
|
|
OSILicenseType('GNU General Public License (GPL)'),
|
|
OSILicenseType('GNU General Public License v2 (GPLv2)'),
|
|
OSILicenseType('GNU General Public License v2 or later (GPLv2+)'),
|
|
OSILicenseType('GNU General Public License v3 (GPLv3)'),
|
|
OSILicenseType('GNU General Public License v3 or later (GPLv3+)'),
|
|
OSILicenseType('GNU Lesser General Public License v2 (LGPLv2)'),
|
|
OSILicenseType('GNU Lesser General Public License v2 or later (LGPLv2+)'),
|
|
OSILicenseType('GNU Lesser General Public License v3 (LGPLv3)'),
|
|
OSILicenseType('GNU Lesser General Public License v3 or later (LGPLv3+)'),
|
|
OSILicenseType('GNU Library or Lesser General Public License (LGPL)'),
|
|
OSILicenseType('Historical Permission Notice and Disclaimer (HPND)'),
|
|
OSILicenseType('IBM Public License'),
|
|
OSILicenseType('ISC License (ISCL)'),
|
|
OSILicenseType('Intel Open Source License'),
|
|
OSILicenseType('Jabber Open Source License'),
|
|
OSILicenseType('MIT License', alt_names=['MIT']),
|
|
OSILicenseType('MIT No Attribution License (MIT-0)'),
|
|
OSILicenseType('MITRE Collaborative Virtual Workspace License (CVW)'),
|
|
OSILicenseType('MirOS License (MirOS)'),
|
|
OSILicenseType('Motosoto License'),
|
|
OSILicenseType('Mozilla Public License 1.0 (MPL)'),
|
|
OSILicenseType('Mozilla Public License 1.1 (MPL 1.1)'),
|
|
OSILicenseType('Mozilla Public License 2.0 (MPL 2.0)'),
|
|
OSILicenseType('Mulan Permissive Software License v2 (MulanPSL-2.0)'),
|
|
OSILicenseType('Nethack General Public License'),
|
|
OSILicenseType('Nokia Open Source License'),
|
|
OSILicenseType('Open Group Test Suite License'),
|
|
OSILicenseType('Open Software License 3.0 (OSL-3.0)'),
|
|
OSILicenseType('PostgreSQL License'),
|
|
OSILicenseType('Python License (CNRI Python License)'),
|
|
OSILicenseType('Python Software Foundation License'),
|
|
OSILicenseType('Qt Public License (QPL)'),
|
|
OSILicenseType('Ricoh Source Code Public License'),
|
|
OSILicenseType('SIL Open Font License 1.1 (OFL-1.1)'),
|
|
OSILicenseType('Sleepycat License'),
|
|
OSILicenseType('Sun Industry Standards Source License (SISSL)'),
|
|
OSILicenseType('Sun Public License'),
|
|
OSILicenseType('The Unlicense (Unlicense)'),
|
|
OSILicenseType('Universal Permissive License (UPL)'),
|
|
OSILicenseType('University of Illinois/NCSA Open Source License'),
|
|
OSILicenseType('Vovida Software License 1.0'),
|
|
OSILicenseType('W3C License'),
|
|
OSILicenseType('X.Net License'),
|
|
OSILicenseType('Zope Public License'),
|
|
OSILicenseType('zlib/libpng License', alt_names=['zlib','libpng']),
|
|
LicenseType('Other/Proprietary License', alt_names=['Proprietary']),
|
|
LicenseType('Public Domain'),
|
|
LicenseType('Repoze Public License'),
|
|
]
|
|
|
|
def __init__(self) -> None:
|
|
self.classifier_map = {x.classifier_str(): x for x in self._licenses}
|
|
|
|
def find_from_classifier(self, classifier: str) -> Union[LicenseType,None]:
|
|
return self.classifier_map.get(classifier)
|
|
|
|
def find_from_license(self, license_line: str) -> Union[LicenseType,None]:
|
|
for lt in self._licenses:
|
|
for an in lt.alt_names:
|
|
if an in license_line.casefold():
|
|
return lt
|
|
return None
|
|
|
|
|
|
def get_metadata_line(line: str, metadata_prefix: str) -> Union[str,None]:
|
|
if not line.startswith(metadata_prefix):
|
|
return None
|
|
return line[len(metadata_prefix)+1:].strip()
|
|
|
|
|
|
def get_license_str(license_classifier_list: list[LicenseType], license_line_list: list[LicenseType]) -> str:
|
|
list_sep = ', '
|
|
# Prefer classifier-based license strings
|
|
license_str = list_sep.join([str(l) for l in license_classifier_list if not l.group])
|
|
|
|
# Use license lines if no (specific) classifier license info
|
|
if not license_str:
|
|
license_str = list_sep.join([str(l) for l in license_line_list])
|
|
|
|
# Allow group classifiers if nothing else is listed (e.g. :: OSI Approved)
|
|
if not license_str:
|
|
license_str = list_sep.join([str(l) for l in license_classifier_list])
|
|
|
|
# Just print unknown otherwise
|
|
if not license_str:
|
|
license_str = 'Unknown'
|
|
return license_str
|
|
|
|
|
|
def print_license_table(package_licenses: dict[str,tuple[str,str]]):
|
|
pkg_header = 'Package'
|
|
ver_header = 'Version'
|
|
lic_header = 'License'
|
|
|
|
pkg_len = max(len(pkg_header), max([len(k) for k in package_licenses.keys()]))
|
|
ver_len = max(len(ver_header), max([len(v[0]) for v in package_licenses.values()]))
|
|
|
|
logging.info(f'{pkg_header:<{pkg_len}} | {ver_header:<{ver_len}} | {lic_header}')
|
|
logging.info(f'{"":-<{pkg_len}}-|-{"":-<{ver_len}}-|-{"":-<{len(lic_header)}}')
|
|
for k,v in package_licenses.items():
|
|
logging.info(f'{k:<{pkg_len}} | {v[0]:<{ver_len}} | {v[1]}')
|
|
|
|
|
|
def check_installed_licenses():
|
|
workset = pkg_resources.working_set
|
|
|
|
license_map = LicenseClassifierMap()
|
|
|
|
package_licenses = dict[str,str]()
|
|
for dist in workset:
|
|
if not dist.has_metadata('METADATA'):
|
|
continue
|
|
|
|
license_classifier_list = list[LicenseType]()
|
|
license_line_list = list[LicenseType]()
|
|
for line in dist.get_metadata_lines('METADATA'):
|
|
classifier = get_metadata_line(line, 'Classifier:')
|
|
if classifier is not None:
|
|
license_type = license_map.find_from_classifier(classifier)
|
|
if license_type is None:
|
|
continue
|
|
|
|
logging.debug(f'{dist.key} v{dist.version}: Classifier: {license_type.classifier_str()}')
|
|
license_classifier_list.append(license_type)
|
|
continue
|
|
|
|
license_line = get_metadata_line(line, 'License:')
|
|
if license_line is not None:
|
|
license_type = license_map.find_from_license(license_line)
|
|
if license_type is None:
|
|
continue
|
|
|
|
logging.debug(f'{dist.key} v{dist.version}: License: {license_type}')
|
|
license_line_list.append(license_type)
|
|
continue
|
|
|
|
license_str = get_license_str(license_classifier_list, license_line_list)
|
|
package_licenses[dist.key] = (dist.version, license_str)
|
|
|
|
package_licenses = dict[str,str](sorted(package_licenses.items()))
|
|
print_license_table(package_licenses)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description='Parse requirements and relative to pkg_resources working set to determine')
|
|
parser.add_argument('-v', '--verbose', action='count', dest='verbosity', default=0, help='Verbosity level (-v=info,-vv=debug)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Forward all
|
|
log_modifier = min(10 * args.verbosity, 20)
|
|
default_level = logging.WARNING
|
|
log_level = default_level - log_modifier
|
|
|
|
logging.basicConfig(level=log_level)
|
|
check_installed_licenses()
|