Skip to content

Commit 6e64c3a

Browse files
committed
wip
Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 810c54a commit 6e64c3a

File tree

92 files changed

+1408
-2615
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+1408
-2615
lines changed

cyclonedx_py/_internal/environment.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from .utils.cdx import licenses_fixup, make_bom
3939
from .utils.packaging import metadata2extrefs, metadata2licenses, normalize_packagename
4040
from .utils.pep610 import PackageSourceArchive, PackageSourceVcs, packagesource2extref, packagesource4dist
41-
from .utils.pep639 import dist2licenses as pep639_dist2licenses
41+
from .utils.pep639 import dist2licenses_from_files as pep639_dist2licenses_from_files
4242
from .utils.pyproject import pyproject2component, pyproject2dependencies, pyproject_load
4343

4444
if TYPE_CHECKING: # pragma: no cover
@@ -184,13 +184,12 @@ def __add_components(self, bom: 'Bom',
184184

185185
# region licenses
186186
lfac = LicenseFactory()
187-
component.licenses.update(pep639_dist2licenses(dist, lfac, self._gather_license_texts, self._logger))
188-
if len(component.licenses) == 0:
189-
# According to PEP 639 spec, if licenses are declared in the "new" style,
190-
# all other license declarations MUST be ignored.
191-
# https://peps.python.org/pep-0639/#converting-legacy-metadata
192-
component.licenses.update(metadata2licenses(dist_meta, lfac))
187+
component.licenses.update(metadata2licenses(dist_meta, lfac,
188+
gather_texts=self._gather_license_texts))
189+
if self._gather_license_texts:
190+
component.licenses.update(pep639_dist2licenses_from_files(dist, lfac, logger=self._logger))
193191
licenses_fixup(component)
192+
del lfac
194193
# endregion licenses
195194

196195
del dist_meta, dist_name, dist_version
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# This file is part of CycloneDX Python
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
# Copyright (c) OWASP Foundation. All Rights Reserved.
17+
18+
__all__ = ['PackageMetadata']
19+
20+
import sys
21+
22+
if sys.version_info >= (3, 10):
23+
from importlib.metadata import PackageMetadata
24+
else:
25+
from email.message import Message as PackageMetadata

cyclonedx_py/_internal/utils/packaging.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,39 +24,46 @@
2424
from cyclonedx.model.license import DisjunctiveLicense, LicenseAcknowledgement
2525

2626
from .cdx import url_label_to_ert
27-
from .pep621 import classifiers2licenses
27+
from .pep621 import classifiers2licenses as pep621_classifiers2licenses
28+
from .pep639 import dist2licenses_from_files as pep639_dist2licenses_from_files
2829

2930
if TYPE_CHECKING: # pragma: no cover
3031
import sys
32+
from logging import Logger
3133

3234
from cyclonedx.factory.license import LicenseFactory
3335
from cyclonedx.model.license import License
3436

35-
if sys.version_info >= (3, 10):
36-
from importlib.metadata import PackageMetadata
37-
else:
38-
from email.message import Message as PackageMetadata
37+
from ..py_interop.packagemetadata import PackageMetadata
3938

4039

41-
def metadata2licenses(metadata: 'PackageMetadata', lfac: 'LicenseFactory') -> Generator['License', None, None]:
40+
def metadata2licenses(metadata: 'PackageMetadata', lfac: 'LicenseFactory',
41+
gather_texts: bool
42+
) -> Generator['License', None, None]:
4243
lack = LicenseAcknowledgement.DECLARED
43-
if 'Classifier' in metadata:
44-
# see spec: https://packaging.python.org/en/latest/specifications/core-metadata/#classifier-multiple-use
45-
classifiers: list[str] = metadata.get_all('Classifier') # type:ignore[assignment]
46-
yield from classifiers2licenses(classifiers, lfac, lack)
47-
for mlicense in set(metadata.get_all('License', ())):
48-
# see spec: https://packaging.python.org/en/latest/specifications/core-metadata/#license
49-
if len(mlicense) <= 0:
50-
continue
51-
license = lfac.make_from_string(mlicense,
52-
license_acknowledgement=lack)
53-
if isinstance(license, DisjunctiveLicense) and license.id is None:
54-
# per spec, `License` is either a SPDX ID/Expression, or a license text(not name!)
55-
yield DisjunctiveLicense(name=f"declared license of '{metadata['Name']}'",
56-
acknowledgement=lack,
57-
text=AttachedText(content=mlicense))
58-
else:
59-
yield license
44+
if (lexp := metadata.get('License-Expression')) is not None:
45+
# see spec: https://peps.python.org/pep-0639/#add-license-expression-field
46+
yield lfac.make_from_string(lexp,
47+
license_acknowledgement=lack)
48+
else: # per PEP630, if License-Expression exists, the deprecated declarations MUST be ignored
49+
if 'Classifier' in metadata:
50+
# see spec: https://packaging.python.org/en/latest/specifications/core-metadata/#classifier-multiple-use
51+
classifiers: list[str] = metadata.get_all('Classifier') # type:ignore[assignment]
52+
yield from pep621_classifiers2licenses(classifiers, lfac, lack)
53+
for mlicense in set(metadata.get_all('License', ())):
54+
# see spec: https://packaging.python.org/en/latest/specifications/core-metadata/#license
55+
if len(mlicense) <= 0:
56+
continue
57+
license = lfac.make_from_string(mlicense,
58+
license_acknowledgement=lack)
59+
if isinstance(license, DisjunctiveLicense) and license.id is None:
60+
if gather_texts:
61+
# per spec, `License` is either a SPDX ID/Expression, or a license text(not name!)
62+
yield DisjunctiveLicense(name=f"declared license of '{metadata['Name']}'",
63+
acknowledgement=lack,
64+
text=AttachedText(content=mlicense))
65+
else:
66+
yield license
6067

6168

6269
def metadata2extrefs(metadata: 'PackageMetadata') -> Generator['ExternalReference', None, None]:

cyclonedx_py/_internal/utils/pep639.py

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
from cyclonedx.factory.license import LicenseFactory
4141
from cyclonedx.model.license import License
4242

43+
from ..py_interop.packagemetadata import PackageMetadata
44+
4345

4446
def project2licenses(project: dict[str, Any], lfac: 'LicenseFactory',
4547
gather_texts: bool, *,
@@ -72,50 +74,44 @@ def project2licenses(project: dict[str, Any], lfac: 'LicenseFactory',
7274
_LICENSE_LOCATIONS = ('licenses', 'license_files', '')
7375

7476

75-
def dist2licenses(
77+
def dist2licenses_from_files(
7678
dist: 'Distribution', lfac: 'LicenseFactory',
77-
gather_texts: bool,
7879
logger: 'Logger'
7980
) -> Generator['License', None, None]:
8081
lack = LicenseAcknowledgement.DECLARED
81-
metadata = dist.metadata # see https://packaging.python.org/en/latest/specifications/core-metadata/
82-
if (lexp := metadata['License-Expression']) is not None:
83-
# see spec: https://peps.python.org/pep-0639/#add-license-expression-field
84-
yield lfac.make_from_string(lexp,
85-
license_acknowledgement=lack)
86-
if gather_texts:
87-
for mlfile in set(metadata.get_all('License-File', ())):
88-
# see spec: https://peps.python.org/pep-0639/#add-license-file-field
89-
# latest spec rev: https://discuss.python.org/t/pep-639-round-3-improving-license-clarity-with-better-package-metadata/53020 # noqa: E501
90-
content = None
91-
for mlpath in _LICENSE_LOCATIONS:
82+
metadata: 'PackageMetadata' = dist.metadata # see https://packaging.python.org/en/latest/specifications/core-metadata/
83+
for mlfile in set(metadata.get_all('License-File', ())):
84+
# see spec: https://peps.python.org/pep-0639/#add-license-file-field
85+
# latest spec rev: https://discuss.python.org/t/pep-639-round-3-improving-license-clarity-with-better-package-metadata/53020 # noqa: E501
86+
content = None
87+
for mlpath in _LICENSE_LOCATIONS:
88+
try:
89+
content = dist.read_text(join(mlpath, mlfile))
90+
except UnicodeDecodeError as err:
9291
try:
93-
content = dist.read_text(join(mlpath, mlfile))
94-
except UnicodeDecodeError as err:
95-
try:
96-
content = bytes2str(err.object)
97-
except UnicodeDecodeError:
98-
pass
99-
else:
100-
break # for-loop
92+
content = bytes2str(err.object)
93+
except UnicodeDecodeError:
94+
pass
10195
else:
102-
if content is not None:
103-
break # for-loop
104-
if content is None: # pragma: no cover
105-
logger.debug('Error: failed to read license file %r for dist %r',
106-
mlfile, metadata['Name'])
107-
continue
108-
encoding = None
109-
content_type = guess_type(mlfile) or AttachedText.DEFAULT_CONTENT_TYPE
110-
# per default, license files are human-readable texts.
111-
if not content_type.startswith('text/'):
112-
encoding = Encoding.BASE_64
113-
content = b64encode(content.encode('utf-8')).decode('ascii')
114-
yield DisjunctiveLicense(
115-
name=f'declared license file: {mlfile}',
116-
acknowledgement=lack,
117-
text=AttachedText(
118-
content=content,
119-
encoding=encoding,
120-
content_type=content_type
121-
))
96+
break # for-loop
97+
else:
98+
if content is not None:
99+
break # for-loop
100+
if content is None: # pragma: no cover
101+
logger.debug('Error: failed to read license file %r for dist %r',
102+
mlfile, metadata['Name'])
103+
continue
104+
encoding = None
105+
content_type = guess_type(mlfile) or AttachedText.DEFAULT_CONTENT_TYPE
106+
# per default, license files are human-readable texts.
107+
if not content_type.startswith('text/'):
108+
encoding = Encoding.BASE_64
109+
content = b64encode(content.encode('utf-8')).decode('ascii')
110+
yield DisjunctiveLicense(
111+
name=f'declared license file: {mlfile}',
112+
acknowledgement=lack,
113+
text=AttachedText(
114+
content=content,
115+
encoding=encoding,
116+
content_type=content_type
117+
))

0 commit comments

Comments
 (0)