Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: migrate runtime dependency to cyclonedx-python-lib>=8<9 #796

Merged
merged 18 commits into from
Oct 14, 2024
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 16 additions & 0 deletions cyclonedx_py/_internal/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 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.
95 changes: 54 additions & 41 deletions cyclonedx_py/_internal/utils/cdx.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,54 +23,67 @@
from re import compile as re_compile
from typing import Any, Dict, Iterable

from cyclonedx.model import ExternalReference, ExternalReferenceType, Tool, XsUri
from cyclonedx.builder.this import this_component as lib_component
from cyclonedx.model import ExternalReference, ExternalReferenceType, XsUri
from cyclonedx.model.bom import Bom
from cyclonedx.model.license import License, LicenseExpression
from cyclonedx.model.component import Component, ComponentType
from cyclonedx.model.license import DisjunctiveLicense, License, LicenseAcknowledgement, LicenseExpression

from cyclonedx_py import __version__
from ... import __version__ as __THIS_VERSION # noqa:N812


def make_bom(**kwargs: Any) -> Bom:
bom = Bom(**kwargs)
bom.metadata.tools.add(Tool(
# keep in sync with `../../../pyproject.toml`
vendor='CycloneDX',
name='cyclonedx-py',
version=__version__,
external_references=[
ExternalReference(
type=ExternalReferenceType.BUILD_SYSTEM,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/actions')
bom.metadata.tools.components.update((
lib_component(),
Component(
type=ComponentType.APPLICATION,
group='CycloneDX',
# package is called 'cyclonedx-bom', but the tool is called 'cyclonedx-py'
name='cyclonedx-py',
version=__THIS_VERSION,
description='CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments',
licenses=(DisjunctiveLicense(id='Apache-2.0',
acknowledgement=LicenseAcknowledgement.DECLARED),),
external_references=(
# let's assume this is not a fork
ExternalReference(
type=ExternalReferenceType.WEBSITE,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/#readme')
),
ExternalReference(
type=ExternalReferenceType.DOCUMENTATION,
url=XsUri('https://cyclonedx-bom-tool.readthedocs.io/')
),
ExternalReference(
type=ExternalReferenceType.VCS,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/')
),
ExternalReference(
type=ExternalReferenceType.BUILD_SYSTEM,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/actions')
),
ExternalReference(
type=ExternalReferenceType.ISSUE_TRACKER,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/issues')
),
ExternalReference(
type=ExternalReferenceType.LICENSE,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE')
),
ExternalReference(
type=ExternalReferenceType.RELEASE_NOTES,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md')
),
# we cannot assert where the lib was fetched from, but we can give a hint
ExternalReference(
type=ExternalReferenceType.DISTRIBUTION,
url=XsUri('https://pypi.org/project/cyclonedx-bom/')
),
),
ExternalReference(
type=ExternalReferenceType.DISTRIBUTION,
url=XsUri('https://pypi.org/project/cyclonedx-bom/')
),
ExternalReference(
type=ExternalReferenceType.DOCUMENTATION,
url=XsUri('https://cyclonedx-bom-tool.readthedocs.io/')
),
ExternalReference(
type=ExternalReferenceType.ISSUE_TRACKER,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/issues')
),
ExternalReference(
type=ExternalReferenceType.LICENSE,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE')
),
ExternalReference(
type=ExternalReferenceType.RELEASE_NOTES,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md')
),
ExternalReference(
type=ExternalReferenceType.VCS,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/')
),
ExternalReference(
type=ExternalReferenceType.WEBSITE,
url=XsUri('https://github.com/CycloneDX/cyclonedx-python/#readme')
)
]))
# to be extended...
),
))
return bom


Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ build-backend = "poetry.core.masonry.api"


[tool.poetry]
# keep in sync with `cyclonedx_py/_internal/utils/cdx.py`
name = "cyclonedx-bom"
version = "4.6.1"
description = "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments"
Expand Down Expand Up @@ -69,7 +68,7 @@ cyclonedx-py = "cyclonedx_py._internal.cli:run"

[tool.poetry.dependencies]
python = "^3.8"
cyclonedx-python-lib = { version = "^7.3.0, !=7.3.1", extras = ["validation"] }
cyclonedx-python-lib = { version = "^8.0", extras = ["validation"] }
packageurl-python = ">=0.11, <2" # keep in sync with same dep in `cyclonedx-python-lib`
pip-requirements-parser = "^32.0"
packaging = "^22 || ^23 || ^24"
Expand All @@ -92,6 +91,7 @@ isort = "5.13.2"
autopep8 = "2.3.1"
mypy = "1.11.2"
bandit = "1.7.10"
tomli = { version = "^2.0.1", python = "<3.11" }
tox = "4.21.2"
# min version required to be able to install some dependencies
# see https://github.com/MichaelKim0407/flake8-use-fstring/issues/33
Expand Down
120 changes: 93 additions & 27 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) OWASP Foundation. All Rights Reserved.


import sys
from json import dumps as json_dumps
from os import getenv
from os.path import dirname, join
from os import getenv, path
from pathlib import Path
from re import sub as re_sub
from sys import stderr
from typing import Union
from typing import Any, Dict, Union
from unittest import TestCase
from xml.sax.saxutils import escape as xml_escape, quoteattr as xml_quoteattr # nosec:B406

Expand All @@ -32,16 +30,16 @@

RECREATE_SNAPSHOTS = '1' == getenv('CDX_TEST_RECREATE_SNAPSHOTS')
if RECREATE_SNAPSHOTS:
print('!!! WILL RECREATE ALL SNAPSHOTS !!!', file=stderr)
print('!!! WILL RECREATE ALL SNAPSHOTS !!!', file=sys.stderr)

INIT_TESTBEDS = '1' != getenv('CDX_TEST_SKIP_INIT_TESTBEDS')
if INIT_TESTBEDS:
print('!!! WILL INIT TESTBEDS !!!', file=stderr)
print('!!! WILL INIT TESTBEDS !!!', file=sys.stderr)

_TESTDATA_DIRECTORY = join(dirname(__file__), '_data')
_TESTDATA_DIRECTORY = path.join(path.dirname(__file__), '_data')

INFILES_DIRECTORY = join(_TESTDATA_DIRECTORY, 'infiles')
SNAPSHOTS_DIRECTORY = join(_TESTDATA_DIRECTORY, 'snapshots')
INFILES_DIRECTORY = path.join(_TESTDATA_DIRECTORY, 'infiles')
SNAPSHOTS_DIRECTORY = path.join(_TESTDATA_DIRECTORY, 'snapshots')

UNSUPPORTED_OF_SV = (
(OutputFormat.JSON, SchemaVersion.V1_1),
Expand All @@ -60,7 +58,7 @@ class SnapshotMixin:

@staticmethod
def getSnapshotFile(snapshot_name: str) -> str: # noqa: N802
return join(SNAPSHOTS_DIRECTORY, f'{snapshot_name}.bin')
return path.join(SNAPSHOTS_DIRECTORY, f'{snapshot_name}.bin')

@classmethod
def writeSnapshot(cls, snapshot_name: str, data: str) -> None: # noqa: N802
Expand Down Expand Up @@ -92,62 +90,121 @@ def assertEqualSnapshot(self: Union[TestCase, 'SnapshotMixin'], # noqa: N802
_root_file_uri_xml_attr = xml_quoteattr(_root_file_uri)[1:-1]
_root_file_uri_json = json_dumps(_root_file_uri)[1:-1]

# package is called 'cyclonedx-bom', but the tool is called 'cyclonedx-py'
EXPECTED_TOOL_NAME = 'cyclonedx-py'


def make_xml_comparable(bom: str) -> str:
bom = bom.replace(_root_file_uri_xml, 'file://.../')
bom = bom.replace(_root_file_uri_xml_attr, 'file://.../')
bom = bom.replace( # replace metadata.tools.version
bom = bom.replace( # replace this version in metadata.tools.components
' <group>CycloneDX</group>\n'
f' <name>{EXPECTED_TOOL_NAME}</name>\n'
f' <version>{__this_version}</version>',
' <group>CycloneDX</group>\n'
f' <name>{EXPECTED_TOOL_NAME}</name>\n'
' <version>thisVersion-testing</version>')
bom = bom.replace( # replace this version in metadata.tools
' <vendor>CycloneDX</vendor>\n'
' <name>cyclonedx-py</name>\n'
f' <name>{EXPECTED_TOOL_NAME}</name>\n'
f' <version>{__this_version}</version>',
' <vendor>CycloneDX</vendor>\n'
' <name>cyclonedx-py</name>\n'
f' <name>{EXPECTED_TOOL_NAME}</name>\n'
' <version>thisVersion-testing</version>')
bom = re_sub( # replace metadata.tools.version
bom = re_sub( # replace lib-dynamics in metadata.tools.components
' <group>CycloneDX</group>\n'
' <name>cyclonedx-python-lib</name>\n'
' <version>.*?</version>\n'
' <description>.*?</description>\n'
' <licenses>\n'
'(?: .*?\n)*'
' </licenses>\n'
' <externalReferences>\n'
'(?: .*?\n)*'
' </externalReferences>',
' <group>CycloneDX</group>\n'
' <name>cyclonedx-python-lib</name>\n'
' <version>libVersion-testing</version>\n'
' <description><!-- stripped --></description>\n'
' <licenses><!-- stripped --></licenses>\n'
' <externalReferences><!-- stripped --></externalReferences>',
bom)
bom = re_sub( # replace lib-dynamics version in metadata.tools[]
' <vendor>CycloneDX</vendor>\n'
' <name>cyclonedx-python-lib</name>\n'
' <version>.*?</version>',
' <vendor>CycloneDX</vendor>\n'
' <name>cyclonedx-python-lib</name>\n'
' <version>libVersion-testing</version>',
bom)
bom = re_sub( # replace metadata.tools.externalReferences
bom = re_sub( # replace lib-dynamics externalReferences in metadata.tools[]
' <vendor>CycloneDX</vendor>\n'
' <name>cyclonedx-python-lib</name>\n'
r' <version>(.*?)</version>\n'
r' <externalReferences>[\s\S]*?</externalReferences>',
' <version>(.*?)</version>\n'
' <externalReferences>\n'
'(?: .*?\n)*'
' </externalReferences>',
' <vendor>CycloneDX</vendor>\n'
' <name>cyclonedx-python-lib</name>\n'
r' <version>\1</version>''\n'
' <version>\\1</version>\n'
' <externalReferences><!-- stripped --></externalReferences>',
bom)
return bom


def make_json_comparable(bom: str) -> str:
bom = bom.replace(_root_file_uri_json, 'file://.../')
bom = bom.replace( # replace metadata.tools.version
' "name": "cyclonedx-py",\n'
bom = bom.replace( # replace this version in metadata.tools.components[]
f' "name": {json_dumps(EXPECTED_TOOL_NAME)},\n'
' "type": "application",\n'
f' "version": {json_dumps(__this_version)}',
f' "name": {json_dumps(EXPECTED_TOOL_NAME)},\n'
' "type": "application",\n'
' "version": "thisVersion-testing"')
bom = bom.replace( # replace this version in metadata.tools[]
f' "name": {json_dumps(EXPECTED_TOOL_NAME)},\n'
' "vendor": "CycloneDX",\n'
f' "version": {json_dumps(__this_version)}',
' "name": "cyclonedx-py",\n'
f' "name": {json_dumps(EXPECTED_TOOL_NAME)},\n'
' "vendor": "CycloneDX",\n'
' "version": "thisVersion-testing"')
bom = re_sub( # replace metadata.tools.version
bom = re_sub( # replace lib-dynamics in metadata.tools.components[]
' "description": ".*?",\n'
' "externalReferences": \\[\n'
'(?: .*?\n)*'
' \\],\n'
' "group": "CycloneDX",\n'
' "licenses": \\[\n'
'(?: .*?\n)*'
' \\],\n'
' "name": "cyclonedx-python-lib",\n'
' "type": "library",\n'
' "version": ".*?"',
' "description": "stripped",\n'
' "externalReferences": [ ],\n'
' "group": "CycloneDX",\n'
' "licenses": [ ],\n'
' "name": "cyclonedx-python-lib",\n'
' "type": "library",\n'
' "version": "libVersion-testing"',
bom)
bom = re_sub( # replace lib-dynamics version in metadata.tools[]
' "name": "cyclonedx-python-lib",\n'
' "vendor": "CycloneDX",\n'
' "version": ".*?"',
' "name": "cyclonedx-python-lib",\n'
' "vendor": "CycloneDX",\n'
' "version": "libVersion-testing"',
bom)
bom = re_sub( # replace metadata.tools.externalReferences
r' "externalReferences": \[[\s\S]*?\],\n'
bom = re_sub( # replace lib-dynamics externalReferences in metadata.tools[]
' "externalReferences": \\[\n'
'(?: .*?\n)*'
' \\],\n'
' "name": "cyclonedx-python-lib",\n'
' "vendor": "CycloneDX"',
' "vendor": "CycloneDX",\n',
' "externalReferences": [ ],\n'
' "name": "cyclonedx-python-lib",\n'
' "vendor": "CycloneDX"',
' "vendor": "CycloneDX",\n',
bom)
return bom

Expand All @@ -160,3 +217,12 @@ def make_comparable(bom: str, of: OutputFormat) -> str:
raise NotImplementedError(f'unknown OutputFormat: {of!r}')

# endregion reproducible test results


def load_pyproject() -> Dict[str, Any]:
if sys.version_info >= (3, 11):
from tomllib import load as toml_load
else:
from tomli import load as toml_load
with open(path.join(path.dirname(__file__), '..', 'pyproject.toml'), 'rb') as f:
return toml_load(f)

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

Loading