scanoss 1.40.1__tar.gz → 1.41.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. {scanoss-1.40.1/src/scanoss.egg-info → scanoss-1.41.0}/PKG-INFO +1 -1
  2. {scanoss-1.40.1 → scanoss-1.41.0}/README.md +5 -0
  3. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/__init__.py +1 -1
  4. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/cli.py +14 -0
  5. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/constants.py +3 -0
  6. scanoss-1.41.0/src/scanoss/data/build_date.txt +1 -0
  7. scanoss-1.41.0/src/scanoss/data/osadl-copyleft.json +133 -0
  8. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/filecount.py +37 -38
  9. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/policy_check/scanoss/copyleft.py +7 -1
  10. scanoss-1.41.0/src/scanoss/inspection/utils/license_utils.py +123 -0
  11. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/utils/scan_result_processor.py +22 -11
  12. scanoss-1.41.0/src/scanoss/osadl.py +125 -0
  13. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanner.py +191 -189
  14. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanners/folder_hasher.py +24 -24
  15. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanners/scanner_hfh.py +20 -15
  16. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/threadedscanning.py +10 -0
  17. {scanoss-1.40.1 → scanoss-1.41.0/src/scanoss.egg-info}/PKG-INFO +1 -1
  18. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss.egg-info/SOURCES.txt +3 -0
  19. scanoss-1.41.0/tests/test_osadl.py +102 -0
  20. {scanoss-1.40.1 → scanoss-1.41.0}/tests/test_policy_inspect.py +255 -0
  21. scanoss-1.40.1/src/scanoss/data/build_date.txt +0 -1
  22. scanoss-1.40.1/src/scanoss/inspection/utils/license_utils.py +0 -137
  23. {scanoss-1.40.1 → scanoss-1.41.0}/LICENSE +0 -0
  24. {scanoss-1.40.1 → scanoss-1.41.0}/PACKAGE.md +0 -0
  25. {scanoss-1.40.1 → scanoss-1.41.0}/pyproject.toml +0 -0
  26. {scanoss-1.40.1 → scanoss-1.41.0}/setup.cfg +0 -0
  27. {scanoss-1.40.1 → scanoss-1.41.0}/src/protoc_gen_swagger/__init__.py +0 -0
  28. {scanoss-1.40.1 → scanoss-1.41.0}/src/protoc_gen_swagger/options/__init__.py +0 -0
  29. {scanoss-1.40.1 → scanoss-1.41.0}/src/protoc_gen_swagger/options/annotations_pb2.py +0 -0
  30. {scanoss-1.40.1 → scanoss-1.41.0}/src/protoc_gen_swagger/options/annotations_pb2.pyi +0 -0
  31. {scanoss-1.40.1 → scanoss-1.41.0}/src/protoc_gen_swagger/options/annotations_pb2_grpc.py +0 -0
  32. {scanoss-1.40.1 → scanoss-1.41.0}/src/protoc_gen_swagger/options/openapiv2_pb2.py +0 -0
  33. {scanoss-1.40.1 → scanoss-1.41.0}/src/protoc_gen_swagger/options/openapiv2_pb2.pyi +0 -0
  34. {scanoss-1.40.1 → scanoss-1.41.0}/src/protoc_gen_swagger/options/openapiv2_pb2_grpc.py +0 -0
  35. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/__init__.py +0 -0
  36. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/common/__init__.py +0 -0
  37. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/common/v2/__init__.py +0 -0
  38. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/common/v2/scanoss_common_pb2.py +0 -0
  39. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/common/v2/scanoss_common_pb2_grpc.py +0 -0
  40. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/components/__init__.py +0 -0
  41. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/components/v2/__init__.py +0 -0
  42. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/components/v2/scanoss_components_pb2.py +0 -0
  43. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/components/v2/scanoss_components_pb2_grpc.py +0 -0
  44. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +0 -0
  45. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +0 -0
  46. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/dependencies/__init__.py +0 -0
  47. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/dependencies/v2/__init__.py +0 -0
  48. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +0 -0
  49. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +0 -0
  50. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/geoprovenance/__init__.py +0 -0
  51. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/geoprovenance/v2/__init__.py +0 -0
  52. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +0 -0
  53. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +0 -0
  54. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/licenses/__init__.py +0 -0
  55. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/licenses/v2/__init__.py +0 -0
  56. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/licenses/v2/scanoss_licenses_pb2.py +0 -0
  57. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +0 -0
  58. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/scanning/__init__.py +0 -0
  59. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/scanning/v2/__init__.py +0 -0
  60. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2.py +0 -0
  61. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +0 -0
  62. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/semgrep/__init__.py +0 -0
  63. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/semgrep/v2/__init__.py +0 -0
  64. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +0 -0
  65. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +0 -0
  66. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/vulnerabilities/__init__.py +0 -0
  67. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/vulnerabilities/v2/__init__.py +0 -0
  68. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +0 -0
  69. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +0 -0
  70. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/components.py +0 -0
  71. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/cryptography.py +0 -0
  72. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/csvoutput.py +0 -0
  73. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/cyclonedx.py +0 -0
  74. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/data/scanoss-settings-schema.json +0 -0
  75. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/data/spdx-exceptions.json +0 -0
  76. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/data/spdx-licenses.json +0 -0
  77. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/delta.py +0 -0
  78. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/export/__init__.py +0 -0
  79. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/export/dependency_track.py +0 -0
  80. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/file_filters.py +0 -0
  81. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/gitlabqualityreport.py +0 -0
  82. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/__init__.py +0 -0
  83. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/policy_check/__init__.py +0 -0
  84. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
  85. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/policy_check/dependency_track/project_violation.py +0 -0
  86. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/policy_check/policy_check.py +0 -0
  87. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
  88. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/policy_check/scanoss/undeclared_component.py +0 -0
  89. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/summary/__init__.py +0 -0
  90. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/summary/component_summary.py +0 -0
  91. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/summary/license_summary.py +0 -0
  92. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/summary/match_summary.py +0 -0
  93. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/utils/file_utils.py +0 -0
  94. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/inspection/utils/markdown_utils.py +0 -0
  95. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/results.py +0 -0
  96. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scancodedeps.py +0 -0
  97. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanners/__init__.py +0 -0
  98. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanners/container_scanner.py +0 -0
  99. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanners/scanner_config.py +0 -0
  100. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanoss_settings.py +0 -0
  101. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanossapi.py +0 -0
  102. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanossbase.py +0 -0
  103. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanossgrpc.py +0 -0
  104. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scanpostprocessor.py +0 -0
  105. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/scantype.py +0 -0
  106. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/services/dependency_track_service.py +0 -0
  107. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/spdxlite.py +0 -0
  108. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/threadeddependencies.py +0 -0
  109. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/utils/__init__.py +0 -0
  110. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/utils/abstract_presenter.py +0 -0
  111. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/utils/crc64.py +0 -0
  112. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/utils/file.py +0 -0
  113. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/utils/scanoss_scan_results_utils.py +0 -0
  114. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/utils/simhash.py +0 -0
  115. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss/winnowing.py +0 -0
  116. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss.egg-info/dependency_links.txt +0 -0
  117. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss.egg-info/entry_points.txt +0 -0
  118. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss.egg-info/requires.txt +0 -0
  119. {scanoss-1.40.1 → scanoss-1.41.0}/src/scanoss.egg-info/top_level.txt +0 -0
  120. {scanoss-1.40.1 → scanoss-1.41.0}/tests/test_csv_output.py +0 -0
  121. {scanoss-1.40.1 → scanoss-1.41.0}/tests/test_file_filters.py +0 -0
  122. {scanoss-1.40.1 → scanoss-1.41.0}/tests/test_scan_post_processor.py +0 -0
  123. {scanoss-1.40.1 → scanoss-1.41.0}/tests/test_spdxlite.py +0 -0
  124. {scanoss-1.40.1 → scanoss-1.41.0}/tests/test_winnowing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scanoss
3
- Version: 1.40.1
3
+ Version: 1.41.0
4
4
  Summary: Simple Python library to leverage the SCANOSS APIs
5
5
  Home-page: https://scanoss.com
6
6
  Author: SCANOSS
@@ -135,3 +135,8 @@ Details of major changes to the library can be found in [CHANGELOG.md](CHANGELOG
135
135
 
136
136
  ## Background
137
137
  Details about the Winnowing algorithm used for scanning can be found [here](WINNOWING.md).
138
+
139
+ ## Dataset License Notice
140
+ This application is licensed under the MIT License. In addition, it includes an unmodified copy of the OSADL copyleft license dataset ([osadl-copyleft.json](src/scanoss/data/osadl-copyleft.json)) which is licensed under the [Creative Commons Attribution 4.0 International license (CC-BY-4.0)](https://creativecommons.org/licenses/by/4.0/) by the [Open Source Automation Development Lab (OSADL) eG](https://www.osadl.org/).
141
+
142
+ **Attribution:** A project by the Open Source Automation Development Lab (OSADL) eG. Original source: [https://www.osadl.org/fileadmin/checklists/copyleft.json](https://www.osadl.org/fileadmin/checklists/copyleft.json)
@@ -22,4 +22,4 @@ SPDX-License-Identifier: MIT
22
22
  THE SOFTWARE.
23
23
  """
24
24
 
25
- __version__ = '1.40.1'
25
+ __version__ = '1.41.0'
@@ -55,6 +55,7 @@ from . import __version__
55
55
  from .components import Components
56
56
  from .constants import (
57
57
  DEFAULT_API_TIMEOUT,
58
+ DEFAULT_COPYLEFT_LICENSE_SOURCES,
58
59
  DEFAULT_HFH_DEPTH,
59
60
  DEFAULT_HFH_MIN_ACCEPTED_SCORE,
60
61
  DEFAULT_HFH_RANK_THRESHOLD,
@@ -64,6 +65,7 @@ from .constants import (
64
65
  DEFAULT_TIMEOUT,
65
66
  MIN_TIMEOUT,
66
67
  PYTHON_MAJOR_VERSION,
68
+ VALID_LICENSE_SOURCES,
67
69
  )
68
70
  from .csvoutput import CsvOutput
69
71
  from .cyclonedx import CycloneDx
@@ -699,6 +701,17 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
699
701
  p.add_argument('--exclude', help='Licenses to exclude from analysis (comma-separated list)')
700
702
  p.add_argument('--explicit', help='Use only these specific licenses for analysis (comma-separated list)')
701
703
 
704
+ # License source filtering
705
+ for p in [p_inspect_raw_copyleft, p_inspect_legacy_copyleft]:
706
+ p.add_argument(
707
+ '-ls', '--license-sources',
708
+ action='extend',
709
+ nargs='+',
710
+ choices=VALID_LICENSE_SOURCES,
711
+ help=f'Specify which license sources to check for copyleft violations. Each license object in scan results '
712
+ f'has a source field indicating its origin. Default: {", ".join(DEFAULT_COPYLEFT_LICENSE_SOURCES)}',
713
+ )
714
+
702
715
  # Common options for (legacy) copyleft and undeclared component inspection
703
716
  for p in [p_inspect_raw_copyleft, p_inspect_raw_undeclared, p_inspect_legacy_copyleft, p_inspect_legacy_undeclared]:
704
717
  p.add_argument('-i', '--input', nargs='?', help='Path to scan results file to analyse')
@@ -1752,6 +1765,7 @@ def inspect_copyleft(parser, args):
1752
1765
  include=args.include, # Additional licenses to check
1753
1766
  exclude=args.exclude, # Licenses to ignore
1754
1767
  explicit=args.explicit, # Explicit license list
1768
+ license_sources=args.license_sources, # License sources to check (list)
1755
1769
  )
1756
1770
  # Execute inspection and exit with appropriate status code
1757
1771
  status, _ = i_copyleft.run()
@@ -17,3 +17,6 @@ DEFAULT_HFH_RANK_THRESHOLD = 5
17
17
  DEFAULT_HFH_DEPTH = 1
18
18
  DEFAULT_HFH_RECURSIVE_THRESHOLD = 0.8
19
19
  DEFAULT_HFH_MIN_ACCEPTED_SCORE = 0.15
20
+
21
+ VALID_LICENSE_SOURCES = ['component_declared', 'license_file', 'file_header', 'file_spdx_tag', 'scancode']
22
+ DEFAULT_COPYLEFT_LICENSE_SOURCES = ['component_declared', 'license_file']
@@ -0,0 +1 @@
1
+ date: 20251117160705, utime: 1763395625
@@ -0,0 +1,133 @@
1
+ {
2
+ "title": "OSADL Open Source License Obligations Checklist (https:\/\/www.osadl.org\/Checklists)",
3
+ "license": "Creative Commons Attribution 4.0 International license (CC-BY-4.0)",
4
+ "attribution": "A project by the Open Source Automation Development Lab (OSADL) eG. For further information about the project see the description at www.osadl.org\/checklists.",
5
+ "copyright": "(C) 2017 - 2024 Open Source Automation Development Lab (OSADL) eG and contributors, info@osadl.org",
6
+ "disclaimer": "The checklists and particularly the copyleft data have been assembled with maximum diligence and care; however, the authors do not warrant nor can be held liable in any way for its correctness, usefulness, merchantibility or fitness for a particular purpose as far as permissible by applicable law. Anyone who uses the information does this on his or her sole responsibility. For any individual legal advice, it is recommended to contact a lawyer.",
7
+ "timeformat": "%Y-%m-%dT%H:%M:%S%z",
8
+ "timestamp": "2025-10-30T11:23:00+0000",
9
+ "copyleft":
10
+ {
11
+ "0BSD": "No",
12
+ "AFL-2.0": "No",
13
+ "AFL-2.1": "No",
14
+ "AFL-3.0": "No",
15
+ "AGPL-3.0-only": "Yes",
16
+ "AGPL-3.0-or-later": "Yes",
17
+ "Apache-1.0": "No",
18
+ "Apache-1.1": "No",
19
+ "Apache-2.0": "No",
20
+ "APSL-2.0": "Yes (restricted)",
21
+ "Artistic-1.0": "No",
22
+ "Artistic-1.0-Perl": "No",
23
+ "Artistic-2.0": "No",
24
+ "Bitstream-Vera": "No",
25
+ "blessing": "No",
26
+ "BlueOak-1.0.0": "No",
27
+ "BSD-1-Clause": "No",
28
+ "BSD-2-Clause": "No",
29
+ "BSD-2-Clause-Patent": "No",
30
+ "BSD-3-Clause": "No",
31
+ "BSD-3-Clause-Open-MPI": "No",
32
+ "BSD-4-Clause": "No",
33
+ "BSD-4-Clause-UC": "No",
34
+ "BSD-4.3TAHOE": "No",
35
+ "BSD-Source-Code": "No",
36
+ "BSL-1.0": "No",
37
+ "bzip2-1.0.5": "No",
38
+ "bzip2-1.0.6": "No",
39
+ "CC-BY-2.5": "No",
40
+ "CC-BY-3.0": "No",
41
+ "CDDL-1.0": "Yes (restricted)",
42
+ "CDDL-1.1": "Yes (restricted)",
43
+ "CPL-1.0": "Yes",
44
+ "curl": "No",
45
+ "ECL-1.0": "No",
46
+ "ECL-2.0": "No",
47
+ "EFL-2.0": "No",
48
+ "EPL-1.0": "Yes",
49
+ "EPL-2.0": "Yes (restricted)",
50
+ "EUPL-1.1": "Yes",
51
+ "EUPL-1.2": "Yes",
52
+ "FSFAP": "No",
53
+ "FSFUL": "No",
54
+ "FSFULLR": "No",
55
+ "FSFULLRWD": "No",
56
+ "FTL": "No",
57
+ "GPL-1.0-only": "Yes",
58
+ "GPL-1.0-or-later": "Yes",
59
+ "GPL-2.0-only": "Yes",
60
+ "GPL-2.0-only WITH Classpath-exception-2.0": "Yes (restricted)",
61
+ "GPL-2.0-or-later": "Yes",
62
+ "GPL-3.0-only": "Yes",
63
+ "GPL-3.0-or-later": "Yes",
64
+ "HPND": "No",
65
+ "IBM-pibs": "No",
66
+ "ICU": "No",
67
+ "IJG": "No",
68
+ "ImageMagick": "No",
69
+ "Info-ZIP": "No",
70
+ "IPL-1.0": "Yes",
71
+ "ISC": "No",
72
+ "JasPer-2.0": "No",
73
+ "LGPL-2.0-only": "Yes (restricted)",
74
+ "LGPL-2.0-or-later": "Yes (restricted)",
75
+ "LGPL-2.1-only": "Yes (restricted)",
76
+ "LGPL-2.1-or-later": "Yes (restricted)",
77
+ "LGPL-3.0-only": "Yes (restricted)",
78
+ "LGPL-3.0-or-later": "Yes (restricted)",
79
+ "Libpng": "No",
80
+ "libpng-2.0": "No",
81
+ "libtiff": "No",
82
+ "LicenseRef-scancode-bsla-no-advert": "No",
83
+ "LicenseRef-scancode-info-zip-2003-05": "No",
84
+ "LicenseRef-scancode-ppp": "No",
85
+ "Minpack": "No",
86
+ "MirOS": "No",
87
+ "MIT": "No",
88
+ "MIT-0": "No",
89
+ "MIT-CMU": "No",
90
+ "MPL-1.1": "Yes (restricted)",
91
+ "MPL-2.0": "Yes (restricted)",
92
+ "MPL-2.0-no-copyleft-exception": "Yes (restricted)",
93
+ "MS-PL": "Questionable",
94
+ "MS-RL": "Yes (restricted)",
95
+ "NBPL-1.0": "No",
96
+ "NCSA": "No",
97
+ "NTP": "No",
98
+ "OFL-1.1": "Yes (restricted)",
99
+ "OGC-1.0": "No",
100
+ "OLDAP-2.8": "No",
101
+ "OpenSSL": "Questionable",
102
+ "OSL-3.0": "Yes",
103
+ "PHP-3.01": "No",
104
+ "PostgreSQL": "No",
105
+ "PSF-2.0": "No",
106
+ "Python-2.0": "No",
107
+ "Qhull": "No",
108
+ "RSA-MD": "No",
109
+ "Saxpath": "No",
110
+ "SGI-B-2.0": "No",
111
+ "Sleepycat": "Yes",
112
+ "SMLNJ": "No",
113
+ "Spencer-86": "No",
114
+ "SSH-OpenSSH": "No",
115
+ "SSH-short": "No",
116
+ "SunPro": "No",
117
+ "Ubuntu-font-1.0": "Yes (restricted)",
118
+ "Unicode-3.0": "No",
119
+ "Unicode-DFS-2015": "No",
120
+ "Unicode-DFS-2016": "No",
121
+ "Unlicense": "No",
122
+ "UPL-1.0": "No",
123
+ "W3C": "No",
124
+ "W3C-19980720": "No",
125
+ "W3C-20150513": "No",
126
+ "WTFPL": "No",
127
+ "X11": "No",
128
+ "XFree86-1.1": "No",
129
+ "Zlib": "No",
130
+ "zlib-acknowledgement": "No",
131
+ "ZPL-2.0": "No"
132
+ }
133
+ }
@@ -26,6 +26,7 @@ import csv
26
26
  import os
27
27
  import pathlib
28
28
  import sys
29
+ from contextlib import nullcontext
29
30
 
30
31
  from progress.spinner import Spinner
31
32
 
@@ -105,48 +106,46 @@ class FileCount(ScanossBase):
105
106
  """
106
107
  success = True
107
108
  if not scan_dir:
108
- raise Exception(f'ERROR: Please specify a folder to scan')
109
+ raise Exception('ERROR: Please specify a folder to scan')
109
110
  if not os.path.exists(scan_dir) or not os.path.isdir(scan_dir):
110
111
  raise Exception(f'ERROR: Specified folder does not exist or is not a folder: {scan_dir}')
111
112
 
112
113
  self.print_msg(f'Searching {scan_dir} for files to count...')
113
- spinner = None
114
- if not self.quiet and self.isatty:
115
- spinner = Spinner('Searching ')
116
- file_types = {}
117
- file_count = 0
118
- file_size = 0
119
- for root, dirs, files in os.walk(scan_dir):
120
- self.print_trace(f'U Root: {root}, Dirs: {dirs}, Files {files}')
121
- dirs[:] = self.__filter_dirs(dirs) # Strip out unwanted directories
122
- filtered_files = self.__filter_files(files) # Strip out unwanted files
123
- self.print_trace(f'F Root: {root}, Dirs: {dirs}, Files {filtered_files}')
124
- for file in filtered_files: # Cycle through each filtered file
125
- path = os.path.join(root, file)
126
- f_size = 0
127
- try:
128
- f_size = os.stat(path).st_size
129
- except Exception as e:
130
- self.print_trace(f'Ignoring missing symlink file: {file} ({e})') # broken symlink
131
- if f_size > 0: # Ignore broken links and empty files
132
- file_count = file_count + 1
133
- file_size = file_size + f_size
134
- f_suffix = pathlib.Path(file).suffix
135
- if not f_suffix or f_suffix == '':
136
- f_suffix = 'no_suffix'
137
- self.print_trace(f'Counting {path} ({f_suffix} - {f_size})..')
138
- fc = file_types.get(f_suffix)
139
- if not fc:
140
- fc = [1, f_size]
141
- else:
142
- fc[0] = fc[0] + 1
143
- fc[1] = fc[1] + f_size
144
- file_types[f_suffix] = fc
145
- if spinner:
146
- spinner.next()
147
- # End for loop
148
- if spinner:
149
- spinner.finish()
114
+ spinner_ctx = Spinner('Searching ') if (not self.quiet and self.isatty) else nullcontext()
115
+
116
+ with spinner_ctx as spinner:
117
+ file_types = {}
118
+ file_count = 0
119
+ file_size = 0
120
+ for root, dirs, files in os.walk(scan_dir):
121
+ self.print_trace(f'U Root: {root}, Dirs: {dirs}, Files {files}')
122
+ dirs[:] = self.__filter_dirs(dirs) # Strip out unwanted directories
123
+ filtered_files = self.__filter_files(files) # Strip out unwanted files
124
+ self.print_trace(f'F Root: {root}, Dirs: {dirs}, Files {filtered_files}')
125
+ for file in filtered_files: # Cycle through each filtered file
126
+ path = os.path.join(root, file)
127
+ f_size = 0
128
+ try:
129
+ f_size = os.stat(path).st_size
130
+ except Exception as e:
131
+ self.print_trace(f'Ignoring missing symlink file: {file} ({e})') # broken symlink
132
+ if f_size > 0: # Ignore broken links and empty files
133
+ file_count = file_count + 1
134
+ file_size = file_size + f_size
135
+ f_suffix = pathlib.Path(file).suffix
136
+ if not f_suffix or f_suffix == '':
137
+ f_suffix = 'no_suffix'
138
+ self.print_trace(f'Counting {path} ({f_suffix} - {f_size})..')
139
+ fc = file_types.get(f_suffix)
140
+ if not fc:
141
+ fc = [1, f_size]
142
+ else:
143
+ fc[0] = fc[0] + 1
144
+ fc[1] = fc[1] + f_size
145
+ file_types[f_suffix] = fc
146
+ if spinner:
147
+ spinner.next()
148
+ # End for loop
150
149
  self.print_stderr(f'Found {file_count:,.0f} files with a total size of {file_size / (1 << 20):,.2f} MB.')
151
150
  if file_types:
152
151
  csv_dict = []
@@ -26,6 +26,8 @@ import json
26
26
  from dataclasses import dataclass
27
27
  from typing import Dict, List
28
28
 
29
+ from scanoss.constants import DEFAULT_COPYLEFT_LICENSE_SOURCES
30
+
29
31
  from ...policy_check.policy_check import PolicyCheck, PolicyOutput, PolicyStatus
30
32
  from ...utils.markdown_utils import generate_jira_table, generate_table
31
33
  from ...utils.scan_result_processor import ScanResultProcessor
@@ -63,6 +65,7 @@ class Copyleft(PolicyCheck[Component]):
63
65
  include: str = None,
64
66
  exclude: str = None,
65
67
  explicit: str = None,
68
+ license_sources: list = None,
66
69
  ):
67
70
  """
68
71
  Initialise the Copyleft class.
@@ -77,6 +80,7 @@ class Copyleft(PolicyCheck[Component]):
77
80
  :param include: Licenses to include in the analysis
78
81
  :param exclude: Licenses to exclude from the analysis
79
82
  :param explicit: Explicitly defined licenses
83
+ :param license_sources: List of license sources to check
80
84
  """
81
85
  super().__init__(
82
86
  debug, trace, quiet, format_type, status, name='Copyleft Policy', output=output
@@ -85,6 +89,7 @@ class Copyleft(PolicyCheck[Component]):
85
89
  self.filepath = filepath
86
90
  self.output = output
87
91
  self.status = status
92
+ self.license_sources = license_sources or DEFAULT_COPYLEFT_LICENSE_SOURCES
88
93
  self.results_processor = ScanResultProcessor(
89
94
  self.debug,
90
95
  self.trace,
@@ -92,7 +97,8 @@ class Copyleft(PolicyCheck[Component]):
92
97
  self.filepath,
93
98
  include,
94
99
  exclude,
95
- explicit)
100
+ explicit,
101
+ self.license_sources)
96
102
 
97
103
  def _json(self, components: list[Component]) -> PolicyOutput:
98
104
  """
@@ -0,0 +1,123 @@
1
+ """
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2024, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ """
24
+
25
+ from scanoss.osadl import Osadl
26
+
27
+ from ...scanossbase import ScanossBase
28
+
29
+
30
+ class LicenseUtil(ScanossBase):
31
+ """
32
+ A utility class for handling software licenses, particularly copyleft licenses.
33
+
34
+ Uses OSADL (Open Source Automation Development Lab) authoritative copyleft data
35
+ with optional include/exclude/explicit filters.
36
+ """
37
+
38
+ BASE_SPDX_ORG_URL = 'https://spdx.org/licenses'
39
+
40
+ def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False):
41
+ super().__init__(debug, trace, quiet)
42
+ self.osadl = Osadl(debug=debug, trace=trace, quiet=quiet)
43
+ self.include_licenses = set()
44
+ self.exclude_licenses = set()
45
+ self.explicit_licenses = set()
46
+
47
+ def init(self, include: str = None, exclude: str = None, explicit: str = None):
48
+ """
49
+ Initialize copyleft license filters.
50
+
51
+ :param include: Comma-separated licenses to mark as copyleft (in addition to OSADL)
52
+ :param exclude: Comma-separated licenses to mark as NOT copyleft (override OSADL)
53
+ :param explicit: Comma-separated licenses to use exclusively (ignore OSADL)
54
+ """
55
+ # Reset previous filters so init() can be safely called multiple times
56
+ self.include_licenses.clear()
57
+ self.exclude_licenses.clear()
58
+ self.explicit_licenses.clear()
59
+
60
+ # Parse explicit list (if provided, ignore OSADL completely)
61
+ if explicit:
62
+ self.explicit_licenses = {lic.strip().lower() for lic in explicit.split(',') if lic.strip()}
63
+ self.print_debug(f'Explicit copyleft licenses: {self.explicit_licenses}')
64
+ return
65
+
66
+ # Parse include list (mark these as copyleft in addition to OSADL)
67
+ if include:
68
+ self.include_licenses = {lic.strip().lower() for lic in include.split(',') if lic.strip()}
69
+ self.print_debug(f'Include licenses: {self.include_licenses}')
70
+
71
+ # Parse exclude list (mark these as NOT copyleft, overriding OSADL)
72
+ if exclude:
73
+ self.exclude_licenses = {lic.strip().lower() for lic in exclude.split(',') if lic.strip()}
74
+ self.print_debug(f'Exclude licenses: {self.exclude_licenses}')
75
+
76
+ def is_copyleft(self, spdxid: str) -> bool:
77
+ """
78
+ Check if a license is copyleft.
79
+
80
+ Logic:
81
+ 1. If explicit list provided → check if license in explicit list
82
+ 2. If license in include list → return True
83
+ 3. If license in exclude list → return False
84
+ 4. Otherwise → use OSADL authoritative data
85
+
86
+ :param spdxid: SPDX license identifier
87
+ :return: True if copyleft, False otherwise
88
+ """
89
+ if not spdxid:
90
+ self.print_debug('No license ID provided for copyleft check')
91
+ return False
92
+
93
+ spdxid_lc = spdxid.lower()
94
+
95
+ # Explicit mode: use only the explicit list
96
+ if self.explicit_licenses:
97
+ return spdxid_lc in self.explicit_licenses
98
+
99
+ # Include filter: if license in include list, force copyleft=True
100
+ if spdxid_lc in self.include_licenses:
101
+ return True
102
+
103
+ # Exclude filter: if license in exclude list, force copyleft=False
104
+ if spdxid_lc in self.exclude_licenses:
105
+ return False
106
+
107
+ # No filters matched, use OSADL authoritative data
108
+ return self.osadl.is_copyleft(spdxid)
109
+
110
+ def get_spdx_url(self, spdxid: str) -> str:
111
+ """
112
+ Generate the URL for the SPDX page of a license.
113
+
114
+ :param spdxid: The SPDX identifier of the license
115
+ :return: The URL of the SPDX page for the given license
116
+ """
117
+ return f'{self.BASE_SPDX_ORG_URL}/{spdxid}.html'
118
+
119
+
120
+
121
+ #
122
+ # End of LicenseUtil Class
123
+ #
@@ -71,11 +71,13 @@ class ScanResultProcessor(ScanossBase):
71
71
  include: str = None,
72
72
  exclude: str = None,
73
73
  explicit: str = None,
74
+ license_sources: list = None,
74
75
  ):
75
76
  super().__init__(debug, trace, quiet)
76
77
  self.result_file_path = result_file_path
77
78
  self.license_util = LicenseUtil()
78
79
  self.license_util.init(include, exclude, explicit)
80
+ self.license_sources = license_sources
79
81
  self.results = self._load_input_file()
80
82
 
81
83
  def get_results(self) -> Dict[str, Any]:
@@ -162,9 +164,11 @@ class ScanResultProcessor(ScanossBase):
162
164
  self.print_debug(f'WARNING: Results missing licenses. Skipping: {new_component}')
163
165
  return
164
166
 
165
- licenses_order_by_source_priority = self._get_licenses_order_by_source_priority(new_component['licenses'])
167
+ # Select licenses based on configuration (filtering or priority mode)
168
+ selected_licenses = self._select_licenses(new_component['licenses'])
169
+
166
170
  # Process licenses for this component
167
- for license_item in licenses_order_by_source_priority:
171
+ for license_item in selected_licenses:
168
172
  if license_item.get('name'):
169
173
  spdxid = license_item['name']
170
174
  source = license_item.get('source')
@@ -309,19 +313,26 @@ class ScanResultProcessor(ScanossBase):
309
313
  component['licenses'] = []
310
314
  return results_list
311
315
 
312
- def _get_licenses_order_by_source_priority(self,licenses_data):
316
+ def _select_licenses(self, licenses_data):
313
317
  """
314
- Select licenses based on source priority:
315
- 1. component_declared (highest priority)
316
- 2. license_file
317
- 3. file_header
318
- 4. scancode (lowest priority)
318
+ Select licenses based on configuration.
319
+
320
+ Two modes:
321
+ - Filtering mode: If license_sources specified, filter to those sources
322
+ - Priority mode: Otherwise, use original priority-based selection
319
323
 
320
- If any high-priority source is found, return only licenses from that source.
321
- If none found, return all licenses.
324
+ Args:
325
+ licenses_data: List of license dictionaries
322
326
 
323
- Returns: list with ordered licenses by source.
327
+ Returns:
328
+ Filtered list of licenses based on configuration
324
329
  """
330
+ # Filtering mode, when license_sources is explicitly provided
331
+ if self.license_sources:
332
+ sources_to_include = set(self.license_sources) | {'unknown'}
333
+ return [lic for lic in licenses_data
334
+ if lic.get('source') in sources_to_include or lic.get('source') is None]
335
+
325
336
  # Define priority order (highest to lowest)
326
337
  priority_sources = ['component_declared', 'license_file', 'file_header', 'scancode']
327
338
 
@@ -0,0 +1,125 @@
1
+ """
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2025, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ """
24
+
25
+ import json
26
+ import sys
27
+
28
+ import importlib_resources
29
+
30
+ from scanoss.scanossbase import ScanossBase
31
+
32
+
33
+ class Osadl(ScanossBase):
34
+ """
35
+ OSADL data accessor class.
36
+
37
+ Provides access to OSADL (Open Source Automation Development Lab) authoritative
38
+ checklist data for license analysis.
39
+
40
+ Data is loaded once at class level and shared across all instances for efficiency.
41
+
42
+ Data source: https://www.osadl.org/fileadmin/checklists/copyleft.json
43
+ License: CC-BY-4.0
44
+ """
45
+
46
+ _shared_copyleft_data = {}
47
+ _data_loaded = False
48
+
49
+ def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False):
50
+ """
51
+ Initialize the Osadl class.
52
+ Data is loaded once at class level and shared across all instances.
53
+ """
54
+ super().__init__(debug, trace, quiet)
55
+ self._load_copyleft_data()
56
+
57
+
58
+ def _load_copyleft_data(self) -> bool:
59
+ """
60
+ Load the embedded OSADL copyleft JSON file into class-level shared data.
61
+ Data is loaded only once and shared across all instances.
62
+
63
+ :return: True if successful, False otherwise
64
+ """
65
+ if Osadl._data_loaded:
66
+ return True
67
+
68
+ # OSADL copyleft license checklist from: https://www.osadl.org/Checklists
69
+ # Data source: https://www.osadl.org/fileadmin/checklists/copyleft.json
70
+ # License: CC-BY-4.0 (Creative Commons Attribution 4.0 International)
71
+ # Copyright: (C) 2017 - 2024 Open Source Automation Development Lab (OSADL) eG
72
+ try:
73
+ f_name = importlib_resources.files(__name__) / 'data/osadl-copyleft.json'
74
+ with importlib_resources.as_file(f_name) as f:
75
+ with open(f, 'r', encoding='utf-8') as file:
76
+ data = json.load(file)
77
+ except Exception as e:
78
+ self.print_stderr(f'ERROR: Problem loading OSADL copyleft data: {e}')
79
+ return False
80
+
81
+ # Process copyleft data
82
+ copyleft = data.get('copyleft', {})
83
+ if not copyleft:
84
+ self.print_stderr('ERROR: No copyleft data found in OSADL JSON')
85
+ return False
86
+
87
+ # Store in class-level shared dictionary
88
+ for lic_id, status in copyleft.items():
89
+ # Normalize license ID (lowercase) for consistent lookup
90
+ lic_id_lc = lic_id.lower()
91
+ Osadl._shared_copyleft_data[lic_id_lc] = status
92
+
93
+ Osadl._data_loaded = True
94
+ self.print_debug(f'Loaded {len(Osadl._shared_copyleft_data)} OSADL copyleft entries')
95
+ return True
96
+
97
+ def is_copyleft(self, spdx_id: str) -> bool:
98
+ """
99
+ Check if a license is copyleft according to OSADL data.
100
+
101
+ Returns True for both strong copyleft ("Yes") and weak/restricted copyleft ("Yes (restricted)").
102
+
103
+ :param spdx_id: SPDX license identifier
104
+ :return: True if copyleft, False otherwise
105
+ """
106
+ if not spdx_id:
107
+ self.print_debug('No license ID provided for copyleft check')
108
+ return False
109
+
110
+ # Normalize lookup
111
+ spdx_id_lc = spdx_id.lower()
112
+ # Use class-level shared data
113
+ status = Osadl._shared_copyleft_data.get(spdx_id_lc)
114
+
115
+ if not status:
116
+ self.print_debug(f'No OSADL copyleft data for license: {spdx_id}')
117
+ return False
118
+
119
+ # Consider both "Yes" and "Yes (restricted)" as copyleft (case-insensitive)
120
+ return status.lower().startswith('yes')
121
+
122
+
123
+ #
124
+ # End of Osadl Class
125
+ #