pybinaryguard 1.0.0__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.
- pybinaryguard/__init__.py +78 -0
- pybinaryguard/__main__.py +7 -0
- pybinaryguard/_compat/__init__.py +0 -0
- pybinaryguard/agent/__init__.py +63 -0
- pybinaryguard/agent/guard.py +232 -0
- pybinaryguard/agent/recommender.py +283 -0
- pybinaryguard/agent/schema.py +200 -0
- pybinaryguard/agent/simulator.py +430 -0
- pybinaryguard/agent/tool_interface.py +474 -0
- pybinaryguard/analyzers/__init__.py +209 -0
- pybinaryguard/analyzers/base.py +40 -0
- pybinaryguard/analyzers/dependency_analyzer.py +336 -0
- pybinaryguard/analyzers/elf_analyzer.py +754 -0
- pybinaryguard/analyzers/symbol_analyzer.py +280 -0
- pybinaryguard/analyzers/wheel_analyzer.py +308 -0
- pybinaryguard/cli/__init__.py +5 -0
- pybinaryguard/cli/commands.py +414 -0
- pybinaryguard/cli/formatters.py +720 -0
- pybinaryguard/cli/main.py +250 -0
- pybinaryguard/diagnostics/__init__.py +13 -0
- pybinaryguard/diagnostics/explainer.py +356 -0
- pybinaryguard/diagnostics/findings.py +146 -0
- pybinaryguard/diagnostics/suggestions.py +508 -0
- pybinaryguard/frameworks/__init__.py +20 -0
- pybinaryguard/frameworks/onnxruntime.py +214 -0
- pybinaryguard/frameworks/pytorch.py +223 -0
- pybinaryguard/frameworks/tensorflow.py +266 -0
- pybinaryguard/frameworks/tensorrt.py +189 -0
- pybinaryguard/models/__init__.py +19 -0
- pybinaryguard/models/enums.py +102 -0
- pybinaryguard/models/finding.py +109 -0
- pybinaryguard/models/package.py +121 -0
- pybinaryguard/models/system.py +118 -0
- pybinaryguard/plugins/__init__.py +19 -0
- pybinaryguard/plugins/contrib/__init__.py +7 -0
- pybinaryguard/plugins/contrib/gstreamer.py +109 -0
- pybinaryguard/plugins/contrib/jetson.py +385 -0
- pybinaryguard/plugins/contrib/opencv.py +306 -0
- pybinaryguard/plugins/contrib/tensorrt.py +426 -0
- pybinaryguard/plugins/hooks.py +273 -0
- pybinaryguard/plugins/loader.py +190 -0
- pybinaryguard/predictor/__init__.py +23 -0
- pybinaryguard/predictor/dependency_graph.py +226 -0
- pybinaryguard/predictor/linker_simulator.py +161 -0
- pybinaryguard/predictor/predictor.py +144 -0
- pybinaryguard/predictor/resolver.py +282 -0
- pybinaryguard/probes/__init__.py +73 -0
- pybinaryguard/probes/base.py +45 -0
- pybinaryguard/probes/board_probe.py +248 -0
- pybinaryguard/probes/cpu_probe.py +176 -0
- pybinaryguard/probes/glibc_probe.py +215 -0
- pybinaryguard/probes/gpu_probe.py +430 -0
- pybinaryguard/probes/library_probe.py +132 -0
- pybinaryguard/probes/os_probe.py +227 -0
- pybinaryguard/probes/python_probe.py +135 -0
- pybinaryguard/probes/toolchain_probe.py +89 -0
- pybinaryguard/probes/venv_probe.py +111 -0
- pybinaryguard/profiles/__init__.py +12 -0
- pybinaryguard/profiles/engine.py +343 -0
- pybinaryguard/rules/__init__.py +24 -0
- pybinaryguard/rules/base.py +57 -0
- pybinaryguard/rules/builtin/__init__.py +136 -0
- pybinaryguard/rules/builtin/arch_rules.py +86 -0
- pybinaryguard/rules/builtin/board_profile_rules.py +282 -0
- pybinaryguard/rules/builtin/container_rules.py +170 -0
- pybinaryguard/rules/builtin/cpu_rules.py +204 -0
- pybinaryguard/rules/builtin/cuda_rules.py +760 -0
- pybinaryguard/rules/builtin/dependency_rules.py +278 -0
- pybinaryguard/rules/builtin/framework_rules.py +252 -0
- pybinaryguard/rules/builtin/glibc_rules.py +320 -0
- pybinaryguard/rules/builtin/numpy_rules.py +110 -0
- pybinaryguard/rules/builtin/predictive_rules.py +165 -0
- pybinaryguard/rules/builtin/python_abi_rules.py +259 -0
- pybinaryguard/rules/builtin/source_build_rules.py +150 -0
- pybinaryguard/rules/builtin/venv_rules.py +159 -0
- pybinaryguard/rules/engine.py +123 -0
- pybinaryguard/scanner.py +904 -0
- pybinaryguard/scoring/__init__.py +19 -0
- pybinaryguard/scoring/engine.py +416 -0
- pybinaryguard/snapshot/__init__.py +20 -0
- pybinaryguard/snapshot/generator.py +168 -0
- pybinaryguard/snapshot/lockfile.py +152 -0
- pybinaryguard/snapshot/verifier.py +315 -0
- pybinaryguard/validators/__init__.py +7 -0
- pybinaryguard/validators/import_validator.py +231 -0
- pybinaryguard-1.0.0.dist-info/METADATA +876 -0
- pybinaryguard-1.0.0.dist-info/RECORD +91 -0
- pybinaryguard-1.0.0.dist-info/WHEEL +5 -0
- pybinaryguard-1.0.0.dist-info/entry_points.txt +8 -0
- pybinaryguard-1.0.0.dist-info/licenses/LICENSE +21 -0
- pybinaryguard-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""CLI command handlers for PyBinaryGuard."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any, Dict, List
|
|
8
|
+
|
|
9
|
+
from pybinaryguard.cli.formatters import get_formatter
|
|
10
|
+
from pybinaryguard.models.enums import ScanMode, Severity
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Map CLI severity names to Severity enum values
|
|
14
|
+
_SEVERITY_MAP = {
|
|
15
|
+
"critical": Severity.CRITICAL,
|
|
16
|
+
"warning": Severity.WARNING,
|
|
17
|
+
"info": Severity.INFO,
|
|
18
|
+
"all": Severity.INFO,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def dispatch(args: argparse.Namespace) -> int:
|
|
23
|
+
"""Dispatch to the appropriate command handler.
|
|
24
|
+
|
|
25
|
+
Returns the exit code.
|
|
26
|
+
"""
|
|
27
|
+
handlers = {
|
|
28
|
+
"scan": cmd_scan,
|
|
29
|
+
"check": cmd_check,
|
|
30
|
+
"profile": cmd_profile,
|
|
31
|
+
"doctor": cmd_doctor,
|
|
32
|
+
"inspect": cmd_inspect,
|
|
33
|
+
"snapshot": cmd_snapshot,
|
|
34
|
+
"verify": cmd_verify,
|
|
35
|
+
"export-tool-schema": cmd_export_tool_schema,
|
|
36
|
+
"simulate": cmd_simulate,
|
|
37
|
+
"validate": cmd_validate,
|
|
38
|
+
}
|
|
39
|
+
handler = handlers.get(args.command)
|
|
40
|
+
if handler is None:
|
|
41
|
+
print(f"Unknown command: {args.command}", file=sys.stderr)
|
|
42
|
+
return 3
|
|
43
|
+
return handler(args)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _resolve_scan_mode(args: argparse.Namespace) -> ScanMode:
|
|
47
|
+
"""Determine scan mode from CLI flags."""
|
|
48
|
+
if getattr(args, "fast", False):
|
|
49
|
+
return ScanMode.FAST
|
|
50
|
+
if getattr(args, "deep", False):
|
|
51
|
+
return ScanMode.DEEP
|
|
52
|
+
return ScanMode.STANDARD
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def cmd_scan(args: argparse.Namespace) -> int:
|
|
56
|
+
"""Full environment scan."""
|
|
57
|
+
from pybinaryguard.scanner import Scanner
|
|
58
|
+
|
|
59
|
+
formatter = get_formatter(args.format, no_color=args.no_color)
|
|
60
|
+
severity_threshold = _SEVERITY_MAP.get(args.severity, Severity.INFO)
|
|
61
|
+
ignored_rules = set(args.ignore) if args.ignore else set()
|
|
62
|
+
scan_mode = _resolve_scan_mode(args)
|
|
63
|
+
|
|
64
|
+
scanner = Scanner(
|
|
65
|
+
severity_threshold=severity_threshold,
|
|
66
|
+
ignored_rules=ignored_rules,
|
|
67
|
+
timeout=args.timeout,
|
|
68
|
+
scan_mode=scan_mode,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
report = scanner.run()
|
|
72
|
+
profile = scanner.get_profile()
|
|
73
|
+
|
|
74
|
+
output = formatter.format_scan(report, profile, verbose=args.verbose)
|
|
75
|
+
print(output)
|
|
76
|
+
|
|
77
|
+
return _exit_code(report.critical_count, report.warning_count)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def cmd_check(args: argparse.Namespace) -> int:
|
|
81
|
+
"""Check a specific installed package."""
|
|
82
|
+
from pybinaryguard.scanner import Scanner
|
|
83
|
+
|
|
84
|
+
formatter = get_formatter(args.format, no_color=args.no_color)
|
|
85
|
+
ignored_rules = set(args.ignore) if args.ignore else set()
|
|
86
|
+
scan_mode = _resolve_scan_mode(args)
|
|
87
|
+
|
|
88
|
+
scanner = Scanner(
|
|
89
|
+
packages=[args.package],
|
|
90
|
+
ignored_rules=ignored_rules,
|
|
91
|
+
timeout=args.timeout,
|
|
92
|
+
scan_mode=scan_mode,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
findings = scanner.check_package(args.package)
|
|
96
|
+
profile = scanner.get_profile()
|
|
97
|
+
|
|
98
|
+
output = formatter.format_check(findings, args.package, profile, verbose=args.verbose)
|
|
99
|
+
print(output)
|
|
100
|
+
|
|
101
|
+
critical = sum(1 for f in findings if f.severity == Severity.CRITICAL)
|
|
102
|
+
warning = sum(1 for f in findings if f.severity == Severity.WARNING)
|
|
103
|
+
return _exit_code(critical, warning)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def cmd_profile(args: argparse.Namespace) -> int:
|
|
107
|
+
"""Show system profile."""
|
|
108
|
+
from pybinaryguard.scanner import Scanner
|
|
109
|
+
|
|
110
|
+
formatter = get_formatter(args.format, no_color=args.no_color)
|
|
111
|
+
|
|
112
|
+
scanner = Scanner(timeout=args.timeout)
|
|
113
|
+
profile = scanner.get_profile()
|
|
114
|
+
|
|
115
|
+
output = formatter.format_profile(profile)
|
|
116
|
+
print(output)
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def cmd_doctor(args: argparse.Namespace) -> int:
|
|
121
|
+
"""Interactive troubleshooting wizard."""
|
|
122
|
+
from pybinaryguard.diagnostics.explainer import diagnose_error
|
|
123
|
+
from pybinaryguard.scanner import Scanner
|
|
124
|
+
|
|
125
|
+
formatter = get_formatter(args.format, no_color=args.no_color)
|
|
126
|
+
|
|
127
|
+
diagnosis: Dict[str, Any] = {}
|
|
128
|
+
|
|
129
|
+
# If an error message is provided, diagnose it
|
|
130
|
+
if args.error:
|
|
131
|
+
diagnosis["error"] = args.error
|
|
132
|
+
result = diagnose_error(args.error)
|
|
133
|
+
if result:
|
|
134
|
+
diagnosis["suggestions"] = [
|
|
135
|
+
result.get("explanation", ""),
|
|
136
|
+
result.get("fix_hint", ""),
|
|
137
|
+
]
|
|
138
|
+
else:
|
|
139
|
+
diagnosis["suggestions"] = [
|
|
140
|
+
"Could not match this error to a known pattern.",
|
|
141
|
+
"Try: pybinaryguard scan --verbose",
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
# If a package is specified, check it
|
|
145
|
+
if args.package:
|
|
146
|
+
diagnosis["package"] = args.package
|
|
147
|
+
scanner = Scanner(
|
|
148
|
+
packages=[args.package],
|
|
149
|
+
timeout=args.timeout,
|
|
150
|
+
)
|
|
151
|
+
findings = scanner.check_package(args.package)
|
|
152
|
+
if findings:
|
|
153
|
+
diagnosis["findings"] = findings
|
|
154
|
+
|
|
155
|
+
# If neither error nor package, prompt for info
|
|
156
|
+
if not args.error and not args.package:
|
|
157
|
+
diagnosis["suggestions"] = [
|
|
158
|
+
"Usage: pybinaryguard doctor --error 'your error message'",
|
|
159
|
+
" pybinaryguard doctor --package torch",
|
|
160
|
+
"Or run: pybinaryguard scan (for a full environment check)",
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
output = formatter.format_doctor(diagnosis)
|
|
164
|
+
print(output)
|
|
165
|
+
|
|
166
|
+
# Exit code based on findings
|
|
167
|
+
if "findings" in diagnosis:
|
|
168
|
+
critical = sum(1 for f in diagnosis["findings"] if f.severity == Severity.CRITICAL)
|
|
169
|
+
warning = sum(1 for f in diagnosis["findings"] if f.severity == Severity.WARNING)
|
|
170
|
+
return _exit_code(critical, warning)
|
|
171
|
+
return 0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def cmd_inspect(args: argparse.Namespace) -> int:
|
|
175
|
+
"""Inspect a .whl or .so file."""
|
|
176
|
+
from pybinaryguard.scanner import Scanner
|
|
177
|
+
|
|
178
|
+
formatter = get_formatter(args.format, no_color=args.no_color)
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
scanner = Scanner(timeout=args.timeout)
|
|
182
|
+
findings = scanner.inspect_file(args.file)
|
|
183
|
+
profile = scanner.get_profile()
|
|
184
|
+
except FileNotFoundError as exc:
|
|
185
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
186
|
+
return 3
|
|
187
|
+
except ValueError as exc:
|
|
188
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
189
|
+
return 3
|
|
190
|
+
|
|
191
|
+
output = formatter.format_check(findings, args.file, profile, verbose=args.verbose)
|
|
192
|
+
print(output)
|
|
193
|
+
|
|
194
|
+
critical = sum(1 for f in findings if f.severity == Severity.CRITICAL)
|
|
195
|
+
warning = sum(1 for f in findings if f.severity == Severity.WARNING)
|
|
196
|
+
return _exit_code(critical, warning)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def cmd_snapshot(args: argparse.Namespace) -> int:
|
|
200
|
+
"""Create environment snapshot."""
|
|
201
|
+
from pybinaryguard.scanner import Scanner
|
|
202
|
+
from pybinaryguard.snapshot import create_snapshot
|
|
203
|
+
|
|
204
|
+
# Get system profile and packages
|
|
205
|
+
scanner = Scanner(timeout=args.timeout)
|
|
206
|
+
profile = scanner.get_profile()
|
|
207
|
+
|
|
208
|
+
# Discover packages
|
|
209
|
+
report = scanner.run() # Run full scan to get packages
|
|
210
|
+
|
|
211
|
+
# Get packages from scanner (this is a simplified approach)
|
|
212
|
+
# In a full implementation, we'd need to expose packages from Scanner
|
|
213
|
+
packages = [] # Would be populated from scanner
|
|
214
|
+
|
|
215
|
+
# Create snapshot
|
|
216
|
+
lockfile = create_snapshot(
|
|
217
|
+
profile=profile,
|
|
218
|
+
packages=packages,
|
|
219
|
+
include_hashes=not args.no_hashes
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Output lockfile
|
|
223
|
+
if args.output:
|
|
224
|
+
lockfile.save(args.output)
|
|
225
|
+
print(f"Snapshot saved to {args.output}")
|
|
226
|
+
else:
|
|
227
|
+
print(lockfile.to_json())
|
|
228
|
+
|
|
229
|
+
return 0
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def cmd_verify(args: argparse.Namespace) -> int:
|
|
233
|
+
"""Verify environment against snapshot."""
|
|
234
|
+
from pybinaryguard.scanner import Scanner
|
|
235
|
+
from pybinaryguard.snapshot import load_lockfile, verify_snapshot
|
|
236
|
+
|
|
237
|
+
# Load lockfile
|
|
238
|
+
try:
|
|
239
|
+
lockfile = load_lockfile(args.lockfile)
|
|
240
|
+
except FileNotFoundError:
|
|
241
|
+
print(f"Error: Lockfile not found: {args.lockfile}", file=sys.stderr)
|
|
242
|
+
return 3
|
|
243
|
+
except Exception as exc:
|
|
244
|
+
print(f"Error loading lockfile: {exc}", file=sys.stderr)
|
|
245
|
+
return 3
|
|
246
|
+
|
|
247
|
+
# Get current system state
|
|
248
|
+
scanner = Scanner(timeout=args.timeout)
|
|
249
|
+
profile = scanner.get_profile()
|
|
250
|
+
|
|
251
|
+
# Run scan to get packages
|
|
252
|
+
report = scanner.run()
|
|
253
|
+
|
|
254
|
+
# Verify
|
|
255
|
+
packages = [] # Would be populated from scanner
|
|
256
|
+
result = verify_snapshot(
|
|
257
|
+
lockfile=lockfile,
|
|
258
|
+
profile=profile,
|
|
259
|
+
packages=packages,
|
|
260
|
+
check_hashes=not args.no_hashes,
|
|
261
|
+
run_compatibility_checks=not args.no_compat_checks
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Print results
|
|
265
|
+
if result.success:
|
|
266
|
+
print("✅ Verification PASSED")
|
|
267
|
+
print(f" Packages verified: {result.packages_verified}")
|
|
268
|
+
print(f" Binaries verified: {result.binaries_verified}")
|
|
269
|
+
else:
|
|
270
|
+
print("❌ Verification FAILED")
|
|
271
|
+
print(f" Errors: {result.error_count}")
|
|
272
|
+
print(f" Warnings: {result.warning_count}")
|
|
273
|
+
print(f" Hash mismatches: {result.hash_mismatches}")
|
|
274
|
+
|
|
275
|
+
# Print issues
|
|
276
|
+
for issue in result.issues[:10]: # Limit to first 10
|
|
277
|
+
severity_symbol = {"error": "❌", "warning": "⚠️", "info": "ℹ️"}.get(issue.severity, "•")
|
|
278
|
+
print(f"\n{severity_symbol} {issue.category.upper()}: {issue.message}")
|
|
279
|
+
if issue.package:
|
|
280
|
+
print(f" Package: {issue.package}")
|
|
281
|
+
if issue.expected:
|
|
282
|
+
print(f" Expected: {issue.expected}")
|
|
283
|
+
if issue.actual:
|
|
284
|
+
print(f" Actual: {issue.actual}")
|
|
285
|
+
|
|
286
|
+
if len(result.issues) > 10:
|
|
287
|
+
print(f"\n... and {len(result.issues) - 10} more issues")
|
|
288
|
+
|
|
289
|
+
return 0 if result.success else 1
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def cmd_export_tool_schema(args: argparse.Namespace) -> int:
|
|
293
|
+
"""Export tool schema for agent framework registration."""
|
|
294
|
+
from pybinaryguard.agent.schema import export_tool_schema
|
|
295
|
+
|
|
296
|
+
fmt = getattr(args, "schema_format", "openai")
|
|
297
|
+
print(export_tool_schema(format=fmt))
|
|
298
|
+
return 0
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def cmd_simulate(args: argparse.Namespace) -> int:
|
|
302
|
+
"""Simulate package installation compatibility."""
|
|
303
|
+
from pybinaryguard.agent import simulate_install
|
|
304
|
+
|
|
305
|
+
result = simulate_install(args.package_spec)
|
|
306
|
+
|
|
307
|
+
fmt = getattr(args, "format", "table")
|
|
308
|
+
if fmt == "json":
|
|
309
|
+
print(result.to_json())
|
|
310
|
+
else:
|
|
311
|
+
# Human-readable output
|
|
312
|
+
status = "COMPATIBLE" if result.predicted_compatible else "INCOMPATIBLE"
|
|
313
|
+
icon = "+" if result.predicted_compatible else "X"
|
|
314
|
+
print(f"[{icon}] {result.package_spec}: {status}")
|
|
315
|
+
print(f" Confidence: {result.confidence:.0%}")
|
|
316
|
+
print(f" Risk Level: {result.risk_level}")
|
|
317
|
+
|
|
318
|
+
if result.blockers:
|
|
319
|
+
print(f"\n Blockers ({len(result.blockers)}):")
|
|
320
|
+
for b in result.blockers:
|
|
321
|
+
print(f" - {b['message']}")
|
|
322
|
+
|
|
323
|
+
if result.warnings:
|
|
324
|
+
print(f"\n Warnings ({len(result.warnings)}):")
|
|
325
|
+
for w in result.warnings:
|
|
326
|
+
print(f" - {w['message']}")
|
|
327
|
+
|
|
328
|
+
return 0 if result.predicted_compatible else 2
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def cmd_validate(args: argparse.Namespace) -> int:
|
|
332
|
+
"""Test actual imports in isolated subprocesses."""
|
|
333
|
+
import json
|
|
334
|
+
|
|
335
|
+
from pybinaryguard.scanner import Scanner
|
|
336
|
+
from pybinaryguard.validators.import_validator import ImportValidator
|
|
337
|
+
|
|
338
|
+
scanner = Scanner(timeout=args.timeout, scan_mode=ScanMode.FAST)
|
|
339
|
+
report = scanner.run()
|
|
340
|
+
|
|
341
|
+
# Discover packages to test
|
|
342
|
+
validator = ImportValidator(
|
|
343
|
+
timeout=getattr(args, "import_timeout", 10.0),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Get packages with binary extensions
|
|
347
|
+
test_packages = getattr(args, "packages", None)
|
|
348
|
+
packages_to_test = []
|
|
349
|
+
|
|
350
|
+
# Walk site-packages to find packages with top_level.txt
|
|
351
|
+
import os
|
|
352
|
+
for sp in scanner.get_profile().site_packages_paths:
|
|
353
|
+
if not os.path.isdir(sp):
|
|
354
|
+
continue
|
|
355
|
+
for entry in os.listdir(sp):
|
|
356
|
+
if entry.endswith(".dist-info"):
|
|
357
|
+
pkg_name = entry.rsplit("-", 2)[0].replace("_", "-")
|
|
358
|
+
if test_packages and pkg_name not in test_packages:
|
|
359
|
+
continue
|
|
360
|
+
top_level = validator.get_top_level_name(
|
|
361
|
+
os.path.join(sp, entry), pkg_name
|
|
362
|
+
)
|
|
363
|
+
# Only test packages with binary files (unless specific list given)
|
|
364
|
+
if test_packages:
|
|
365
|
+
packages_to_test.append((pkg_name, top_level))
|
|
366
|
+
else:
|
|
367
|
+
# Check if package has .so files
|
|
368
|
+
pkg_dir = os.path.join(sp, top_level)
|
|
369
|
+
has_so = False
|
|
370
|
+
if os.path.isdir(pkg_dir):
|
|
371
|
+
for root, _dirs, files in os.walk(pkg_dir):
|
|
372
|
+
if any(f.endswith(".so") for f in files):
|
|
373
|
+
has_so = True
|
|
374
|
+
break
|
|
375
|
+
if has_so:
|
|
376
|
+
packages_to_test.append((pkg_name, top_level))
|
|
377
|
+
|
|
378
|
+
if not packages_to_test:
|
|
379
|
+
print("No packages with binary extensions found to validate.")
|
|
380
|
+
return 0
|
|
381
|
+
|
|
382
|
+
fmt = getattr(args, "format", "table")
|
|
383
|
+
print(f"Testing {len(packages_to_test)} package imports...\n")
|
|
384
|
+
|
|
385
|
+
results = validator.test_packages(packages_to_test)
|
|
386
|
+
failures = [r for r in results if not r.success]
|
|
387
|
+
|
|
388
|
+
if fmt == "json":
|
|
389
|
+
output = json.dumps([r.as_dict() for r in results], indent=2)
|
|
390
|
+
print(output)
|
|
391
|
+
else:
|
|
392
|
+
for r in results:
|
|
393
|
+
if r.success:
|
|
394
|
+
print(f" [OK] {r.package_name} ({r.duration_ms:.0f}ms)")
|
|
395
|
+
else:
|
|
396
|
+
print(f" [FAIL] {r.package_name}: {r.error_type} — {r.error_message}")
|
|
397
|
+
if r.category:
|
|
398
|
+
print(f" Category: {r.category}")
|
|
399
|
+
|
|
400
|
+
print(f"\n{len(results) - len(failures)} passed, {len(failures)} failed")
|
|
401
|
+
|
|
402
|
+
return 2 if failures else 0
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _exit_code(critical: int, warning: int) -> int:
|
|
406
|
+
"""Compute exit code from finding counts.
|
|
407
|
+
|
|
408
|
+
0 = all passed, 1 = warnings only, 2 = critical issues.
|
|
409
|
+
"""
|
|
410
|
+
if critical > 0:
|
|
411
|
+
return 2
|
|
412
|
+
if warning > 0:
|
|
413
|
+
return 1
|
|
414
|
+
return 0
|