Skip to content

Commit e4f6267

Browse files
Add handler for packages.lock.json in nuget (#3825)
Add handler for packages.lock.json in nuget Adds support for parsing resolved packages and dependency relationships from nuget lockfile `packages.lock.json`. Reference: aboutcode-org/scancode.io#1263 Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> Signed-off-by: Ayan Sinha Mahapatra <ayansmahapatra@gmail.com> Co-authored-by: Ayan Sinha Mahapatra <ayansmahapatra@gmail.com>
1 parent c326a0b commit e4f6267

File tree

7 files changed

+3517
-0
lines changed

7 files changed

+3517
-0
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ v33.0.0 (next next, roadmap)
3434
of these in other summary plugins.
3535
See https://github.com/nexB/scancode-toolkit/issues/1745
3636

37+
- Add support for parsing resolved packages and dependency relationships
38+
from nuget lockfile `packages.lock.json`.
39+
See https://github.com/nexB/scancode-toolkit/pull/3825
40+
3741
v32.2.0 - 2024-06-19
3842
----------------------
3943

src/packagedcode/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156

157157
nuget.NugetNupkgHandler,
158158
nuget.NugetNuspecHandler,
159+
nuget.NugetPackagesLockHandler,
159160

160161
opam.OpamFileHandler,
161162

src/packagedcode/nuget.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10+
import json
1011
import xmltodict
1112
from packageurl import PackageURL
1213

@@ -179,3 +180,88 @@ def parse(cls, location, package_only=False):
179180
)
180181
yield models.PackageData.from_data(package_data, package_only)
181182

183+
184+
class NugetPackagesLockHandler(models.DatafileHandler):
185+
datasource_id = 'nuget_packages_lock'
186+
path_patterns = ('*packages.lock.json',)
187+
default_package_type = 'nuget'
188+
description = 'NuGet packages.lock.json file'
189+
documentation_url = 'https://learn.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-restore'
190+
191+
@classmethod
192+
def get_dependencies(cls, package_info, scope):
193+
dependencies = []
194+
dependencies_mapping = package_info.get("dependencies") or {}
195+
for name, version in dependencies_mapping.items():
196+
dependency = models.DependentPackage(
197+
purl=str(PackageURL(type='nuget', name=name, version=version)),
198+
extracted_requirement=version,
199+
is_resolved=True,
200+
scope=scope,
201+
is_optional=False,
202+
is_runtime=True,
203+
is_direct=True,
204+
)
205+
dependencies.append(dependency)
206+
return dependencies
207+
208+
@classmethod
209+
def parse(cls, location, package_only=False):
210+
with open(location) as loc:
211+
parsed = json.load(loc)
212+
213+
if not parsed:
214+
return
215+
216+
top_dependencies = []
217+
for target_framework, packages in parsed.get('dependencies', {}).items():
218+
extra_data = dict(
219+
target_framework=target_framework,
220+
)
221+
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)
235+
package_type = package_info.get('type')
236+
if package_type == "Direct":
237+
is_direct = True
238+
elif package_type == "Transitive":
239+
is_direct = False
240+
else:
241+
raise Exception(f"Unknown package type: {package_type} for package {package_name} in {location}")
242+
243+
244+
version = package_info.get('resolved')
245+
requested = package_info.get('requested')
246+
dependency = models.DependentPackage(
247+
purl=str(PackageURL(type='nuget', name=package_name, version=version)),
248+
extracted_requirement=requested or version,
249+
is_resolved=True,
250+
resolved_package=resolved_package.to_dict(),
251+
# We use the target framework as scope since there is no concept of scope in .NET
252+
# and we may have different resolutions for different target frameworks
253+
scope=target_framework,
254+
is_optional=False,
255+
is_runtime=True,
256+
is_direct=is_direct,
257+
)
258+
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)
267+

0 commit comments

Comments
 (0)