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,189 @@
1
+ """TensorRT framework deep inspection.
2
+
3
+ Validates TensorRT engine metadata, CUDA compatibility, and
4
+ plugin availability beyond generic binary checks.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from typing import Dict, List, Optional
11
+
12
+ from pybinaryguard.models.package import PackageBinaryInfo
13
+ from pybinaryguard.models.system import SystemProfile
14
+
15
+
16
+ def validate_tensorrt_engine(
17
+ package: PackageBinaryInfo,
18
+ profile: SystemProfile
19
+ ) -> List[Dict[str, str]]:
20
+ """Validate TensorRT installation and compatibility.
21
+
22
+ Checks:
23
+ - TensorRT version compatibility with CUDA
24
+ - Required plugins availability
25
+ - Compute capability support
26
+ - cuDNN version requirements
27
+
28
+ Args:
29
+ package: PackageBinaryInfo for tensorrt package
30
+ profile: System profile
31
+
32
+ Returns:
33
+ List of compatibility issues found
34
+ """
35
+ issues: List[Dict[str, str]] = []
36
+
37
+ if not package.name.lower().startswith("tensorrt"):
38
+ return issues
39
+
40
+ # Extract TensorRT version from package
41
+ trt_version = package.version
42
+ if not trt_version:
43
+ return issues
44
+
45
+ # TensorRT 8.6+ requires CUDA 11.x or 12.x
46
+ if trt_version.startswith("8.6") or trt_version.startswith("8.5"):
47
+ if profile.cuda_runtime_version:
48
+ cuda_major = int(profile.cuda_runtime_version.split(".")[0])
49
+ if cuda_major < 11:
50
+ issues.append({
51
+ "issue": "tensorrt_cuda_too_old",
52
+ "severity": "critical",
53
+ "message": (
54
+ f"TensorRT {trt_version} requires CUDA 11.x or 12.x, "
55
+ f"but system has CUDA {profile.cuda_runtime_version}"
56
+ ),
57
+ "recommendation": "Upgrade CUDA runtime or use TensorRT 7.x",
58
+ })
59
+
60
+ # Check compute capability requirements
61
+ if profile.cuda_compute_capability:
62
+ compute_major = int(profile.cuda_compute_capability.split(".")[0])
63
+
64
+ # TensorRT 8.x requires compute capability >= 5.0
65
+ if trt_version.startswith("8."):
66
+ if compute_major < 5:
67
+ issues.append({
68
+ "issue": "tensorrt_compute_capability_too_low",
69
+ "severity": "critical",
70
+ "message": (
71
+ f"TensorRT 8.x requires compute capability >= 5.0, "
72
+ f"but GPU has {profile.cuda_compute_capability}"
73
+ ),
74
+ "recommendation": "Upgrade GPU or use TensorRT 7.x",
75
+ })
76
+
77
+ # Check for common TensorRT plugins
78
+ available_plugins = detect_tensorrt_plugins(package)
79
+
80
+ if not available_plugins:
81
+ issues.append({
82
+ "issue": "no_tensorrt_plugins",
83
+ "severity": "info",
84
+ "message": "No TensorRT plugins detected in installation",
85
+ "recommendation": "Install tensorrt-plugins package if needed for custom layers",
86
+ })
87
+
88
+ return issues
89
+
90
+
91
+ def detect_tensorrt_plugins(package: PackageBinaryInfo) -> List[str]:
92
+ """Detect available TensorRT plugins from shared objects.
93
+
94
+ Args:
95
+ package: PackageBinaryInfo for tensorrt package
96
+
97
+ Returns:
98
+ List of detected plugin names
99
+ """
100
+ plugins = []
101
+
102
+ for so in package.shared_objects:
103
+ if not so.path:
104
+ continue
105
+
106
+ # TensorRT plugins typically named libnvinfer_plugin.so
107
+ if "plugin" in so.path.lower():
108
+ plugins.append(so.path)
109
+
110
+ return plugins
111
+
112
+
113
+ def check_tensorrt_cudnn_compatibility(
114
+ tensorrt_version: Optional[str],
115
+ cudnn_version: Optional[str]
116
+ ) -> Optional[str]:
117
+ """Check TensorRT and cuDNN version compatibility.
118
+
119
+ Args:
120
+ tensorrt_version: TensorRT version (e.g., "8.6.1")
121
+ cudnn_version: cuDNN version (e.g., "8.9.0")
122
+
123
+ Returns:
124
+ Error message if incompatible, None if compatible
125
+ """
126
+ if not tensorrt_version or not cudnn_version:
127
+ return None
128
+
129
+ # Compatibility matrix (TensorRT -> cuDNN major version)
130
+ compatibility_matrix = {
131
+ "8.6": "8",
132
+ "8.5": "8",
133
+ "8.4": "8",
134
+ "8.2": "8",
135
+ "8.0": "8",
136
+ "7.2": "7",
137
+ "7.1": "7",
138
+ }
139
+
140
+ try:
141
+ trt_major_minor = ".".join(tensorrt_version.split(".")[:2])
142
+ cudnn_major = cudnn_version.split(".")[0]
143
+
144
+ expected_cudnn_major = compatibility_matrix.get(trt_major_minor)
145
+ if expected_cudnn_major and cudnn_major != expected_cudnn_major:
146
+ return (
147
+ f"TensorRT {trt_major_minor} expects cuDNN {expected_cudnn_major}.x, "
148
+ f"but found cuDNN {cudnn_version}"
149
+ )
150
+ except (ValueError, IndexError):
151
+ pass
152
+
153
+ return None
154
+
155
+
156
+ def detect_tensorrt_precision_support(
157
+ package: PackageBinaryInfo,
158
+ profile: SystemProfile
159
+ ) -> Dict[str, bool]:
160
+ """Detect supported precision modes (FP32, FP16, INT8).
161
+
162
+ Args:
163
+ package: PackageBinaryInfo for tensorrt package
164
+ profile: System profile
165
+
166
+ Returns:
167
+ Dictionary with supported precision modes
168
+ """
169
+ support = {
170
+ "fp32": True, # Always supported
171
+ "fp16": False,
172
+ "int8": False,
173
+ }
174
+
175
+ # FP16 requires compute capability >= 5.3
176
+ if profile.cuda_compute_capability:
177
+ try:
178
+ major, minor = profile.cuda_compute_capability.split(".")
179
+ compute_val = int(major) * 10 + int(minor)
180
+
181
+ if compute_val >= 53: # 5.3
182
+ support["fp16"] = True
183
+
184
+ if compute_val >= 61: # 6.1
185
+ support["int8"] = True
186
+ except (ValueError, IndexError):
187
+ pass
188
+
189
+ return support
@@ -0,0 +1,19 @@
1
+ """Core data models for PyBinaryGuard."""
2
+
3
+ from .enums import Architecture, ContainerRuntime, ScanMode, Severity
4
+ from .finding import Finding, ScanReport
5
+ from .package import PackageBinaryInfo, SharedObjectInfo, WheelTag
6
+ from .system import SystemProfile
7
+
8
+ __all__ = [
9
+ "Architecture",
10
+ "ContainerRuntime",
11
+ "Finding",
12
+ "PackageBinaryInfo",
13
+ "ScanMode",
14
+ "ScanReport",
15
+ "Severity",
16
+ "SharedObjectInfo",
17
+ "SystemProfile",
18
+ "WheelTag",
19
+ ]
@@ -0,0 +1,102 @@
1
+ """Core enumerations for PyBinaryGuard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class Severity(Enum):
9
+ """Severity level for diagnostic findings."""
10
+
11
+ CRITICAL = "critical"
12
+ WARNING = "warning"
13
+ INFO = "info"
14
+ PASSED = "passed"
15
+
16
+ def __lt__(self, other: object) -> bool:
17
+ if not isinstance(other, Severity):
18
+ return NotImplemented
19
+ order = {Severity.CRITICAL: 0, Severity.WARNING: 1, Severity.INFO: 2, Severity.PASSED: 3}
20
+ return order[self] < order[other]
21
+
22
+ def __le__(self, other: object) -> bool:
23
+ if not isinstance(other, Severity):
24
+ return NotImplemented
25
+ return self == other or self < other
26
+
27
+
28
+ class Architecture(Enum):
29
+ """CPU architecture identifiers."""
30
+
31
+ X86_64 = "x86_64"
32
+ AARCH64 = "aarch64"
33
+ ARMV7L = "armv7l"
34
+ I686 = "i686"
35
+ PPC64LE = "ppc64le"
36
+ S390X = "s390x"
37
+ UNKNOWN = "unknown"
38
+
39
+ @classmethod
40
+ def from_machine(cls, machine: str) -> Architecture:
41
+ """Convert platform.machine() output to Architecture enum."""
42
+ mapping = {
43
+ "x86_64": cls.X86_64,
44
+ "AMD64": cls.X86_64,
45
+ "aarch64": cls.AARCH64,
46
+ "arm64": cls.AARCH64,
47
+ "armv7l": cls.ARMV7L,
48
+ "armv6l": cls.ARMV7L,
49
+ "i686": cls.I686,
50
+ "i386": cls.I686,
51
+ "ppc64le": cls.PPC64LE,
52
+ "s390x": cls.S390X,
53
+ }
54
+ return mapping.get(machine, cls.UNKNOWN)
55
+
56
+ @property
57
+ def elf_machine(self) -> int:
58
+ """Return the ELF e_machine constant for this architecture."""
59
+ mapping = {
60
+ Architecture.X86_64: 62, # EM_X86_64
61
+ Architecture.AARCH64: 183, # EM_AARCH64
62
+ Architecture.ARMV7L: 40, # EM_ARM
63
+ Architecture.I686: 3, # EM_386
64
+ Architecture.PPC64LE: 21, # EM_PPC64
65
+ Architecture.S390X: 22, # EM_S390
66
+ }
67
+ return mapping.get(self, 0)
68
+
69
+
70
+ class ContainerRuntime(Enum):
71
+ """Container runtime types."""
72
+
73
+ DOCKER = "docker"
74
+ PODMAN = "podman"
75
+ LXC = "lxc"
76
+ CONTAINERD = "containerd"
77
+ NONE = "none"
78
+
79
+
80
+ class ScanMode(Enum):
81
+ """Scan depth mode.
82
+
83
+ FAST:
84
+ Metadata-only scan. Reads WHEEL/METADATA files and checks tags
85
+ against the system profile. Skips ELF binary parsing entirely.
86
+ Target: < 1 second for 100 packages.
87
+
88
+ STANDARD:
89
+ Default mode. Parses ELF headers for architecture, GLIBC version
90
+ requirements, and DT_NEEDED libraries. Runs all built-in rules.
91
+ Target: < 3 seconds for 100 packages (x86).
92
+
93
+ DEEP:
94
+ Full analysis. Everything in STANDARD plus: SHA256 hash verification
95
+ of all shared objects, recursive DT_NEEDED dependency chain resolution,
96
+ symbol-level version checking, and predictive import failure simulation.
97
+ Target: < 10 seconds for 100 packages.
98
+ """
99
+
100
+ FAST = "fast"
101
+ STANDARD = "standard"
102
+ DEEP = "deep"
@@ -0,0 +1,109 @@
1
+ """Finding and report data models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional, List, Dict
7
+
8
+ from .enums import Severity
9
+
10
+
11
+ @dataclass
12
+ class Finding:
13
+ """A single diagnostic result from the compatibility check."""
14
+
15
+ rule_id: str
16
+ severity: Severity
17
+ title: str
18
+ explanation: str
19
+ technical_detail: str = ""
20
+ suggestion: str = ""
21
+ package: Optional[str] = None
22
+ package_version: Optional[str] = None
23
+ confidence: float = 1.0
24
+ related_error: Optional[str] = None
25
+
26
+ def as_dict(self) -> Dict[str, object]:
27
+ """Convert to a JSON-serializable dict."""
28
+ result: Dict[str, object] = {
29
+ "rule_id": self.rule_id,
30
+ "severity": self.severity.value,
31
+ "title": self.title,
32
+ "explanation": self.explanation,
33
+ }
34
+ if self.technical_detail:
35
+ result["technical_detail"] = self.technical_detail
36
+ if self.suggestion:
37
+ result["suggestion"] = self.suggestion
38
+ if self.package:
39
+ result["package"] = self.package
40
+ if self.package_version:
41
+ result["package_version"] = self.package_version
42
+ if self.confidence < 1.0:
43
+ result["confidence"] = self.confidence
44
+ if self.related_error:
45
+ result["related_error"] = self.related_error
46
+ return result
47
+
48
+
49
+ @dataclass
50
+ class ScanReport:
51
+ """Complete scan report containing all findings and metadata."""
52
+
53
+ findings: List[Finding] = field(default_factory=list)
54
+ packages_scanned: int = 0
55
+ total_packages: int = 0
56
+ scan_duration_ms: float = 0.0
57
+ detected_board: Optional[str] = None # Display name of detected embedded board
58
+ score_breakdown: Optional[object] = None # ScoreBreakdown from scoring.engine
59
+
60
+ @property
61
+ def critical_count(self) -> int:
62
+ return sum(1 for f in self.findings if f.severity == Severity.CRITICAL)
63
+
64
+ @property
65
+ def warning_count(self) -> int:
66
+ return sum(1 for f in self.findings if f.severity == Severity.WARNING)
67
+
68
+ @property
69
+ def info_count(self) -> int:
70
+ return sum(1 for f in self.findings if f.severity == Severity.INFO)
71
+
72
+ @property
73
+ def passed_count(self) -> int:
74
+ return sum(1 for f in self.findings if f.severity == Severity.PASSED)
75
+
76
+ @property
77
+ def health_score(self) -> int:
78
+ """Return the v2 weighted health score if available, else v1 linear."""
79
+ if self.score_breakdown is not None:
80
+ return round(self.score_breakdown.overall_score)
81
+ score = 100 - (self.critical_count * 25) - (self.warning_count * 5) - (self.info_count * 1)
82
+ return max(0, min(100, score))
83
+
84
+ @property
85
+ def health_label(self) -> str:
86
+ if self.score_breakdown is not None:
87
+ return self.score_breakdown.overall_label
88
+ score = self.health_score
89
+ if score >= 90:
90
+ return "Excellent"
91
+ elif score >= 70:
92
+ return "Good"
93
+ elif score >= 50:
94
+ return "Needs Attention"
95
+ else:
96
+ return "Critical"
97
+
98
+ def summary_dict(self) -> Dict[str, object]:
99
+ result: Dict[str, object] = {
100
+ "total_packages": self.total_packages,
101
+ "packages_scanned": self.packages_scanned,
102
+ "critical": self.critical_count,
103
+ "warning": self.warning_count,
104
+ "info": self.info_count,
105
+ "passed": self.passed_count,
106
+ }
107
+ if self.detected_board:
108
+ result["detected_board"] = self.detected_board
109
+ return result
@@ -0,0 +1,121 @@
1
+ """Package binary information data models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional, Tuple, List, Set
7
+
8
+ from .enums import Architecture
9
+
10
+
11
+ @dataclass
12
+ class SharedObjectInfo:
13
+ """Information extracted from a single .so file."""
14
+
15
+ path: str
16
+ filename: str
17
+ architecture: Architecture = Architecture.UNKNOWN
18
+ elf_class: int = 0 # 32 or 64
19
+ endianness: str = "little"
20
+ dt_needed: List[str] = field(default_factory=list)
21
+ dt_soname: Optional[str] = None
22
+ dt_rpath: Optional[str] = None
23
+ dt_runpath: Optional[str] = None
24
+ required_glibc: Optional[Tuple[int, int]] = None
25
+ required_glibcxx: Optional[str] = None
26
+ gnu_version_requirements: List[str] = field(default_factory=list)
27
+ has_python_symbols: bool = False
28
+ build_id: Optional[str] = None
29
+ file_size: int = 0
30
+ sha256: Optional[str] = None # Populated in DEEP scan mode
31
+
32
+
33
+ @dataclass
34
+ class WheelTag:
35
+ """A parsed wheel compatibility tag."""
36
+
37
+ interpreter: str # e.g., "cp312"
38
+ abi: str # e.g., "cp312"
39
+ platform: str # e.g., "manylinux_2_17_x86_64"
40
+
41
+
42
+ @dataclass
43
+ class PackageBinaryInfo:
44
+ """Complete binary analysis of an installed Python package."""
45
+
46
+ package_name: str
47
+ package_version: str
48
+ install_path: str
49
+
50
+ # Wheel metadata
51
+ wheel_tags: List[WheelTag] = field(default_factory=list)
52
+ is_pure_python: bool = True
53
+
54
+ # Binary analysis
55
+ shared_objects: List[SharedObjectInfo] = field(default_factory=list)
56
+ required_glibc: Optional[Tuple[int, int]] = None
57
+ required_glibcxx: Optional[str] = None
58
+ target_architecture: Optional[Architecture] = None
59
+ required_libraries: Set[str] = field(default_factory=set)
60
+ missing_libraries: Set[str] = field(default_factory=set)
61
+
62
+ # Framework-specific
63
+ cuda_build_version: Optional[Tuple[int, int]] = None
64
+ numpy_api_version: Optional[int] = None
65
+
66
+ @property
67
+ def name(self) -> str:
68
+ """Alias for package_name (used by snapshot module)."""
69
+ return self.package_name
70
+
71
+ @property
72
+ def version(self) -> Optional[str]:
73
+ """Alias for package_version (used by snapshot module)."""
74
+ return self.package_version
75
+
76
+ @property
77
+ def cuda_version(self) -> Optional[str]:
78
+ """CUDA version string extracted from cuda_build_version tuple."""
79
+ if self.cuda_build_version:
80
+ return f"{self.cuda_build_version[0]}.{self.cuda_build_version[1]}"
81
+ return None
82
+
83
+ @property
84
+ def has_binaries(self) -> bool:
85
+ """Whether this package contains any compiled extensions."""
86
+ return len(self.shared_objects) > 0
87
+
88
+ @property
89
+ def so_count(self) -> int:
90
+ """Number of shared object files in this package."""
91
+ return len(self.shared_objects)
92
+
93
+ @property
94
+ def manylinux_tag(self) -> Optional[str]:
95
+ """Extract the manylinux tag from wheel tags, if any."""
96
+ for tag in self.wheel_tags:
97
+ if "manylinux" in tag.platform:
98
+ return tag.platform
99
+ return None
100
+
101
+ @property
102
+ def manylinux_glibc(self) -> Optional[Tuple[int, int]]:
103
+ """Extract the GLIBC version claimed by the manylinux tag."""
104
+ tag = self.manylinux_tag
105
+ if not tag:
106
+ return None
107
+ # Parse manylinux_2_17_x86_64 or manylinux2014_x86_64
108
+ import re
109
+ match = re.search(r"manylinux_(\d+)_(\d+)", tag)
110
+ if match:
111
+ return (int(match.group(1)), int(match.group(2)))
112
+ # Legacy tags
113
+ legacy_map = {
114
+ "manylinux1": (2, 5),
115
+ "manylinux2010": (2, 12),
116
+ "manylinux2014": (2, 17),
117
+ }
118
+ for prefix, version in legacy_map.items():
119
+ if tag.startswith(prefix):
120
+ return version
121
+ return None
@@ -0,0 +1,118 @@
1
+ """System profile data model — the fingerprint of your machine."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional, Tuple, List, FrozenSet, Dict
7
+
8
+ from .enums import Architecture, ContainerRuntime
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class SystemProfile:
13
+ """Complete profile of the current system's binary compatibility surface."""
14
+
15
+ # Python
16
+ python_version: Tuple[int, int, int] = (0, 0, 0)
17
+ python_abi_tag: str = ""
18
+ python_implementation: str = "cpython"
19
+ python_executable: str = ""
20
+ stable_abi_supported: bool = False
21
+ python_debug_build: bool = False
22
+
23
+ # System
24
+ os_name: str = ""
25
+ os_version: str = ""
26
+ kernel_version: str = ""
27
+ architecture: Architecture = Architecture.UNKNOWN
28
+ glibc_version: Optional[Tuple[int, int]] = None
29
+ musl_version: Optional[Tuple[int, int]] = None
30
+
31
+ # CPU
32
+ cpu_model: str = ""
33
+ cpu_flags: FrozenSet[str] = field(default_factory=frozenset)
34
+ has_avx: bool = False
35
+ has_avx2: bool = False
36
+ has_avx512: bool = False
37
+ has_sse42: bool = False
38
+ has_neon: bool = False
39
+ cpu_cores: int = 0
40
+
41
+ # GPU
42
+ gpu_available: bool = False
43
+ gpu_driver_version: Optional[str] = None
44
+ cuda_runtime_version: Optional[Tuple[int, int]] = None
45
+ cuda_toolkit_version: Optional[Tuple[int, int]] = None
46
+ gpu_compute_capability: Optional[Tuple[int, int]] = None
47
+ gpu_name: Optional[str] = None
48
+ gpu_memory_mb: Optional[int] = None
49
+ cudnn_version: Optional[Tuple[int, int, int]] = None
50
+
51
+ # Environment
52
+ is_container: bool = False
53
+ container_runtime: ContainerRuntime = ContainerRuntime.NONE
54
+ is_virtual_machine: bool = False
55
+ is_embedded_board: bool = False
56
+ board_name: Optional[str] = None
57
+ jetpack_version: Optional[str] = None
58
+
59
+ # Build toolchain
60
+ toolchain_versions: Dict[str, str] = field(default_factory=dict)
61
+ default_cc: str = ""
62
+ default_cxx: str = ""
63
+ has_build_tools: bool = False
64
+ has_python_dev_headers: bool = False
65
+
66
+ # Virtual environment
67
+ venv_type: str = "system"
68
+ is_system_python: bool = True
69
+ is_virtual_env: bool = False
70
+ base_prefix: str = ""
71
+ prefix: str = ""
72
+ conda_env_name: str = ""
73
+ conda_prefix: str = ""
74
+ pip_user_site_enabled: bool = False
75
+ mixed_env_risk: bool = False
76
+
77
+ # Library paths
78
+ ld_library_path: Tuple[str, ...] = ()
79
+ ldconfig_cache: Dict[str, str] = field(default_factory=dict)
80
+ site_packages_paths: Tuple[str, ...] = ()
81
+
82
+ def summary(self) -> Dict[str, str]:
83
+ """Return a human-readable summary dict for display."""
84
+ info: Dict[str, str] = {}
85
+ if self.python_version != (0, 0, 0):
86
+ py_ver = ".".join(str(v) for v in self.python_version)
87
+ info["Python"] = f"{py_ver} ({self.python_abi_tag}) @ {self.python_executable}"
88
+ if self.os_name:
89
+ info["OS"] = f"{self.os_name} {self.os_version} (kernel {self.kernel_version})"
90
+ if self.architecture != Architecture.UNKNOWN:
91
+ info["Architecture"] = self.architecture.value
92
+ if self.glibc_version:
93
+ info["GLIBC"] = f"{self.glibc_version[0]}.{self.glibc_version[1]}"
94
+ elif self.musl_version:
95
+ info["musl"] = f"{self.musl_version[0]}.{self.musl_version[1]}"
96
+ if self.cuda_runtime_version:
97
+ info["CUDA Runtime"] = f"{self.cuda_runtime_version[0]}.{self.cuda_runtime_version[1]}"
98
+ if self.gpu_driver_version:
99
+ info["GPU Driver"] = self.gpu_driver_version
100
+ if self.gpu_name:
101
+ cc = ""
102
+ if self.gpu_compute_capability:
103
+ cc = f" (compute {self.gpu_compute_capability[0]}.{self.gpu_compute_capability[1]})"
104
+ info["GPU"] = f"{self.gpu_name}{cc}"
105
+ if self.is_embedded_board and self.board_name:
106
+ info["Board"] = self.board_name
107
+ if self.jetpack_version:
108
+ info["JetPack"] = self.jetpack_version
109
+ if self.is_container:
110
+ info["Container"] = self.container_runtime.value
111
+ if self.venv_type != "system":
112
+ info["Environment"] = self.venv_type
113
+ if self.toolchain_versions:
114
+ cc = self.toolchain_versions.get("gcc") or self.toolchain_versions.get("clang")
115
+ if cc:
116
+ compiler = "gcc" if "gcc" in self.toolchain_versions else "clang"
117
+ info["Compiler"] = f"{compiler} {cc}"
118
+ return info
@@ -0,0 +1,19 @@
1
+ """Plugin subsystem for PyBinaryGuard.
2
+
3
+ The plugin system allows third-party packages (and built-in contrib
4
+ modules) to extend PyBinaryGuard with additional probes, rules, board
5
+ detectors, framework checkers, reporters, and lifecycle hooks.
6
+
7
+ Public API
8
+ ----------
9
+ .. autoclass:: HookRegistry
10
+ .. autofunction:: discover_plugins
11
+ """
12
+
13
+ from pybinaryguard.plugins.hooks import HookRegistry
14
+ from pybinaryguard.plugins.loader import discover_plugins
15
+
16
+ __all__ = [
17
+ "HookRegistry",
18
+ "discover_plugins",
19
+ ]
@@ -0,0 +1,7 @@
1
+ """Built-in contrib plugins for PyBinaryGuard.
2
+
3
+ Each module in this package provides extensions for a specific platform
4
+ or framework. Contrib plugins are loaded automatically by
5
+ :func:`~pybinaryguard.plugins.loader.discover_plugins` and activate
6
+ only when the relevant hardware or software is detected on the system.
7
+ """