scanoss 1.12.2__py3-none-any.whl → 1.43.1__py3-none-any.whl

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 (109) hide show
  1. protoc_gen_swagger/__init__.py +13 -13
  2. protoc_gen_swagger/options/__init__.py +13 -13
  3. protoc_gen_swagger/options/annotations_pb2.py +18 -12
  4. protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
  5. protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
  6. protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
  7. protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
  8. protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
  9. scanoss/__init__.py +18 -18
  10. scanoss/api/__init__.py +17 -17
  11. scanoss/api/common/__init__.py +17 -17
  12. scanoss/api/common/v2/__init__.py +17 -17
  13. scanoss/api/common/v2/scanoss_common_pb2.py +49 -20
  14. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
  15. scanoss/api/components/__init__.py +17 -17
  16. scanoss/api/components/v2/__init__.py +17 -17
  17. scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
  18. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
  19. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -21
  20. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +766 -13
  21. scanoss/api/dependencies/__init__.py +17 -17
  22. scanoss/api/dependencies/v2/__init__.py +17 -17
  23. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -29
  24. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +94 -8
  25. scanoss/api/geoprovenance/__init__.py +23 -0
  26. scanoss/api/geoprovenance/v2/__init__.py +23 -0
  27. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +92 -0
  28. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +381 -0
  29. scanoss/api/licenses/__init__.py +23 -0
  30. scanoss/api/licenses/v2/__init__.py +23 -0
  31. scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
  32. scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
  33. scanoss/api/scanning/__init__.py +17 -17
  34. scanoss/api/scanning/v2/__init__.py +17 -17
  35. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +42 -13
  36. scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +86 -7
  37. scanoss/api/semgrep/__init__.py +17 -17
  38. scanoss/api/semgrep/v2/__init__.py +17 -17
  39. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
  40. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
  41. scanoss/api/vulnerabilities/__init__.py +17 -17
  42. scanoss/api/vulnerabilities/v2/__init__.py +17 -17
  43. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
  44. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
  45. scanoss/cli.py +2359 -370
  46. scanoss/components.py +187 -94
  47. scanoss/constants.py +22 -0
  48. scanoss/cryptography.py +308 -0
  49. scanoss/csvoutput.py +91 -58
  50. scanoss/cyclonedx.py +221 -63
  51. scanoss/data/build_date.txt +1 -1
  52. scanoss/data/osadl-copyleft.json +133 -0
  53. scanoss/data/scanoss-settings-schema.json +254 -0
  54. scanoss/delta.py +197 -0
  55. scanoss/export/__init__.py +23 -0
  56. scanoss/export/dependency_track.py +227 -0
  57. scanoss/file_filters.py +582 -0
  58. scanoss/filecount.py +75 -69
  59. scanoss/gitlabqualityreport.py +214 -0
  60. scanoss/header_filter.py +563 -0
  61. scanoss/inspection/__init__.py +23 -0
  62. scanoss/inspection/policy_check/__init__.py +0 -0
  63. scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
  64. scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
  65. scanoss/inspection/policy_check/policy_check.py +222 -0
  66. scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
  67. scanoss/inspection/policy_check/scanoss/copyleft.py +243 -0
  68. scanoss/inspection/policy_check/scanoss/undeclared_component.py +309 -0
  69. scanoss/inspection/summary/__init__.py +0 -0
  70. scanoss/inspection/summary/component_summary.py +170 -0
  71. scanoss/inspection/summary/license_summary.py +191 -0
  72. scanoss/inspection/summary/match_summary.py +341 -0
  73. scanoss/inspection/utils/file_utils.py +44 -0
  74. scanoss/inspection/utils/license_utils.py +123 -0
  75. scanoss/inspection/utils/markdown_utils.py +63 -0
  76. scanoss/inspection/utils/scan_result_processor.py +417 -0
  77. scanoss/osadl.py +125 -0
  78. scanoss/results.py +275 -0
  79. scanoss/scancodedeps.py +87 -38
  80. scanoss/scanner.py +431 -539
  81. scanoss/scanners/__init__.py +23 -0
  82. scanoss/scanners/container_scanner.py +476 -0
  83. scanoss/scanners/folder_hasher.py +358 -0
  84. scanoss/scanners/scanner_config.py +73 -0
  85. scanoss/scanners/scanner_hfh.py +252 -0
  86. scanoss/scanoss_settings.py +337 -0
  87. scanoss/scanossapi.py +140 -101
  88. scanoss/scanossbase.py +59 -22
  89. scanoss/scanossgrpc.py +799 -251
  90. scanoss/scanpostprocessor.py +294 -0
  91. scanoss/scantype.py +22 -21
  92. scanoss/services/dependency_track_service.py +132 -0
  93. scanoss/spdxlite.py +532 -174
  94. scanoss/threadeddependencies.py +148 -47
  95. scanoss/threadedscanning.py +53 -37
  96. scanoss/utils/__init__.py +23 -0
  97. scanoss/utils/abstract_presenter.py +103 -0
  98. scanoss/utils/crc64.py +96 -0
  99. scanoss/utils/file.py +84 -0
  100. scanoss/utils/scanoss_scan_results_utils.py +41 -0
  101. scanoss/utils/simhash.py +198 -0
  102. scanoss/winnowing.py +241 -63
  103. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/METADATA +18 -9
  104. scanoss-1.43.1.dist-info/RECORD +110 -0
  105. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/WHEEL +1 -1
  106. scanoss-1.12.2.dist-info/RECORD +0 -58
  107. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
  108. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info/licenses}/LICENSE +0 -0
  109. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
@@ -1,52 +1,78 @@
1
1
  """
2
- SPDX-License-Identifier: MIT
3
-
4
- Copyright (c) 2021, 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.
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2021, 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
23
  """
24
24
 
25
- import threading
25
+ import json
26
26
  import queue
27
- from typing import Dict
27
+ import threading
28
28
  from dataclasses import dataclass
29
+ from enum import Enum
30
+ from typing import Dict
29
31
 
30
32
  from .scancodedeps import ScancodeDeps
31
33
  from .scanossbase import ScanossBase
32
34
  from .scanossgrpc import ScanossGrpc
33
35
 
34
- DEP_FILE_PREFIX = "file=" # Default prefix to signify an existing parsed dependency file
36
+ DEP_FILE_PREFIX = 'file=' # Default prefix to signify an existing parsed dependency file
37
+
38
+ DEV_DEPENDENCIES = {
39
+ 'dev',
40
+ 'test',
41
+ 'development',
42
+ 'provided',
43
+ 'runtime',
44
+ 'devDependencies',
45
+ 'dev-dependencies',
46
+ 'testImplementation',
47
+ 'testCompile',
48
+ 'Test',
49
+ 'require-dev',
50
+ }
51
+
52
+
53
+ # Define an enum class
54
+ class SCOPE(Enum):
55
+ PRODUCTION = 'prod'
56
+ DEVELOPMENT = 'dev'
35
57
 
36
58
 
37
59
  @dataclass
38
60
  class ThreadedDependencies(ScanossBase):
39
- """
61
+ """ """
40
62
 
41
- """
42
63
  inputs: queue.Queue = queue.Queue()
43
64
  output: queue.Queue = queue.Queue()
44
65
 
45
- def __init__(self, sc_deps: ScancodeDeps, grpc_api: ScanossGrpc, what_to_scan: str = None, debug: bool = False,
46
- trace: bool = False, quiet: bool = False) -> None:
47
- """
48
-
49
- """
66
+ def __init__( # noqa: PLR0913
67
+ self,
68
+ sc_deps: ScancodeDeps,
69
+ grpc_api: ScanossGrpc,
70
+ what_to_scan: str = None,
71
+ debug: bool = False,
72
+ trace: bool = False,
73
+ quiet: bool = False,
74
+ ) -> None:
75
+ """ """
50
76
  super().__init__(debug, trace, quiet)
51
77
  self.sc_deps = sc_deps
52
78
  self.grpc_api = grpc_api
@@ -66,9 +92,20 @@ class ThreadedDependencies(ScanossBase):
66
92
  return resp
67
93
  return None
68
94
 
69
- def run(self, what_to_scan: str = None, deps_file: str = None, wait: bool = True) -> bool:
95
+ def run(
96
+ self,
97
+ what_to_scan: str = None,
98
+ deps_file: str = None,
99
+ wait: bool = True,
100
+ dep_scope: SCOPE = None,
101
+ dep_scope_include: str = None,
102
+ dep_scope_exclude: str = None,
103
+ ) -> bool:
70
104
  """
71
105
  Initiate a background scan for the specified file/dir
106
+ :param dep_scope_exclude: comma separated list of dependency scopes to exclude
107
+ :param dep_scope_include: comma separated list of dependency scopes to include
108
+ :param dep_scope: Enum dependency scope to use
72
109
  :param what_to_scan: file/folder to scan
73
110
  :param deps_file: file to decorate instead of scan (overrides what_to_scan option)
74
111
  :param wait: wait for completion
@@ -77,42 +114,105 @@ class ThreadedDependencies(ScanossBase):
77
114
  what_to_scan = what_to_scan if what_to_scan else self.what_to_scan
78
115
  self._errors = False
79
116
  try:
80
- if deps_file: # Decorate the given dependencies file
117
+ if deps_file: # Decorate the given dependencies file
81
118
  self.print_msg(f'Decorating {deps_file} dependencies...')
82
- self.inputs.put(f'{DEP_FILE_PREFIX}{deps_file}') # Add to queue and have parent wait on it
83
- else: # Search for dependencies to decorate
119
+ self.inputs.put(f'{DEP_FILE_PREFIX}{deps_file}') # Add to queue and have parent wait on it
120
+ else: # Search for dependencies to decorate
84
121
  self.print_msg(f'Searching {what_to_scan} for dependencies...')
85
- self.inputs.put(what_to_scan) # Add to queue and have parent wait on it
86
- self._thread = threading.Thread(target=self.scan_dependencies, daemon=True)
122
+ self.inputs.put(what_to_scan)
123
+ # Add to queue and have parent wait on it
124
+ self._thread = threading.Thread(
125
+ target=self.scan_dependencies(dep_scope, dep_scope_include, dep_scope_exclude), daemon=True
126
+ )
87
127
  self._thread.start()
88
128
  except Exception as e:
89
129
  self.print_stderr(f'ERROR: Problem running threaded dependencies: {e}')
90
130
  self._errors = True
91
- if wait and not self._errors: # Wait for all inputs to complete
131
+ if wait and not self._errors: # Wait for all inputs to complete
92
132
  self.complete()
93
133
  return False if self._errors else True
94
134
 
95
- def scan_dependencies(self) -> None:
135
+ def filter_dependencies(self, deps, filter_dep) -> json:
136
+ files = deps.get('files', [])
137
+ # Iterate over files and their purls
138
+ for file in files:
139
+ if 'purls' in file:
140
+ # Filter purls with scope 'dependencies' and remove the scope field
141
+ file['purls'] = [
142
+ {key: value for key, value in purl.items() if key != 'scope'}
143
+ for purl in file['purls']
144
+ if filter_dep(purl.get('scope'))
145
+ ]
146
+ # End of for loop
147
+
148
+ return {'files': [file for file in deps.get('files', []) if file.get('purls')]}
149
+
150
+ def filter_dependencies_by_scopes(
151
+ self, deps: json, dep_scope: SCOPE = None, dep_scope_include: str = None, dep_scope_exclude: str = None
152
+ ) -> json:
153
+ # Predefined set of scopes to filter
154
+
155
+ # Include all scopes
156
+ include_all = (dep_scope is None or dep_scope == '') and dep_scope_include is None and dep_scope_exclude is None
157
+ ## All dependencies, remove scope key
158
+ if include_all:
159
+ return self.filter_dependencies(deps, lambda purl: True)
160
+
161
+ # Use default list of scopes if a custom list is not set
162
+ if (dep_scope is not None and dep_scope != '') and dep_scope_include is None and dep_scope_exclude is None:
163
+ return self.filter_dependencies(
164
+ deps,
165
+ lambda purl: (dep_scope == SCOPE.PRODUCTION and purl not in DEV_DEPENDENCIES)
166
+ or dep_scope == SCOPE.DEVELOPMENT
167
+ and purl in DEV_DEPENDENCIES,
168
+ )
169
+
170
+ if (
171
+ (dep_scope_include is not None and dep_scope_include != '')
172
+ or dep_scope_exclude is not None
173
+ and dep_scope_exclude != ''
174
+ ):
175
+ # Create sets from comma-separated strings, if provided
176
+ exclude = set(dep_scope_exclude.split(',')) if dep_scope_exclude else set()
177
+ include = set(dep_scope_include.split(',')) if dep_scope_include else set()
178
+
179
+ # Define a lambda function that checks the inclusion/exclusion logic
180
+ return self.filter_dependencies(
181
+ deps, lambda purl: (exclude and purl not in exclude) or (not exclude and purl in include)
182
+ )
183
+ return None
184
+
185
+ def scan_dependencies( # noqa: PLR0912
186
+ self, dep_scope: SCOPE = None, dep_scope_include: str = None, dep_scope_exclude: str = None
187
+ ) -> None:
96
188
  """
97
189
  Scan for dependencies from the given file/dir or from an input file (from the input queue).
98
190
  """
191
+ # TODO refactor to simplify branches based on PLR0912
99
192
  current_thread = threading.get_ident()
100
193
  self.print_trace(f'Starting dependency worker {current_thread}...')
101
194
  try:
102
- what_to_scan = self.inputs.get(timeout=5) # Begin processing the dependency request
195
+ what_to_scan = self.inputs.get(timeout=5) # Begin processing the dependency request
103
196
  deps = None
104
- if what_to_scan.startswith(DEP_FILE_PREFIX): # We have a pre-parsed dependency file, load it
197
+ if what_to_scan.startswith(DEP_FILE_PREFIX): # We have a pre-parsed dependency file, load it
105
198
  deps = self.sc_deps.load_from_file(what_to_scan.strip(DEP_FILE_PREFIX))
106
- else: # Search the file/folder for dependency files to parse
107
- if not self.sc_deps.run_scan(what_to_scan=what_to_scan):
108
- self._errors = True
109
- else:
110
- deps = self.sc_deps.produce_from_file()
199
+ elif not self.sc_deps.run_scan(what_to_scan=what_to_scan):
200
+ self._errors = True
201
+ else:
202
+ deps = self.sc_deps.produce_from_file()
203
+ if dep_scope is not None:
204
+ self.print_debug(f'Filtering {dep_scope.name} dependencies')
205
+ if dep_scope_include is not None:
206
+ self.print_debug(f"Including dependencies with '{dep_scope_include.split(',')}' scopes")
207
+ if dep_scope_exclude is not None:
208
+ self.print_debug(f"Excluding dependencies with '{dep_scope_exclude.split(',')}' scopes")
209
+ deps = self.filter_dependencies_by_scopes(deps, dep_scope, dep_scope_include, dep_scope_exclude)
210
+
111
211
  if not self._errors:
112
212
  if deps is None:
113
213
  self.print_stderr(f'Problem searching for dependencies for: {what_to_scan}')
114
214
  self._errors = True
115
- elif not deps or len(deps.get("files", [])) == 0:
215
+ elif not deps or len(deps.get('files', [])) == 0:
116
216
  self.print_debug(f'No dependencies found to decorate for: {what_to_scan}')
117
217
  else:
118
218
  decorated_deps = self.grpc_api.get_dependencies(deps)
@@ -140,6 +240,7 @@ class ThreadedDependencies(ScanossBase):
140
240
  self._errors = True
141
241
  return True if not self._errors else False
142
242
 
243
+
143
244
  #
144
245
  # End of ThreadedDependencies Class
145
246
  #
@@ -1,41 +1,45 @@
1
1
  """
2
- SPDX-License-Identifier: MIT
3
-
4
- Copyright (c) 2021, 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.
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2021, 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
23
  """
24
+
25
+ import atexit
24
26
  import os
27
+ import queue
25
28
  import sys
26
29
  import threading
27
- import queue
28
30
  import time
29
-
30
- from typing import Dict, List
31
31
  from dataclasses import dataclass
32
+ from typing import Dict, List
33
+
32
34
  from progress.bar import Bar
33
35
 
34
36
  from .scanossapi import ScanossApi
35
37
  from .scanossbase import ScanossBase
36
38
 
37
- WFP_FILE_START = "file="
38
- MAX_ALLOWED_THREADS = int(os.environ.get("SCANOSS_MAX_ALLOWED_THREADS")) if os.environ.get("SCANOSS_MAX_ALLOWED_THREADS") else 30
39
+ WFP_FILE_START = 'file='
40
+ MAX_ALLOWED_THREADS = (
41
+ int(os.environ.get('SCANOSS_MAX_ALLOWED_THREADS')) if os.environ.get('SCANOSS_MAX_ALLOWED_THREADS') else 30
42
+ )
39
43
 
40
44
 
41
45
  @dataclass
@@ -45,13 +49,12 @@ class ThreadedScanning(ScanossBase):
45
49
  WFP scan requests are loaded into the input queue.
46
50
  Multiple threads pull messages off this queue, process the request and put the results into an output queue
47
51
  """
48
- inputs: queue.Queue = queue.Queue()
49
- output: queue.Queue = queue.Queue()
52
+
50
53
  bar: Bar = None
51
54
 
52
- def __init__(self, scanapi: ScanossApi, debug: bool = False, trace: bool = False, quiet: bool = False,
53
- nb_threads: int = 5
54
- ) -> None:
55
+ def __init__(
56
+ self, scanapi: ScanossApi, debug: bool = False, trace: bool = False, quiet: bool = False, nb_threads: int = 5
57
+ ) -> None:
55
58
  """
56
59
  Initialise the ThreadedScanning class
57
60
  :param scanapi: SCANOSS API to send scan requests to
@@ -61,6 +64,8 @@ class ThreadedScanning(ScanossBase):
61
64
  :param nb_threads: Number of thread to run (default 5)
62
65
  """
63
66
  super().__init__(debug, trace, quiet)
67
+ self.inputs = queue.Queue()
68
+ self.output = queue.Queue()
64
69
  self.scanapi = scanapi
65
70
  self.nb_threads = nb_threads
66
71
  self._isatty = sys.stderr.isatty()
@@ -73,6 +78,8 @@ class ThreadedScanning(ScanossBase):
73
78
  if nb_threads > MAX_ALLOWED_THREADS:
74
79
  self.print_msg(f'Warning: Requested threads too large: {nb_threads}. Reducing to {MAX_ALLOWED_THREADS}')
75
80
  self.nb_threads = MAX_ALLOWED_THREADS
81
+ # Register cleanup to ensure progress bar is finished on exit
82
+ atexit.register(self.complete_bar)
76
83
 
77
84
  @staticmethod
78
85
  def __count_files_in_wfp(wfp: str):
@@ -97,6 +104,13 @@ class ThreadedScanning(ScanossBase):
97
104
  if self.bar:
98
105
  self.bar.finish()
99
106
 
107
+ def __del__(self):
108
+ """Ensure progress bar is cleaned up when object is destroyed"""
109
+ try:
110
+ self.complete_bar()
111
+ except Exception:
112
+ pass # Ignore errors during cleanup
113
+
100
114
  def set_bar(self, bar: Bar) -> None:
101
115
  """
102
116
  Set the Progress Bar to display progress while scanning
@@ -130,7 +144,7 @@ class ThreadedScanning(ScanossBase):
130
144
  :param wfp: WFP to add to queue
131
145
  """
132
146
  if wfp is None or wfp == '':
133
- self.print_stderr(f'Warning: empty WFP. Skipping from scan...')
147
+ self.print_stderr('Warning: empty WFP. Skipping from scan...')
134
148
  else:
135
149
  self.inputs.put(wfp)
136
150
 
@@ -158,8 +172,9 @@ class ThreadedScanning(ScanossBase):
158
172
  """
159
173
  qsize = self.inputs.qsize()
160
174
  if qsize < self.nb_threads:
161
- self.print_debug(f'Input queue ({qsize}) smaller than requested threads: {self.nb_threads}. '
162
- f'Reducing to queue size.')
175
+ self.print_debug(
176
+ f'Input queue ({qsize}) smaller than requested threads: {self.nb_threads}. Reducing to queue size.'
177
+ )
163
178
  self.nb_threads = qsize
164
179
  else:
165
180
  self.print_debug(f'Starting {self.nb_threads} threads to process {qsize} requests...')
@@ -171,7 +186,7 @@ class ThreadedScanning(ScanossBase):
171
186
  except Exception as e:
172
187
  self.print_stderr(f'ERROR: Problem running threaded scanning: {e}')
173
188
  self._errors = True
174
- if wait: # Wait for all inputs to complete
189
+ if wait: # Wait for all inputs to complete
175
190
  self.complete()
176
191
  return False if self._errors else True
177
192
 
@@ -180,7 +195,7 @@ class ThreadedScanning(ScanossBase):
180
195
  Wait for input queue to complete processing and complete the worker threads
181
196
  """
182
197
  self.inputs.join()
183
- self._stop_event.set() # Tell the worker threads to stop
198
+ self._stop_event.set() # Tell the worker threads to stop
184
199
  try:
185
200
  for t in self._threads: # Complete the threads
186
201
  t.join(timeout=5)
@@ -199,7 +214,7 @@ class ThreadedScanning(ScanossBase):
199
214
  api_error = False
200
215
  while not self._stop_event.is_set():
201
216
  wfp = None
202
- if not self.inputs.empty(): # Only try to get a message if there is one on the queue
217
+ if not self.inputs.empty(): # Only try to get a message if there is one on the queue
203
218
  try:
204
219
  wfp = self.inputs.get(timeout=5)
205
220
  if api_error: # API error encountered, so stop processing anymore requests
@@ -228,6 +243,7 @@ class ThreadedScanning(ScanossBase):
228
243
  time.sleep(1) # Sleep while waiting for the queue depth to build up
229
244
  self.print_trace(f'Thread complete ({current_thread}).')
230
245
 
246
+
231
247
  #
232
248
  # End of ThreadedScanning Class
233
249
  #
@@ -0,0 +1,23 @@
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
+ """
@@ -0,0 +1,103 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from scanoss.scanossbase import ScanossBase
4
+
5
+
6
+ class AbstractPresenter(ABC):
7
+ """
8
+ Abstract presenter class for presenting output in a given format.
9
+ Subclasses must implement the _format_json_output and _format_plain_output methods.
10
+ """
11
+
12
+ def __init__(
13
+ self,
14
+ debug: bool = False,
15
+ trace: bool = False,
16
+ quiet: bool = False,
17
+ output_file: str = None,
18
+ output_format: str = None,
19
+ ):
20
+ """
21
+ Initialize the presenter with the given output file and format.
22
+ """
23
+ self.AVAILABLE_OUTPUT_FORMATS = ['json', 'plain', 'cyclonedx', 'spdxlite', 'csv', 'raw']
24
+ self.base = ScanossBase(debug=debug, trace=trace, quiet=quiet)
25
+ self.output_file = output_file
26
+ self.output_format = output_format
27
+
28
+ def present(self, output_format: str = None, output_file: str = None):
29
+ """
30
+ Present the formatted output to a file if provided; otherwise, print to stdout.
31
+ """
32
+ file_path = output_file or self.output_file
33
+ fmt = output_format or self.output_format
34
+
35
+ if fmt and fmt not in self.AVAILABLE_OUTPUT_FORMATS:
36
+ raise ValueError(
37
+ f"ERROR: Invalid output format '{fmt}'. Valid values are: {', '.join(self.AVAILABLE_OUTPUT_FORMATS)}"
38
+ )
39
+
40
+ if fmt == 'json':
41
+ content = self._format_json_output()
42
+ elif fmt == 'plain':
43
+ content = self._format_plain_output()
44
+ elif fmt == 'cyclonedx':
45
+ content = self._format_cyclonedx_output()
46
+ elif fmt == 'spdxlite':
47
+ content = self._format_spdxlite_output()
48
+ elif fmt == 'csv':
49
+ content = self._format_csv_output()
50
+ elif fmt == 'raw':
51
+ content = self._format_raw_output()
52
+ else:
53
+ content = self._format_plain_output()
54
+
55
+ self._present_output(content, file_path)
56
+
57
+ def _present_output(self, content: str, file_path: str = None):
58
+ """
59
+ If a file path is provided, write to that file; otherwise, print the content to stdout.
60
+ """
61
+ self.base.print_to_file_or_stdout(content, file_path)
62
+
63
+ @abstractmethod
64
+ def _format_cyclonedx_output(self) -> str:
65
+ """
66
+ Return a CycloneDX string representation of the data.
67
+ """
68
+ pass
69
+
70
+ @abstractmethod
71
+ def _format_spdxlite_output(self) -> str:
72
+ """
73
+ Return a SPDX-Lite string representation of the data.
74
+ """
75
+ pass
76
+
77
+ @abstractmethod
78
+ def _format_csv_output(self) -> str:
79
+ """
80
+ Return a CSV string representation of the data.
81
+ """
82
+ pass
83
+
84
+ @abstractmethod
85
+ def _format_json_output(self) -> str:
86
+ """
87
+ Return a JSON string representation of the data.
88
+ """
89
+ pass
90
+
91
+ @abstractmethod
92
+ def _format_plain_output(self) -> str:
93
+ """
94
+ Return a plain text string representation of the data.
95
+ """
96
+ pass
97
+
98
+ @abstractmethod
99
+ def _format_raw_output(self) -> str:
100
+ """
101
+ Return a raw string representation of the data.
102
+ """
103
+ pass
scanoss/utils/crc64.py ADDED
@@ -0,0 +1,96 @@
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 struct
26
+ from typing import List
27
+
28
+
29
+ class CRC64:
30
+ """
31
+ CRC64 ECMA implementation matching Go's hash/crc64 package.
32
+ Uses polynomial: 0xC96C5795D7870F42
33
+ """
34
+
35
+ POLY = 0xC96C5795D7870F42
36
+ _TABLE = None
37
+
38
+ def __init__(self):
39
+ if CRC64._TABLE is None:
40
+ CRC64._TABLE = self._make_table()
41
+ self.crc = 0xFFFFFFFFFFFFFFFF # Initial value
42
+
43
+ def _make_table(self) -> list:
44
+ """Generate the CRC64 lookup table."""
45
+ table = []
46
+ for i in range(256):
47
+ crc = i
48
+ for _ in range(8):
49
+ if crc & 1:
50
+ crc = (crc >> 1) ^ self.POLY
51
+ else:
52
+ crc >>= 1
53
+ table.append(crc)
54
+ return table
55
+
56
+ def update(self, data: bytes) -> None:
57
+ """Update the CRC with new data."""
58
+ if isinstance(data, str):
59
+ data = data.encode('utf-8')
60
+
61
+ crc = self.crc
62
+ for b in data:
63
+ crc = (crc >> 8) ^ CRC64._TABLE[(crc ^ b) & 0xFF] # Use class-level table
64
+ self.crc = crc
65
+
66
+ def digest(self) -> int:
67
+ """Get the current CRC value."""
68
+ return self.crc ^ 0xFFFFFFFFFFFFFFFF # Final XOR value
69
+
70
+ def hexdigest(self):
71
+ """Get the current CRC value as a hexadecimal string."""
72
+ return format(self.digest(), '016x')
73
+
74
+ @classmethod
75
+ def checksum(cls, data: bytes) -> int:
76
+ """Calculate CRC64 checksum for the given data."""
77
+ crc = cls()
78
+ crc.update(data)
79
+ return crc.digest()
80
+
81
+ @classmethod
82
+ def get_hash_buff(cls, buff: bytes) -> List[bytes]:
83
+ """
84
+ Get the hash value of the given buffer, and converts it to 8 bytes in big-endian order.
85
+
86
+ Args:
87
+ buff (bytes): The buffer to get the hash value of.
88
+
89
+ Returns:
90
+ bytes: The hash value of the given buffer, and converts it to 8 bytes in big-endian order.
91
+ """
92
+ crc = cls()
93
+ crc.update(buff)
94
+ hash_val = crc.digest()
95
+
96
+ return list(struct.pack('>Q', hash_val))