From 8d361d1aed1d00af5110ede2e8c488488969dafb Mon Sep 17 00:00:00 2001 From: Jatin Pandey Date: Tue, 3 Nov 2026 09:19:14 +0530 Subject: [PATCH 1/3] Handle Debian copyright paragraphs without a License field Fixes #2977 AI-assisted: Codex assisted with investigation, implementation and local tests. Signed-off-by: Jatin Pandey --- AUTHORS.rst | 1 + CHANGELOG.rst | 3 ++ src/packagedcode/debian_copyright.py | 17 ++++++- tests/packagedcode/test_debian_copyright.py | 56 +++++++++++++++++++++ 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 9a8224dc7da..4b8f48fcb1e 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -36,6 +36,7 @@ The following organizations or individuals have contributed to ScanCode: - Hanif Ali @hanif-ali - Horie Issei @is2ei - James Ward @jamesward +- Jatin Pandey @satiricalguru - Jelmer Vernooij @jelmer - Jens Keim @pepper-jk - Kunal Chhabra @kunalchhabra37 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index abc3ae41ad2..e0d600bb4b1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,9 @@ Changelog Next release -------------- +- Handle Debian copyright paragraphs without a License field and detect misplaced license notices in their copyright text. + https://github.com/aboutcode-org/scancode-toolkit/issues/2977 + - Fix the optional ``licenses`` extra dependency typo to install ``licensedcode-data``. https://github.com/aboutcode-org/scancode-toolkit/pull/5056 diff --git a/src/packagedcode/debian_copyright.py b/src/packagedcode/debian_copyright.py index 7f08592fe8d..e3f04228c0d 100644 --- a/src/packagedcode/debian_copyright.py +++ b/src/packagedcode/debian_copyright.py @@ -949,6 +949,11 @@ def get_license_detections(paragraph, debian_licensing): name = paragraph.license.name if not name: + if 'license' not in paragraph.line_numbers_by_field: + return get_license_detections_from_other_fields( + paragraph=paragraph, + check_copyright=True, + ) or [] license_detections.append( get_license_detection_from_nameless_paragraph(paragraph=paragraph) ) @@ -1815,10 +1820,12 @@ def add_undetected_debian_matches(name, text): return get_undetected_matches(query_string) -def get_license_detections_from_other_fields(paragraph): +def get_license_detections_from_other_fields(paragraph, check_copyright=False): """ Return a list of DebianLicenseDetection found in a ``paragraph`` comment, - disclaimer or extra_data fields, e.g. all but the "license" field + disclaimer or extra_data fields, e.g. all but the "license" field. + Also check the copyright field for misplaced notices when ``check_copyright`` + is True (for paragraphs without a License field). """ def detect_license_and_save(fname, fvalue): @@ -1837,6 +1844,12 @@ def detect_license_and_save(fname, fvalue): license_detections.append(fdetection) license_detections = [] + if check_copyright and getattr(paragraph, 'copyright', None): + detect_license_and_save( + fname='copyright', + fvalue=paragraph.copyright.dumps(), + ) + for field_name in ('comment', 'disclaimer',): field = getattr(paragraph, field_name, None) if not field: diff --git a/tests/packagedcode/test_debian_copyright.py b/tests/packagedcode/test_debian_copyright.py index 5ea04483a96..8fe82497e6f 100644 --- a/tests/packagedcode/test_debian_copyright.py +++ b/tests/packagedcode/test_debian_copyright.py @@ -225,6 +225,62 @@ def test_add_unknown_matches(self): match = matches[0] assert match.matched_text() == 'license foo\nbar' + def test_parse_copyright_file_missing_license_field_with_license(self): + content = ( + 'Format: https://www.debian.org/doc/packaging-manuals/' + 'copyright-format/1.0/\n' + '\n' + 'Files: *\n' + 'Copyright: 2020 ACME\n' + 'License: Apache-2.0\n' + ' Licensed under Apache 2.0\n' + '\n' + 'Files: clang/lib/Headers/cuda_wrappers/*\n' + 'Copyright: 2016-2020, Google Inc.\n' + ' Permission is hereby granted, free of charge, to any person ' + 'obtaining a copy\n' + ' of this software and associated documentation files (the ' + '"Software"), to deal\n' + ' in the Software without restriction, including without ' + 'limitation the rights\n' + ' to use, copy, modify, merge, publish, distribute, sublicense, ' + 'and/or sell\n' + ' copies of the Software, and to permit persons to whom the ' + 'Software is\n' + ' furnished to do so, subject to the following conditions:\n' + ) + test_loc = self.get_temp_file(file_name='copyright', extension='') + with open(test_loc, 'w') as f: + f.write(content) + + dc = debian_copyright.parse_copyright_file(location=test_loc) + assert dc is not None + lic_exp = dc.get_license_expression() + assert 'apache-2.0' in lic_exp + assert 'x11' in lic_exp or 'mit' in lic_exp + + def test_parse_copyright_file_missing_license_field_without_license(self): + content = ( + 'Format: https://www.debian.org/doc/packaging-manuals/' + 'copyright-format/1.0/\n' + '\n' + 'Files: *\n' + 'Copyright: 2020 ACME\n' + 'License: Apache-2.0\n' + ' Licensed under Apache 2.0\n' + '\n' + 'Files: no-license/*\n' + 'Copyright: 2020 Bob\n' + ) + test_loc = self.get_temp_file(file_name='copyright', extension='') + with open(test_loc, 'w') as f: + f.write(content) + + dc = debian_copyright.parse_copyright_file(location=test_loc) + assert dc is not None + lic_exp = dc.get_license_expression() + assert 'apache-2.0' in lic_exp + class TestMetadataDebianCopyright(PackageTester): test_data_dir = path.join(path.dirname(__file__), 'data/debian/copyright/') From 8ad59de1d4ab7b4c06927ab20c3aeeed7ed5e4b2 Mon Sep 17 00:00:00 2001 From: Gracy769 <169199616+Gracy769@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:35:22 +0530 Subject: [PATCH 2/3] Fix missing ignorable_urls in fsl-1.1 rules Signed-off-by: Gracy769 <169199616+Gracy769@users.noreply.github.com> (cherry picked from commit e5af0d35d987a7f98c034bd13d67e561b4005808) Signed-off-by: Jatin Pandey --- src/licensedcode/data/rules/fsl-1.1_1.RULE | 2 ++ src/licensedcode/data/rules/fsl-1.1_2.RULE | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/licensedcode/data/rules/fsl-1.1_1.RULE b/src/licensedcode/data/rules/fsl-1.1_1.RULE index 004881548a3..cf8ef0871be 100644 --- a/src/licensedcode/data/rules/fsl-1.1_1.RULE +++ b/src/licensedcode/data/rules/fsl-1.1_1.RULE @@ -5,6 +5,8 @@ is_continuous: yes relevance: 100 minimum_coverage: 100 notes: Rule based on the manifest file of the liquidbase source jar. +ignorable_urls: + - https://fsl.software/FSL-1.1-ALv2.template.md --- Bundle-License: https://fsl.software/FSL-1.1-ALv2.template.md diff --git a/src/licensedcode/data/rules/fsl-1.1_2.RULE b/src/licensedcode/data/rules/fsl-1.1_2.RULE index 2e4d72ff45c..e219829edb0 100644 --- a/src/licensedcode/data/rules/fsl-1.1_2.RULE +++ b/src/licensedcode/data/rules/fsl-1.1_2.RULE @@ -5,6 +5,8 @@ is_continuous: yes relevance: 100 minimum_coverage: 100 notes: Rule based on the manifest file of the liquidbase source jar. +ignorable_urls: + - https://fsl.software/FSL-1.1-ALv2.template.md --- https://fsl.software/FSL-1.1-ALv2.template.md From d8ea0e5e7117084c580c16915b9850fd11e3ae92 Mon Sep 17 00:00:00 2001 From: Jatin Pandey Date: Fri, 13 Nov 2026 13:54:36 +0530 Subject: [PATCH 3/3] Account for argument help added by Click 8.5.1 Include the documented argument section in the full help expectation when Click reports help for undocumented arguments. Retain the existing complete expectation for older supported Click releases. AI-assisted: Codex investigated the CI failure and updated the compatibility test. Signed-off-by: Jatin Pandey (cherry picked from commit 9b0437f6536bd5ac31d6c6a3d0a37421ef4fe7b7) --- tests/scancode/test_cli.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/scancode/test_cli.py b/tests/scancode/test_cli.py index dfe5a6dfa7f..ae0c689c334 100644 --- a/tests/scancode/test_cli.py +++ b/tests/scancode/test_cli.py @@ -11,6 +11,7 @@ import json import os +import click import pytest from unittest import skipIf @@ -722,6 +723,16 @@ def no_spaces(s): with open(expected_file) as ef: expected = ef.read() + # Click 8.5.1 also lists arguments without help text. + input_argument = click.Argument(['input']) + if input_argument.get_help_record(click.Context(click.Command('scancode'))): + expected = expected.replace( + ' documentation:\n', + ' miscellaneous:\n' + ' ...\n\n' + ' documentation:\n', + ) + if no_spaces(result) != no_spaces(expected): assert result == expected