diff --git a/src/packagedcode/__init__.py b/src/packagedcode/__init__.py index d3c48b6e259..fba40b2acf2 100644 --- a/src/packagedcode/__init__.py +++ b/src/packagedcode/__init__.py @@ -156,6 +156,7 @@ nuget.NugetNupkgHandler, nuget.NugetNuspecHandler, nuget.NugetPackagesLockHandler, + nuget.DotNetDepsJsonHandler, opam.OpamFileHandler, diff --git a/src/packagedcode/nuget.py b/src/packagedcode/nuget.py index d0d7e110f2f..f9f5143fdfd 100644 --- a/src/packagedcode/nuget.py +++ b/src/packagedcode/nuget.py @@ -265,3 +265,167 @@ def parse(cls, location, package_only=False): ) yield models.PackageData.from_data(package_data, package_only) + +class DotNetDepsJsonHandler(models.DatafileHandler): + datasource_id = 'nuget_deps_json' + path_patterns = ('*.deps.json',) + default_package_type = 'nuget' + description = 'NuGet .deps.json lockfile' + documentation_url = 'https://github.com/dotnet/sdk/blob/main/documentation/specs/runtime-configuration-file.md' + + @classmethod + def parse(cls, location, package_only=False): + with open(location) as loc: + parsed = json.load(loc) + + if not parsed or not isinstance(parsed, dict): + return + + libraries = parsed.get('libraries') + if not libraries or not isinstance(libraries, dict): + return + + target_framework = None + runtime_target = parsed.get('runtimeTarget') + if runtime_target and isinstance(runtime_target, dict): + target_framework = runtime_target.get('name') + + # Collect target sections to look up dependencies. We prefer the runtime + # target when available, but fall back to other targets to avoid missing + # dependencies in valid .deps.json files without runtimeTarget. + targets_dict = parsed.get('targets') or {} + if not isinstance(targets_dict, dict): + targets_dict = {} + + available_targets = [ + (target_name, target_obj) + for target_name, target_obj in targets_dict.items() + if isinstance(target_obj, dict) + ] + + runtime_target_matched = False + if target_framework: + selected_targets = [ + (target_name, target_obj) + for target_name, target_obj in available_targets + if target_name == target_framework + ] + runtime_target_matched = bool(selected_targets) + else: + selected_targets = [] + + if not selected_targets: + selected_targets = available_targets + + packages = parse_deps_json_libraries( + libraries=libraries, + selected_targets=selected_targets, + runtime_target_matched=runtime_target_matched, + target_framework=target_framework, + datasource_id=cls.datasource_id, + default_package_type=cls.default_package_type, + ) + + for package_data in packages: + yield models.PackageData.from_data(package_data, package_only) + + +def parse_deps_json_libraries( + libraries, + selected_targets, + runtime_target_matched, + target_framework, + datasource_id, + default_package_type, +): + """ + Parse the libraries and targets sections of a .deps.json file. + Returns a list of package dictionaries. + """ + packages = [] + if not selected_targets: + selected_targets = [] + + for lib_key, lib_info in libraries.items(): + if not lib_key or '/' not in lib_key: + continue + if not isinstance(lib_info, dict): + continue + + name, version = lib_key.split('/', 1) + package_type = lib_info.get('type') + dependencies = get_deps_json_dependencies( + lib_key=lib_key, + selected_targets=selected_targets, + default_package_type=default_package_type, + ) + + extra_data = {} + if runtime_target_matched: + extra_data['target_framework'] = target_framework + elif len(selected_targets) == 1: + extra_data['target_framework'] = selected_targets[0][0] + elif selected_targets: + extra_data['target_frameworks'] = [scope for scope, _target_obj in selected_targets] + + if package_type: + extra_data['type'] = package_type + + package_data = dict( + datasource_id=datasource_id, + type=default_package_type, + name=name, + version=version, + dependencies=dependencies, + extra_data=extra_data, + ) + packages.append(package_data) + + return packages + + +def get_deps_json_dependencies(lib_key, selected_targets, default_package_type): + """ + Return dependency mappings for ``lib_key`` gathered from all ``selected_targets``. + """ + dependencies = [] + seen_dependencies = set() + + for scope, target_obj in selected_targets: + target_lib = target_obj.get(lib_key) or {} + if not isinstance(target_lib, dict): + continue + + deps = target_lib.get('dependencies') + if not deps or not isinstance(deps, dict): + continue + + for dep_name, dep_version in deps.items(): + if not dep_name: + continue + + dep_key = (dep_name, dep_version, scope) + if dep_key in seen_dependencies: + continue + seen_dependencies.add(dep_key) + + purl = PackageURL(type=default_package_type, name=dep_name, version=dep_version) + resolved_package = models.PackageData( + type=purl.type, + name=dep_name, + version=dep_version, + ).to_dict() + + dependency = models.DependentPackage( + purl=str(purl), + extracted_requirement=dep_version, + scope=scope, + is_runtime=True, + is_optional=False, + is_pinned=True, + is_direct=True, + resolved_package=resolved_package, + ) + dependencies.append(dependency.to_dict()) + + return dependencies diff --git a/tests/packagedcode/data/nuget/deps_json/Snoop.Core.deps.json b/tests/packagedcode/data/nuget/deps_json/Snoop.Core.deps.json new file mode 100644 index 00000000000..7ceaa916c17 --- /dev/null +++ b/tests/packagedcode/data/nuget/deps_json/Snoop.Core.deps.json @@ -0,0 +1,76 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v3.1", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v3.1": { + "Snoop.Core/1.0.0": { + "dependencies": { + "JetBrains.Annotations": "2023.2.0", + "Microsoft.CodeAnalysis.CSharp": "4.7.0", + "Microsoft.NETFramework.ReferenceAssemblies.net452": "1.0.3" + }, + "runtime": { + "Snoop.Core.dll": {} + } + }, + "JetBrains.Annotations/2023.2.0": {}, + "Microsoft.CodeAnalysis.Common/4.7.0": { + "dependencies": { + "System.Collections.Immutable": "7.0.0" + } + }, + "Microsoft.CodeAnalysis.CSharp/4.7.0": { + "dependencies": { + "Microsoft.CodeAnalysis.Common": "4.7.0" + } + }, + "Microsoft.NETFramework.ReferenceAssemblies.net452/1.0.3": {}, + "System.Collections.Immutable/7.0.0": {} + } + }, + "libraries": { + "Snoop.Core/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "JetBrains.Annotations/2023.2.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-dvO//8aLmLRsCVVgoc/7qBqi2/y4BTyRcg20LCBWtK4n6E9Um06Zp7jF1n0hOE+yqBHwcrDzAjWvCaM3qH8flg==", + "path": "jetbrains.annotations/2023.2.0", + "hashPath": "jetbrains.annotations.2023.2.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Common/4.7.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-pD5S14xMUebSGYe75kt0q/aaS/ftvktSo/pEv7aX7hNPHfdZS+SZeXvkvcffGxWkunYOyRF9m1oN7zzSdYj9dQ==", + "path": "microsoft.codeanalysis.common/4.7.0", + "hashPath": "microsoft.codeanalysis.common.4.7.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.CSharp/4.7.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-JHCP2L6lB0oJ3tQoHkC67SFZxW+KbJVOnAo+6L01K5r/NlBlSUhTk5nUAldWhTVwGdzqNeHqGtnEqpsCmGSwQA==", + "path": "microsoft.codeanalysis.csharp/4.7.0", + "hashPath": "microsoft.codeanalysis.csharp.4.7.0.nupkg.sha512" + }, + "Microsoft.NETFramework.ReferenceAssemblies.net452/1.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-kuFOgilYbs29xENHlqQ6aJYa+t56u+OqHx85P7GYLVlo7HL3nsug9IQY2DoPgkOpZ2xb9btYV2EFK7Enll8S3A==", + "path": "microsoft.netframework.referenceassemblies.net452/1.0.3", + "hashPath": "microsoft.netframework.referenceassemblies.net452.1.0.3.nupkg.sha512" + }, + "System.Collections.Immutable/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "path": "system.collections.immutable/7.0.0", + "hashPath": "system.collections.immutable.7.0.0.nupkg.sha512" + } + } +} diff --git a/tests/packagedcode/data/nuget/deps_json/Snoop.Core.deps.json.ABOUT b/tests/packagedcode/data/nuget/deps_json/Snoop.Core.deps.json.ABOUT new file mode 100644 index 00000000000..707414e0f90 --- /dev/null +++ b/tests/packagedcode/data/nuget/deps_json/Snoop.Core.deps.json.ABOUT @@ -0,0 +1,3 @@ +about_resource: Snoop.Core.deps.json +download_url: https://github.com/snoopwpf/snoopwpf/releases/tag/v5.1.0 +notes: Trimmed from the upstream release asset to keep the fixture small while preserving libraries and targets consistency for tests. diff --git a/tests/packagedcode/data/nuget/deps_json/Snoop.Core.deps.json.expected b/tests/packagedcode/data/nuget/deps_json/Snoop.Core.deps.json.expected new file mode 100644 index 00000000000..3bc14db69bb --- /dev/null +++ b/tests/packagedcode/data/nuget/deps_json/Snoop.Core.deps.json.expected @@ -0,0 +1,557 @@ +[ + { + "type": "nuget", + "namespace": null, + "name": "Snoop.Core", + "version": "1.0.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v3.1", + "type": "project" + }, + "dependencies": [ + { + "purl": "pkg:nuget/JetBrains.Annotations@2023.2.0", + "extracted_requirement": "2023.2.0", + "scope": ".NETCoreApp,Version=v3.1", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": { + "type": "nuget", + "namespace": null, + "name": "JetBrains.Annotations", + "version": "2023.2.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:nuget/JetBrains.Annotations@2023.2.0" + }, + "extra_data": {} + }, + { + "purl": "pkg:nuget/Microsoft.CodeAnalysis.CSharp@4.7.0", + "extracted_requirement": "4.7.0", + "scope": ".NETCoreApp,Version=v3.1", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": { + "type": "nuget", + "namespace": null, + "name": "Microsoft.CodeAnalysis.CSharp", + "version": "4.7.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:nuget/Microsoft.CodeAnalysis.CSharp@4.7.0" + }, + "extra_data": {} + }, + { + "purl": "pkg:nuget/Microsoft.NETFramework.ReferenceAssemblies.net452@1.0.3", + "extracted_requirement": "1.0.3", + "scope": ".NETCoreApp,Version=v3.1", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": { + "type": "nuget", + "namespace": null, + "name": "Microsoft.NETFramework.ReferenceAssemblies.net452", + "version": "1.0.3", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:nuget/Microsoft.NETFramework.ReferenceAssemblies.net452@1.0.3" + }, + "extra_data": {} + } + ], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/Snoop.Core@1.0.0" + }, + { + "type": "nuget", + "namespace": null, + "name": "JetBrains.Annotations", + "version": "2023.2.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v3.1", + "type": "package" + }, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/JetBrains.Annotations@2023.2.0" + }, + { + "type": "nuget", + "namespace": null, + "name": "Microsoft.CodeAnalysis.Common", + "version": "4.7.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v3.1", + "type": "package" + }, + "dependencies": [ + { + "purl": "pkg:nuget/System.Collections.Immutable@7.0.0", + "extracted_requirement": "7.0.0", + "scope": ".NETCoreApp,Version=v3.1", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": { + "type": "nuget", + "namespace": null, + "name": "System.Collections.Immutable", + "version": "7.0.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:nuget/System.Collections.Immutable@7.0.0" + }, + "extra_data": {} + } + ], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/Microsoft.CodeAnalysis.Common@4.7.0" + }, + { + "type": "nuget", + "namespace": null, + "name": "Microsoft.CodeAnalysis.CSharp", + "version": "4.7.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v3.1", + "type": "package" + }, + "dependencies": [ + { + "purl": "pkg:nuget/Microsoft.CodeAnalysis.Common@4.7.0", + "extracted_requirement": "4.7.0", + "scope": ".NETCoreApp,Version=v3.1", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": { + "type": "nuget", + "namespace": null, + "name": "Microsoft.CodeAnalysis.Common", + "version": "4.7.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:nuget/Microsoft.CodeAnalysis.Common@4.7.0" + }, + "extra_data": {} + } + ], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/Microsoft.CodeAnalysis.CSharp@4.7.0" + }, + { + "type": "nuget", + "namespace": null, + "name": "Microsoft.NETFramework.ReferenceAssemblies.net452", + "version": "1.0.3", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v3.1", + "type": "package" + }, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/Microsoft.NETFramework.ReferenceAssemblies.net452@1.0.3" + }, + { + "type": "nuget", + "namespace": null, + "name": "System.Collections.Immutable", + "version": "7.0.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v3.1", + "type": "package" + }, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/System.Collections.Immutable@7.0.0" + } +] \ No newline at end of file diff --git a/tests/packagedcode/data/nuget/deps_json/simple.deps.json b/tests/packagedcode/data/nuget/deps_json/simple.deps.json new file mode 100644 index 00000000000..83f1b0030a5 --- /dev/null +++ b/tests/packagedcode/data/nuget/deps_json/simple.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v6.0" + }, + "targets": { + ".NETCoreApp,Version=v6.0": { + "MyApp/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.1" + } + }, + "Newtonsoft.Json/13.0.1": {} + } + }, + "libraries": { + "MyApp/1.0.0": { + "type": "project" + }, + "Newtonsoft.Json/13.0.1": { + "type": "package" + } + } +} \ No newline at end of file diff --git a/tests/packagedcode/data/nuget/deps_json/simple.deps.json.ABOUT b/tests/packagedcode/data/nuget/deps_json/simple.deps.json.ABOUT new file mode 100644 index 00000000000..d7243c60ae9 --- /dev/null +++ b/tests/packagedcode/data/nuget/deps_json/simple.deps.json.ABOUT @@ -0,0 +1,2 @@ +about_resource: simple.deps.json +notes: Minimal hand-crafted .deps.json fixture used to cover parser edge cases that are awkward to isolate from larger real-world files. diff --git a/tests/packagedcode/data/nuget/deps_json/simple.deps.json.expected b/tests/packagedcode/data/nuget/deps_json/simple.deps.json.expected new file mode 100644 index 00000000000..7973669c73d --- /dev/null +++ b/tests/packagedcode/data/nuget/deps_json/simple.deps.json.expected @@ -0,0 +1,151 @@ +[ + { + "type": "nuget", + "namespace": null, + "name": "MyApp", + "version": "1.0.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v6.0", + "type": "project" + }, + "dependencies": [ + { + "purl": "pkg:nuget/Newtonsoft.Json@13.0.1", + "extracted_requirement": "13.0.1", + "scope": ".NETCoreApp,Version=v6.0", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": { + "type": "nuget", + "namespace": null, + "name": "Newtonsoft.Json", + "version": "13.0.1", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:nuget/Newtonsoft.Json@13.0.1" + }, + "extra_data": {} + } + ], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/MyApp@1.0.0" + }, + { + "type": "nuget", + "namespace": null, + "name": "Newtonsoft.Json", + "version": "13.0.1", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v6.0", + "type": "package" + }, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/Newtonsoft.Json@13.0.1" + } +] \ No newline at end of file diff --git a/tests/packagedcode/data/nuget/deps_json/small_app.deps.json b/tests/packagedcode/data/nuget/deps_json/small_app.deps.json new file mode 100644 index 00000000000..5b76766a280 --- /dev/null +++ b/tests/packagedcode/data/nuget/deps_json/small_app.deps.json @@ -0,0 +1,54 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0/linux-x64", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "jellyfin/10.8.10": { + "dependencies": { + "MediaBrowser.Common": "10.8.10" + }, + "runtime": { + "jellyfin.dll": {} + } + }, + "MediaBrowser.Common/10.8.10": { + "dependencies": { + "Newtonsoft.Json": "13.0.3" + }, + "runtime": { + "MediaBrowser.Common.dll": {} + } + }, + "Newtonsoft.Json/13.0.3": { + "runtime": { + "lib/net6.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.3.27908" + } + } + } + } + }, + "libraries": { + "jellyfin/10.8.10": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "MediaBrowser.Common/10.8.10": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Newtonsoft.Json/13.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + "path": "newtonsoft.json/13.0.3", + "hashPath": "newtonsoft.json.13.0.3.nupkg.sha512" + } + } +} diff --git a/tests/packagedcode/data/nuget/deps_json/small_app.deps.json.ABOUT b/tests/packagedcode/data/nuget/deps_json/small_app.deps.json.ABOUT new file mode 100644 index 00000000000..3000f634377 --- /dev/null +++ b/tests/packagedcode/data/nuget/deps_json/small_app.deps.json.ABOUT @@ -0,0 +1,3 @@ +about_resource: small_app.deps.json +download_url: https://github.com/jellyfin/jellyfin/releases/download/v10.8.10/jellyfin_10.8.10_amd64.tar.gz +notes: Trimmed from the Jellyfin release to provide a smaller real-world .deps.json fixture. diff --git a/tests/packagedcode/data/nuget/deps_json/small_app.deps.json.expected b/tests/packagedcode/data/nuget/deps_json/small_app.deps.json.expected new file mode 100644 index 00000000000..29b87add83d --- /dev/null +++ b/tests/packagedcode/data/nuget/deps_json/small_app.deps.json.expected @@ -0,0 +1,253 @@ +[ + { + "type": "nuget", + "namespace": null, + "name": "jellyfin", + "version": "10.8.10", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v8.0", + "type": "project" + }, + "dependencies": [ + { + "purl": "pkg:nuget/MediaBrowser.Common@10.8.10", + "extracted_requirement": "10.8.10", + "scope": ".NETCoreApp,Version=v8.0", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": { + "type": "nuget", + "namespace": null, + "name": "MediaBrowser.Common", + "version": "10.8.10", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:nuget/MediaBrowser.Common@10.8.10" + }, + "extra_data": {} + } + ], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/jellyfin@10.8.10" + }, + { + "type": "nuget", + "namespace": null, + "name": "MediaBrowser.Common", + "version": "10.8.10", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v8.0", + "type": "project" + }, + "dependencies": [ + { + "purl": "pkg:nuget/Newtonsoft.Json@13.0.3", + "extracted_requirement": "13.0.3", + "scope": ".NETCoreApp,Version=v8.0", + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": true, + "resolved_package": { + "type": "nuget", + "namespace": null, + "name": "Newtonsoft.Json", + "version": "13.0.3", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:nuget/Newtonsoft.Json@13.0.3" + }, + "extra_data": {} + } + ], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/MediaBrowser.Common@10.8.10" + }, + { + "type": "nuget", + "namespace": null, + "name": "Newtonsoft.Json", + "version": "13.0.3", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "target_framework": ".NETCoreApp,Version=v8.0", + "type": "package" + }, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "nuget_deps_json", + "purl": "pkg:nuget/Newtonsoft.Json@13.0.3" + } +] \ No newline at end of file diff --git a/tests/packagedcode/test_nuget.py b/tests/packagedcode/test_nuget.py index d3af13ab51b..c7182742a0b 100644 --- a/tests/packagedcode/test_nuget.py +++ b/tests/packagedcode/test_nuget.py @@ -7,7 +7,9 @@ # See https://aboutcode.org for more information about nexB OSS projects. # +import json import os +import tempfile from packagedcode import nuget from packages_test_utils import PackageTester @@ -72,3 +74,163 @@ def test_parse_nuget_package_lock_json(self): def test_package_lock_json_is_package_data_file(self): test_file = self.get_test_loc('nuget/packages.lock.json') assert nuget.NugetPackagesLockHandler.is_datafile(test_file) + + def test_deps_json_is_datafile(self): + test_file = self.get_test_loc('nuget/deps_json/simple.deps.json') + assert nuget.DotNetDepsJsonHandler.is_datafile(test_file) + + def test_parse_simple_deps_json(self): + test_file = self.get_test_loc('nuget/deps_json/simple.deps.json') + packages = nuget.DotNetDepsJsonHandler.parse(test_file) + expected_loc = self.get_test_loc('nuget/deps_json/simple.deps.json.expected') + self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES) + + def test_parse_snoop_deps_json(self): + test_file = self.get_test_loc('nuget/deps_json/Snoop.Core.deps.json') + packages = nuget.DotNetDepsJsonHandler.parse(test_file) + expected_loc = self.get_test_loc('nuget/deps_json/Snoop.Core.deps.json.expected') + self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES) + + def test_parse_small_app_deps_json(self): + test_file = self.get_test_loc('nuget/deps_json/small_app.deps.json') + packages = nuget.DotNetDepsJsonHandler.parse(test_file) + expected_loc = self.get_test_loc('nuget/deps_json/small_app.deps.json.expected') + self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES) + + def test_parse_empty_libraries_deps_json(self): + fd, temp_path = tempfile.mkstemp(suffix='.deps.json') + with os.fdopen(fd, 'w') as f: + json.dump({ + "runtimeTarget": {"name": ".NETCoreApp,Version=v6.0"}, + "libraries": {} + }, f) + + try: + packages = list(nuget.DotNetDepsJsonHandler.parse(temp_path)) + assert len(packages) == 0 + finally: + os.remove(temp_path) + + def test_parse_without_runtime_target_uses_targets(self): + fd, temp_path = tempfile.mkstemp(suffix='.deps.json') + with os.fdopen(fd, 'w') as f: + json.dump({ + "targets": { + ".NETCoreApp,Version=v8.0": { + "App/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json/13.0.3": {} + } + }, + "libraries": { + "App/1.0.0": {"type": "project"}, + "Newtonsoft.Json/13.0.3": {"type": "package"} + } + }, f) + + try: + packages = list(nuget.DotNetDepsJsonHandler.parse(temp_path)) + assert len(packages) == 2 + + app = [p for p in packages if p.name == 'App'][0] + assert app.extra_data.get('target_framework') == '.NETCoreApp,Version=v8.0' + assert len(app.dependencies) == 1 + dependency = app.dependencies[0] + assert dependency.get('purl') == 'pkg:nuget/Newtonsoft.Json@13.0.3' + assert dependency.get('scope') == '.NETCoreApp,Version=v8.0' + assert dependency.get('resolved_package', {}).get('purl') == 'pkg:nuget/Newtonsoft.Json@13.0.3' + finally: + os.remove(temp_path) + + def test_parse_runtime_target_mismatch_falls_back_to_targets(self): + fd, temp_path = tempfile.mkstemp(suffix='.deps.json') + with os.fdopen(fd, 'w') as f: + json.dump({ + "runtimeTarget": {"name": ".NETCoreApp,Version=v8.0/win-x64"}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "App/1.0.0": { + "dependencies": { + "Serilog": "3.1.0" + } + }, + "Serilog/3.1.0": {} + } + }, + "libraries": { + "App/1.0.0": {"type": "project"}, + "Serilog/3.1.0": {"type": "package"} + } + }, f) + + try: + packages = list(nuget.DotNetDepsJsonHandler.parse(temp_path)) + assert len(packages) == 2 + + app = [p for p in packages if p.name == 'App'][0] + assert app.extra_data.get('target_framework') == '.NETCoreApp,Version=v8.0' + assert len(app.dependencies) == 1 + dependency = app.dependencies[0] + assert dependency.get('purl') == 'pkg:nuget/Serilog@3.1.0' + assert dependency.get('scope') == '.NETCoreApp,Version=v8.0' + assert dependency.get('resolved_package', {}).get('purl') == 'pkg:nuget/Serilog@3.1.0' + finally: + os.remove(temp_path) + + def test_parse_without_runtime_target_merges_multiple_targets(self): + fd, temp_path = tempfile.mkstemp(suffix='.deps.json') + with os.fdopen(fd, 'w') as f: + json.dump({ + "targets": { + ".NETCoreApp,Version=v8.0": { + "App/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json/13.0.3": {} + }, + ".NETFramework,Version=v4.7.2": { + "App/1.0.0": { + "dependencies": { + "Serilog": "3.1.0" + } + }, + "Serilog/3.1.0": {} + } + }, + "libraries": { + "App/1.0.0": {"type": "project"}, + "Newtonsoft.Json/13.0.3": {"type": "package"}, + "Serilog/3.1.0": {"type": "package"} + } + }, f) + + try: + packages = list(nuget.DotNetDepsJsonHandler.parse(temp_path)) + assert len(packages) == 3 + + app = [p for p in packages if p.name == 'App'][0] + assert app.extra_data.get('target_frameworks') == [ + '.NETCoreApp,Version=v8.0', + '.NETFramework,Version=v4.7.2', + ] + + dependencies = sorted(app.dependencies, key=lambda dep: dep.get('purl')) + assert [dep.get('purl') for dep in dependencies] == [ + 'pkg:nuget/Newtonsoft.Json@13.0.3', + 'pkg:nuget/Serilog@3.1.0', + ] + assert [dep.get('scope') for dep in dependencies] == [ + '.NETCoreApp,Version=v8.0', + '.NETFramework,Version=v4.7.2', + ] + assert [dep.get('resolved_package', {}).get('purl') for dep in dependencies] == [ + 'pkg:nuget/Newtonsoft.Json@13.0.3', + 'pkg:nuget/Serilog@3.1.0', + ] + finally: + os.remove(temp_path)