1515# SPDX-License-Identifier: Apache-2.0
1616# Copyright (c) OWASP Foundation. All Rights Reserved.
1717
18-
1918"""
2019Functionality related to PEP 621.
2120
2221See https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
2322See https://peps.python.org/pep-0621/
2423"""
2524
25+ from base64 import b64encode
2626from itertools import chain
27+ from os .path import dirname , join
2728from typing import TYPE_CHECKING , Any , Dict , Generator , Iterable , Iterator
2829
2930from cyclonedx .exception .model import InvalidUriException
3031from cyclonedx .factory .license import LicenseFactory
31- from cyclonedx .model import ExternalReference , XsUri
32+ from cyclonedx .model import AttachedText , Encoding , ExternalReference , XsUri
3233from cyclonedx .model .component import Component
34+ from cyclonedx .model .license import DisjunctiveLicense
3335from packaging .requirements import Requirement
3436
3537from .cdx import licenses_fixup , url_label_to_ert
@@ -50,18 +52,37 @@ def classifiers2licenses(classifiers: Iterable[str], lfac: 'LicenseFactory') ->
5052 classifiers )))
5153
5254
53- def project2licenses (project : Dict [str , Any ], lfac : 'LicenseFactory' ) -> Generator ['License' , None , None ]:
54- if 'classifiers' in project :
55+ def project2licenses (project : Dict [str , Any ], lfac : 'LicenseFactory' , * ,
56+ fpath : str ) -> Generator ['License' , None , None ]:
57+ if classifiers := project .get ('classifiers' ):
5558 # https://packaging.python.org/en/latest/specifications/pyproject-toml/#classifiers
5659 # https://peps.python.org/pep-0621/#classifiers
5760 # https://packaging.python.org/en/latest/specifications/core-metadata/#classifier-multiple-use
58- yield from classifiers2licenses (project ['classifiers' ], lfac )
59- license = project .get ('license' )
60- # https://packaging.python.org/en/latest/specifications/pyproject-toml/#license
61- # https://peps.python.org/pep-0621/#license
62- # https://packaging.python.org/en/latest/specifications/core-metadata/#license
63- if isinstance (license , dict ) and 'text' in license :
64- yield lfac .make_from_string (license ['text' ])
61+ yield from classifiers2licenses (classifiers , lfac )
62+ if plicense := project .get ('license' ):
63+ # https://packaging.python.org/en/latest/specifications/pyproject-toml/#license
64+ # https://peps.python.org/pep-0621/#license
65+ # https://packaging.python.org/en/latest/specifications/core-metadata/#license
66+ if 'file' in plicense and 'text' in plicense :
67+ # per spec:
68+ # > These keys are mutually exclusive, so a tool MUST raise an error if the metadata specifies both keys.
69+ raise ValueError ('`license.file` and `license.text` are mutually exclusive,' )
70+ if 'file' in plicense :
71+ # per spec:
72+ # > [...] a string value that is a relative file path [...].
73+ # > Tools MUST assume the file’s encoding is UTF-8.
74+ with open (join (dirname (fpath ), plicense ['file' ]), 'rb' ) as plicense_fileh :
75+ yield DisjunctiveLicense (name = f"declared license of '{ project ['name' ]} '" ,
76+ text = AttachedText (encoding = Encoding .BASE_64 ,
77+ content = b64encode (plicense_fileh .read ()).decode ()))
78+ elif len (plicense_text := plicense .get ('text' , '' )) > 0 :
79+ license = lfac .make_from_string (plicense_text )
80+ if isinstance (license , DisjunctiveLicense ) and license .id is None :
81+ # per spec, `License` is either a SPDX ID/Expression, or a license text(not name!)
82+ yield DisjunctiveLicense (name = f"declared license of '{ project ['name' ]} '" ,
83+ text = AttachedText (content = plicense_text ))
84+ else :
85+ yield license
6586
6687
6788def project2extrefs (project : Dict [str , Any ]) -> Generator ['ExternalReference' , None , None ]:
@@ -77,14 +98,14 @@ def project2extrefs(project: Dict[str, Any]) -> Generator['ExternalReference', N
7798
7899
79100def project2component (project : Dict [str , Any ], * ,
80- type : 'ComponentType' ) -> 'Component' :
101+ ctype : 'ComponentType' , fpath : str ) -> 'Component' :
81102 dynamic = project .get ('dynamic' , ())
82103 return Component (
83- type = type ,
104+ type = ctype ,
84105 name = project ['name' ],
85106 version = project .get ('version' , None ) if 'version' not in dynamic else None ,
86107 description = project .get ('description' , None ) if 'description' not in dynamic else None ,
87- licenses = licenses_fixup (project2licenses (project , LicenseFactory ())),
108+ licenses = licenses_fixup (project2licenses (project , LicenseFactory (), fpath = fpath )),
88109 external_references = project2extrefs (project ),
89110 # TODO add more properties according to spec
90111 )
0 commit comments