Skip to content
Open
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
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ The following organizations or individuals have contributed to ScanCode:
- Li Ha @linexb
- Mankaran Singh @MankaranSingh
- Marc-Etienne Vargenau @vargenau
- Marco Berger @marcoberger
- Martin Petkov @MartinPetkov
- Maximilian Huber @maxhbr
- Michael Herzog @mjherzog
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ with the licensedcode-data and licensedcode-index being published
in two seperate wheels. Also adds linux/macos ARM support in
release archives and pypi wheels.

- Fix NuGet ``packages.lock.json`` parsing to support ``Project`` and
``CentralTransitive`` dependency types. ``Project`` entries are skipped
because they are project references, while ``CentralTransitive`` entries
are treated as transitive package dependencies. This prevents parsing from
aborting for lockfiles generated by projects using project references and
Central Package Management.

- Remove the licensedcode data and built license indexes from the
main scancode-toolkit built wheel, and release them as
seperate wheels which scancode-toolkit depends on.
Expand Down
56 changes: 32 additions & 24 deletions src/packagedcode/nuget.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,28 +218,36 @@ def parse(cls, location, package_only=False):
extra_data = dict(
target_framework=target_framework,
)

for package_name, package_info in packages.items():
dependencies = cls.get_dependencies(package_info=package_info, scope=target_framework)
resolved_package_mapping = dict(
datasource_id=cls.datasource_id,
type=cls.default_package_type,
primary_language=cls.default_primary_language,
name=package_name,
dependencies=[
dep.to_dict() for dep in dependencies
],
is_virtual=True,
version=package_info.get('resolved'),
)
resolved_package = models.PackageData.from_data(resolved_package_mapping)
package_type = package_info.get('type')

if package_type == "Project":
continue

if package_type == "Direct":
is_direct = True
elif package_type == "Transitive":
elif package_type in {"Transitive", "CentralTransitive"}:
is_direct = False
else:
raise Exception(f"Unknown package type: {package_type} for package {package_name} in {location}")

raise Exception(
f"Unknown package type: {package_type} "
f"for package {package_name} in {location}"
)

dependencies = cls.get_dependencies(package_info=package_info, scope=target_framework)
resolved_package_mapping = dict(
datasource_id=cls.datasource_id,
type=cls.default_package_type,
primary_language=cls.default_primary_language,
name=package_name,
dependencies=[
dep.to_dict() for dep in dependencies
],
is_virtual=True,
version=package_info.get('resolved'),
)
resolved_package = models.PackageData.from_data(resolved_package_mapping)

version = package_info.get('resolved')
requested = package_info.get('requested')
Expand All @@ -256,12 +264,12 @@ def parse(cls, location, package_only=False):
is_direct=is_direct,
)
top_dependencies.append(dependency.to_dict())
package_data = dict(
datasource_id=cls.datasource_id,
type=cls.default_package_type,
primary_language=cls.default_primary_language,
extra_data=extra_data,
dependencies=top_dependencies,
)
yield models.PackageData.from_data(package_data, package_only)

package_data = dict(
datasource_id=cls.datasource_id,
type=cls.default_package_type,
primary_language=cls.default_primary_language,
extra_data=extra_data,
dependencies=top_dependencies,
)
yield models.PackageData.from_data(package_data, package_only)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"version": 2,
"dependencies": {
"net8.0": {
"Direct.Package": {
"type": "Direct",
"requested": "[1.0.0, )",
"resolved": "1.0.0",
"contentHash": "direct-package-content-hash",
"dependencies": {
"Transitive.Package": "2.0.0"
}
},
"Transitive.Package": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "transitive-package-content-hash"
},
"CentralTransitive.Package": {
"type": "CentralTransitive",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "central-transitive-package-content-hash"
},
"Local.Project": {
"type": "Project",
"dependencies": {
"CentralTransitive.Package": "[3.0.0, )"
}
}
}
}
}
36 changes: 34 additions & 2 deletions tests/packagedcode/test_nuget.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,45 @@ def test_parse_as_package_only(self):
package = nuget.NugetNuspecHandler.parse(location=test_file, package_only=True)
expected_loc = self.get_test_loc('nuget/Castle.Core.nuspec-package-only.json.expected')
self.check_packages_data(package, expected_loc, regen=REGEN_TEST_FIXTURES, package_only=True)

def test_parse_nuget_package_lock_json(self):
test_file = self.get_test_loc('nuget/packages.lock.json')
package = nuget.NugetPackagesLockHandler.parse(location=test_file)
expected_loc = self.get_test_loc('nuget/packages.lock.json.expected')
self.check_packages_data(package, expected_loc, regen=REGEN_TEST_FIXTURES, package_only=True)


def test_parse_nuget_package_lock_json_with_project_and_central_transitive_types(self):
test_file = self.get_test_loc(
'nuget/packages-with-project-and-central-transitive.lock.json'
)

packages = list(
nuget.NugetPackagesLockHandler.parse(
location=test_file,
package_only=True,
)
)

assert len(packages) == 1

package = packages[0].to_dict()
dependencies = package['dependencies']
dependencies_by_purl = {
dependency['purl']: dependency
for dependency in dependencies
}

assert 'pkg:nuget/Local.Project@1.0.0' not in dependencies_by_purl

assert dependencies_by_purl['pkg:nuget/Direct.Package@1.0.0']['is_direct'] is True
assert dependencies_by_purl['pkg:nuget/Direct.Package@1.0.0']['extracted_requirement'] == '[1.0.0, )'

assert dependencies_by_purl['pkg:nuget/Transitive.Package@2.0.0']['is_direct'] is False
assert dependencies_by_purl['pkg:nuget/Transitive.Package@2.0.0']['extracted_requirement'] == '2.0.0'

assert dependencies_by_purl['pkg:nuget/CentralTransitive.Package@3.0.0']['is_direct'] is False
assert dependencies_by_purl['pkg:nuget/CentralTransitive.Package@3.0.0']['extracted_requirement'] == '[3.0.0, )'

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)