Skip to content

Commit 4155b4d

Browse files
author
Marco Berger
committed
Handle NuGet lock dependency types
Support NuGet packages.lock.json files with Project and CentralTransitive dependency entries. * Skip Project entries because they are project references, not NuGet package dependencies. * Treat CentralTransitive entries as transitive package dependencies. This prevents parsing from aborting for lockfiles generated by projects using project references and Central Package Management. Signed-off-by: Marco Berger <marco@biberei.de>
1 parent ea42c1d commit 4155b4d

5 files changed

Lines changed: 107 additions & 26 deletions

File tree

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ The following organizations or individuals have contributed to ScanCode:
4949
- Li Ha @linexb
5050
- Mankaran Singh @MankaranSingh
5151
- Marc-Etienne Vargenau @vargenau
52+
- Marco Berger @marcoberger
5253
- Martin Petkov @MartinPetkov
5354
- Maximilian Huber @maxhbr
5455
- Michael Herzog @mjherzog

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ with the licensedcode-data and licensedcode-index being published
1616
in two seperate wheels. Also adds linux/macos ARM support in
1717
release archives and pypi wheels.
1818

19+
- Fix NuGet ``packages.lock.json`` parsing to support ``Project`` and
20+
``CentralTransitive`` dependency types. ``Project`` entries are skipped
21+
because they are project references, while ``CentralTransitive`` entries
22+
are treated as transitive package dependencies. This prevents parsing from
23+
aborting for lockfiles generated by projects using project references and
24+
Central Package Management.
25+
1926
- Remove the licensedcode data and built license indexes from the
2027
main scancode-toolkit built wheel, and release them as
2128
seperate wheels which scancode-toolkit depends on.

src/packagedcode/nuget.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -218,28 +218,36 @@ def parse(cls, location, package_only=False):
218218
extra_data = dict(
219219
target_framework=target_framework,
220220
)
221+
221222
for package_name, package_info in packages.items():
222-
dependencies = cls.get_dependencies(package_info=package_info, scope=target_framework)
223-
resolved_package_mapping = dict(
224-
datasource_id=cls.datasource_id,
225-
type=cls.default_package_type,
226-
primary_language=cls.default_primary_language,
227-
name=package_name,
228-
dependencies=[
229-
dep.to_dict() for dep in dependencies
230-
],
231-
is_virtual=True,
232-
version=package_info.get('resolved'),
233-
)
234-
resolved_package = models.PackageData.from_data(resolved_package_mapping)
235223
package_type = package_info.get('type')
224+
225+
if package_type == "Project":
226+
continue
227+
236228
if package_type == "Direct":
237229
is_direct = True
238-
elif package_type == "Transitive":
230+
elif package_type in {"Transitive", "CentralTransitive"}:
239231
is_direct = False
240232
else:
241-
raise Exception(f"Unknown package type: {package_type} for package {package_name} in {location}")
242-
233+
raise Exception(
234+
f"Unknown package type: {package_type} "
235+
f"for package {package_name} in {location}"
236+
)
237+
238+
dependencies = cls.get_dependencies(package_info=package_info, scope=target_framework)
239+
resolved_package_mapping = dict(
240+
datasource_id=cls.datasource_id,
241+
type=cls.default_package_type,
242+
primary_language=cls.default_primary_language,
243+
name=package_name,
244+
dependencies=[
245+
dep.to_dict() for dep in dependencies
246+
],
247+
is_virtual=True,
248+
version=package_info.get('resolved'),
249+
)
250+
resolved_package = models.PackageData.from_data(resolved_package_mapping)
243251

244252
version = package_info.get('resolved')
245253
requested = package_info.get('requested')
@@ -256,12 +264,12 @@ def parse(cls, location, package_only=False):
256264
is_direct=is_direct,
257265
)
258266
top_dependencies.append(dependency.to_dict())
259-
package_data = dict(
260-
datasource_id=cls.datasource_id,
261-
type=cls.default_package_type,
262-
primary_language=cls.default_primary_language,
263-
extra_data=extra_data,
264-
dependencies=top_dependencies,
265-
)
266-
yield models.PackageData.from_data(package_data, package_only)
267267

268+
package_data = dict(
269+
datasource_id=cls.datasource_id,
270+
type=cls.default_package_type,
271+
primary_language=cls.default_primary_language,
272+
extra_data=extra_data,
273+
dependencies=top_dependencies,
274+
)
275+
yield models.PackageData.from_data(package_data, package_only)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"version": 2,
3+
"dependencies": {
4+
"net8.0": {
5+
"Direct.Package": {
6+
"type": "Direct",
7+
"requested": "[1.0.0, )",
8+
"resolved": "1.0.0",
9+
"contentHash": "direct-package-content-hash",
10+
"dependencies": {
11+
"Transitive.Package": "2.0.0"
12+
}
13+
},
14+
"Transitive.Package": {
15+
"type": "Transitive",
16+
"resolved": "2.0.0",
17+
"contentHash": "transitive-package-content-hash"
18+
},
19+
"CentralTransitive.Package": {
20+
"type": "CentralTransitive",
21+
"requested": "[3.0.0, )",
22+
"resolved": "3.0.0",
23+
"contentHash": "central-transitive-package-content-hash"
24+
},
25+
"Local.Project": {
26+
"type": "Project",
27+
"dependencies": {
28+
"CentralTransitive.Package": "[3.0.0, )"
29+
}
30+
}
31+
}
32+
}
33+
}

tests/packagedcode/test_nuget.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,45 @@ def test_parse_as_package_only(self):
6262
package = nuget.NugetNuspecHandler.parse(location=test_file, package_only=True)
6363
expected_loc = self.get_test_loc('nuget/Castle.Core.nuspec-package-only.json.expected')
6464
self.check_packages_data(package, expected_loc, regen=REGEN_TEST_FIXTURES, package_only=True)
65-
65+
6666
def test_parse_nuget_package_lock_json(self):
6767
test_file = self.get_test_loc('nuget/packages.lock.json')
6868
package = nuget.NugetPackagesLockHandler.parse(location=test_file)
6969
expected_loc = self.get_test_loc('nuget/packages.lock.json.expected')
7070
self.check_packages_data(package, expected_loc, regen=REGEN_TEST_FIXTURES, package_only=True)
71-
71+
72+
def test_parse_nuget_package_lock_json_with_project_and_central_transitive_types(self):
73+
test_file = self.get_test_loc(
74+
'nuget/packages-with-project-and-central-transitive.lock.json'
75+
)
76+
77+
packages = list(
78+
nuget.NugetPackagesLockHandler.parse(
79+
location=test_file,
80+
package_only=True,
81+
)
82+
)
83+
84+
assert len(packages) == 1
85+
86+
package = packages[0].to_dict()
87+
dependencies = package['dependencies']
88+
dependencies_by_purl = {
89+
dependency['purl']: dependency
90+
for dependency in dependencies
91+
}
92+
93+
assert 'pkg:nuget/Local.Project@1.0.0' not in dependencies_by_purl
94+
95+
assert dependencies_by_purl['pkg:nuget/Direct.Package@1.0.0']['is_direct'] is True
96+
assert dependencies_by_purl['pkg:nuget/Direct.Package@1.0.0']['extracted_requirement'] == '[1.0.0, )'
97+
98+
assert dependencies_by_purl['pkg:nuget/Transitive.Package@2.0.0']['is_direct'] is False
99+
assert dependencies_by_purl['pkg:nuget/Transitive.Package@2.0.0']['extracted_requirement'] == '2.0.0'
100+
101+
assert dependencies_by_purl['pkg:nuget/CentralTransitive.Package@3.0.0']['is_direct'] is False
102+
assert dependencies_by_purl['pkg:nuget/CentralTransitive.Package@3.0.0']['extracted_requirement'] == '[3.0.0, )'
103+
72104
def test_package_lock_json_is_package_data_file(self):
73105
test_file = self.get_test_loc('nuget/packages.lock.json')
74106
assert nuget.NugetPackagesLockHandler.is_datafile(test_file)

0 commit comments

Comments
 (0)