Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nuget nuspec dependencies #3206

Merged
merged 8 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
72 changes: 67 additions & 5 deletions src/packagedcode/nuget.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,75 @@
#

import xmltodict
from packageurl import PackageURL

from packagedcode import models
from packagedcode.utils import build_description

"""
Handle NuGet packages and their manifests.
"""
# TODO: add dependencies


def get_dependencies(nuspec):
"""
Yield DependentPackage found in a NuGet ``nuspec`` object.
"""
# This is either a list of dependency or a list of group/dependency
# or a single dep or a single group mapping

dependencies = nuspec.get('dependencies') or []
if isinstance(dependencies, dict):
# wrap the mapping in a list if we have more than one dependencies
dependencies = [dependencies]

for depends in dependencies:
groups = depends.get('group') or []
if groups:
if isinstance(groups, dict):
# wrap the mapping in a list
groups = [groups]

for group in groups:
extra_data = dict(framework=group['@targetFramework'])
deps = group.get('dependency') or []
yield from _get_dep_packs(deps, extra_data)
else:
# {'dependency': {'@id': 'jQuery', '@version': '1.9.1'}}
deps = depends.get('dependency') or []
yield from _get_dep_packs(deps, extra_data={})


def _get_dep_packs(deps, extra_data):
"""
Yield DependentPackage found in a NuGet ``deps`` mapping or list of mappings.
"""
if not deps:
return

if isinstance(deps, dict):
# wrap the mapping in a list
deps = [deps]

for dep in deps:
extra = dict(extra_data) or {}
include = dep.get('@include')
if include:
extra['include'] = include
exclude = dep.get('@exclude')
if exclude:
extra['exclude'] = exclude

yield models.DependentPackage(
purl=str(PackageURL(type='nuget', name=dep.get('@id'))),
# this is a range, not a version
extracted_requirement=dep.get('@version'),
scope='dependency',
is_runtime=True,
is_optional=False,
is_resolved=False,
extra_data=extra,
)


def get_urls(name, version, **kwargs):
Expand Down Expand Up @@ -46,7 +107,7 @@ class NugetNuspecHandler(models.DatafileHandler):

@classmethod
def parse(cls, location):
with open(location , 'rb') as loc:
with open(location, 'rb') as loc:
parsed = xmltodict.parse(loc)

if not parsed:
Expand All @@ -62,14 +123,14 @@ def parse(cls, location):

# Summary: A short description of the package for UI display. If omitted, a
# truncated version of description is used.
description = build_description(nuspec.get('summary') , nuspec.get('description'))
description = build_description(nuspec.get('summary'), nuspec.get('description'))

# title: A human-friendly title of the package, typically used in UI
# displays as on nuget.org and the Package Manager in Visual Studio. If not
# specified, the package ID is used.
title = nuspec.get('title')
if title and title != name:
description = build_description(nuspec.get('title') , description)
description = build_description(title, description)

parties = []
authors = nuspec.get('authors')
Expand Down Expand Up @@ -98,7 +159,6 @@ def parse(cls, location):
# This is a SPDX license expression
if 'license' in nuspec:
extracted_license_statement = nuspec.get('license')
# TODO: try to convert to normal license expression
# Deprecated and not a license expression, just a URL
elif 'licenseUrl' in nuspec:
extracted_license_statement = nuspec.get('licenseUrl')
Expand All @@ -111,8 +171,10 @@ def parse(cls, location):
description=description or None,
homepage_url=nuspec.get('projectUrl') or None,
parties=parties,
dependencies=list(get_dependencies(nuspec)),
extracted_license_statement=extracted_license_statement,
copyright=nuspec.get('copyright') or None,
vcs_url=vcs_url,
**urls,
)

133 changes: 132 additions & 1 deletion tests/packagedcode/data/nuget/Castle.Core.nuspec.json.expected
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,138 @@
"source_packages": [],
"file_references": [],
"extra_data": {},
"dependencies": [],
"dependencies": [
{
"purl": "pkg:nuget/NETStandard.Library",
"extracted_requirement": "1.6.1",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
},
{
"purl": "pkg:nuget/System.Diagnostics.TraceSource",
"extracted_requirement": "4.3.0",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
},
{
"purl": "pkg:nuget/System.Reflection.TypeExtensions",
"extracted_requirement": "4.3.0",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
},
{
"purl": "pkg:nuget/System.Xml.XmlDocument",
"extracted_requirement": "4.3.0",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
},
{
"purl": "pkg:nuget/System.Dynamic.Runtime",
"extracted_requirement": "4.3.0",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
},
{
"purl": "pkg:nuget/System.Reflection",
"extracted_requirement": "4.3.0",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
},
{
"purl": "pkg:nuget/System.Reflection.Emit",
"extracted_requirement": "4.3.0",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
},
{
"purl": "pkg:nuget/System.Collections.Specialized",
"extracted_requirement": "4.3.0",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
},
{
"purl": "pkg:nuget/System.ComponentModel",
"extracted_requirement": "4.3.0",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
},
{
"purl": "pkg:nuget/System.ComponentModel.TypeConverter",
"extracted_requirement": "4.3.0",
"scope": "dependency",
"is_runtime": true,
"is_optional": false,
"is_resolved": false,
"resolved_package": {},
"extra_data": {
"framework": ".NETStandard1.3",
"exclude": "Build,Analyzers"
}
}
],
"repository_homepage_url": "https://www.nuget.org/packages/Castle.Core/4.2.1",
"repository_download_url": "https://www.nuget.org/api/v2/package/Castle.Core/4.2.1",
"api_data_url": "https://api.nuget.org/v3/registration3/castle.core/4.2.1.json",
Expand Down
Loading