diff --git a/complassist/_flict.py b/complassist/_flict.py index fab8e75..7e34466 100644 --- a/complassist/_flict.py +++ b/complassist/_flict.py @@ -69,3 +69,10 @@ def flict_outbound_candidate(expression: str, output_format: str) -> str: "outbound-candidate", expression, options=["-nr", "-of", output_format] ) return outbound_candidate + + +def flict_display_compatibility(licenses: list[str], output_format: str) -> tuple[int, str, str]: + """Display compatbility of licenses using flict. Returns exit code, + compatibility message, error message""" + logging.debug("Calculate compatibility using flict with: %s", " ".join(licenses)) + return _run_flict("display-compatibility", *licenses, options=["-of", output_format]) diff --git a/complassist/_licensing.py b/complassist/_licensing.py index d468c80..859e06e 100644 --- a/complassist/_licensing.py +++ b/complassist/_licensing.py @@ -8,7 +8,12 @@ import logging from license_expression import ExpressionError, Licensing, get_spdx_licensing -from ._flict import flict_outbound_candidate, flict_simplify, flict_simplify_list +from ._flict import ( + flict_display_compatibility, + flict_outbound_candidate, + flict_simplify, + flict_simplify_list, +) from ._sbom_parse import extract_items_from_cdx_sbom @@ -106,3 +111,24 @@ def get_outbound_candidate(sbom_path: str, simplify: bool = True) -> dict[str, s "checked_expression": expression, "outbound_candidate": outbound_candidate, } + + +def calculate_compatibity(sbom_path: str, simplify: bool = True): + """Check whether the licenses contained in an SBOM are compatible with each other""" + licenses_in_sbom = list_all_licenses(sbom_path, use_flict=simplify) + + # Check whether all licenses are valid SPDX expressions + licenses: list[str] = _validate_spdx_licenses(licenses_in_sbom) + + # Un-AND licenses + licenses_single: list[str] = [] + for lic in licenses: + licenses_single.extend([lic for lic in lic.split(" AND ") if lic != "AND"]) + # Uniquify licenses + licenses_single = list(set(licenses_single)) + + code, compatibility, error = flict_display_compatibility(licenses_single, output_format="text") + # Invalid license expression, possible caused by missing -nr flag + #if code == 11: + + return compatibility diff --git a/complassist/main.py b/complassist/main.py index 109f309..9ba049a 100644 --- a/complassist/main.py +++ b/complassist/main.py @@ -19,7 +19,7 @@ from ._clearlydefined import ( purl_to_cd_coordinates, ) from ._helpers import dict_to_json -from ._licensing import get_outbound_candidate, list_all_licenses +from ._licensing import calculate_compatibity, get_outbound_candidate, list_all_licenses from ._sbom_enrich import enrich_sbom_with_clearlydefined from ._sbom_generate import generate_cdx_sbom from ._sbom_parse import extract_items_from_cdx_sbom @@ -183,6 +183,30 @@ licensing_outbound.add_argument( action="store_true", ) +# License compatibility +licensing_compatibility = licensing_subparser.add_parser( + "compatibility", + help="Check whether the licenses contained in an SBOM are compatible with each other", +) +licensing_compatibility.add_argument( + "-f", + "--file", + help="Path to the CycloneDX SBOM (JSON format) from which licenses are read", + required=True, +) +licensing_compatibility.add_argument( + "-o", + "--output", + default="json", + choices=["json", "dict", "plain", "none"], + help="Desired output format. json and dict contain the most helpful output", +) +licensing_compatibility.add_argument( + "--no-simplify", + help="Do not simplify SPDX license expression using flict. May increase speed", + action="store_true", +) + # General flags parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output") @@ -275,6 +299,13 @@ def main(): # pylint: disable=too-many-branches, too-many-statements elif args.output == "none": pass + # Calculate compatibility of licenses + elif args.licensing_command == "compatibility": + compatibility = calculate_compatibity( + sbom_path=args.file, simplify=not args.no_simplify + ) + print(compatibility) + # No subcommand given, show help else: parser_licensing.print_help()