qyro 2.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.
- qyro/__init__.py +17 -0
- qyro/adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/c/__init__.py +4 -0
- qyro/adapters/language_adapters/python/__init__.py +4 -0
- qyro/adapters/language_adapters/python/python_adapter.py +584 -0
- qyro/cli/__init__.py +8 -0
- qyro/cli/__main__.py +5 -0
- qyro/cli/cli.py +392 -0
- qyro/cli/interactive.py +297 -0
- qyro/common/__init__.py +37 -0
- qyro/common/animation.py +82 -0
- qyro/common/builder.py +434 -0
- qyro/common/compiler.py +895 -0
- qyro/common/config.py +93 -0
- qyro/common/constants.py +99 -0
- qyro/common/errors.py +176 -0
- qyro/common/frontend.py +74 -0
- qyro/common/health.py +358 -0
- qyro/common/kafka_manager.py +192 -0
- qyro/common/logging.py +149 -0
- qyro/common/memory.py +147 -0
- qyro/common/metrics.py +301 -0
- qyro/common/monitoring.py +468 -0
- qyro/common/parser.py +91 -0
- qyro/common/platform.py +609 -0
- qyro/common/redis_memory.py +1108 -0
- qyro/common/rpc.py +287 -0
- qyro/common/sandbox.py +191 -0
- qyro/common/schema_loader.py +33 -0
- qyro/common/secure_sandbox.py +490 -0
- qyro/common/toolchain_validator.py +617 -0
- qyro/common/type_generator.py +176 -0
- qyro/common/validation.py +401 -0
- qyro/common/validator.py +204 -0
- qyro/gateway/__init__.py +8 -0
- qyro/gateway/gateway.py +303 -0
- qyro/orchestrator/__init__.py +8 -0
- qyro/orchestrator/orchestrator.py +1223 -0
- qyro-2.0.0.dist-info/METADATA +244 -0
- qyro-2.0.0.dist-info/RECORD +45 -0
- qyro-2.0.0.dist-info/WHEEL +5 -0
- qyro-2.0.0.dist-info/entry_points.txt +2 -0
- qyro-2.0.0.dist-info/licenses/LICENSE +21 -0
- qyro-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nexus Toolchain Validator
|
|
3
|
+
Provides pre-flight validation for all supported language toolchains.
|
|
4
|
+
Checks availability and minimum versions with platform-specific installation instructions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import re
|
|
10
|
+
from typing import Dict, List, Tuple, Optional
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
from .logging import get_logger
|
|
15
|
+
|
|
16
|
+
logger = get_logger("nexus.toolchain_validator")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ToolchainStatus(Enum):
|
|
20
|
+
"""Status of a toolchain check."""
|
|
21
|
+
AVAILABLE = "available"
|
|
22
|
+
MISSING = "missing"
|
|
23
|
+
VERSION_TOO_OLD = "version_too_old"
|
|
24
|
+
ERROR = "error"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ToolchainCheck:
|
|
29
|
+
"""Result of a toolchain check."""
|
|
30
|
+
name: str
|
|
31
|
+
status: ToolchainStatus
|
|
32
|
+
version: Optional[str] = None
|
|
33
|
+
error_message: Optional[str] = None
|
|
34
|
+
install_instructions: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ToolchainValidator:
|
|
38
|
+
"""
|
|
39
|
+
Validates toolchain availability and versions for all supported languages.
|
|
40
|
+
|
|
41
|
+
Supported Languages:
|
|
42
|
+
- C (gcc/clang)
|
|
43
|
+
- Java (javac)
|
|
44
|
+
- Rust (cargo)
|
|
45
|
+
- Go (go)
|
|
46
|
+
- Node.js (node/npm)
|
|
47
|
+
- Python (python)
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Minimum version requirements
|
|
51
|
+
MIN_VERSIONS = {
|
|
52
|
+
'gcc': '9.0',
|
|
53
|
+
'clang': '11.0',
|
|
54
|
+
'javac': '11',
|
|
55
|
+
'cargo': '1.70',
|
|
56
|
+
'go': '1.20',
|
|
57
|
+
'node': '18.0.0',
|
|
58
|
+
'python': '3.8',
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Recommended versions
|
|
62
|
+
RECOMMENDED_VERSIONS = {
|
|
63
|
+
'gcc': '11.0',
|
|
64
|
+
'clang': '15.0',
|
|
65
|
+
'javac': '17',
|
|
66
|
+
'cargo': '1.75',
|
|
67
|
+
'go': '1.21',
|
|
68
|
+
'node': '20.0.0',
|
|
69
|
+
'python': '3.11',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Platform-specific installation commands
|
|
73
|
+
INSTALL_COMMANDS = {
|
|
74
|
+
'linux': {
|
|
75
|
+
'gcc': 'sudo apt install gcc || sudo yum install gcc',
|
|
76
|
+
'clang': 'sudo apt install clang || sudo yum install clang',
|
|
77
|
+
'javac': 'sudo apt install openjdk-17-jdk || sudo yum install java-17-openjdk-devel',
|
|
78
|
+
'cargo': 'curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh',
|
|
79
|
+
'go': 'wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz && sudo tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz',
|
|
80
|
+
'node': 'curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt install nodejs',
|
|
81
|
+
'python': 'sudo apt install python3 python3-pip || sudo yum install python3 python3-pip',
|
|
82
|
+
},
|
|
83
|
+
'darwin': { # macOS
|
|
84
|
+
'gcc': 'xcode-select --install',
|
|
85
|
+
'clang': 'xcode-select --install',
|
|
86
|
+
'javac': 'brew install openjdk@17',
|
|
87
|
+
'cargo': 'curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh',
|
|
88
|
+
'go': 'brew install go',
|
|
89
|
+
'node': 'brew install node',
|
|
90
|
+
'python': 'brew install python',
|
|
91
|
+
},
|
|
92
|
+
'win32': { # Windows
|
|
93
|
+
'gcc': 'Download and install MinGW-w64 from https://www.mingw-w64.org/ or use: choco install mingw',
|
|
94
|
+
'clang': 'Download and install LLVM from https://llvm.org/ or use: choco install llvm',
|
|
95
|
+
'javac': 'Download and install JDK 17 from https://adoptium.net/ or use: choco install temurin17jdk',
|
|
96
|
+
'cargo': 'Download and install from https://rustup.rs/ or use: choco install rust',
|
|
97
|
+
'go': 'Download and install from https://go.dev/dl/ or use: choco install golang',
|
|
98
|
+
'node': 'Download and install from https://nodejs.org/ or use: choco install nodejs',
|
|
99
|
+
'python': 'Download and install from https://python.org/ or use: choco install python',
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def __init__(self):
|
|
104
|
+
self.platform = self._detect_platform()
|
|
105
|
+
self.checks: Dict[str, ToolchainCheck] = {}
|
|
106
|
+
|
|
107
|
+
def _detect_platform(self) -> str:
|
|
108
|
+
"""Detect the current platform."""
|
|
109
|
+
if sys.platform == 'win32':
|
|
110
|
+
return 'win32'
|
|
111
|
+
elif sys.platform == 'darwin':
|
|
112
|
+
return 'darwin'
|
|
113
|
+
else:
|
|
114
|
+
return 'linux'
|
|
115
|
+
|
|
116
|
+
def _get_install_command(self, tool: str) -> str:
|
|
117
|
+
"""Get platform-specific installation command for a tool."""
|
|
118
|
+
return self.INSTALL_COMMANDS.get(self.platform, {}).get(
|
|
119
|
+
tool,
|
|
120
|
+
f"Please install {tool} manually"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def _run_command(self, cmd: List[str], timeout: int = 5) -> Tuple[int, str, str]:
|
|
124
|
+
"""
|
|
125
|
+
Run a command and return (returncode, stdout, stderr).
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
cmd: Command to run as a list
|
|
129
|
+
timeout: Timeout in seconds
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Tuple of (returncode, stdout, stderr)
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
result = subprocess.run(
|
|
136
|
+
cmd,
|
|
137
|
+
capture_output=True,
|
|
138
|
+
timeout=timeout,
|
|
139
|
+
text=True,
|
|
140
|
+
encoding='utf-8',
|
|
141
|
+
errors='replace'
|
|
142
|
+
)
|
|
143
|
+
return result.returncode, result.stdout, result.stderr
|
|
144
|
+
except FileNotFoundError:
|
|
145
|
+
return 1, "", "Command not found"
|
|
146
|
+
except subprocess.TimeoutExpired:
|
|
147
|
+
return 1, "", "Command timed out"
|
|
148
|
+
except Exception as e:
|
|
149
|
+
return 1, "", str(e)
|
|
150
|
+
|
|
151
|
+
def _parse_version(self, version_string: str, tool: str) -> Optional[str]:
|
|
152
|
+
"""
|
|
153
|
+
Parse version string from tool output.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
version_string: Raw version output from tool
|
|
157
|
+
tool: Name of the tool
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Parsed version string or None
|
|
161
|
+
"""
|
|
162
|
+
version_string = version_string.strip()
|
|
163
|
+
|
|
164
|
+
# Tool-specific version parsing patterns
|
|
165
|
+
patterns = {
|
|
166
|
+
'gcc': r'gcc\s+\(.*?\)\s+([\d.]+)',
|
|
167
|
+
'clang': r'clang\s+version\s+([\d.]+)',
|
|
168
|
+
'javac': r'javac\s+([\d.]+)',
|
|
169
|
+
'cargo': r'cargo\s+([\d.]+)',
|
|
170
|
+
'go': r'go\s+version\s+go([\d.]+)',
|
|
171
|
+
'node': r'v?([\d.]+)',
|
|
172
|
+
'python': r'Python\s+([\d.]+)',
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
pattern = patterns.get(tool)
|
|
176
|
+
if pattern:
|
|
177
|
+
match = re.search(pattern, version_string)
|
|
178
|
+
if match:
|
|
179
|
+
return match.group(1)
|
|
180
|
+
|
|
181
|
+
# Fallback: try to extract first version-like string
|
|
182
|
+
version_match = re.search(r'(\d+(?:\.\d+)*)', version_string)
|
|
183
|
+
if version_match:
|
|
184
|
+
return version_match.group(1)
|
|
185
|
+
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
def _compare_versions(self, version1: str, version2: str) -> int:
|
|
189
|
+
"""
|
|
190
|
+
Compare two version strings.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
version1: First version string
|
|
194
|
+
version2: Second version string
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
-1 if version1 < version2, 0 if equal, 1 if version1 > version2
|
|
198
|
+
"""
|
|
199
|
+
v1_parts = [int(x) for x in version1.split('.')]
|
|
200
|
+
v2_parts = [int(x) for x in version2.split('.')]
|
|
201
|
+
|
|
202
|
+
# Pad shorter version with zeros
|
|
203
|
+
max_len = max(len(v1_parts), len(v2_parts))
|
|
204
|
+
v1_parts.extend([0] * (max_len - len(v1_parts)))
|
|
205
|
+
v2_parts.extend([0] * (max_len - len(v2_parts)))
|
|
206
|
+
|
|
207
|
+
for v1, v2 in zip(v1_parts, v2_parts):
|
|
208
|
+
if v1 < v2:
|
|
209
|
+
return -1
|
|
210
|
+
elif v1 > v2:
|
|
211
|
+
return 1
|
|
212
|
+
|
|
213
|
+
return 0
|
|
214
|
+
|
|
215
|
+
def check_gcc(self) -> ToolchainCheck:
|
|
216
|
+
"""Check for GCC compiler."""
|
|
217
|
+
returncode, stdout, stderr = self._run_command(['gcc', '--version'])
|
|
218
|
+
|
|
219
|
+
if returncode != 0:
|
|
220
|
+
return ToolchainCheck(
|
|
221
|
+
name='gcc',
|
|
222
|
+
status=ToolchainStatus.MISSING,
|
|
223
|
+
install_instructions=self._get_install_command('gcc')
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
version = self._parse_version(stdout, 'gcc')
|
|
227
|
+
if not version:
|
|
228
|
+
return ToolchainCheck(
|
|
229
|
+
name='gcc',
|
|
230
|
+
status=ToolchainStatus.ERROR,
|
|
231
|
+
error_message='Could not parse version',
|
|
232
|
+
install_instructions=self._get_install_command('gcc')
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
min_version = self.MIN_VERSIONS.get('gcc', '9.0')
|
|
236
|
+
if self._compare_versions(version, min_version) < 0:
|
|
237
|
+
return ToolchainCheck(
|
|
238
|
+
name='gcc',
|
|
239
|
+
status=ToolchainStatus.VERSION_TOO_OLD,
|
|
240
|
+
version=version,
|
|
241
|
+
error_message=f'GCC version {version} is too old (minimum: {min_version})',
|
|
242
|
+
install_instructions=self._get_install_command('gcc')
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return ToolchainCheck(
|
|
246
|
+
name='gcc',
|
|
247
|
+
status=ToolchainStatus.AVAILABLE,
|
|
248
|
+
version=version
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def check_clang(self) -> ToolchainCheck:
|
|
252
|
+
"""Check for Clang compiler."""
|
|
253
|
+
returncode, stdout, stderr = self._run_command(['clang', '--version'])
|
|
254
|
+
|
|
255
|
+
if returncode != 0:
|
|
256
|
+
return ToolchainCheck(
|
|
257
|
+
name='clang',
|
|
258
|
+
status=ToolchainStatus.MISSING,
|
|
259
|
+
install_instructions=self._get_install_command('clang')
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
version = self._parse_version(stdout, 'clang')
|
|
263
|
+
if not version:
|
|
264
|
+
return ToolchainCheck(
|
|
265
|
+
name='clang',
|
|
266
|
+
status=ToolchainStatus.ERROR,
|
|
267
|
+
error_message='Could not parse version',
|
|
268
|
+
install_instructions=self._get_install_command('clang')
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
min_version = self.MIN_VERSIONS.get('clang', '11.0')
|
|
272
|
+
if self._compare_versions(version, min_version) < 0:
|
|
273
|
+
return ToolchainCheck(
|
|
274
|
+
name='clang',
|
|
275
|
+
status=ToolchainStatus.VERSION_TOO_OLD,
|
|
276
|
+
version=version,
|
|
277
|
+
error_message=f'Clang version {version} is too old (minimum: {min_version})',
|
|
278
|
+
install_instructions=self._get_install_command('clang')
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return ToolchainCheck(
|
|
282
|
+
name='clang',
|
|
283
|
+
status=ToolchainStatus.AVAILABLE,
|
|
284
|
+
version=version
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def check_javac(self) -> ToolchainCheck:
|
|
288
|
+
"""Check for Java compiler."""
|
|
289
|
+
returncode, stdout, stderr = self._run_command(['javac', '-version'])
|
|
290
|
+
|
|
291
|
+
if returncode != 0:
|
|
292
|
+
return ToolchainCheck(
|
|
293
|
+
name='javac',
|
|
294
|
+
status=ToolchainStatus.MISSING,
|
|
295
|
+
install_instructions=self._get_install_command('javac')
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# javac -version may output to stdout or stderr depending on platform
|
|
299
|
+
version_output = stdout if stdout else stderr
|
|
300
|
+
version = self._parse_version(version_output, 'javac')
|
|
301
|
+
if not version:
|
|
302
|
+
return ToolchainCheck(
|
|
303
|
+
name='javac',
|
|
304
|
+
status=ToolchainStatus.ERROR,
|
|
305
|
+
error_message='Could not parse version',
|
|
306
|
+
install_instructions=self._get_install_command('javac')
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
min_version = self.MIN_VERSIONS.get('javac', '11')
|
|
310
|
+
if self._compare_versions(version, min_version) < 0:
|
|
311
|
+
return ToolchainCheck(
|
|
312
|
+
name='javac',
|
|
313
|
+
status=ToolchainStatus.VERSION_TOO_OLD,
|
|
314
|
+
version=version,
|
|
315
|
+
error_message=f'Java version {version} is too old (minimum: {min_version})',
|
|
316
|
+
install_instructions=self._get_install_command('javac')
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
return ToolchainCheck(
|
|
320
|
+
name='javac',
|
|
321
|
+
status=ToolchainStatus.AVAILABLE,
|
|
322
|
+
version=version
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def check_cargo(self) -> ToolchainCheck:
|
|
326
|
+
"""Check for Cargo (Rust)."""
|
|
327
|
+
returncode, stdout, stderr = self._run_command(['cargo', '--version'])
|
|
328
|
+
|
|
329
|
+
if returncode != 0:
|
|
330
|
+
return ToolchainCheck(
|
|
331
|
+
name='cargo',
|
|
332
|
+
status=ToolchainStatus.MISSING,
|
|
333
|
+
install_instructions=self._get_install_command('cargo')
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
version = self._parse_version(stdout, 'cargo')
|
|
337
|
+
if not version:
|
|
338
|
+
return ToolchainCheck(
|
|
339
|
+
name='cargo',
|
|
340
|
+
status=ToolchainStatus.ERROR,
|
|
341
|
+
error_message='Could not parse version',
|
|
342
|
+
install_instructions=self._get_install_command('cargo')
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
min_version = self.MIN_VERSIONS.get('cargo', '1.70')
|
|
346
|
+
if self._compare_versions(version, min_version) < 0:
|
|
347
|
+
return ToolchainCheck(
|
|
348
|
+
name='cargo',
|
|
349
|
+
status=ToolchainStatus.VERSION_TOO_OLD,
|
|
350
|
+
version=version,
|
|
351
|
+
error_message=f'Cargo version {version} is too old (minimum: {min_version})',
|
|
352
|
+
install_instructions=self._get_install_command('cargo')
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
return ToolchainCheck(
|
|
356
|
+
name='cargo',
|
|
357
|
+
status=ToolchainStatus.AVAILABLE,
|
|
358
|
+
version=version
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def check_go(self) -> ToolchainCheck:
|
|
362
|
+
"""Check for Go compiler."""
|
|
363
|
+
returncode, stdout, stderr = self._run_command(['go', 'version'])
|
|
364
|
+
|
|
365
|
+
if returncode != 0:
|
|
366
|
+
return ToolchainCheck(
|
|
367
|
+
name='go',
|
|
368
|
+
status=ToolchainStatus.MISSING,
|
|
369
|
+
install_instructions=self._get_install_command('go')
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
version = self._parse_version(stdout, 'go')
|
|
373
|
+
if not version:
|
|
374
|
+
return ToolchainCheck(
|
|
375
|
+
name='go',
|
|
376
|
+
status=ToolchainStatus.ERROR,
|
|
377
|
+
error_message='Could not parse version',
|
|
378
|
+
install_instructions=self._get_install_command('go')
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
min_version = self.MIN_VERSIONS.get('go', '1.20')
|
|
382
|
+
if self._compare_versions(version, min_version) < 0:
|
|
383
|
+
return ToolchainCheck(
|
|
384
|
+
name='go',
|
|
385
|
+
status=ToolchainStatus.VERSION_TOO_OLD,
|
|
386
|
+
version=version,
|
|
387
|
+
error_message=f'Go version {version} is too old (minimum: {min_version})',
|
|
388
|
+
install_instructions=self._get_install_command('go')
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return ToolchainCheck(
|
|
392
|
+
name='go',
|
|
393
|
+
status=ToolchainStatus.AVAILABLE,
|
|
394
|
+
version=version
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
def check_node(self) -> ToolchainCheck:
|
|
398
|
+
"""Check for Node.js."""
|
|
399
|
+
returncode, stdout, stderr = self._run_command(['node', '--version'])
|
|
400
|
+
|
|
401
|
+
if returncode != 0:
|
|
402
|
+
return ToolchainCheck(
|
|
403
|
+
name='node',
|
|
404
|
+
status=ToolchainStatus.MISSING,
|
|
405
|
+
install_instructions=self._get_install_command('node')
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
version = self._parse_version(stdout, 'node')
|
|
409
|
+
if not version:
|
|
410
|
+
return ToolchainCheck(
|
|
411
|
+
name='node',
|
|
412
|
+
status=ToolchainStatus.ERROR,
|
|
413
|
+
error_message='Could not parse version',
|
|
414
|
+
install_instructions=self._get_install_command('node')
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
min_version = self.MIN_VERSIONS.get('node', '18.0.0')
|
|
418
|
+
if self._compare_versions(version, min_version) < 0:
|
|
419
|
+
return ToolchainCheck(
|
|
420
|
+
name='node',
|
|
421
|
+
status=ToolchainStatus.VERSION_TOO_OLD,
|
|
422
|
+
version=version,
|
|
423
|
+
error_message=f'Node.js version {version} is too old (minimum: {min_version})',
|
|
424
|
+
install_instructions=self._get_install_command('node')
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
return ToolchainCheck(
|
|
428
|
+
name='node',
|
|
429
|
+
status=ToolchainStatus.AVAILABLE,
|
|
430
|
+
version=version
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
def check_python(self) -> ToolchainCheck:
|
|
434
|
+
"""Check for Python."""
|
|
435
|
+
returncode, stdout, stderr = self._run_command([sys.executable, '--version'])
|
|
436
|
+
|
|
437
|
+
if returncode != 0:
|
|
438
|
+
return ToolchainCheck(
|
|
439
|
+
name='python',
|
|
440
|
+
status=ToolchainStatus.MISSING,
|
|
441
|
+
install_instructions=self._get_install_command('python')
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
version = self._parse_version(stdout, 'python')
|
|
445
|
+
if not version:
|
|
446
|
+
return ToolchainCheck(
|
|
447
|
+
name='python',
|
|
448
|
+
status=ToolchainStatus.ERROR,
|
|
449
|
+
error_message='Could not parse version',
|
|
450
|
+
install_instructions=self._get_install_command('python')
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
min_version = self.MIN_VERSIONS.get('python', '3.8')
|
|
454
|
+
if self._compare_versions(version, min_version) < 0:
|
|
455
|
+
return ToolchainCheck(
|
|
456
|
+
name='python',
|
|
457
|
+
status=ToolchainStatus.VERSION_TOO_OLD,
|
|
458
|
+
version=version,
|
|
459
|
+
error_message=f'Python version {version} is too old (minimum: {min_version})',
|
|
460
|
+
install_instructions=self._get_install_command('python')
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
return ToolchainCheck(
|
|
464
|
+
name='python',
|
|
465
|
+
status=ToolchainStatus.AVAILABLE,
|
|
466
|
+
version=version
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
def check_all(self) -> Dict[str, ToolchainCheck]:
|
|
470
|
+
"""
|
|
471
|
+
Check all toolchains.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Dictionary mapping toolchain names to check results
|
|
475
|
+
"""
|
|
476
|
+
self.checks = {
|
|
477
|
+
'gcc': self.check_gcc(),
|
|
478
|
+
'clang': self.check_clang(),
|
|
479
|
+
'javac': self.check_javac(),
|
|
480
|
+
'cargo': self.check_cargo(),
|
|
481
|
+
'go': self.check_go(),
|
|
482
|
+
'node': self.check_node(),
|
|
483
|
+
'python': self.check_python(),
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return self.checks
|
|
487
|
+
|
|
488
|
+
def get_available_languages(self) -> List[str]:
|
|
489
|
+
"""
|
|
490
|
+
Get list of languages with available toolchains.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
List of language names that have available toolchains
|
|
494
|
+
"""
|
|
495
|
+
available = []
|
|
496
|
+
|
|
497
|
+
# Check C compilers
|
|
498
|
+
if self.checks.get('gcc', ToolchainCheck('gcc', ToolchainStatus.MISSING)).status == ToolchainStatus.AVAILABLE:
|
|
499
|
+
available.append('c')
|
|
500
|
+
if self.checks.get('clang', ToolchainCheck('clang', ToolchainStatus.MISSING)).status == ToolchainStatus.AVAILABLE:
|
|
501
|
+
available.append('c')
|
|
502
|
+
|
|
503
|
+
# Check other languages
|
|
504
|
+
if self.checks.get('javac', ToolchainCheck('javac', ToolchainStatus.MISSING)).status == ToolchainStatus.AVAILABLE:
|
|
505
|
+
available.append('java')
|
|
506
|
+
if self.checks.get('cargo', ToolchainCheck('cargo', ToolchainStatus.MISSING)).status == ToolchainStatus.AVAILABLE:
|
|
507
|
+
available.append('rust')
|
|
508
|
+
if self.checks.get('go', ToolchainCheck('go', ToolchainStatus.MISSING)).status == ToolchainStatus.AVAILABLE:
|
|
509
|
+
available.append('go')
|
|
510
|
+
if self.checks.get('node', ToolchainCheck('node', ToolchainStatus.MISSING)).status == ToolchainStatus.AVAILABLE:
|
|
511
|
+
available.append('typescript')
|
|
512
|
+
if self.checks.get('python', ToolchainCheck('python', ToolchainStatus.MISSING)).status == ToolchainStatus.AVAILABLE:
|
|
513
|
+
available.append('python')
|
|
514
|
+
|
|
515
|
+
return list(set(available)) # Remove duplicates
|
|
516
|
+
|
|
517
|
+
def is_language_available(self, language: str) -> bool:
|
|
518
|
+
"""
|
|
519
|
+
Check if a specific language toolchain is available.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
language: Language name (c, java, rust, go, typescript, python)
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
True if toolchain is available, False otherwise
|
|
526
|
+
"""
|
|
527
|
+
language_map = {
|
|
528
|
+
'c': ['gcc', 'clang'],
|
|
529
|
+
'java': ['javac'],
|
|
530
|
+
'rust': ['cargo'],
|
|
531
|
+
'go': ['go'],
|
|
532
|
+
'typescript': ['node'],
|
|
533
|
+
'ts': ['node'],
|
|
534
|
+
'python': ['python'],
|
|
535
|
+
'py': ['python'],
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
tools = language_map.get(language.lower(), [])
|
|
539
|
+
for tool in tools:
|
|
540
|
+
check = self.checks.get(tool)
|
|
541
|
+
if check and check.status == ToolchainStatus.AVAILABLE:
|
|
542
|
+
return True
|
|
543
|
+
|
|
544
|
+
return False
|
|
545
|
+
|
|
546
|
+
def print_report(self):
|
|
547
|
+
"""Print a formatted report of all toolchain checks."""
|
|
548
|
+
print("\n" + "=" * 70)
|
|
549
|
+
print("NEXUS TOOLCHAIN VALIDATION REPORT")
|
|
550
|
+
print("=" * 70)
|
|
551
|
+
print(f"Platform: {self.platform}")
|
|
552
|
+
print("=" * 70)
|
|
553
|
+
|
|
554
|
+
available_count = 0
|
|
555
|
+
missing_count = 0
|
|
556
|
+
warning_count = 0
|
|
557
|
+
|
|
558
|
+
for tool, check in self.checks.items():
|
|
559
|
+
status_icon = {
|
|
560
|
+
ToolchainStatus.AVAILABLE: "✓",
|
|
561
|
+
ToolchainStatus.MISSING: "✗",
|
|
562
|
+
ToolchainStatus.VERSION_TOO_OLD: "⚠",
|
|
563
|
+
ToolchainStatus.ERROR: "!",
|
|
564
|
+
}.get(check.status, "?")
|
|
565
|
+
|
|
566
|
+
status_color = {
|
|
567
|
+
ToolchainStatus.AVAILABLE: "green",
|
|
568
|
+
ToolchainStatus.MISSING: "red",
|
|
569
|
+
ToolchainStatus.VERSION_TOO_OLD: "yellow",
|
|
570
|
+
ToolchainStatus.ERROR: "red",
|
|
571
|
+
}.get(check.status, "white")
|
|
572
|
+
|
|
573
|
+
version_str = f" (v{check.version})" if check.version else ""
|
|
574
|
+
|
|
575
|
+
if check.status == ToolchainStatus.AVAILABLE:
|
|
576
|
+
available_count += 1
|
|
577
|
+
elif check.status == ToolchainStatus.MISSING:
|
|
578
|
+
missing_count += 1
|
|
579
|
+
else:
|
|
580
|
+
warning_count += 1
|
|
581
|
+
|
|
582
|
+
print(f"{status_icon} {tool.upper():10} {check.status.value:20} {version_str}")
|
|
583
|
+
|
|
584
|
+
if check.error_message:
|
|
585
|
+
print(f" └─ {check.error_message}")
|
|
586
|
+
|
|
587
|
+
if check.install_instructions:
|
|
588
|
+
print(f" └─ Install: {check.install_instructions}")
|
|
589
|
+
|
|
590
|
+
print("=" * 70)
|
|
591
|
+
print(f"Summary: {available_count} available, {missing_count} missing, {warning_count} warnings")
|
|
592
|
+
print("=" * 70)
|
|
593
|
+
|
|
594
|
+
if missing_count > 0 or warning_count > 0:
|
|
595
|
+
print("\n⚠ Some toolchains are missing or outdated.")
|
|
596
|
+
print("You can still use Nexus with available languages.")
|
|
597
|
+
print("Install missing toolchains for full functionality.\n")
|
|
598
|
+
else:
|
|
599
|
+
print("\n✓ All toolchains are available and up to date!\n")
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def validate_toolchains() -> ToolchainValidator:
|
|
603
|
+
"""
|
|
604
|
+
Convenience function to validate all toolchains.
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
ToolchainValidator instance with check results
|
|
608
|
+
"""
|
|
609
|
+
validator = ToolchainValidator()
|
|
610
|
+
validator.check_all()
|
|
611
|
+
return validator
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
if __name__ == "__main__":
|
|
615
|
+
# Run validation when executed directly
|
|
616
|
+
validator = validate_toolchains()
|
|
617
|
+
validator.print_report()
|