diff --git a/src/packagedcode/__init__.py b/src/packagedcode/__init__.py index fc1e490eef..5e59c6a8bb 100644 --- a/src/packagedcode/__init__.py +++ b/src/packagedcode/__init__.py @@ -161,6 +161,7 @@ phpcomposer.PhpComposerJsonHandler, phpcomposer.PhpComposerLockHandler, + phpcomposer.PhpSymfonyLockHandler, pubspec.DartPubspecYamlHandler, pubspec.DartPubspecLockHandler, diff --git a/src/packagedcode/phpcomposer.py b/src/packagedcode/phpcomposer.py index 5a3ee59af1..199601cbb1 100644 --- a/src/packagedcode/phpcomposer.py +++ b/src/packagedcode/phpcomposer.py @@ -207,6 +207,61 @@ def parse(cls, location, package_only=False): for package in packages + packages_dev: yield package +class PhpSymfonyLockHandler(models.DatafileHandler): + datasource_id = 'php_symfony_lock' + path_patterns = ('*symfony.lock',) + default_package_type = 'composer' + default_primary_language = 'PHP' + is_lockfile = True + description = 'Symfony Flex lockfile' + documentation_url = 'https://symfony.com/doc/current/setup/flex.html' + + @classmethod + def parse(cls, location, package_only=False): + with io.open(location, encoding='utf-8') as loc: + lock_data = json.load(loc) + + if not isinstance(lock_data, dict): + return + + dependencies = [] + + for package_name, package_details in lock_data.items(): + if not isinstance(package_details, dict): + continue + + namespace, separator, name = package_name.rpartition('/') + + if not separator or not namespace or not name: + continue + + version = package_details.get('version') + if not isinstance(version, str): + version = None + else: + version = version.strip() + + if not version: + version = None + + purl = models.PackageURL( + type=cls.default_package_type, + namespace=namespace, + name=name, + version=version, + ).to_string() + + dependency = models.DependentPackage( + purl=purl, + extracted_requirement=version, + is_pinned=bool(version), + ) + + dependencies.append(dependency) + + yield cls.create_default_package_data( + dependencies=dependencies, + ) def licensing_mapper(licenses, package, is_private=False): """ diff --git a/tests/packagedcode/data/phpcomposer/symfony.lock b/tests/packagedcode/data/phpcomposer/symfony.lock new file mode 100644 index 0000000000..c06bed3416 --- /dev/null +++ b/tests/packagedcode/data/phpcomposer/symfony.lock @@ -0,0 +1,11 @@ +{ + "composer/pcre": { + "version": "1.0.0" + }, + "symfony/console": { + "version": "5.4" + }, + "symfony/flex": { + "version": "1.20" + } +} diff --git a/tests/packagedcode/data/plugin/plugins_list_linux.txt b/tests/packagedcode/data/plugin/plugins_list_linux.txt index 34078bc580..d1972b580e 100755 --- a/tests/packagedcode/data/plugin/plugins_list_linux.txt +++ b/tests/packagedcode/data/plugin/plugins_list_linux.txt @@ -181,6 +181,13 @@ Package type: composer description: PHP composer lockfile path_patterns: '*composer.lock' -------------------------------------------- +Package type: composer + datasource_id: php_symfony_lock + documentation URL: https://symfony.com/doc/current/setup/flex.html + primary language: PHP + description: Symfony Flex lockfile + path_patterns: '*symfony.lock' +-------------------------------------------- Package type: conan datasource_id: conan_conandata_yml documentation URL: https://docs.conan.io/2/tutorial/creating_packages/handle_sources_in_packages.html#using-the-conandata-yml-file diff --git a/tests/packagedcode/test_phpcomposer.py b/tests/packagedcode/test_phpcomposer.py index 4de0e451ec..e2e965cd69 100644 --- a/tests/packagedcode/test_phpcomposer.py +++ b/tests/packagedcode/test_phpcomposer.py @@ -86,3 +86,27 @@ def test_parse_composer_lock(self): expected_loc = self.get_test_loc('phpcomposer/composer.lock-expected.json') packages = phpcomposer.PhpComposerLockHandler.parse(test_file) self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES) + + def test_is_manifest_php_symfony_lock(self): + test_file = self.get_test_loc('phpcomposer/symfony.lock') + assert phpcomposer.PhpSymfonyLockHandler.is_datafile(test_file) + + def test_parse_symfony_lock(self): + test_file = self.get_test_loc('phpcomposer/symfony.lock') + + package_data = list( + phpcomposer.PhpSymfonyLockHandler.parse(test_file) + ) + + assert len(package_data) == 1 + + dependencies = package_data[0].dependencies + actual_purls = {dependency.purl for dependency in dependencies} + + expected_purls = { + 'pkg:composer/composer/pcre@1.0.0', + 'pkg:composer/symfony/console@5.4', + 'pkg:composer/symfony/flex@1.20', + } + + assert actual_purls == expected_purls