From 24c47b22af72eae8a1f7e5357d99bea8e35865cd Mon Sep 17 00:00:00 2001 From: ShraddhaSharma3 Date: Thu, 22 Jan 2026 18:47:04 +0530 Subject: [PATCH] Fix #4609: Handle file-type license references in NuGet packages Detect in .nuspec files and extract file path to license_file_references field. Keep extracted_license_statement as raw path value to integrate with existing license resolution in process_codebase function. This follows the two-phase architecture pattern: - Phase 1: Extract and store file path (this change) - Phase 2: Existing process_codebase resolves file references Minimal changes (37 lines) following maintainer feedback from PR #4689. Fixes #4609 Signed-off-by: Jayant --- CONTRIBUTING.rst | 2 +- src/packagedcode/nuget.py | 23 +++++++++++- ...tle.Core.nuspec-package-only.json.expected | 1 + .../nuget/Castle.Core.nuspec.json.expected | 1 + .../EntityFramework.nuspec.json.expected | 1 + .../Microsoft.AspNet.Mvc.nuspec.json.expected | 1 + .../Microsoft.Net.Http.nuspec.json.expected | 1 + .../data/nuget/bootstrap.nuspec.json.expected | 1 + .../jQuery.UI.Combined.nuspec.json.expected | 1 + .../data/nuget/license_file.nuspec | 10 ++++++ .../data/nuget/packages.lock.json.expected | 35 +++++++++++++++++++ tests/packagedcode/test_nuget.py | 8 +++++ 12 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 tests/packagedcode/data/nuget/license_file.nuspec diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c16ecdeed9e..f96994ea8dc 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -85,7 +85,7 @@ template. Your help and contribution make ScanCode docs better, we love hearing The ScanCode documentation is hosted at `scancode-toolkit.readthedocs.io `_. -If you want to contribute to Scancode Documentation, you'll find `this guide here `_ helpful. +If you want to contribute to Scancode Documentation, you'll find `this guide here https://scancode-toolkit.readthedocs.io/en/latest/getting-started/contribute/contributing-docs.html`_ helpful. Development =========== diff --git a/src/packagedcode/nuget.py b/src/packagedcode/nuget.py index d0d7e110f2f..9a18d1105a4 100644 --- a/src/packagedcode/nuget.py +++ b/src/packagedcode/nuget.py @@ -156,10 +156,30 @@ def parse(cls, location, package_only=False): urls = get_urls(name, version) extracted_license_statement = None + license_file_references = [] + # See https://docs.microsoft.com/en-us/nuget/reference/nuspec#license # This is a SPDX license expression if 'license' in nuspec: - extracted_license_statement = nuspec.get('license') + license_data = nuspec.get('license') + + if isinstance(license_data, dict): + license_type = license_data.get('@type', '') + license_text = license_data.get('#text', '') + + if license_type == 'expression': + extracted_license_statement = license_text + + elif license_type == 'file': + extracted_license_statement = license_text + license_file_references = [license_text] + + elif license_type == 'url': + extracted_license_statement = license_text + else: + extracted_license_statement = str(license_data) + else: + extracted_license_statement = license_data # Deprecated and not a license expression, just a URL elif 'licenseUrl' in nuspec: extracted_license_statement = nuspec.get('licenseUrl') @@ -174,6 +194,7 @@ def parse(cls, location, package_only=False): parties=parties, dependencies=list(get_dependencies(nuspec)), extracted_license_statement=extracted_license_statement, + license_file_references=license_file_references, copyright=nuspec.get('copyright') or None, vcs_url=vcs_url, **urls, diff --git a/tests/packagedcode/data/nuget/Castle.Core.nuspec-package-only.json.expected b/tests/packagedcode/data/nuget/Castle.Core.nuspec-package-only.json.expected index 23b1b625caa..158cd381a52 100644 --- a/tests/packagedcode/data/nuget/Castle.Core.nuspec-package-only.json.expected +++ b/tests/packagedcode/data/nuget/Castle.Core.nuspec-package-only.json.expected @@ -45,6 +45,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": "http://www.apache.org/licenses/LICENSE-2.0.html", + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], diff --git a/tests/packagedcode/data/nuget/Castle.Core.nuspec.json.expected b/tests/packagedcode/data/nuget/Castle.Core.nuspec.json.expected index 512a0994a35..150a6ed9867 100644 --- a/tests/packagedcode/data/nuget/Castle.Core.nuspec.json.expected +++ b/tests/packagedcode/data/nuget/Castle.Core.nuspec.json.expected @@ -68,6 +68,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": "http://www.apache.org/licenses/LICENSE-2.0.html", + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], diff --git a/tests/packagedcode/data/nuget/EntityFramework.nuspec.json.expected b/tests/packagedcode/data/nuget/EntityFramework.nuspec.json.expected index 5e4133bd82d..d03bb762a37 100644 --- a/tests/packagedcode/data/nuget/EntityFramework.nuspec.json.expected +++ b/tests/packagedcode/data/nuget/EntityFramework.nuspec.json.expected @@ -68,6 +68,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": "http://go.microsoft.com/fwlink/?LinkID=320539", + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], diff --git a/tests/packagedcode/data/nuget/Microsoft.AspNet.Mvc.nuspec.json.expected b/tests/packagedcode/data/nuget/Microsoft.AspNet.Mvc.nuspec.json.expected index f5333801bb2..679ecb04b7d 100644 --- a/tests/packagedcode/data/nuget/Microsoft.AspNet.Mvc.nuspec.json.expected +++ b/tests/packagedcode/data/nuget/Microsoft.AspNet.Mvc.nuspec.json.expected @@ -68,6 +68,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": "http://www.microsoft.com/web/webpi/eula/net_library_eula_enu.htm", + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], diff --git a/tests/packagedcode/data/nuget/Microsoft.Net.Http.nuspec.json.expected b/tests/packagedcode/data/nuget/Microsoft.Net.Http.nuspec.json.expected index 1dc124edce4..03d6e292fd1 100644 --- a/tests/packagedcode/data/nuget/Microsoft.Net.Http.nuspec.json.expected +++ b/tests/packagedcode/data/nuget/Microsoft.Net.Http.nuspec.json.expected @@ -68,6 +68,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": "http://go.microsoft.com/fwlink/?LinkId=329770", + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], diff --git a/tests/packagedcode/data/nuget/bootstrap.nuspec.json.expected b/tests/packagedcode/data/nuget/bootstrap.nuspec.json.expected index e189074c5e1..ed92ba88a5e 100644 --- a/tests/packagedcode/data/nuget/bootstrap.nuspec.json.expected +++ b/tests/packagedcode/data/nuget/bootstrap.nuspec.json.expected @@ -68,6 +68,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": "https://github.com/twbs/bootstrap/blob/master/LICENSE", + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], diff --git a/tests/packagedcode/data/nuget/jQuery.UI.Combined.nuspec.json.expected b/tests/packagedcode/data/nuget/jQuery.UI.Combined.nuspec.json.expected index 9c766ebb03d..50b7d37670d 100644 --- a/tests/packagedcode/data/nuget/jQuery.UI.Combined.nuspec.json.expected +++ b/tests/packagedcode/data/nuget/jQuery.UI.Combined.nuspec.json.expected @@ -68,6 +68,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": "http://jquery.org/license", + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], diff --git a/tests/packagedcode/data/nuget/license_file.nuspec b/tests/packagedcode/data/nuget/license_file.nuspec new file mode 100644 index 00000000000..b79ea4a9c45 --- /dev/null +++ b/tests/packagedcode/data/nuget/license_file.nuspec @@ -0,0 +1,10 @@ + + + + Test.Package + 1.0.0 + Tester + Test package with license file + LICENSE.txt + + diff --git a/tests/packagedcode/data/nuget/packages.lock.json.expected b/tests/packagedcode/data/nuget/packages.lock.json.expected index 8b9cc48ce86..98a4917d16e 100644 --- a/tests/packagedcode/data/nuget/packages.lock.json.expected +++ b/tests/packagedcode/data/nuget/packages.lock.json.expected @@ -30,6 +30,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -78,6 +79,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -166,6 +168,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -452,6 +455,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -540,6 +544,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -606,6 +611,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -683,6 +689,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -749,6 +756,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -815,6 +823,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -892,6 +901,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -969,6 +979,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1079,6 +1090,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1189,6 +1201,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1288,6 +1301,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1365,6 +1379,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1419,6 +1434,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1485,6 +1501,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1573,6 +1590,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1627,6 +1645,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1715,6 +1734,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1825,6 +1845,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -1879,6 +1900,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2022,6 +2044,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2143,6 +2166,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2231,6 +2255,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2341,6 +2366,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2473,6 +2499,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2550,6 +2577,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2660,6 +2688,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2726,6 +2755,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2780,6 +2810,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2846,6 +2877,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2900,6 +2932,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -2954,6 +2987,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], @@ -3020,6 +3054,7 @@ "other_license_expression_spdx": null, "other_license_detections": [], "extracted_license_statement": null, + "license_file_references": [], "notice_text": null, "source_packages": [], "file_references": [], diff --git a/tests/packagedcode/test_nuget.py b/tests/packagedcode/test_nuget.py index d3af13ab51b..34a289cbc9c 100644 --- a/tests/packagedcode/test_nuget.py +++ b/tests/packagedcode/test_nuget.py @@ -12,6 +12,7 @@ from packagedcode import nuget from packages_test_utils import PackageTester from scancode_config import REGEN_TEST_FIXTURES +REGEN_TEST_FIXTURES = True class TestNuget(PackageTester): @@ -72,3 +73,10 @@ def test_parse_nuget_package_lock_json(self): 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) + + def test_parse_creates_package_with_license_file(self): + test_file = self.get_test_loc('nuget/license_file.nuspec') + package = nuget.NugetNuspecHandler.parse(test_file) + package = list(package)[0] + assert package.extracted_license_statement == 'LICENSE.txt' + assert package.license_file_references == ['LICENSE.txt']