Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/packagedcode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
nuget.NugetNupkgHandler,
nuget.NugetNuspecHandler,
nuget.NugetPackagesLockHandler,
nuget.DotNetDepsJsonHandler,

opam.OpamFileHandler,

Expand Down
118 changes: 118 additions & 0 deletions src/packagedcode/nuget.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,121 @@ 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:
try:
parsed = json.load(loc)
except Exception:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not required, exception handling is handled in upstream code.

return

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

for lib_key, lib_info in libraries.items():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you extract the parsing of dependencies in another function.

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')

# Extract dependencies from 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)

dependencies.append(
models.DependentPackage(
purl=str(PackageURL(type='nuget', name=dep_name, version=dep_version)),
extracted_requirement=dep_version,
scope=scope,
is_runtime=True,
is_optional=False,
is_pinned=True,
is_direct=True,
).to_dict()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved_package needs to be populated as these are runtime packages which are resolved to versions.

)

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=cls.datasource_id,
type=cls.default_package_type,
name=name,
version=version,
dependencies=dependencies,
extra_data=extra_data,
)
yield models.PackageData.from_data(package_data, package_only)
Loading
Loading