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.
Files changed (91) hide show
  1. pybinaryguard/__init__.py +78 -0
  2. pybinaryguard/__main__.py +7 -0
  3. pybinaryguard/_compat/__init__.py +0 -0
  4. pybinaryguard/agent/__init__.py +63 -0
  5. pybinaryguard/agent/guard.py +232 -0
  6. pybinaryguard/agent/recommender.py +283 -0
  7. pybinaryguard/agent/schema.py +200 -0
  8. pybinaryguard/agent/simulator.py +430 -0
  9. pybinaryguard/agent/tool_interface.py +474 -0
  10. pybinaryguard/analyzers/__init__.py +209 -0
  11. pybinaryguard/analyzers/base.py +40 -0
  12. pybinaryguard/analyzers/dependency_analyzer.py +336 -0
  13. pybinaryguard/analyzers/elf_analyzer.py +754 -0
  14. pybinaryguard/analyzers/symbol_analyzer.py +280 -0
  15. pybinaryguard/analyzers/wheel_analyzer.py +308 -0
  16. pybinaryguard/cli/__init__.py +5 -0
  17. pybinaryguard/cli/commands.py +414 -0
  18. pybinaryguard/cli/formatters.py +720 -0
  19. pybinaryguard/cli/main.py +250 -0
  20. pybinaryguard/diagnostics/__init__.py +13 -0
  21. pybinaryguard/diagnostics/explainer.py +356 -0
  22. pybinaryguard/diagnostics/findings.py +146 -0
  23. pybinaryguard/diagnostics/suggestions.py +508 -0
  24. pybinaryguard/frameworks/__init__.py +20 -0
  25. pybinaryguard/frameworks/onnxruntime.py +214 -0
  26. pybinaryguard/frameworks/pytorch.py +223 -0
  27. pybinaryguard/frameworks/tensorflow.py +266 -0
  28. pybinaryguard/frameworks/tensorrt.py +189 -0
  29. pybinaryguard/models/__init__.py +19 -0
  30. pybinaryguard/models/enums.py +102 -0
  31. pybinaryguard/models/finding.py +109 -0
  32. pybinaryguard/models/package.py +121 -0
  33. pybinaryguard/models/system.py +118 -0
  34. pybinaryguard/plugins/__init__.py +19 -0
  35. pybinaryguard/plugins/contrib/__init__.py +7 -0
  36. pybinaryguard/plugins/contrib/gstreamer.py +109 -0
  37. pybinaryguard/plugins/contrib/jetson.py +385 -0
  38. pybinaryguard/plugins/contrib/opencv.py +306 -0
  39. pybinaryguard/plugins/contrib/tensorrt.py +426 -0
  40. pybinaryguard/plugins/hooks.py +273 -0
  41. pybinaryguard/plugins/loader.py +190 -0
  42. pybinaryguard/predictor/__init__.py +23 -0
  43. pybinaryguard/predictor/dependency_graph.py +226 -0
  44. pybinaryguard/predictor/linker_simulator.py +161 -0
  45. pybinaryguard/predictor/predictor.py +144 -0
  46. pybinaryguard/predictor/resolver.py +282 -0
  47. pybinaryguard/probes/__init__.py +73 -0
  48. pybinaryguard/probes/base.py +45 -0
  49. pybinaryguard/probes/board_probe.py +248 -0
  50. pybinaryguard/probes/cpu_probe.py +176 -0
  51. pybinaryguard/probes/glibc_probe.py +215 -0
  52. pybinaryguard/probes/gpu_probe.py +430 -0
  53. pybinaryguard/probes/library_probe.py +132 -0
  54. pybinaryguard/probes/os_probe.py +227 -0
  55. pybinaryguard/probes/python_probe.py +135 -0
  56. pybinaryguard/probes/toolchain_probe.py +89 -0
  57. pybinaryguard/probes/venv_probe.py +111 -0
  58. pybinaryguard/profiles/__init__.py +12 -0
  59. pybinaryguard/profiles/engine.py +343 -0
  60. pybinaryguard/rules/__init__.py +24 -0
  61. pybinaryguard/rules/base.py +57 -0
  62. pybinaryguard/rules/builtin/__init__.py +136 -0
  63. pybinaryguard/rules/builtin/arch_rules.py +86 -0
  64. pybinaryguard/rules/builtin/board_profile_rules.py +282 -0
  65. pybinaryguard/rules/builtin/container_rules.py +170 -0
  66. pybinaryguard/rules/builtin/cpu_rules.py +204 -0
  67. pybinaryguard/rules/builtin/cuda_rules.py +760 -0
  68. pybinaryguard/rules/builtin/dependency_rules.py +278 -0
  69. pybinaryguard/rules/builtin/framework_rules.py +252 -0
  70. pybinaryguard/rules/builtin/glibc_rules.py +320 -0
  71. pybinaryguard/rules/builtin/numpy_rules.py +110 -0
  72. pybinaryguard/rules/builtin/predictive_rules.py +165 -0
  73. pybinaryguard/rules/builtin/python_abi_rules.py +259 -0
  74. pybinaryguard/rules/builtin/source_build_rules.py +150 -0
  75. pybinaryguard/rules/builtin/venv_rules.py +159 -0
  76. pybinaryguard/rules/engine.py +123 -0
  77. pybinaryguard/scanner.py +904 -0
  78. pybinaryguard/scoring/__init__.py +19 -0
  79. pybinaryguard/scoring/engine.py +416 -0
  80. pybinaryguard/snapshot/__init__.py +20 -0
  81. pybinaryguard/snapshot/generator.py +168 -0
  82. pybinaryguard/snapshot/lockfile.py +152 -0
  83. pybinaryguard/snapshot/verifier.py +315 -0
  84. pybinaryguard/validators/__init__.py +7 -0
  85. pybinaryguard/validators/import_validator.py +231 -0
  86. pybinaryguard-1.0.0.dist-info/METADATA +876 -0
  87. pybinaryguard-1.0.0.dist-info/RECORD +91 -0
  88. pybinaryguard-1.0.0.dist-info/WHEEL +5 -0
  89. pybinaryguard-1.0.0.dist-info/entry_points.txt +8 -0
  90. pybinaryguard-1.0.0.dist-info/licenses/LICENSE +21 -0
  91. pybinaryguard-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,343 @@
1
+ """Board profile engine for embedded device intelligence.
2
+
3
+ This module provides the core profile system that loads board-specific
4
+ configuration and provides intelligent recommendations based on detected hardware.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from dataclasses import dataclass, field
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from pybinaryguard.models.system import SystemProfile
15
+
16
+
17
+ @dataclass
18
+ class BrokenWheel:
19
+ """Information about a known broken wheel for a specific board."""
20
+
21
+ package: str
22
+ versions: List[str]
23
+ reason: str
24
+ recommendation: str
25
+
26
+
27
+ @dataclass
28
+ class ValidatedStack:
29
+ """A validated combination of packages known to work together."""
30
+
31
+ name: str
32
+ packages: Dict[str, str]
33
+ build_opencv: bool = False
34
+
35
+
36
+ @dataclass
37
+ class BoardProfile:
38
+ """Comprehensive profile for a specific embedded board.
39
+
40
+ Contains hardware specifications, compatibility matrices, known issues,
41
+ and validated software stacks for embedded devices.
42
+ """
43
+
44
+ board_id: str
45
+ display_name: str
46
+ vendor: str
47
+ architecture: str
48
+
49
+ # Detection patterns
50
+ dt_model_patterns: List[str] = field(default_factory=list)
51
+ tegra_version: Optional[str] = None
52
+ cpu_model_patterns: List[str] = field(default_factory=list)
53
+ revision_codes: List[str] = field(default_factory=list)
54
+
55
+ # Hardware specs
56
+ cuda_compute_capability: Optional[str] = None
57
+ max_cuda_version: Optional[str] = None
58
+ recommended_jetpack: Optional[str] = None
59
+ recommended_os: Optional[str] = None
60
+ gpu_model: Optional[str] = None
61
+ tpu: Optional[str] = None
62
+ ram_gb: Optional[int] = None
63
+ recommended_glibc: Optional[str] = None
64
+ kernel_version_min: Optional[str] = None
65
+
66
+ # Compatibility
67
+ python_versions: List[str] = field(default_factory=list)
68
+ manylinux_tags: List[str] = field(default_factory=list)
69
+ cuda_versions: List[str] = field(default_factory=list)
70
+ tensorrt_versions: List[str] = field(default_factory=list)
71
+ opencv_backends: List[str] = field(default_factory=list)
72
+ tflite_versions: List[str] = field(default_factory=list)
73
+ required_packages: List[str] = field(default_factory=list)
74
+
75
+ # Known issues
76
+ broken_wheels: List[BrokenWheel] = field(default_factory=list)
77
+ incompatible_packages: List[str] = field(default_factory=list)
78
+
79
+ # Recommendations
80
+ recommendations: Dict[str, Any] = field(default_factory=dict)
81
+
82
+ # Validated stacks
83
+ validated_stacks: List[ValidatedStack] = field(default_factory=list)
84
+
85
+ @classmethod
86
+ def from_dict(cls, data: Dict[str, Any]) -> BoardProfile:
87
+ """Create a BoardProfile from a dictionary (loaded from JSON)."""
88
+ # Extract top-level fields
89
+ board_id = data["board_id"]
90
+ display_name = data["display_name"]
91
+ vendor = data["vendor"]
92
+ architecture = data["architecture"]
93
+
94
+ # Extract detection patterns
95
+ detection = data.get("detection", {})
96
+ dt_model_patterns = detection.get("dt_model_patterns", [])
97
+ tegra_version = detection.get("tegra_version")
98
+ cpu_model_patterns = detection.get("cpu_model_patterns", [])
99
+ revision_codes = detection.get("revision_codes", [])
100
+
101
+ # Extract specs
102
+ specs = data.get("specs", {})
103
+ cuda_compute_capability = specs.get("cuda_compute_capability")
104
+ max_cuda_version = specs.get("max_cuda_version")
105
+ recommended_jetpack = specs.get("recommended_jetpack")
106
+ recommended_os = specs.get("recommended_os")
107
+ gpu_model = specs.get("gpu_model")
108
+ tpu = specs.get("tpu")
109
+ ram_gb = specs.get("ram_gb")
110
+ recommended_glibc = specs.get("recommended_glibc")
111
+ kernel_version_min = specs.get("kernel_version_min")
112
+
113
+ # Extract compatibility
114
+ compat = data.get("compatibility", {})
115
+ python_versions = compat.get("python_versions", [])
116
+ manylinux_tags = compat.get("manylinux_tags", [])
117
+ cuda_versions = compat.get("cuda_versions", [])
118
+ tensorrt_versions = compat.get("tensorrt_versions", [])
119
+ opencv_backends = compat.get("opencv_backends", [])
120
+ tflite_versions = compat.get("tflite_versions", [])
121
+ required_packages = compat.get("required_packages", [])
122
+
123
+ # Extract known issues
124
+ issues = data.get("known_issues", {})
125
+ broken_wheels_data = issues.get("broken_wheels", [])
126
+ broken_wheels = [
127
+ BrokenWheel(
128
+ package=bw["package"],
129
+ versions=bw["versions"],
130
+ reason=bw["reason"],
131
+ recommendation=bw["recommendation"]
132
+ )
133
+ for bw in broken_wheels_data
134
+ ]
135
+ incompatible_packages = issues.get("incompatible_packages", [])
136
+
137
+ # Extract recommendations
138
+ recommendations = data.get("recommendations", {})
139
+
140
+ # Extract validated stacks
141
+ stacks_data = data.get("validated_stacks", [])
142
+ validated_stacks = [
143
+ ValidatedStack(
144
+ name=stack["name"],
145
+ packages=stack["packages"],
146
+ build_opencv=stack.get("build_opencv", False)
147
+ )
148
+ for stack in stacks_data
149
+ ]
150
+
151
+ return cls(
152
+ board_id=board_id,
153
+ display_name=display_name,
154
+ vendor=vendor,
155
+ architecture=architecture,
156
+ dt_model_patterns=dt_model_patterns,
157
+ tegra_version=tegra_version,
158
+ cpu_model_patterns=cpu_model_patterns,
159
+ revision_codes=revision_codes,
160
+ cuda_compute_capability=cuda_compute_capability,
161
+ max_cuda_version=max_cuda_version,
162
+ recommended_jetpack=recommended_jetpack,
163
+ recommended_os=recommended_os,
164
+ gpu_model=gpu_model,
165
+ tpu=tpu,
166
+ ram_gb=ram_gb,
167
+ recommended_glibc=recommended_glibc,
168
+ kernel_version_min=kernel_version_min,
169
+ python_versions=python_versions,
170
+ manylinux_tags=manylinux_tags,
171
+ cuda_versions=cuda_versions,
172
+ tensorrt_versions=tensorrt_versions,
173
+ opencv_backends=opencv_backends,
174
+ tflite_versions=tflite_versions,
175
+ required_packages=required_packages,
176
+ broken_wheels=broken_wheels,
177
+ incompatible_packages=incompatible_packages,
178
+ recommendations=recommendations,
179
+ validated_stacks=validated_stacks,
180
+ )
181
+
182
+ def matches_system(self, profile: SystemProfile) -> bool:
183
+ """Check if this board profile matches the given system profile.
184
+
185
+ Uses getattr for board-specific fields that may not be present on
186
+ every SystemProfile (dt_model, tegra_release, rpi_revision are
187
+ populated only by the BoardProbe on embedded hardware).
188
+ """
189
+ # Check device tree model
190
+ dt_model = getattr(profile, "dt_model", None) or ""
191
+ if dt_model:
192
+ for pattern in self.dt_model_patterns:
193
+ if pattern.lower() in dt_model.lower():
194
+ return True
195
+
196
+ # Check Tegra version (Jetson-specific)
197
+ tegra_release = getattr(profile, "tegra_release", None) or ""
198
+ if self.tegra_version and tegra_release:
199
+ if self.tegra_version in tegra_release:
200
+ return True
201
+
202
+ # Check CPU model
203
+ if profile.cpu_model:
204
+ for pattern in self.cpu_model_patterns:
205
+ if pattern.lower() in profile.cpu_model.lower():
206
+ # Additional architecture check
207
+ if profile.architecture.value == self.architecture:
208
+ return True
209
+
210
+ # Check revision codes (Raspberry Pi-specific)
211
+ rpi_revision = getattr(profile, "rpi_revision", None) or ""
212
+ if rpi_revision:
213
+ if rpi_revision.lower() in [rc.lower() for rc in self.revision_codes]:
214
+ return True
215
+
216
+ return False
217
+
218
+ def is_package_known_broken(self, package_name: str, version: Optional[str] = None) -> Optional[BrokenWheel]:
219
+ """Check if a package version is known to be broken on this board."""
220
+ for broken in self.broken_wheels:
221
+ if broken.package.lower() == package_name.lower():
222
+ if version is None or "*" in broken.versions:
223
+ return broken
224
+ # Simple version matching (could be enhanced)
225
+ for ver_pattern in broken.versions:
226
+ if ver_pattern.startswith(">=") and version:
227
+ min_ver = ver_pattern[2:]
228
+ if self._version_gte(version, min_ver):
229
+ return broken
230
+ elif ver_pattern.startswith("<") and version:
231
+ max_ver = ver_pattern[1:]
232
+ if self._version_lt(version, max_ver):
233
+ return broken
234
+ elif version and ver_pattern == version:
235
+ return broken
236
+ return None
237
+
238
+ def is_package_incompatible(self, package_name: str) -> bool:
239
+ """Check if a package is fundamentally incompatible with this board."""
240
+ for pkg in self.incompatible_packages:
241
+ if pkg.endswith("*"):
242
+ prefix = pkg[:-1]
243
+ if package_name.lower().startswith(prefix.lower()):
244
+ return True
245
+ elif pkg.lower() == package_name.lower():
246
+ return True
247
+ return False
248
+
249
+ @staticmethod
250
+ def _version_gte(v1: str, v2: str) -> bool:
251
+ """Compare versions (simple numeric comparison)."""
252
+ try:
253
+ parts1 = [int(p) for p in v1.split(".")[:3]]
254
+ parts2 = [int(p) for p in v2.split(".")[:3]]
255
+ return parts1 >= parts2
256
+ except (ValueError, IndexError):
257
+ return False
258
+
259
+ @staticmethod
260
+ def _version_lt(v1: str, v2: str) -> bool:
261
+ """Compare versions (simple numeric comparison)."""
262
+ try:
263
+ parts1 = [int(p) for p in v1.split(".")[:3]]
264
+ parts2 = [int(p) for p in v2.split(".")[:3]]
265
+ return parts1 < parts2
266
+ except (ValueError, IndexError):
267
+ return False
268
+
269
+
270
+ class ProfileEngine:
271
+ """Engine for loading and matching board profiles."""
272
+
273
+ def __init__(self, profile_dir: Optional[Path] = None):
274
+ """Initialize the profile engine.
275
+
276
+ Args:
277
+ profile_dir: Directory containing profile JSON files.
278
+ If None, uses the built-in profiles directory.
279
+ """
280
+ if profile_dir is None:
281
+ # Use built-in profiles
282
+ profile_dir = Path(__file__).parent
283
+
284
+ self.profile_dir = Path(profile_dir)
285
+ self.profiles: Dict[str, BoardProfile] = {}
286
+ self._load_all_profiles()
287
+
288
+ def _load_all_profiles(self) -> None:
289
+ """Load all board profiles from the profile directory."""
290
+ if not self.profile_dir.exists():
291
+ return
292
+
293
+ for json_file in self.profile_dir.glob("*.json"):
294
+ try:
295
+ profile = self._load_profile_file(json_file)
296
+ self.profiles[profile.board_id] = profile
297
+ except Exception:
298
+ # Skip invalid profiles silently
299
+ pass
300
+
301
+ def _load_profile_file(self, path: Path) -> BoardProfile:
302
+ """Load a single profile from a JSON file."""
303
+ with open(path, "r") as f:
304
+ data = json.load(f)
305
+ return BoardProfile.from_dict(data)
306
+
307
+ def match_profile(self, system: SystemProfile) -> Optional[BoardProfile]:
308
+ """Find the board profile that matches the given system profile."""
309
+ for profile in self.profiles.values():
310
+ if profile.matches_system(system):
311
+ return profile
312
+ return None
313
+
314
+ def get_profile(self, board_id: str) -> Optional[BoardProfile]:
315
+ """Get a specific board profile by ID."""
316
+ return self.profiles.get(board_id)
317
+
318
+ def list_profiles(self) -> List[str]:
319
+ """List all available board profile IDs."""
320
+ return list(self.profiles.keys())
321
+
322
+
323
+ # Module-level convenience functions
324
+
325
+ _engine: Optional[ProfileEngine] = None
326
+
327
+
328
+ def _get_engine() -> ProfileEngine:
329
+ """Get or create the global profile engine."""
330
+ global _engine
331
+ if _engine is None:
332
+ _engine = ProfileEngine()
333
+ return _engine
334
+
335
+
336
+ def load_profile(board_id: str) -> Optional[BoardProfile]:
337
+ """Load a board profile by ID."""
338
+ return _get_engine().get_profile(board_id)
339
+
340
+
341
+ def match_board_profile(system: SystemProfile) -> Optional[BoardProfile]:
342
+ """Match a system profile to a board profile."""
343
+ return _get_engine().match_profile(system)
@@ -0,0 +1,24 @@
1
+ """Rules subsystem for PyBinaryGuard.
2
+
3
+ This package provides the rule engine and all built-in compatibility rules
4
+ that PyBinaryGuard uses to detect binary incompatibilities.
5
+
6
+ Quick start::
7
+
8
+ from pybinaryguard.rules import RuleEngine
9
+
10
+ engine = RuleEngine.with_builtin_rules()
11
+ findings = engine.evaluate(system_profile, packages)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from pybinaryguard.rules.base import Rule
17
+ from pybinaryguard.rules.builtin import get_all_builtin_rules
18
+ from pybinaryguard.rules.engine import RuleEngine
19
+
20
+ __all__ = [
21
+ "Rule",
22
+ "RuleEngine",
23
+ "get_all_builtin_rules",
24
+ ]
@@ -0,0 +1,57 @@
1
+ """Base rule interface for compatibility checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import List
7
+
8
+ from pybinaryguard.models.system import SystemProfile
9
+ from pybinaryguard.models.package import PackageBinaryInfo
10
+ from pybinaryguard.models.finding import Finding
11
+
12
+
13
+ class Rule(ABC):
14
+ """Base class for all binary compatibility rules.
15
+
16
+ Every rule has a unique ``rule_id`` (e.g. ``"GLIBC_VERSION_MISMATCH"``),
17
+ a human-readable ``description``, and an ``evaluate`` method that inspects
18
+ the system profile together with a list of package binary analyses and
19
+ returns zero or more :class:`Finding` objects.
20
+
21
+ Subclasses may override :meth:`is_applicable` to skip evaluation when the
22
+ rule cannot possibly apply (e.g. CUDA rules when no GPU is present).
23
+ """
24
+
25
+ rule_id: str
26
+ description: str
27
+
28
+ def is_applicable(self, profile: SystemProfile) -> bool:
29
+ """Whether this rule should run given the system profile.
30
+
31
+ Override in subclasses to gate on system capabilities such as the
32
+ presence of a GPU, a specific libc implementation, etc.
33
+
34
+ Args:
35
+ profile: The current system profile.
36
+
37
+ Returns:
38
+ ``True`` if the rule should be evaluated; ``False`` to skip.
39
+ """
40
+ return True
41
+
42
+ @abstractmethod
43
+ def evaluate(
44
+ self,
45
+ profile: SystemProfile,
46
+ packages: List[PackageBinaryInfo],
47
+ ) -> List[Finding]:
48
+ """Evaluate this rule and return findings.
49
+
50
+ Args:
51
+ profile: The current system profile.
52
+ packages: List of packages with their binary analysis results.
53
+
54
+ Returns:
55
+ A (possibly empty) list of findings produced by this rule.
56
+ """
57
+ ...
@@ -0,0 +1,136 @@
1
+ """Built-in compatibility rules for PyBinaryGuard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List
6
+
7
+ from pybinaryguard.rules.base import Rule
8
+
9
+
10
+ def get_all_builtin_rules() -> List[Rule]:
11
+ """Instantiate and return all built-in rule classes.
12
+
13
+ Returns:
14
+ One instance of each built-in rule, ready for registration with a
15
+ :class:`~pybinaryguard.rules.engine.RuleEngine`.
16
+ """
17
+ from pybinaryguard.rules.builtin.arch_rules import ArchMismatchRule
18
+ from pybinaryguard.rules.builtin.container_rules import (
19
+ ContainerDriverMismatchRule,
20
+ ContainerNoGPUMountRule,
21
+ )
22
+ from pybinaryguard.rules.builtin.cpu_rules import (
23
+ AVX2RequiredRule,
24
+ IllegalInstructionRiskRule,
25
+ )
26
+ from pybinaryguard.rules.builtin.cuda_rules import (
27
+ ComputeCapabilityLowRule,
28
+ CUDADriverTooOldRule,
29
+ CUDALibMissingRule,
30
+ CUDAMinorMismatchRule,
31
+ CUDANotFoundRule,
32
+ CUDARuntimeMismatchRule,
33
+ CUDNNVersionMismatchRule,
34
+ )
35
+ from pybinaryguard.rules.builtin.glibc_rules import (
36
+ GLIBCVersionMismatchRule,
37
+ LibstdcxxVersionRule,
38
+ ManylinuxTagViolationRule,
39
+ MuslGlibcConflictRule,
40
+ )
41
+ from pybinaryguard.rules.builtin.numpy_rules import NumpyABIMismatchRule
42
+ from pybinaryguard.rules.builtin.python_abi_rules import (
43
+ DebugReleaseMixRule,
44
+ PythonABIMismatchRule,
45
+ PythonVersionMismatchRule,
46
+ )
47
+ from pybinaryguard.rules.builtin.board_profile_rules import (
48
+ BOARD_CUDA_VERSION_MISMATCH,
49
+ BOARD_GLIBC_MISMATCH,
50
+ BOARD_INCOMPATIBLE_PACKAGE,
51
+ BOARD_PYTHON_VERSION_UNSUPPORTED,
52
+ KNOWN_BROKEN_WHEEL,
53
+ )
54
+ from pybinaryguard.rules.builtin.framework_rules import (
55
+ ONNX_RUNTIME_PROVIDER_MISMATCH,
56
+ PYTORCH_CUDA_ABI_MISMATCH,
57
+ PYTORCH_TORCHVISION_INCOMPATIBLE,
58
+ TENSORFLOW_COMPUTE_CAPABILITY_LOW,
59
+ TENSORRT_INCOMPATIBLE,
60
+ )
61
+ from pybinaryguard.rules.builtin.predictive_rules import (
62
+ PREDICTED_IMPORT_ERROR,
63
+ UNRESOLVED_DEPENDENCY_CHAIN,
64
+ )
65
+ from pybinaryguard.rules.builtin.source_build_rules import (
66
+ MissingPythonHeadersRule,
67
+ SourceBuildDetectionRule,
68
+ SourceBuildNoCompilerRule,
69
+ )
70
+ from pybinaryguard.rules.builtin.dependency_rules import (
71
+ DependencyConflictRule,
72
+ MissingDependencyRule,
73
+ )
74
+ from pybinaryguard.rules.builtin.venv_rules import (
75
+ CondaPipMixingRule,
76
+ MixedEnvironmentRule,
77
+ SystemPythonWarningRule,
78
+ UserSiteLeakRule,
79
+ )
80
+
81
+ return [
82
+ # GLIBC rules
83
+ GLIBCVersionMismatchRule(),
84
+ MuslGlibcConflictRule(),
85
+ ManylinuxTagViolationRule(),
86
+ LibstdcxxVersionRule(),
87
+ # Python ABI rules
88
+ PythonABIMismatchRule(),
89
+ PythonVersionMismatchRule(),
90
+ DebugReleaseMixRule(),
91
+ # Architecture rules
92
+ ArchMismatchRule(),
93
+ # CPU instruction-set rules
94
+ AVX2RequiredRule(),
95
+ IllegalInstructionRiskRule(),
96
+ # CUDA / GPU rules
97
+ CUDADriverTooOldRule(),
98
+ CUDARuntimeMismatchRule(),
99
+ CUDAMinorMismatchRule(),
100
+ CUDNNVersionMismatchRule(),
101
+ CUDANotFoundRule(),
102
+ ComputeCapabilityLowRule(),
103
+ CUDALibMissingRule(),
104
+ # NumPy ABI rules
105
+ NumpyABIMismatchRule(),
106
+ # Container rules
107
+ ContainerNoGPUMountRule(),
108
+ ContainerDriverMismatchRule(),
109
+ # Board profile rules (embedded intelligence)
110
+ KNOWN_BROKEN_WHEEL(),
111
+ BOARD_INCOMPATIBLE_PACKAGE(),
112
+ BOARD_CUDA_VERSION_MISMATCH(),
113
+ BOARD_GLIBC_MISMATCH(),
114
+ BOARD_PYTHON_VERSION_UNSUPPORTED(),
115
+ # AI framework rules (deep inspection)
116
+ PYTORCH_CUDA_ABI_MISMATCH(),
117
+ PYTORCH_TORCHVISION_INCOMPATIBLE(),
118
+ TENSORFLOW_COMPUTE_CAPABILITY_LOW(),
119
+ TENSORRT_INCOMPATIBLE(),
120
+ ONNX_RUNTIME_PROVIDER_MISMATCH(),
121
+ # Predictive rules (runtime simulation)
122
+ PREDICTED_IMPORT_ERROR(),
123
+ UNRESOLVED_DEPENDENCY_CHAIN(),
124
+ # Source build rules
125
+ SourceBuildDetectionRule(),
126
+ SourceBuildNoCompilerRule(),
127
+ MissingPythonHeadersRule(),
128
+ # Dependency conflict rules
129
+ DependencyConflictRule(),
130
+ MissingDependencyRule(),
131
+ # Virtual environment rules
132
+ SystemPythonWarningRule(),
133
+ MixedEnvironmentRule(),
134
+ UserSiteLeakRule(),
135
+ CondaPipMixingRule(),
136
+ ]
@@ -0,0 +1,86 @@
1
+ """Architecture compatibility rules.
2
+
3
+ These rules detect when a package contains shared objects compiled for a
4
+ different CPU architecture than the one running on the host. For example,
5
+ an x86_64 ``.so`` file will not load on an ARM system.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import List
11
+
12
+ from pybinaryguard.models.enums import Architecture, Severity
13
+ from pybinaryguard.models.finding import Finding
14
+ from pybinaryguard.models.package import PackageBinaryInfo
15
+ from pybinaryguard.models.system import SystemProfile
16
+ from pybinaryguard.rules.base import Rule
17
+
18
+
19
+ class ArchMismatchRule(Rule):
20
+ """Detects packages containing binaries for the wrong CPU architecture.
21
+
22
+ Every ELF shared object records the target architecture in its header.
23
+ If that does not match the host CPU, the dynamic linker will refuse to
24
+ load it, resulting in an ``OSError`` or ``ImportError``.
25
+ """
26
+
27
+ rule_id = "ARCH_MISMATCH"
28
+ description = (
29
+ "Check that each package's binary architecture matches the host CPU."
30
+ )
31
+
32
+ def evaluate(
33
+ self,
34
+ profile: SystemProfile,
35
+ packages: List[PackageBinaryInfo],
36
+ ) -> List[Finding]:
37
+ findings: List[Finding] = []
38
+ sys_arch = profile.architecture
39
+
40
+ if sys_arch == Architecture.UNKNOWN:
41
+ return findings
42
+
43
+ for pkg in packages:
44
+ if pkg.target_architecture is None:
45
+ continue
46
+ if pkg.target_architecture == Architecture.UNKNOWN:
47
+ continue
48
+ if pkg.target_architecture != sys_arch:
49
+ findings.append(
50
+ Finding(
51
+ rule_id=self.rule_id,
52
+ severity=Severity.CRITICAL,
53
+ title=(
54
+ f"{pkg.package_name} is built for "
55
+ f"{pkg.target_architecture.value}"
56
+ ),
57
+ explanation=(
58
+ f"Package {pkg.package_name} "
59
+ f"{pkg.package_version} contains binaries "
60
+ f"compiled for {pkg.target_architecture.value} "
61
+ f"but your system is {sys_arch.value}. "
62
+ f"Binary code is architecture-specific: an "
63
+ f"x86_64 library cannot run on an ARM CPU and "
64
+ f"vice versa. The package will fail to import "
65
+ f"with an error like 'wrong ELF class' or "
66
+ f"'cannot open shared object file'."
67
+ ),
68
+ technical_detail=(
69
+ f"Package arch: {pkg.target_architecture.value}, "
70
+ f"System arch: {sys_arch.value}"
71
+ ),
72
+ suggestion=(
73
+ f"Reinstall the package so pip selects the "
74
+ f"correct wheel for your architecture:\n"
75
+ f" pip install --force-reinstall "
76
+ f"{pkg.package_name}=={pkg.package_version}\n\n"
77
+ f"If no pre-built wheel exists for "
78
+ f"{sys_arch.value}, build from source:\n"
79
+ f" pip install --no-binary :all: "
80
+ f"{pkg.package_name}"
81
+ ),
82
+ package=pkg.package_name,
83
+ package_version=pkg.package_version,
84
+ )
85
+ )
86
+ return findings