diff --git a/composer.json b/composer.json index b7088742c..c699dadc0 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "amphp/parallel-functions": "^1.1", "composer/semver": "^3.3.2", "composer/xdebug-handler": "^3.0.3", + "cyclonedx/cyclonedx-php-composer": "^4.2", "fidry/console": "^0.5.3 || ^0.6.0", "fidry/filesystem": "^1.1", "humbug/php-scoper": "^0.18.3", @@ -98,7 +99,8 @@ "allow-plugins": { "bamarni/composer-bin-plugin": true, "composer/package-versions-deprecated": false, - "ergebnis/composer-normalize": true + "ergebnis/composer-normalize": true, + "cyclonedx/cyclonedx-php-composer": false }, "platform": { "php": "8.1" diff --git a/composer.lock b/composer.lock index fd58ec4bd..d6221849e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9d93c5fcbe62d3a1cc7a1ca2dd1fdfcd", + "content-hash": "01afbefcfcbefc849c84f2b2cff00e7b", "packages": [ { "name": "amphp/amp", @@ -660,6 +660,86 @@ ], "time": "2022-04-01T19:23:25+00:00" }, + { + "name": "composer/spdx-licenses", + "version": "1.5.7", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "c848241796da2abf65837d51dce1fae55a960149" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149", + "reference": "c848241796da2abf65837d51dce1fae55a960149", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.7" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-05-23T07:37:50+00:00" + }, { "name": "composer/xdebug-handler", "version": "3.0.3", @@ -726,6 +806,176 @@ ], "time": "2022-02-25T21:32:43+00:00" }, + { + "name": "cyclonedx/cyclonedx-library", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/CycloneDX/cyclonedx-php-library.git", + "reference": "6819a286e7b8e8514e511dd1bbd9a88d3e45bf8f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-library/zipball/6819a286e7b8e8514e511dd1bbd9a88d3e45bf8f", + "reference": "6819a286e7b8e8514e511dd1bbd9a88d3e45bf8f", + "shasum": "" + }, + "require": { + "composer/spdx-licenses": "^1.5", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "opis/json-schema": "^2.0", + "package-url/packageurl-php": "^1.0", + "php": "^8.1" + }, + "require-dev": { + "ext-simplexml": "*", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + }, + "composer-normalize": { + "indent-size": 4, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "CycloneDX\\Core\\": "src/Core/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Jan Kowalleck", + "email": "jan.kowalleck@gmail.com", + "homepage": "https://github.com/jkowalleck" + } + ], + "description": "Work with Bill-of-Materials (BOM) in CycloneDX format.", + "homepage": "https://github.com/CycloneDX/cyclonedx-php-library/#readme", + "keywords": [ + "CycloneDX", + "MBOM", + "OBOM", + "SBOM", + "SaaSBOM", + "bill-of-materials", + "bom", + "models", + "normalizer", + "owasp", + "package-url", + "purl", + "serializer", + "software-bill-of-materials", + "spdx", + "validator", + "vex" + ], + "support": { + "issues": "https://github.com/CycloneDX/cyclonedx-php-library/issues", + "source": "https://github.com/CycloneDX/cyclonedx-php-library/" + }, + "funding": [ + { + "url": "https://github.com/sponsors/jkowalleck", + "type": "github" + }, + { + "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX", + "type": "other" + } + ], + "time": "2023-09-16T16:42:36+00:00" + }, + { + "name": "cyclonedx/cyclonedx-php-composer", + "version": "v4.2.0", + "source": { + "type": "git", + "url": "https://github.com/CycloneDX/cyclonedx-php-composer.git", + "reference": "3078cdca96914cdeea2ec28c0b9313db74c94927" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-composer/zipball/3078cdca96914cdeea2ec28c0b9313db74c94927", + "reference": "3078cdca96914cdeea2ec28c0b9313db74c94927", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.3", + "cyclonedx/cyclonedx-library": "^2.3 || ^3.0", + "package-url/packageurl-php": "^1.0", + "php": "^8.1" + }, + "require-dev": { + "composer/composer": "^2.3.0", + "roave/security-advisories": "dev-latest" + }, + "type": "composer-plugin", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + }, + "class": "CycloneDX\\Composer\\Plugin", + "composer-normalize": { + "indent-size": 4, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "CycloneDX\\Composer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Jan Kowalleck", + "email": "jan.kowalleck@gmail.com", + "homepage": "https://github.com/jkowalleck" + } + ], + "description": "Creates CycloneDX Software Bill-of-Materials (SBOM) from PHP Composer projects", + "homepage": "https://github.com/CycloneDX/cyclonedx-php-composer/#readme", + "keywords": [ + "CycloneDX", + "SBOM", + "bill-of-materials", + "bom", + "composer", + "package-url", + "purl", + "software-bill-of-materials", + "spdx" + ], + "support": { + "issues": "https://github.com/CycloneDX/cyclonedx-php-composer/issues", + "source": "https://github.com/CycloneDX/cyclonedx-php-composer/" + }, + "funding": [ + { + "url": "https://github.com/sponsors/jkowalleck", + "type": "github" + }, + { + "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX", + "type": "other" + } + ], + "time": "2023-09-04T07:48:20+00:00" + }, { "name": "doctrine/deprecations", "version": "v1.0.0", @@ -1283,6 +1533,262 @@ }, "time": "2022-11-12T15:38:23+00:00" }, + { + "name": "opis/json-schema", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/opis/json-schema.git", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/json-schema/zipball/c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "opis/string": "^2.0", + "opis/uri": "^1.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ext-bcmath": "*", + "ext-intl": "*", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\JsonSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + }, + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + } + ], + "description": "Json Schema Validator for PHP", + "homepage": "https://opis.io/json-schema", + "keywords": [ + "json", + "json-schema", + "schema", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/opis/json-schema/issues", + "source": "https://github.com/opis/json-schema/tree/2.3.0" + }, + "time": "2022-01-08T20:38:03+00:00" + }, + { + "name": "opis/string", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/opis/string.git", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/string/zipball/9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\String\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Multibyte strings as objects", + "homepage": "https://opis.io/string", + "keywords": [ + "multi-byte", + "opis", + "string", + "string manipulation", + "utf-8" + ], + "support": { + "issues": "https://github.com/opis/string/issues", + "source": "https://github.com/opis/string/tree/2.0.1" + }, + "time": "2022-01-14T15:42:23+00:00" + }, + { + "name": "opis/uri", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opis/uri.git", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "shasum": "" + }, + "require": { + "opis/string": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Build, parse and validate URIs and URI-templates", + "homepage": "https://opis.io", + "keywords": [ + "URI Template", + "parse url", + "punycode", + "uri", + "uri components", + "url", + "validate uri" + ], + "support": { + "issues": "https://github.com/opis/uri/issues", + "source": "https://github.com/opis/uri/tree/1.1.0" + }, + "time": "2021-05-22T15:57:08+00:00" + }, + { + "name": "package-url/packageurl-php", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/package-url/packageurl-php.git", + "reference": "ba6e2761f48a0599b66209ed1296b689b89b32ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/package-url/packageurl-php/zipball/ba6e2761f48a0599b66209ed1296b689b89b32ec", + "reference": "ba6e2761f48a0599b66209ed1296b689b89b32ec", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "ext-json": "*", + "phpunit/phpunit": "9.6.5", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "composer-normalize": { + "indent-size": 4, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "PackageUrl\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Kowalleck", + "email": "jan.kowalleck@gmail.com", + "homepage": "https://github.com/jkowalleck" + } + ], + "description": "Builder and parser based on the package URL (purl) specification.", + "homepage": "https://github.com/package-url/packageurl-php#readme", + "keywords": [ + "package", + "package-url", + "packageurl", + "purl", + "url" + ], + "support": { + "issues": "https://github.com/package-url/packageurl-php/issues", + "source": "https://github.com/package-url/packageurl-php/tree/1.0.6" + }, + "funding": [ + { + "url": "https://github.com/sponsors/jkowalleck", + "type": "github" + } + ], + "time": "2023-03-18T08:35:25+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v2.6.0", diff --git a/src/Console/Command/Compile.php b/src/Console/Command/Compile.php index 5958b249a..b0119e66c 100644 --- a/src/Console/Command/Compile.php +++ b/src/Console/Command/Compile.php @@ -35,6 +35,7 @@ use KevinGH\Box\Configuration\Configuration; use KevinGH\Box\Console\Logger\CompilerLogger; use KevinGH\Box\Console\MessageRenderer; +use KevinGH\Box\Cyclonedx\ManifestFactory; use KevinGH\Box\MapFile; use KevinGH\Box\Phar\CompressionAlgorithm; use KevinGH\Box\RequirementChecker\DecodedComposerJson; @@ -64,6 +65,7 @@ use function KevinGH\Box\disable_parallel_processing; use function KevinGH\Box\format_size; use function KevinGH\Box\format_time; +use function KevinGH\Box\get_box_version; use function memory_get_peak_usage; use function memory_get_usage; use function microtime; @@ -267,6 +269,7 @@ private function createPhar( $main = self::registerMainScript($config, $box, $logger); $check = self::registerRequirementsChecker($config, $box, $logger); + self::addSbom($config, $box, $logger); self::addFiles($config, $box, $logger, $io); @@ -579,6 +582,36 @@ private static function registerRequirementsChecker(Configuration $config, Box $ return true; } + private static function addSbom(Configuration $config, Box $box, CompilerLogger $logger): void + { + // TODO: add base on the config +// if (false === $config->checkRequirements()) { +// $logger->log( +// CompilerLogger::QUESTION_MARK_PREFIX, +// 'Skip requirements checker', +// ); +// +// return false; +// } + + $logger->log( + CompilerLogger::QUESTION_MARK_PREFIX, + 'Adding the SBOM', + ); + + $manifestFactory = ManifestFactory::create( + // TODO: check if we don't already get the version somewhere else... Might be worth caching it otherwise + get_box_version(), + ); + + $sbom = $manifestFactory->generate( + $config->getDecodedComposerJsonContents() ?? [], + $config->getDecodedComposerLockContents() ?? [], + ); + + $box->addFile('.box/sbom.json', $sbom, true); + } + private static function registerStub( Configuration $config, Box $box, diff --git a/src/Cyclonedx/ManifestFactory.php b/src/Cyclonedx/ManifestFactory.php new file mode 100644 index 000000000..881df886a --- /dev/null +++ b/src/Cyclonedx/ManifestFactory.php @@ -0,0 +1,264 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Cyclonedx; + +use CycloneDX\Core\Collections\ComponentRepository; +use CycloneDX\Core\Enums\ComponentType; +use CycloneDX\Core\Factories\LicenseFactory; +use CycloneDX\Core\Models\Bom; +use CycloneDX\Core\Models\Component; +use CycloneDX\Core\Models\Property; +use CycloneDX\Core\Models\Tool; +use CycloneDX\Core\Serialization\JSON\NormalizerFactory as JsonNormalizerFactory; +use CycloneDX\Core\Serialization\JsonSerializer; +use CycloneDX\Core\Serialization\Serializer; +use CycloneDX\Core\Spec\_SpecProtocol; +use CycloneDX\Core\Spec\SpecFactory; +use CycloneDX\Core\Spec\Version; +use CycloneDX\Core\Utils\BomUtility; +use DateTimeImmutable; +use DomainException; +use KevinGH\Box\RequirementChecker\DecodedComposerJson; +use PackageUrl\PackageUrl; +use ValueError; +use function array_column; +use function count; +use function explode; +use function implode; +use function sprintf; + +/** + * MIT License. + * + * Copyright (c) 2022-2023 Laurent Laville + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @author Laurent Laville + * @since Release 3.0.0 + */ +final class ManifestFactory +{ + // https://github.com/CycloneDX/specification/tree/master#media-types + private const SBOM_FORMAT = 'json'; + // https://github.com/CycloneDX/specification/tree/master#release-history + private const SBOM_VERSION = '1.5'; + + public static function create( + string $boxVersion, + string $sbomVersion = self::SBOM_VERSION, + string $sbomFormat = self::SBOM_FORMAT, + ): self { + $version = self::getVersion($sbomVersion); + $spec = SpecFactory::makeForVersion($version); + $serializer = new JsonSerializer( + new JsonNormalizerFactory($spec), + ); + + return new self( + $version, + $sbomFormat, + $serializer, + $boxVersion, + ); + } + + public function __construct( + private Version $version, + private string $format, + private Serializer $serializer, + private string $boxVersion, + ) { + } + + public function generate( + array $composerJsonDecodedContent, + array $installedPhpDecodedContent, + ): string { + dd($installedPhpDecodedContent); + $rootPackage = $installedPhpDecodedContent['root']; + $version = self::getRootPackageVersion($rootPackage); + + $bom = new Bom(); + // TODO: this is an issue for reproducible builds + $bom->setSerialNumber(BomUtility::randomSerialNumber()); + + $component = self::createComponent( + $rootPackage, + $version, + $composerJsonDecodedContent['description'] ?? '', + ); + + $packageUrl = new PackageUrl('composer', $rootPackage['name']); + $packageUrl->setVersion($version); + $component->setPackageUrl($packageUrl); + $component->setBomRefValue((string) $packageUrl); + + // scope + if (isset($composerJsonDecodedContent['license'])) { + $licenseFactory = new LicenseFactory(); + + if (!empty($composerJsonDecodedContent['license'])) { + $component->getLicenses()->addItems( + $licenseFactory->makeFromString($composerJsonDecodedContent['license']) + ); + } + } + + // metadata + // TODO: check if this is necessary? + $boxTool = new Tool(); + $boxTool->setVendor('box-project'); + $boxTool->setName('box'); + $boxTool->setVersion($this->boxVersion); + + // TODO: the time here is also problematic, should probably take the one from the Compile command. + // TODO: also an issue for reproducible builds + $bom->getMetadata()->setTimestamp(new DateTimeImmutable()); + $bom->getMetadata()->getProperties()->addItems( + new Property('specVersion', $this->version->value), + new Property('bomFormat', $this->format), + ); + + $componentRepository = $bom->getComponents(); + + self::addInstalledPackages( + $installedPhpDecodedContent['versions'], + $rootPackage['name'], + $componentRepository, + ); + + return $this->serializer->serialize($bom, true); + } + + private static function getVersion(string $version): Version + { + try { + return Version::from(self::SBOM_VERSION); + } catch (ValueError) { + throw new DomainException( + sprintf( + 'Unsupported spec version "%s" for SBOM format. Expected one of : "%s". Got "%s".', + $specVersion, + implode( + '", "', + array_column(Version::cases(), 'value'), + ), + $version, + ), + ); + } + } + + private static function getRootPackageVersion(array $rootPackage): string + { + if (!empty($rootPackage['aliases'])) { + return sprintf( + '%s@%s', + $rootPackage['aliases'][0], + mb_substr($rootPackage['reference'], 0, 7) + ); + } + + if (isset($rootPackage['pretty_version'])) { + return sprintf( + '%s@%s', + $rootPackage['pretty_version'], + mb_substr($rootPackage['reference'], 0, 7) + ); + } + + return $rootPackage['version']; + } + + /** + * @param mixed $rootPackage + */ + private static function createComponent( + array $rootPackage, + string $version, + string $description, + ): Component { + [$vendor, $name] = explode('/', $rootPackage['name']); + + $type = 'library' === $rootPackage['type'] ? ComponentType::Library : ComponentType::Application; + + $component = new Component($type, $name); + $component->setVersion($version); + $component->setGroup($vendor); + $component->setDescription($description); + + return $component; + } + + private static function addInstalledPackages( + array $versions, + string $packageName, + ComponentRepository $componentRepository, + ): void { + foreach ($versions as $package => $values) { + if ($package === $packageName) { + // does not include root package + continue; + } + + if (!isset($values['pretty_version'])) { + // it's a virtual package + continue; + } + + if (0 === count($values['aliases'])) { + $version = $values['pretty_version']; + } else { + $version = sprintf( + '%s@%s', + $values['pretty_version'], + mb_substr($values['reference'], 0, 7) + ); + } + + [$vendor, $name] = explode('/', $package); + + $type = 'library' === $values['type'] ? ComponentType::Library : ComponentType::Application; + + $component = new Component($type, $name); + $component->setVersion($version); + $component->setGroup($vendor); + + $packageUrl = new PackageUrl('composer', $package); + $packageUrl->setVersion($version); + $component->setPackageUrl($packageUrl); + $component->setBomRefValue((string) $packageUrl); + + $componentRepository->addItems($component); + } + } +}