diff --git a/README.md b/README.md index 74d211e19..900f9a088 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,7 @@ Supported data sources are: * Pip's `requirements.txt` format * `PDM` manifest and lockfile are not explicitly supported. However, PDM's Python virtual environments are fully supported. See the docs for an example. -* `uv` manifest and lockfile are not explicitly supported. - However, uv's Python virtual environments are fully supported. See the docs for an example. +* `uv` manifest and lockfile * `Conda` as a package manager is no longer supported since version 4. However, conda's Python environments are fully supported via the methods listed above. See the docs for an example. @@ -86,6 +85,7 @@ positional arguments: requirements Build an SBOM from Pip requirements pipenv Build an SBOM from Pipenv manifest poetry Build an SBOM from Poetry project + uv Build an SBOM from uv project options: -h, --help show this help message and exit diff --git a/cyclonedx_py/_internal/cli.py b/cyclonedx_py/_internal/cli.py index 6e444dc12..b2757961c 100644 --- a/cyclonedx_py/_internal/cli.py +++ b/cyclonedx_py/_internal/cli.py @@ -34,6 +34,7 @@ from .poetry import PoetryBB from .requirements import RequirementsBB from .utils.args import argparse_type4enum, choices4enum +from .uv import UvBB if TYPE_CHECKING: # pragma: no cover from cyclonedx.model.bom import Bom @@ -115,6 +116,7 @@ def make_argument_parser(cls, sco: ArgumentParser, **kwargs: Any) -> ArgumentPar (RequirementsBB, 'requirements'), (PipenvBB, 'pipenv'), (PoetryBB, 'poetry'), + (UvBB, 'uv'), ): spp = scbbc.make_argument_parser(add_help=False) sp.add_parser(sct, aliases=scta, diff --git a/cyclonedx_py/_internal/uv.py b/cyclonedx_py/_internal/uv.py new file mode 100644 index 000000000..8c17c9c55 --- /dev/null +++ b/cyclonedx_py/_internal/uv.py @@ -0,0 +1,810 @@ +# 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 argparse import OPTIONAL, ArgumentParser +from collections.abc import Generator, Iterable +from dataclasses import dataclass +from os.path import basename, dirname, isfile, join +from textwrap import dedent +from typing import TYPE_CHECKING, Any, Optional + +from cyclonedx.exception.model import InvalidUriException, UnknownHashTypeException +from cyclonedx.model import ExternalReference, ExternalReferenceType, HashType, Property, XsUri +from cyclonedx.model.component import Component, ComponentType +from cyclonedx.model.dependency import Dependency +from packageurl import PackageURL +from packaging.markers import Marker, default_environment +from packaging.requirements import Requirement + +from . import BomBuilder, PropertyName, PurlTypePypi +from .cli_common import add_argument_mc_type +from .utils.cdx import make_bom +from .utils.packaging import normalize_packagename +from .utils.pyproject import pyproject2component +from .utils.secret import redact_auth_from_url +from .utils.toml import toml_loads + +if TYPE_CHECKING: # pragma: no cover + from logging import Logger + + from cyclonedx.model.bom import Bom + + T_NameDict = dict[str, Any] + + +def _bom_ref_value(component: Component) -> str: + ref = component.bom_ref.value + if ref is None: + raise ValueError(f'component {component.name!r} is missing bom_ref') + return ref + + +class ExtrasNotFoundError(ValueError): + def __init__(self, extras: Iterable[str]) -> None: + self.__extras = frozenset(extras) + + def __str__(self) -> str: + return f'Extra(s) [{",".join(sorted(self.__extras))}] not specified.' + + +class GroupsNotFoundError(ValueError): + def __init__(self, groups: Iterable[str]) -> None: + self.__groups = frozenset(groups) + + def __str__(self) -> str: + return 'Group(s) not found: ' + ', '.join(sorted(self.__groups)) + + +@dataclass +class _LockEntry: + name: str + component: Component + dependencies: frozenset[str] # keys MUST go through `normalize_packagename()` + added2bom: bool + + +class UvBB(BomBuilder): + + @staticmethod + def make_argument_parser(**kwargs: Any) -> 'ArgumentParser': + p = ArgumentParser( + description=dedent("""\ + Build an SBOM from uv project. + + This requires parsing your `pyproject.toml` and `uv.lock` file which details exact pinned versions of + dependencies. + """), + **kwargs) + p.add_argument('--group', + metavar='', + help='Include dependencies from the specified dependency group' + ' (multiple values allowed)', + action='append', + dest='groups_with', + default=[]) + p.add_argument('--no-group', + metavar='', + help='Exclude dependencies from the specified dependency group' + ' (multiple values allowed)', + action='append', + dest='groups_without', + default=[]) + og = p.add_mutually_exclusive_group() + og.add_argument('--only-group', + metavar='', + help='Only include dependencies from the specified dependency group' + ' (multiple values allowed)', + action='append', + dest='groups_only', + default=[]) + og.add_argument('--only-dev', + help='Alias for: --only-group dev', + dest='only_dev', + action='store_true') + del og + p.add_argument('--all-groups', + help='Include all dependency groups' + ' (default: %(default)s)', + dest='all_groups', + action='store_true', + default=False) + p.add_argument('--no-default-groups', + help='Ignore the default dependency groups' + ' (default: %(default)s)', + dest='no_default_groups', + action='store_true', + default=False) + p.add_argument('--no-dev', + help='Alias for: --no-group dev', + dest='no_dev', + action='store_true', + default=False) + eg = p.add_mutually_exclusive_group() + eg.add_argument('-E', '--extras', + metavar='', + help='Extra sets of dependencies to include' + ' (multiple values allowed)', + action='append', + dest='extras', + default=[]) + eg.add_argument('--all-extras', + help='Include all extra dependencies' + ' (default: %(default)s)', + action='store_true', + dest='all_extras', + default=False) + del eg + add_argument_mc_type(p) + p.add_argument('project_directory', + metavar='', + help='The project directory for uv (containing `pyproject.toml` and `uv.lock`),' + ' or a path to `uv.lock`' + ' (default: current working directory)', + nargs=OPTIONAL, + default='.') + return p + + def __init__(self, *, + logger: 'Logger', + **__: Any) -> None: + self._logger = logger + self.__marker_env_base: dict[str, str] = {} + self.__marker_env_by_extra: dict[str, dict[str, str]] = {} + self.__active_resolution_markers: Optional[frozenset[str]] = None + + def __marker_env(self, *, extra: Optional[str]) -> dict[str, str]: + extra_n = normalize_packagename(extra) if extra else '' + cached = self.__marker_env_by_extra.get(extra_n) + if cached is not None: + return cached + env = dict(self.__marker_env_base) + env['extra'] = extra_n + self.__marker_env_by_extra[extra_n] = env + return env + + def __is_marker_ok(self, marker: Any, *, extra: Optional[str] = None) -> bool: + if not marker: + return True + try: + marker_s = str(marker) + return Marker(marker_s).evaluate(self.__marker_env(extra=extra)) + except Exception as err: # pragma: no cover + self._logger.debug('failed evaluating marker %r', marker, exc_info=err) + return True + + def __select_active_resolution_markers(self, locker: 'T_NameDict') -> Optional[frozenset[str]]: + markers = locker.get('resolution-markers') + if not isinstance(markers, list) or len(markers) == 0: + return None + active = frozenset(str(m) for m in markers if self.__is_marker_ok(m)) + if len(active) == 0: + raise ValueError('uv lock has resolution-markers but none match the current environment') + if len(active) > 1: + self._logger.warning('uv lock has multiple matching resolution-markers: %s', ', '.join(sorted(active))) + return active + + def __call__(self, *, # type:ignore[override] # noqa: C901 + project_directory: str, + groups_with: list[str], + groups_without: list[str], + groups_only: list[str], + only_dev: bool, + all_groups: bool, + no_default_groups: bool, + no_dev: bool, + extras: list[str], + all_extras: bool, + mc_type: 'ComponentType', + **__: Any) -> 'Bom': + if isfile(project_directory) and basename(project_directory) == 'uv.lock': + lock_file = project_directory + project_directory = dirname(project_directory) or '.' + pyproject_file = join(project_directory, 'pyproject.toml') + else: + pyproject_file = join(project_directory, 'pyproject.toml') + lock_file = join(project_directory, 'uv.lock') + try: + pyproject_fh = open(pyproject_file, encoding='utf8', errors='replace') + except OSError as err: + raise ValueError(f'Could not open pyproject file: {pyproject_file}') from err + try: + lock_fh = open(lock_file, encoding='utf8', errors='replace') + except OSError as err: + pyproject_fh.close() + raise ValueError(f'Could not open lock file: {lock_file}') from err + + with pyproject_fh, lock_fh: + pyproject: 'T_NameDict' = toml_loads(pyproject_fh.read()) + locker: 'T_NameDict' = toml_loads(lock_fh.read()) + _marker_defaults = default_environment() + self.__marker_env_base = {str(k): str(v) for k, v in _marker_defaults.items()} + self.__marker_env_by_extra.clear() + self.__active_resolution_markers = self.__select_active_resolution_markers(locker) + + root_c = pyproject2component(pyproject, + ctype=mc_type, + fpath=pyproject_file, + gather_license_texts=False, + logger=self._logger) + root_c.bom_ref.value = 'root-component' + + groups_available = self.__get_dependency_groups(pyproject, locker) + groups_with_s = frozenset(map(normalize_packagename, + filter(None, ','.join(groups_with).split(',')))) + groups_without_s = frozenset(map(normalize_packagename, + filter(None, ','.join(groups_without).split(',')))) + groups_only_s = frozenset(map(normalize_packagename, + filter(None, ','.join(groups_only).split(',')))) + del groups_with, groups_without, groups_only + + if only_dev: + groups_only_s = frozenset({'dev', }) + if no_dev: + groups_without_s = groups_without_s | frozenset({'dev', }) + if only_dev and no_dev: + raise ValueError('`--only-dev` and `--no-dev` are mutually exclusive') + + all_groups_s = frozenset(groups_available) + groups_requested = groups_with_s | groups_without_s | groups_only_s + groups_unknown = groups_requested - all_groups_s + if len(groups_unknown) > 0: + groups_error = GroupsNotFoundError(groups_unknown) + self._logger.error(groups_error) + raise ValueError('some uv dependency groups are unknown') from groups_error + + include_project_deps = len(groups_only_s) == 0 + if groups_only_s: + use_groups = groups_only_s - groups_without_s + else: + acc: set[str] = set() + if all_groups: + acc.update(all_groups_s) + else: + if not no_default_groups: + acc.update(self.__get_default_groups(pyproject, all_groups_s)) + acc.update(groups_with_s) + use_groups = frozenset(acc - groups_without_s) + del groups_with_s, groups_without_s, groups_only_s, groups_requested, groups_unknown + + extras_s: frozenset[str] + if include_project_deps: + if all_extras: + extras_s = frozenset(self.__get_optional_dependencies(pyproject, locker)) + else: + extras_s = frozenset(map(normalize_packagename, + filter(None, ','.join(extras).split(',')))) + + optional_deps = self.__get_optional_dependencies(pyproject, locker) + extras_not_found = extras_s - optional_deps.keys() + if len(extras_not_found) > 0: + extras_error = ExtrasNotFoundError(extras_not_found) + self._logger.error(extras_error) + raise ValueError('some package extras are unknown') from extras_error + del extras_not_found + else: + # `--only-group` / `--only-dev` implies no project selection, so extras are ignored. + extras_s = frozenset() + + return self._make_bom( + root_c, + locker, + self.__get_dependency_seeds(pyproject, locker, extras_s, use_groups, include_project_deps), + extras_s, + ) + + def __get_dependency_seeds( # noqa: C901 + self, pyproject: 'T_NameDict', locker: 'T_NameDict', + extras: frozenset[str], + groups: frozenset[str], + include_project_deps: bool + ) -> tuple[frozenset[str], dict[str, frozenset[str]]]: + """ + Determine which packages are included, based on `pyproject.toml` manifest. + + This mimics how `uv sync` behaves by default: base dependencies, dependencies from the default group set, + plus selected extras. + """ + dep_names: set[str] = set() + required_extras: dict[str, set[str]] = {} + + def add_req(req: Requirement, *, marker_extra: Optional[str]) -> None: + if req.marker is not None: + try: + if not req.marker.evaluate(self.__marker_env(extra=marker_extra)): + return + except Exception as err: # pragma: no cover + self._logger.debug('failed evaluating marker %r', req.marker, exc_info=err) + name = normalize_packagename(req.name) + dep_names.add(name) + if req.extras: + required_extras.setdefault(name, set()).update(map(normalize_packagename, req.extras)) + + project = pyproject.get('project') + if include_project_deps: + if isinstance(project, dict): + for dep in project.get('dependencies', ()): + try: + add_req(Requirement(dep), marker_extra=None) + except Exception as err: # pragma: no cover + self._logger.debug('failed parsing dependency %r', dep, exc_info=err) + + raw_optional = project.get('optional-dependencies', {}) + if isinstance(raw_optional, dict): + for extra_raw, dep_specs in raw_optional.items(): + extra = normalize_packagename(str(extra_raw)) + if extra not in extras: + continue + for dep_spec in dep_specs or (): + try: + add_req(Requirement(dep_spec), marker_extra=extra) + except Exception as err: # pragma: no cover + self._logger.debug('failed parsing optional dependency %r', dep_spec, exc_info=err) + else: + # best-effort fallback: rely on lockfile-only metadata + lock_root = self.__get_lock_root(locker, root_name=None) + if lock_root is not None: + dep_names.update(lock_root.dependencies) + optional_deps = self.__optional_dependencies_from_lock(lock_root.package) + for extra in extras: + dep_names.update(optional_deps.get(extra, ())) + + if groups: + for group in groups: + for dep_name, dep_extras in self.__group_dependencies(pyproject, locker, group).items(): + dep_names.add(dep_name) + if dep_extras: + required_extras.setdefault(dep_name, set()).update(dep_extras) + for dep_spec in self.__group_dependency_specs(pyproject, group): + try: + add_req(Requirement(dep_spec), marker_extra=None) + except Exception as err: # pragma: no cover + self._logger.debug('failed parsing group dependency %r', dep_spec, exc_info=err) + + return frozenset(dep_names), {k: frozenset(v) for k, v in required_extras.items()} + + def __get_optional_dependencies(self, pyproject: 'T_NameDict', locker: 'T_NameDict') -> dict[str, frozenset[str]]: + project = pyproject.get('project') + if isinstance(project, dict): + raw = project.get('optional-dependencies', {}) + if isinstance(raw, dict): + deps: dict[str, set[str]] = {} + for extra_raw, dep_specs in raw.items(): + extra = normalize_packagename(str(extra_raw)) + deps.setdefault(extra, set()) + for dep_spec in dep_specs or (): + try: + req = Requirement(dep_spec) + except Exception as err: # pragma: no cover + self._logger.debug('failed parsing optional dependency %r', dep_spec, exc_info=err) + continue + deps[extra].add(normalize_packagename(req.name)) + return {k: frozenset(v) for k, v in deps.items()} + + # best-effort fallback: lockfile may still contain this info + lock_root = self.__get_lock_root(locker, root_name=None) + if lock_root is None: + return {} + return {k: frozenset(v) for k, v in self.__optional_dependencies_from_lock(lock_root.package).items()} + + def __get_dependency_groups( # noqa: C901 + self, pyproject: 'T_NameDict', locker: 'T_NameDict', + ) -> dict[str, frozenset[str]]: + """ + Determine which dependency groups are available. + + Groups are primarily sourced from PEP 735 `dependency-groups` and uv's legacy `tool.uv.dev-dependencies`. + As a fallback, groups are also discovered from `uv.lock`. + """ + groups: dict[str, set[str]] = {} + + raw = pyproject.get('dependency-groups', {}) + if isinstance(raw, dict): + for group_raw, dep_specs in raw.items(): + group = normalize_packagename(str(group_raw)) + groups.setdefault(group, set()) + for dep_spec in dep_specs or (): + try: + req = Requirement(str(dep_spec)) + except Exception as err: # pragma: no cover + self._logger.debug('failed parsing dependency group requirement %r', dep_spec, exc_info=err) + continue + groups[group].add(normalize_packagename(req.name)) + + tool = pyproject.get('tool', {}) + if isinstance(tool, dict): + tool_uv = tool.get('uv', {}) + if isinstance(tool_uv, dict): + legacy_dev = tool_uv.get('dev-dependencies', ()) + if isinstance(legacy_dev, list): + groups.setdefault('dev', set()) + for dep_spec in legacy_dev or (): + try: + req = Requirement(str(dep_spec)) + except Exception as err: # pragma: no cover + self._logger.debug('failed parsing legacy dev dependency %r', dep_spec, exc_info=err) + continue + groups['dev'].add(normalize_packagename(req.name)) + + lock_root = self.__get_lock_root(locker, root_name=None) + if lock_root is not None: + raw_lock = lock_root.package.get('dev-dependencies', {}) + if isinstance(raw_lock, dict): + for group_raw, deps in raw_lock.items(): + group = normalize_packagename(str(group_raw)) + groups.setdefault(group, set()) + for dep in deps or (): + if isinstance(dep, dict) and 'name' in dep: + groups[group].add(normalize_packagename(str(dep['name']))) + + # uv defaults to syncing the `dev` group; treat it as existing even if empty. + groups.setdefault('dev', set()) + + return {k: frozenset(v) for k, v in groups.items()} + + def __get_default_groups(self, pyproject: 'T_NameDict', available_groups: frozenset[str]) -> frozenset[str]: + default_groups = frozenset({'dev', }) # uv default + + tool = pyproject.get('tool', {}) + if isinstance(tool, dict): + tool_uv = tool.get('uv', {}) + if isinstance(tool_uv, dict) and 'default-groups' in tool_uv: + raw = tool_uv.get('default-groups') + if raw == 'all': + default_groups = available_groups + elif isinstance(raw, list): + default_groups = frozenset( + normalize_packagename(str(g)) + for g in raw + if g + ) + elif isinstance(raw, str) and raw: + default_groups = frozenset({normalize_packagename(raw), }) + + unknown = default_groups - available_groups + if unknown: + self._logger.warning('skip unknown default groups: %s', ', '.join(sorted(unknown))) + default_groups = default_groups - unknown + + return default_groups + + def __group_dependency_specs(self, pyproject: 'T_NameDict', group: str) -> tuple[str, ...]: + group_n = normalize_packagename(group) + specs: list[str] = [] + + raw = pyproject.get('dependency-groups', {}) + if isinstance(raw, dict): + for group_raw, dep_specs in raw.items(): + if normalize_packagename(str(group_raw)) != group_n: + continue + specs.extend(str(d) for d in (dep_specs or ()) if d) + + # legacy field; included in the `dev` group + if group_n == 'dev': + tool = pyproject.get('tool', {}) + if isinstance(tool, dict): + tool_uv = tool.get('uv', {}) + if isinstance(tool_uv, dict): + legacy_dev = tool_uv.get('dev-dependencies', ()) + if isinstance(legacy_dev, list): + specs.extend(str(d) for d in legacy_dev or () if d) + + return tuple(specs) + + def __group_dependencies(self, pyproject: 'T_NameDict', locker: 'T_NameDict', group: str) -> dict[str, set[str]]: + """ + Best-effort group dependencies from `uv.lock`. + + Returns mapping of dependency-name to required extras. + """ + del pyproject # reserved for potential future use + + lock_root = self.__get_lock_root(locker, root_name=None) + if lock_root is None: + return {} + raw = lock_root.package.get('dev-dependencies', {}) + if not isinstance(raw, dict): + return {} + group_n = normalize_packagename(group) + + deps: dict[str, set[str]] = {} + for group_raw, group_deps in raw.items(): + if normalize_packagename(str(group_raw)) != group_n: + continue + for dep in group_deps or (): + if not isinstance(dep, dict) or 'name' not in dep: + continue + if not self.__is_marker_ok(dep.get('marker')): + continue + dep_name = normalize_packagename(str(dep['name'])) + dep_extras_raw = dep.get('extra', dep.get('extras')) + if isinstance(dep_extras_raw, list): + deps.setdefault(dep_name, set()).update( + normalize_packagename(str(e)) for e in dep_extras_raw if e + ) + else: + deps.setdefault(dep_name, set()) + return deps + + def __optional_dependencies_from_lock(self, lock_root_package: 'T_NameDict') -> dict[str, set[str]]: + optional_deps: dict[str, set[str]] = {} + raw = lock_root_package.get('optional-dependencies', {}) + if not isinstance(raw, dict): + return optional_deps + for extra_raw, deps in raw.items(): + extra = normalize_packagename(str(extra_raw)) + optional_deps.setdefault(extra, set()) + for dep in deps or (): + if not isinstance(dep, dict) or 'name' not in dep: + continue + if not self.__is_marker_ok(dep.get('marker'), extra=extra): + continue + optional_deps.setdefault(extra, set()).add(normalize_packagename(str(dep['name']))) + return optional_deps + + @dataclass(frozen=True) + class _LockRoot: + package: 'T_NameDict' + dependencies: frozenset[str] + + def __get_lock_root(self, locker: 'T_NameDict', *, root_name: Optional[str]) -> Optional['_LockRoot']: + """ + Best-effort lookup of the "root" package entry in `uv.lock`. + + Currently, uv includes the project as a local package, e.g. `source = { virtual = "." }` or + `source = { editable = "." }`. + """ + packages = locker.get('package', ()) + if not isinstance(packages, list): + return None + root_name_n = normalize_packagename(root_name) if root_name else None + + def is_project_source(pkg: 'T_NameDict') -> bool: + source = pkg.get('source', {}) + if not isinstance(source, dict): + return False + for k in ('virtual', 'editable', 'path', 'directory'): + if k in source and str(source.get(k)) in {'.', './'}: + return True + return False + + candidate = None + for pkg in packages: + if not isinstance(pkg, dict): + continue + if root_name_n is not None and normalize_packagename(str(pkg.get('name', ''))) != root_name_n: + continue + if is_project_source(pkg): + candidate = pkg + break + if candidate is None: + candidate = pkg + if candidate is None: + return None + + deps = frozenset( + normalize_packagename(str(d['name'])) + for d in candidate.get('dependencies', ()) + if isinstance(d, dict) and 'name' in d and self.__is_marker_ok(d.get('marker')) + ) + return self._LockRoot(candidate, deps) + + def _make_bom( # noqa: C901 + self, root_c: Component, locker: 'T_NameDict', + seed_deps: tuple[frozenset[str], dict[str, frozenset[str]]], + use_extras: frozenset[str], + ) -> 'Bom': + bom = make_bom() + bom.metadata.component = root_c + self._logger.debug('root-component: %r', root_c) + + if use_extras: + root_c.properties.update( + Property( + name=PropertyName.PythonPackageExtra.value, + value=extra + ) for extra in use_extras + ) + + lock_data: dict[str, list[_LockEntry]] = {} + for entry in self._parse_lock(locker): + _ld = lock_data.setdefault(entry.name, []) + _ldl = len(_ld) + if _ldl > 0 and entry.component.bom_ref.value: + entry.component.bom_ref.value += f'#{_ldl}' + _ld.append(entry) + + root_name_n = normalize_packagename(root_c.name) + seed_names, required_extras = seed_deps + for dep_name, extras in required_extras.items(): + for lock_entry in lock_data.get(dep_name, ()): + lock_entry.component.properties.update( + Property( + name=PropertyName.PythonPackageExtra.value, + value=extra + ) for extra in extras + ) + + included = self.__collect_lock_entries(lock_data, seed_names, root_name_n) + + root_dep = Dependency(root_c.bom_ref) + bom.dependencies.add(root_dep) + + def deps_for_name(dep_name_n: str) -> Iterable[_LockEntry]: + if dep_name_n == root_name_n: + return () + return lock_data.get(dep_name_n, ()) + + for dep_name_n in sorted(seed_names): + entries = deps_for_name(dep_name_n) + if not entries: + self._logger.warning('skip unlocked dependency: %s', dep_name_n) + continue + for entry in entries: + root_dep.dependencies.add(Dependency(entry.component.bom_ref)) + + root_bref = _bom_ref_value(root_c) + deps_by_ref: dict[str, Dependency] = {root_bref: root_dep} + + for entry in included: + if entry.name == root_name_n: + continue + if not entry.added2bom: + entry.added2bom = True + self._logger.info('add component for package %r', entry.component.name) + self._logger.debug('add component: %r', entry.component) + bom.components.add(entry.component) + bref = _bom_ref_value(entry.component) + dep = deps_by_ref.get(bref) + if dep is None: + dep = deps_by_ref[bref] = Dependency(entry.component.bom_ref) + bom.dependencies.add(dep) + + for entry in included: + if entry.name == root_name_n: + continue + dep = deps_by_ref.get(_bom_ref_value(entry.component)) + if dep is None: + continue + for dep_name_n in sorted(entry.dependencies): + if dep_name_n == root_name_n: + dep.dependencies.add(Dependency(root_c.bom_ref)) + continue + dep_entries = lock_data.get(dep_name_n) + if dep_entries is None: + self._logger.warning('skip unlocked component: %s', dep_name_n) + continue + for dep_entry in dep_entries: + dep.dependencies.add(Dependency(dep_entry.component.bom_ref)) + + return bom + + def __collect_lock_entries(self, lock_data: dict[str, list[_LockEntry]], + seed_names: Iterable[str], + root_name: str) -> tuple[_LockEntry, ...]: + included: dict[str, _LockEntry] = {} + pending = list(sorted(seed_names)) + while pending: + name = pending.pop() + if name == root_name: + continue + entries = lock_data.get(name) + if not entries: + continue + for entry in entries: + ref = _bom_ref_value(entry.component) + if ref in included: + continue + included[ref] = entry + pending.extend(sorted(entry.dependencies)) + return tuple(sorted(included.values(), key=lambda e: _bom_ref_value(e.component))) + + def _parse_lock(self, locker: 'T_NameDict') -> Generator[_LockEntry, None, None]: + package: 'T_NameDict' + for package in locker.get('package', []): + res_markers = package.get('resolution-markers') + if isinstance(res_markers, list) and len(res_markers) > 0: + if self.__active_resolution_markers is not None: + if not any(str(m) in self.__active_resolution_markers for m in res_markers): + if not any(self.__is_marker_ok(m) for m in res_markers): + continue + elif not any(self.__is_marker_ok(m) for m in res_markers): + continue + yield _LockEntry( + name=normalize_packagename(str(package['name'])), + component=self.__make_component4lock(package), + dependencies=frozenset( + normalize_packagename(str(d['name'])) + for d in package.get('dependencies', ()) + if isinstance(d, dict) and 'name' in d and self.__is_marker_ok(d.get('marker')) + ), + added2bom=False, + ) + + def __make_component4lock(self, package: 'T_NameDict') -> Component: + source = package.get('source', {}) + is_local = isinstance(source, dict) and ( + 'virtual' in source or 'editable' in source or 'path' in source or 'directory' in source + ) + + version = package.get('version') + bom_ref = f'{package["name"]}@{version}' if version else str(package['name']) + + return Component( + type=ComponentType.LIBRARY, + bom_ref=bom_ref, + name=package['name'], + version=version, + external_references=self.__extrefs4lock(package), + purl=PackageURL( + type=PurlTypePypi, + name=package['name'], + version=version, + qualifiers=self.__purl_qualifiers4lock(package) + ) if not is_local else None + ) + + def __purl_qualifiers4lock(self, package: 'T_NameDict') -> 'T_NameDict': + qs: 'T_NameDict' = {} + + source = package.get('source', {}) + if not isinstance(source, dict): + return qs + + if 'registry' in source: + source_url = redact_auth_from_url(str(source.get('registry', '')).rstrip('/')) + if source_url and '://pypi.org/' not in source_url: + qs['repository_url'] = source_url + elif 'url' in source: + source_url = redact_auth_from_url(str(source.get('url', ''))) + if source_url and '://files.pythonhosted.org/' not in source_url: + qs['download_url'] = source_url + elif 'git' in source: + # best-effort: uv lock format might evolve; keep this flexible. + url = redact_auth_from_url(str(source.get('git', ''))) + rev = str(source.get('rev', source.get('reference', source.get('tag', '')))) + qs['vcs_url'] = f'git+{url}@{rev}' if rev else f'git+{url}' + + return qs + + def __extrefs4lock(self, package: 'T_NameDict') -> Generator['ExternalReference', None, None]: + if isinstance(sdist := package.get('sdist'), dict) and 'url' in sdist: + try: + yield ExternalReference( + comment='sdist', + type=ExternalReferenceType.DISTRIBUTION, + url=XsUri(redact_auth_from_url(str(sdist['url']))), + hashes=[HashType.from_composite_str(str(sdist['hash']))] if 'hash' in sdist else None + ) + except (InvalidUriException, UnknownHashTypeException) as error: # pragma: nocover + self._logger.debug('skipped sdist-extRef for: %r', package.get('name'), exc_info=error) + del error + + wheels = package.get('wheels', ()) + if isinstance(wheels, list): + for wheel in wheels: + if not isinstance(wheel, dict) or 'url' not in wheel: + continue + try: + yield ExternalReference( + comment='wheel', + type=ExternalReferenceType.DISTRIBUTION, + url=XsUri(redact_auth_from_url(str(wheel['url']))), + hashes=[HashType.from_composite_str(str(wheel['hash']))] if 'hash' in wheel else None + ) + except (InvalidUriException, UnknownHashTypeException) as error: # pragma: nocover + self._logger.debug('skipped wheel-extRef for: %r', package.get('name'), exc_info=error) + del error diff --git a/docs/usage.rst b/docs/usage.rst index 2bd433744..f12bdae6c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -348,6 +348,84 @@ The full documentation can be issued by running with ``poetry --help``: (default: silent) +For uv +------ + +**subcommand:** ``uv`` + +Support for `uv`_ dependency management and package manifest. +This requires parsing your ``pyproject.toml`` and ``uv.lock`` file which details exact pinned versions of +dependencies. +By default, dependencies from uv's default dependency groups (e.g. ``dev``) are included; +use ``--no-dev`` or ``--no-default-groups`` to exclude them. + +.. _uv: https://docs.astral.sh/uv/ + +The full documentation can be issued by running with ``uv --help``: + +.. code-block:: shell-session + + $ cyclonedx-py uv --help + usage: cyclonedx-py uv [-h] [--group ] [--no-group ] + [--only-group | --only-dev] [--all-groups] + [--no-default-groups] [--no-dev] + [-E | --all-extras] [--mc-type ] + [--short-PURLs] [--sv ] + [--output-reproducible] [--of ] [-o ] + [--validate | --no-validate] [-v] + [] + + Build an SBOM from uv project. + + This requires parsing your `pyproject.toml` and `uv.lock` file which details exact pinned versions of + dependencies. + + positional arguments: + The project directory for uv (containing + `pyproject.toml` and `uv.lock`), or a path to + `uv.lock` (default: current working directory) + + options: + -h, --help show this help message and exit + --group Include dependencies from the specified dependency + group (multiple values allowed) + --no-group Exclude dependencies from the specified dependency + group (multiple values allowed) + --only-group Only include dependencies from the specified + dependency group (multiple values allowed) + --only-dev Alias for: --only-group dev + --all-groups Include all dependency groups (default: False) + --no-default-groups Ignore the default dependency groups (default: False) + --no-dev Alias for: --no-group dev + -E, --extras + Extra sets of dependencies to include (multiple values + allowed) + --all-extras Include all extra dependencies (default: False) + --mc-type Type of the main component. {choices: application, + firmware, library} (default: application) + --short-PURLs Omit all qualifiers from PackageURLs. This causes + information loss in trade-off shorter PURLs, which + might improve ingesting these strings. + --sv, --spec-version + Which version of CycloneDX to use. {choices: 1.7, 1.6, + 1.5, 1.4, 1.3, 1.2, 1.1, 1.0} (default: 1.6) + --output-reproducible + Whether to go the extra mile and make the output + reproducible. This might result in loss of time- and + random-based values. + --of, --output-format + Which output format to use. {choices: JSON, XML} + (default: JSON) + -o, --output-file + Path to the output file. (set to "-" to output to + ) (default: -) + --validate, --no-validate + Whether to validate resulting BOM before outputting. + (default: True) + -v, --verbose Increase the verbosity of messages (multiple for more + effect) (default: silent) + + For Pip requirements -------------------- @@ -478,22 +556,6 @@ it is possible to use the functionality for Python (virtual) environments as des -For uv -------- - -Support for `uv`_ manifest and lockfile is not explicitly implemented, yet. - -However, since uv utilizes Python virtual environments under the hood, -it is possible to use the functionality for Python (virtual) environments as described above. - -.. _uv: https://docs.astral.sh/uv/ - - - -***** - - - For Conda --------- diff --git a/pyproject.toml b/pyproject.toml index 88b53aede..30925bfd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ exclude = [ keywords = [ "OWASP", "CycloneDX", "bill-of-materials", "BOM", "software-bill-of-materials", "SBOM", - "environment", "virtualenv", "venv", "Poetry", "Pipenv", "requirements", "PDM", "Conda", + "environment", "virtualenv", "venv", "Poetry", "Pipenv", "uv", "requirements", "PDM", "Conda", "SPDX", "licenses", "PURL", "package-url", "dependency-graph", ] classifiers = [ diff --git a/tests/_data/infiles/uv/via-uv/pyproject.toml b/tests/_data/infiles/uv/via-uv/pyproject.toml new file mode 100644 index 000000000..c3135950f --- /dev/null +++ b/tests/_data/infiles/uv/via-uv/pyproject.toml @@ -0,0 +1,46 @@ +[project] +# https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#declaring-project-metadata +name = "via-uv" +version = "0.1.0" +description = "environment via uv" +license = { text = "Apache-2.0 OR MIT" } +readme = "README.md" +requires-python = ">=3.8" + +# dynamic = [] # TODO + +authors = ["Your Name ", "My Name"] +maintainers = [ + "John Smith ", + "Jane Smith ", +] + +keywords = ["packaging", "pipenv", "test"] +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "License :: OSI Approved :: MIT License", + "Classifier: Development Status :: 4 - Beta", + "Intended Audience :: Developers" +] + +dependencies = [ + 'toml' +] +optional-dependencies = { 'foo' = ['ddt'] } + +# entry-point = {} # TODO + +# gui-scripts = {} # TODO +# scripts = {} # TODO + +[project.urls] +homepage = "https://oss.acme.org/my-project/" +repository = "https://oss.acme.org/my-project.git" +documentation = "https://oss.acme.org/my-project/docs/" +"Bug Tracker" = "https://oss.acme.org/my-project/bugs/" +"Funding" = "https://oss.acme.org/my-project/funding/" +"Change log" = "https://oss.acme.org/my-project/changelog/" + + +[tool.uv] +# https://docs.astral.sh/uv/reference/settings/ diff --git a/tests/_data/infiles/uv/via-uv/uv.lock b/tests/_data/infiles/uv/via-uv/uv.lock new file mode 100644 index 000000000..192664c23 --- /dev/null +++ b/tests/_data/infiles/uv/via-uv/uv.lock @@ -0,0 +1,41 @@ +version = 1 +revision = 1 +requires-python = ">=3.8" + +[[package]] +name = "ddt" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz", hash = "sha256:d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b", size = 13673 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl", hash = "sha256:6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354", size = 7065 }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, +] + +[[package]] +name = "via-uv" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "toml" }, +] + +[package.optional-dependencies] +foo = [ + { name = "ddt" }, +] + +[package.metadata] +requires-dist = [ + { name = "ddt", marker = "extra == 'foo'" }, + { name = "toml" }, +] +provides-extras = ["foo"] diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.0.xml.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.0.xml.bin new file mode 100644 index 000000000..fbb8f2699 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.0.xml.bin @@ -0,0 +1,17 @@ + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + false + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + false + + + diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.1.xml.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.1.xml.bin new file mode 100644 index 000000000..669b0112f --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.1.xml.bin @@ -0,0 +1,35 @@ + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + + + + diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.2.json.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.2.json.bin new file mode 100644 index 000000000..98a80d19f --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.2.json.bin @@ -0,0 +1,119 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "other", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "type": "application", + "version": "0.1.0" + }, + "tools": [ + { + "name": "cyclonedx-py", + "vendor": "CycloneDX", + "version": "thisVersion-testing" + }, + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "libVersion-testing" + } + ] + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.2" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.2.xml.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.2.xml.bin new file mode 100644 index 000000000..7faf48e25 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.2.xml.bin @@ -0,0 +1,91 @@ + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.3.json.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.3.json.bin new file mode 100644 index 000000000..4ce52b028 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.3.json.bin @@ -0,0 +1,169 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "other", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": [ + { + "name": "cyclonedx-py", + "vendor": "CycloneDX", + "version": "thisVersion-testing" + }, + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "libVersion-testing" + } + ] + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.3" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.3.xml.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.3.xml.bin new file mode 100644 index 000000000..2c3c87896 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.3.xml.bin @@ -0,0 +1,119 @@ + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.4.json.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.4.json.bin new file mode 100644 index 000000000..6586fc2f4 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.4.json.bin @@ -0,0 +1,204 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": [ + { + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "name": "cyclonedx-py", + "vendor": "CycloneDX", + "version": "thisVersion-testing" + }, + { + "externalReferences": [ ], + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "libVersion-testing" + } + ] + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.4" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.4.xml.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.4.xml.bin new file mode 100644 index 000000000..53deec66d --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.4.xml.bin @@ -0,0 +1,146 @@ + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.5.json.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.5.json.bin new file mode 100644 index 000000000..d48665d25 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.5.json.bin @@ -0,0 +1,218 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "thisVersion-testing" + }, + { + "description": "stripped", + "externalReferences": [ ], + "group": "CycloneDX", + "licenses": [ ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "libVersion-testing" + } + ] + } + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.5.xml.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.5.xml.bin new file mode 100644 index 000000000..9aab63096 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.5.xml.bin @@ -0,0 +1,156 @@ + + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments + + + Apache-2.0 + + + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.6.json.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.6.json.bin new file mode 100644 index 000000000..0f6a4c8c0 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.6.json.bin @@ -0,0 +1,222 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "MIT" + } + }, + { + "license": { + "acknowledgement": "declared", + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "acknowledgement": "declared", + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "thisVersion-testing" + }, + { + "description": "stripped", + "externalReferences": [ ], + "group": "CycloneDX", + "licenses": [ ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "libVersion-testing" + } + ] + } + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.6.xml.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.6.xml.bin new file mode 100644 index 000000000..d733ee4e8 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.6.xml.bin @@ -0,0 +1,156 @@ + + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments + + + Apache-2.0 + + + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.7.json.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.7.json.bin new file mode 100644 index 000000000..3ef347d66 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.7.json.bin @@ -0,0 +1,222 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "MIT" + } + }, + { + "license": { + "acknowledgement": "declared", + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "acknowledgement": "declared", + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "thisVersion-testing" + }, + { + "description": "stripped", + "externalReferences": [ ], + "group": "CycloneDX", + "licenses": [ ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "libVersion-testing" + } + ] + } + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/all-extras_via-uv_1.7.xml.bin b/tests/_data/snapshots/uv/all-extras_via-uv_1.7.xml.bin new file mode 100644 index 000000000..6b4b22a49 --- /dev/null +++ b/tests/_data/snapshots/uv/all-extras_via-uv_1.7.xml.bin @@ -0,0 +1,156 @@ + + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments + + + Apache-2.0 + + + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.0.xml.bin b/tests/_data/snapshots/uv/plain_via-uv_1.0.xml.bin new file mode 100644 index 000000000..3c181bc65 --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.0.xml.bin @@ -0,0 +1,11 @@ + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + false + + + diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.1.xml.bin b/tests/_data/snapshots/uv/plain_via-uv_1.1.xml.bin new file mode 100644 index 000000000..b66b328cb --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.1.xml.bin @@ -0,0 +1,20 @@ + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + + + + diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.2.json.bin b/tests/_data/snapshots/uv/plain_via-uv_1.2.json.bin new file mode 100644 index 000000000..19292c30e --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.2.json.bin @@ -0,0 +1,96 @@ +{ + "components": [ + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "dependsOn": [ + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "other", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "type": "application", + "version": "0.1.0" + }, + "tools": [ + { + "name": "cyclonedx-py", + "vendor": "CycloneDX", + "version": "thisVersion-testing" + }, + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "libVersion-testing" + } + ] + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.2" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.2.xml.bin b/tests/_data/snapshots/uv/plain_via-uv_1.2.xml.bin new file mode 100644 index 000000000..8501e19d0 --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.2.xml.bin @@ -0,0 +1,74 @@ + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.3.json.bin b/tests/_data/snapshots/uv/plain_via-uv_1.3.json.bin new file mode 100644 index 000000000..b90b0240b --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.3.json.bin @@ -0,0 +1,128 @@ +{ + "components": [ + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "dependsOn": [ + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "other", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": [ + { + "name": "cyclonedx-py", + "vendor": "CycloneDX", + "version": "thisVersion-testing" + }, + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "libVersion-testing" + } + ] + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.3" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.3.xml.bin b/tests/_data/snapshots/uv/plain_via-uv_1.3.xml.bin new file mode 100644 index 000000000..205643b35 --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.3.xml.bin @@ -0,0 +1,93 @@ + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.4.json.bin b/tests/_data/snapshots/uv/plain_via-uv_1.4.json.bin new file mode 100644 index 000000000..e252d62b3 --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.4.json.bin @@ -0,0 +1,163 @@ +{ + "components": [ + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "dependsOn": [ + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": [ + { + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "name": "cyclonedx-py", + "vendor": "CycloneDX", + "version": "thisVersion-testing" + }, + { + "externalReferences": [ ], + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "libVersion-testing" + } + ] + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.4" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.4.xml.bin b/tests/_data/snapshots/uv/plain_via-uv_1.4.xml.bin new file mode 100644 index 000000000..a9f92f6b5 --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.4.xml.bin @@ -0,0 +1,120 @@ + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.5.json.bin b/tests/_data/snapshots/uv/plain_via-uv_1.5.json.bin new file mode 100644 index 000000000..2049fb98b --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.5.json.bin @@ -0,0 +1,177 @@ +{ + "components": [ + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "dependsOn": [ + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "thisVersion-testing" + }, + { + "description": "stripped", + "externalReferences": [ ], + "group": "CycloneDX", + "licenses": [ ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "libVersion-testing" + } + ] + } + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.5.xml.bin b/tests/_data/snapshots/uv/plain_via-uv_1.5.xml.bin new file mode 100644 index 000000000..df850b052 --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.5.xml.bin @@ -0,0 +1,130 @@ + + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments + + + Apache-2.0 + + + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.6.json.bin b/tests/_data/snapshots/uv/plain_via-uv_1.6.json.bin new file mode 100644 index 000000000..b80b6543e --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.6.json.bin @@ -0,0 +1,181 @@ +{ + "components": [ + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "dependsOn": [ + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "MIT" + } + }, + { + "license": { + "acknowledgement": "declared", + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "acknowledgement": "declared", + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "thisVersion-testing" + }, + { + "description": "stripped", + "externalReferences": [ ], + "group": "CycloneDX", + "licenses": [ ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "libVersion-testing" + } + ] + } + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.6.xml.bin b/tests/_data/snapshots/uv/plain_via-uv_1.6.xml.bin new file mode 100644 index 000000000..4a480af97 --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.6.xml.bin @@ -0,0 +1,130 @@ + + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments + + + Apache-2.0 + + + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.7.json.bin b/tests/_data/snapshots/uv/plain_via-uv_1.7.json.bin new file mode 100644 index 000000000..7caccaf07 --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.7.json.bin @@ -0,0 +1,181 @@ +{ + "components": [ + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "dependsOn": [ + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "MIT" + } + }, + { + "license": { + "acknowledgement": "declared", + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "acknowledgement": "declared", + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "thisVersion-testing" + }, + { + "description": "stripped", + "externalReferences": [ ], + "group": "CycloneDX", + "licenses": [ ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "libVersion-testing" + } + ] + } + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/plain_via-uv_1.7.xml.bin b/tests/_data/snapshots/uv/plain_via-uv_1.7.xml.bin new file mode 100644 index 000000000..5fec3e8e3 --- /dev/null +++ b/tests/_data/snapshots/uv/plain_via-uv_1.7.xml.bin @@ -0,0 +1,130 @@ + + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments + + + Apache-2.0 + + + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.0.xml.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.0.xml.bin new file mode 100644 index 000000000..fbb8f2699 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.0.xml.bin @@ -0,0 +1,17 @@ + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + false + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + false + + + diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.1.xml.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.1.xml.bin new file mode 100644 index 000000000..669b0112f --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.1.xml.bin @@ -0,0 +1,35 @@ + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + + + + diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.2.json.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.2.json.bin new file mode 100644 index 000000000..98a80d19f --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.2.json.bin @@ -0,0 +1,119 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "other", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "type": "application", + "version": "0.1.0" + }, + "tools": [ + { + "name": "cyclonedx-py", + "vendor": "CycloneDX", + "version": "thisVersion-testing" + }, + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "libVersion-testing" + } + ] + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.2" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.2.xml.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.2.xml.bin new file mode 100644 index 000000000..7faf48e25 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.2.xml.bin @@ -0,0 +1,91 @@ + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.3.json.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.3.json.bin new file mode 100644 index 000000000..4ce52b028 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.3.json.bin @@ -0,0 +1,169 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "other", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": [ + { + "name": "cyclonedx-py", + "vendor": "CycloneDX", + "version": "thisVersion-testing" + }, + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "libVersion-testing" + } + ] + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.3" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.3.xml.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.3.xml.bin new file mode 100644 index 000000000..2c3c87896 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.3.xml.bin @@ -0,0 +1,119 @@ + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.4.json.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.4.json.bin new file mode 100644 index 000000000..6586fc2f4 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.4.json.bin @@ -0,0 +1,204 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": [ + { + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "name": "cyclonedx-py", + "vendor": "CycloneDX", + "version": "thisVersion-testing" + }, + { + "externalReferences": [ ], + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "libVersion-testing" + } + ] + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.4" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.4.xml.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.4.xml.bin new file mode 100644 index 000000000..53deec66d --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.4.xml.bin @@ -0,0 +1,146 @@ + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.5.json.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.5.json.bin new file mode 100644 index 000000000..d48665d25 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.5.json.bin @@ -0,0 +1,218 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "thisVersion-testing" + }, + { + "description": "stripped", + "externalReferences": [ ], + "group": "CycloneDX", + "licenses": [ ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "libVersion-testing" + } + ] + } + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.5.xml.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.5.xml.bin new file mode 100644 index 000000000..9aab63096 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.5.xml.bin @@ -0,0 +1,156 @@ + + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments + + + Apache-2.0 + + + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.6.json.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.6.json.bin new file mode 100644 index 000000000..0f6a4c8c0 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.6.json.bin @@ -0,0 +1,222 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "MIT" + } + }, + { + "license": { + "acknowledgement": "declared", + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "acknowledgement": "declared", + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "thisVersion-testing" + }, + { + "description": "stripped", + "externalReferences": [ ], + "group": "CycloneDX", + "licenses": [ ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "libVersion-testing" + } + ] + } + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.6.xml.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.6.xml.bin new file mode 100644 index 000000000..d733ee4e8 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.6.xml.bin @@ -0,0 +1,156 @@ + + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments + + + Apache-2.0 + + + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.7.json.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.7.json.bin new file mode 100644 index 000000000..3ef347d66 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.7.json.bin @@ -0,0 +1,222 @@ +{ + "components": [ + { + "bom-ref": "ddt@1.7.2", + "externalReferences": [ + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz" + }, + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl" + } + ], + "name": "ddt", + "purl": "pkg:pypi/ddt@1.7.2", + "type": "library", + "version": "1.7.2" + }, + { + "bom-ref": "toml@0.10.2", + "externalReferences": [ + { + "comment": "wheel", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl" + }, + { + "comment": "sdist", + "hashes": [ + { + "alg": "SHA-256", + "content": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ], + "type": "distribution", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz" + } + ], + "name": "toml", + "purl": "pkg:pypi/toml@0.10.2", + "type": "library", + "version": "0.10.2" + } + ], + "dependencies": [ + { + "ref": "ddt@1.7.2" + }, + { + "dependsOn": [ + "ddt@1.7.2", + "toml@0.10.2" + ], + "ref": "root-component" + }, + { + "ref": "toml@0.10.2" + } + ], + "metadata": { + "component": { + "bom-ref": "root-component", + "description": "environment via uv", + "evidence": { + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "MIT" + } + }, + { + "license": { + "acknowledgement": "declared", + "name": "License :: OSI Approved :: Apache Software License" + } + } + ] + }, + "externalReferences": [ + { + "comment": "from pyproject urls: documentation", + "type": "documentation", + "url": "https://oss.acme.org/my-project/docs/" + }, + { + "comment": "from pyproject urls: Bug Tracker", + "type": "issue-tracker", + "url": "https://oss.acme.org/my-project/bugs/" + }, + { + "comment": "from pyproject urls: Funding", + "type": "other", + "url": "https://oss.acme.org/my-project/funding/" + }, + { + "comment": "from pyproject urls: Change log", + "type": "release-notes", + "url": "https://oss.acme.org/my-project/changelog/" + }, + { + "comment": "from pyproject urls: repository", + "type": "vcs", + "url": "https://oss.acme.org/my-project.git" + }, + { + "comment": "from pyproject urls: homepage", + "type": "website", + "url": "https://oss.acme.org/my-project/" + } + ], + "licenses": [ + { + "acknowledgement": "declared", + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "via-uv", + "properties": [ + { + "name": "cdx:python:package:required-extra", + "value": "foo" + } + ], + "type": "application", + "version": "0.1.0" + }, + "properties": [ + { + "name": "cdx:reproducible", + "value": "true" + } + ], + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "acknowledgement": "declared", + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "thisVersion-testing" + }, + { + "description": "stripped", + "externalReferences": [ ], + "group": "CycloneDX", + "licenses": [ ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "libVersion-testing" + } + ] + } + }, + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7" +} \ No newline at end of file diff --git a/tests/_data/snapshots/uv/some-extras_via-uv_1.7.xml.bin b/tests/_data/snapshots/uv/some-extras_via-uv_1.7.xml.bin new file mode 100644 index 000000000..6b4b22a49 --- /dev/null +++ b/tests/_data/snapshots/uv/some-extras_via-uv_1.7.xml.bin @@ -0,0 +1,156 @@ + + + + + + + CycloneDX + cyclonedx-py + thisVersion-testing + CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments + + + Apache-2.0 + + + + + https://github.com/CycloneDX/cyclonedx-python/actions + + + https://pypi.org/project/cyclonedx-bom/ + + + https://cyclonedx-bom-tool.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python/issues + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python/ + + + https://github.com/CycloneDX/cyclonedx-python/#readme + + + + + CycloneDX + cyclonedx-python-lib + libVersion-testing + + + + + + + + via-uv + 0.1.0 + environment via uv + + Apache-2.0 OR MIT + + + + https://oss.acme.org/my-project/docs/ + from pyproject urls: documentation + + + https://oss.acme.org/my-project/bugs/ + from pyproject urls: Bug Tracker + + + https://oss.acme.org/my-project/funding/ + from pyproject urls: Funding + + + https://oss.acme.org/my-project/changelog/ + from pyproject urls: Change log + + + https://oss.acme.org/my-project.git + from pyproject urls: repository + + + https://oss.acme.org/my-project/ + from pyproject urls: homepage + + + + foo + + + + + MIT + + + License :: OSI Approved :: Apache Software License + + + + + + true + + + + + ddt + 1.7.2 + pkg:pypi/ddt@1.7.2 + + + https://files.pythonhosted.org/packages/51/d4/bdea45c5c1f1f0ae55844d841101b00905c9863ee1004da37d911253abb2/ddt-1.7.2.tar.gz + sdist + + d215d6b083963013c4a19b1e4dcd6a96e80e43ab77519597a6acfcf2e9a3e04b + + + + https://files.pythonhosted.org/packages/61/7c/38d1aec205833096eddefcbb3492fbb2c886e74174c72bc160da9522b2f0/ddt-1.7.2-py2.py3-none-any.whl + wheel + + 6adcfaf9785f0a36f9e73a89b91e412de9ef8649e289b750e3683bc79d5e2354 + + + + + + toml + 0.10.2 + pkg:pypi/toml@0.10.2 + + + https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + wheel + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz + sdist + + b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + + + + + + + + + + + + + + diff --git a/tests/integration/test_cli_uv.py b/tests/integration/test_cli_uv.py new file mode 100644 index 000000000..2f1bafede --- /dev/null +++ b/tests/integration/test_cli_uv.py @@ -0,0 +1,130 @@ +# 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. + +import random +from collections.abc import Generator +from glob import glob +from os.path import basename, dirname, join +from typing import Any +from unittest import TestCase + +from cyclonedx.schema import OutputFormat, SchemaVersion +from ddt import ddt, named_data + +from tests import INFILES_DIRECTORY, SUPPORTED_OF_SV, SnapshotMixin, make_comparable +from tests.integration import run_cli + +lockfiles = glob(join(INFILES_DIRECTORY, 'uv', '*', 'uv.lock')) +projectdirs = list(dirname(lockfile) for lockfile in lockfiles) + +test_data = tuple( + (f'{basename(projectdir)}-{sv.name}-{of.name}', projectdir, sv, of) + for projectdir in projectdirs + for of, sv in SUPPORTED_OF_SV +) + + +def test_data_file_filter(s: str) -> Generator[Any, None, None]: + return ((n, d, sv, of) for n, d, sv, of in test_data if s in n) + + +@ddt +class TestCliUv(TestCase, SnapshotMixin): + + def test_help(self) -> None: + res, out, err = run_cli('uv', '--help') + self.assertEqual(0, res, '\n'.join((out, err))) + + def test_fails_with_dir_not_found(self) -> None: + _, projectdir, sv, of = random.choice(test_data) # nosec B311 + res, out, err = run_cli( + 'uv', + '-vvv', + '--sv', sv.to_version(), + '--of', of.name, + '-o=-', + 'something-that-must-not-exist.testing') + self.assertNotEqual(0, res, err) + self.assertIn('Could not open pyproject file: something-that-must-not-exist.testing', err) + + def test_fails_with_extras_not_found(self) -> None: + projectdir = random.choice(projectdirs) # nosec B311 + res, out, err = run_cli( + 'uv', + '-vvv', + '-E', 'MNE-extra-C,MNE-extra-B', + '--extras', 'MNE-extra-A', + projectdir) + self.assertNotEqual(0, res, err) + self.assertIn('Extra(s) [' + # extra names were normalized! + 'mne-extra-a,' + 'mne-extra-b,' + 'mne-extra-c' + '] not specified', err) + + @named_data(*test_data) + def test_plain_as_expected(self, projectdir: str, sv: SchemaVersion, of: OutputFormat) -> None: + res, out, err = run_cli( + 'uv', + '-vvv', + '--sv', sv.to_version(), + '--of', of.name, + '--output-reproducible', + '-o=-', + projectdir) + self.assertEqual(0, res, err) + self.assertEqualSnapshot(out, 'plain', projectdir, sv, of) + + @named_data(*test_data_file_filter('via-uv')) + def test_with_extras_as_expected(self, projectdir: str, sv: SchemaVersion, of: OutputFormat) -> None: + res, out, err = run_cli( + 'uv', + '-vvv', + '-E', 'FOO', # expected to be normalized + '--sv', sv.to_version(), + '--of', of.name, + '--output-reproducible', + '-o=-', + projectdir) + self.assertEqual(0, res, err) + self.assertEqualSnapshot(out, 'some-extras', projectdir, sv, of) + + @named_data(*test_data_file_filter('via-uv')) + def test_with_all_extras_as_expected(self, projectdir: str, sv: SchemaVersion, of: OutputFormat) -> None: + res, out, err = run_cli( + 'uv', + '-vvv', + '--all-extras', + '--sv', sv.to_version(), + '--of', of.name, + '--output-reproducible', + '-o=-', + projectdir) + self.assertEqual(0, res, err) + self.assertEqualSnapshot(out, 'all-extras', projectdir, sv, of) + + def assertEqualSnapshot(self, actual: str, # noqa:N802 + purpose: str, + projectdir: str, + sv: SchemaVersion, + of: OutputFormat + ) -> None: + super().assertEqualSnapshot( + make_comparable(actual, of), + join('uv', f'{purpose}_{basename(projectdir)}_{sv.to_version()}.{of.name.lower()}') + ) diff --git a/tests/unit/test_uv.py b/tests/unit/test_uv.py new file mode 100644 index 000000000..e56345eff --- /dev/null +++ b/tests/unit/test_uv.py @@ -0,0 +1,245 @@ +# 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. + +import logging +from os.path import join +from tempfile import TemporaryDirectory +from unittest import TestCase + +from cyclonedx.model.component import ComponentType + +from cyclonedx_py._internal.uv import UvBB + + +class TestUv(TestCase): + + def test_filters_resolution_and_dependency_markers(self) -> None: + with TemporaryDirectory() as project_dir: + pyproject = ( + '[project]\n' + 'name = "my-project"\n' + 'version = "0.1.0"\n' + 'dependencies = ["anyio"]\n' + ) + uv_lock = ( + 'version = 1\n' + 'resolution-markers = [\n' + ' "python_full_version >= \'0\'",\n' + ' "python_full_version < \'0\'",\n' + ']\n' + '\n' + '[[package]]\n' + 'name = "anyio"\n' + 'version = "1.0.0"\n' + 'dependencies = [\n' + ' { name = "bar" },\n' + ' { name = "foo", marker = "python_full_version < \'0\'" },\n' + ']\n' + '\n' + '[[package]]\n' + 'name = "bar"\n' + 'version = "1.0.0"\n' + 'resolution-markers = ["python_full_version >= \'0\'"]\n' + '\n' + '[[package]]\n' + 'name = "bar"\n' + 'version = "2.0.0"\n' + 'resolution-markers = ["python_full_version < \'0\'"]\n' + '\n' + '[[package]]\n' + 'name = "foo"\n' + 'version = "1.0.0"\n' + ) + with open(join(project_dir, 'pyproject.toml'), 'w', encoding='utf8', newline='\n') as fh: + fh.write(pyproject) + with open(join(project_dir, 'uv.lock'), 'w', encoding='utf8', newline='\n') as fh: + fh.write(uv_lock) + + bom = UvBB(logger=logging.getLogger(__name__))( + project_directory=join(project_dir, 'uv.lock'), + groups_with=[], + groups_without=[], + groups_only=[], + only_dev=False, + all_groups=False, + no_default_groups=False, + no_dev=False, + extras=[], + all_extras=False, + mc_type=ComponentType.APPLICATION, + ) + + components = {(c.name, c.version) for c in bom.components} + self.assertIn(('anyio', '1.0.0'), components) + self.assertIn(('bar', '1.0.0'), components) + self.assertNotIn(('bar', '2.0.0'), components) + self.assertNotIn(('foo', '1.0.0'), components) + + def test_fails_when_resolution_markers_do_not_match_environment(self) -> None: + with TemporaryDirectory() as project_dir: + pyproject = ( + '[project]\n' + 'name = "my-project"\n' + 'version = "0.1.0"\n' + 'dependencies = ["anyio"]\n' + ) + uv_lock = ( + 'version = 1\n' + 'resolution-markers = ["python_full_version < \'0\'"]\n' + '\n' + '[[package]]\n' + 'name = "anyio"\n' + 'version = "1.0.0"\n' + ) + with open(join(project_dir, 'pyproject.toml'), 'w', encoding='utf8', newline='\n') as fh: + fh.write(pyproject) + with open(join(project_dir, 'uv.lock'), 'w', encoding='utf8', newline='\n') as fh: + fh.write(uv_lock) + + with self.assertRaisesRegex(ValueError, 'resolution-markers'): + UvBB(logger=logging.getLogger(__name__))( + project_directory=project_dir, + groups_with=[], + groups_without=[], + groups_only=[], + only_dev=False, + all_groups=False, + no_default_groups=False, + no_dev=False, + extras=[], + all_extras=False, + mc_type=ComponentType.APPLICATION, + ) + + def test_dependency_groups(self) -> None: + with TemporaryDirectory() as project_dir: + pyproject = ( + '[project]\n' + 'name = "my-project"\n' + 'version = "0.1.0"\n' + 'dependencies = ["requests"]\n' + '\n' + '[dependency-groups]\n' + 'dev = [\n' + ' "pytest",\n' + ' "mkdocstrings[python]",\n' + ']\n' + 'docs = ["sphinx"]\n' + ) + uv_lock = ( + 'version = 1\n' + '\n' + '[[package]]\n' + 'name = "requests"\n' + 'version = "1.0.0"\n' + '\n' + '[[package]]\n' + 'name = "pytest"\n' + 'version = "2.0.0"\n' + '\n' + '[[package]]\n' + 'name = "mkdocstrings"\n' + 'version = "3.0.0"\n' + '\n' + '[[package]]\n' + 'name = "sphinx"\n' + 'version = "4.0.0"\n' + '\n' + '[[package]]\n' + 'name = "my-project"\n' + 'version = "0.1.0"\n' + 'source = { editable = "." }\n' + 'dependencies = [\n' + ' { name = "requests" },\n' + ']\n' + '\n' + '[package.dev-dependencies]\n' + 'dev = [\n' + ' { name = "pytest" },\n' + ' { name = "mkdocstrings", extra = ["python"] },\n' + ']\n' + ) + with open(join(project_dir, 'pyproject.toml'), 'w', encoding='utf8', newline='\n') as fh: + fh.write(pyproject) + with open(join(project_dir, 'uv.lock'), 'w', encoding='utf8', newline='\n') as fh: + fh.write(uv_lock) + + components_default = {(c.name, c.version) for c in UvBB(logger=logging.getLogger(__name__))( + project_directory=project_dir, + groups_with=[], + groups_without=[], + groups_only=[], + only_dev=False, + all_groups=False, + no_default_groups=False, + no_dev=False, + extras=[], + all_extras=False, + mc_type=ComponentType.APPLICATION, + ).components} + self.assertIn(('requests', '1.0.0'), components_default) + self.assertIn(('pytest', '2.0.0'), components_default) + self.assertIn(('mkdocstrings', '3.0.0'), components_default) + self.assertNotIn(('sphinx', '4.0.0'), components_default) + + components_no_dev = {(c.name, c.version) for c in UvBB(logger=logging.getLogger(__name__))( + project_directory=project_dir, + groups_with=[], + groups_without=[], + groups_only=[], + only_dev=False, + all_groups=False, + no_default_groups=False, + no_dev=True, + extras=[], + all_extras=False, + mc_type=ComponentType.APPLICATION, + ).components} + self.assertIn(('requests', '1.0.0'), components_no_dev) + self.assertNotIn(('pytest', '2.0.0'), components_no_dev) + + components_docs_only = {(c.name, c.version) for c in UvBB(logger=logging.getLogger(__name__))( + project_directory=project_dir, + groups_with=[], + groups_without=[], + groups_only=['docs'], + only_dev=False, + all_groups=False, + no_default_groups=False, + no_dev=False, + extras=[], + all_extras=False, + mc_type=ComponentType.APPLICATION, + ).components} + self.assertIn(('sphinx', '4.0.0'), components_docs_only) + self.assertNotIn(('requests', '1.0.0'), components_docs_only) + + components_only_dev = {(c.name, c.version) for c in UvBB(logger=logging.getLogger(__name__))( + project_directory=project_dir, + groups_with=[], + groups_without=[], + groups_only=[], + only_dev=True, + all_groups=False, + no_default_groups=False, + no_dev=False, + extras=[], + all_extras=False, + mc_type=ComponentType.APPLICATION, + ).components} + self.assertIn(('pytest', '2.0.0'), components_only_dev) + self.assertNotIn(('requests', '1.0.0'), components_only_dev)