Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions cyclonedx_py/_internal/utils/bytes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This file is part of CycloneDX Python
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) OWASP Foundation. All Rights Reserved.

from sys import getdefaultencoding

from chardet import detect as chardetect


def bytes2str(data: bytes, *, errors: str = 'strict') -> str:
# see https://docs.python.org/3/library/codecs.html#standard-encodings
encoding = (chardetect(data)['encoding'] or getdefaultencoding()).replace(
# replace Windows-encoding with code-page
'Windows-', 'cp')
return data.decode(encoding, errors)
10 changes: 2 additions & 8 deletions cyclonedx_py/_internal/utils/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,14 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) OWASP Foundation. All Rights Reserved.

from sys import getdefaultencoding
from tempfile import NamedTemporaryFile
from typing import BinaryIO

from chardet import detect as chardetect
from .bytes import bytes2str


def io2str(io: BinaryIO, *, errors: str = 'strict') -> str:
data = io.read()
# see https://docs.python.org/3/library/codecs.html#standard-encodings
encoding = (chardetect(data)['encoding'] or getdefaultencoding()).replace(
# replace Windows-encoding with code-page
'Windows-', 'cp')
return data.decode(encoding, errors)
return bytes2str(io.read(), errors=errors)


def io2file(io: BinaryIO, *, errors: str = 'strict') -> str:
Expand Down
25 changes: 19 additions & 6 deletions cyclonedx_py/_internal/utils/pep639.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from cyclonedx.model import AttachedText, Encoding
from cyclonedx.model.license import DisjunctiveLicense, LicenseAcknowledgement

from .bytes import bytes2str
from .mimetypes import guess_type

if TYPE_CHECKING: # pragma: no cover
Expand All @@ -38,6 +39,10 @@

from cyclonedx.model.license import License

# per spec > license files are stored in the `.dist-info/licenses/` subdirectory of the produced wheel.
# but in practice, other locations are used, too.
_LICENSE_LOCATIONS = ('licenses', 'license_files', '')


def dist2licenses(
dist: 'Distribution',
Expand All @@ -55,12 +60,20 @@ def dist2licenses(
for mlfile in set(metadata.get_all('License-File', ())):
# see spec: https://peps.python.org/pep-0639/#add-license-file-field
# latest spec rev: https://discuss.python.org/t/pep-639-round-3-improving-license-clarity-with-better-package-metadata/53020 # noqa: E501

# per spec > license files are stored in the `.dist-info/licenses/` subdirectory of the produced wheel.
# but in practice, other locations are used, too.
content = dist.read_text(join('licenses', mlfile)) \
or dist.read_text(join('license_files', mlfile)) \
or dist.read_text(mlfile)
content = None
for mlpath in _LICENSE_LOCATIONS:
try:
content = dist.read_text(join(mlpath, mlfile))
except UnicodeDecodeError as err:
try:
content = bytes2str(err.object)
except UnicodeDecodeError:
pass
else:
break # for-loop
else:
if content is not None:
break # for-loop
if content is None: # pragma: no cover
logger.debug('Error: failed to read license file %r for dist %r',
mlfile, metadata['Name'])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# EditorConfig is awesome: https://editorconfig.org

[my_licenses/utf-8*]
charset = utf-8

[my_licenses/utf-16le*]
charset = utf-16le

[my_licenses/utf-16be*]
charset = utf-16be

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Licenses/* binary
Licenses/*.txt binary diff=txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# PEP 639 - regression 868

see <https://github.com/CycloneDX/cyclonedx-python/issues/868>

PEP-630 expects license gfiles to be UTF8 encoded text.
some license files may not be text, some may not be UTF8 encoded, but still be added as license files.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
this file is
utf-8 encoded
without BOM
😃
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
this file is
utf-8 encoded
with BOM
😃
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[build-system]
# Known broken version
requires = ["setuptools == 78.1.0"]
build-backend = "setuptools.build_meta"

[project]
name = "regression-issue868"
version = "0.1"
license-files = ["my_licenses/*"]
readme = "README.md"

[tool.setuptools]
include-package-data = false
exclude-package-data = { "*" = ["*", "**"] }
[tool.setuptools.package-data]
# do not want any content installed
3 changes: 3 additions & 0 deletions tests/_data/infiles/environment/with-license-pep639/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def main() -> None:
'lxml',
# with expression-like License AND License-File
'cryptography==43.0.1', # https://github.com/CycloneDX/cyclonedx-python/issues/826
# with possibly unexpected license files
# https://github.com/CycloneDX/cyclonedx-python/issues/868
'../../_helpers/local_pckages/with-license-pep639_regression-issue868',
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ name = "with-extras"
version = "0.1.0"
description = "depenndencies with license declaration accoring to PEP 639"

dependencies = [
# with License-Expression
"attrs",
# with License-File
"boolean.py",
"jsonpointer",
"license_expression",
"lxml",
# with expression-like License AND License-File
"cryptography",
]
[project.dependencies]
# with License-Expression
"attrs" = { }
# with License-File
"boolean.py" = { }
"jsonpointer" = { }
"license_expression" = { }
"lxml" = { }
# with expression-like License AND License-File
"cryptography" = { }
# with possibly unexpected license files
"regression-issue868" = { path = "../../_helpers/local_pckages/with-license-pep639_regression-issue868" }

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading