fbuild 1.1.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.
Potentially problematic release.
This version of fbuild might be problematic. Click here for more details.
- fbuild/__init__.py +0 -0
- fbuild/assets/example.txt +1 -0
- fbuild/build/__init__.py +117 -0
- fbuild/build/archive_creator.py +186 -0
- fbuild/build/binary_generator.py +444 -0
- fbuild/build/build_component_factory.py +131 -0
- fbuild/build/build_state.py +325 -0
- fbuild/build/build_utils.py +98 -0
- fbuild/build/compilation_executor.py +422 -0
- fbuild/build/compiler.py +165 -0
- fbuild/build/compiler_avr.py +574 -0
- fbuild/build/configurable_compiler.py +612 -0
- fbuild/build/configurable_linker.py +637 -0
- fbuild/build/flag_builder.py +186 -0
- fbuild/build/library_dependency_processor.py +185 -0
- fbuild/build/linker.py +708 -0
- fbuild/build/orchestrator.py +67 -0
- fbuild/build/orchestrator_avr.py +656 -0
- fbuild/build/orchestrator_esp32.py +797 -0
- fbuild/build/orchestrator_teensy.py +543 -0
- fbuild/build/source_compilation_orchestrator.py +220 -0
- fbuild/build/source_scanner.py +516 -0
- fbuild/cli.py +566 -0
- fbuild/cli_utils.py +312 -0
- fbuild/config/__init__.py +16 -0
- fbuild/config/board_config.py +457 -0
- fbuild/config/board_loader.py +92 -0
- fbuild/config/ini_parser.py +209 -0
- fbuild/config/mcu_specs.py +88 -0
- fbuild/daemon/__init__.py +34 -0
- fbuild/daemon/client.py +929 -0
- fbuild/daemon/compilation_queue.py +293 -0
- fbuild/daemon/daemon.py +474 -0
- fbuild/daemon/daemon_context.py +196 -0
- fbuild/daemon/error_collector.py +263 -0
- fbuild/daemon/file_cache.py +332 -0
- fbuild/daemon/lock_manager.py +270 -0
- fbuild/daemon/logging_utils.py +149 -0
- fbuild/daemon/messages.py +301 -0
- fbuild/daemon/operation_registry.py +288 -0
- fbuild/daemon/process_tracker.py +366 -0
- fbuild/daemon/processors/__init__.py +12 -0
- fbuild/daemon/processors/build_processor.py +157 -0
- fbuild/daemon/processors/deploy_processor.py +327 -0
- fbuild/daemon/processors/monitor_processor.py +146 -0
- fbuild/daemon/request_processor.py +401 -0
- fbuild/daemon/status_manager.py +216 -0
- fbuild/daemon/subprocess_manager.py +316 -0
- fbuild/deploy/__init__.py +17 -0
- fbuild/deploy/deployer.py +67 -0
- fbuild/deploy/deployer_esp32.py +314 -0
- fbuild/deploy/monitor.py +495 -0
- fbuild/interrupt_utils.py +34 -0
- fbuild/packages/__init__.py +53 -0
- fbuild/packages/archive_utils.py +1098 -0
- fbuild/packages/arduino_core.py +412 -0
- fbuild/packages/cache.py +249 -0
- fbuild/packages/downloader.py +366 -0
- fbuild/packages/framework_esp32.py +538 -0
- fbuild/packages/framework_teensy.py +346 -0
- fbuild/packages/github_utils.py +96 -0
- fbuild/packages/header_trampoline_cache.py +394 -0
- fbuild/packages/library_compiler.py +203 -0
- fbuild/packages/library_manager.py +549 -0
- fbuild/packages/library_manager_esp32.py +413 -0
- fbuild/packages/package.py +163 -0
- fbuild/packages/platform_esp32.py +383 -0
- fbuild/packages/platform_teensy.py +312 -0
- fbuild/packages/platform_utils.py +131 -0
- fbuild/packages/platformio_registry.py +325 -0
- fbuild/packages/sdk_utils.py +231 -0
- fbuild/packages/toolchain.py +436 -0
- fbuild/packages/toolchain_binaries.py +196 -0
- fbuild/packages/toolchain_esp32.py +484 -0
- fbuild/packages/toolchain_metadata.py +185 -0
- fbuild/packages/toolchain_teensy.py +404 -0
- fbuild/platform_configs/esp32.json +150 -0
- fbuild/platform_configs/esp32c2.json +144 -0
- fbuild/platform_configs/esp32c3.json +143 -0
- fbuild/platform_configs/esp32c5.json +151 -0
- fbuild/platform_configs/esp32c6.json +151 -0
- fbuild/platform_configs/esp32p4.json +149 -0
- fbuild/platform_configs/esp32s3.json +151 -0
- fbuild/platform_configs/imxrt1062.json +56 -0
- fbuild-1.1.0.dist-info/METADATA +447 -0
- fbuild-1.1.0.dist-info/RECORD +93 -0
- fbuild-1.1.0.dist-info/WHEEL +5 -0
- fbuild-1.1.0.dist-info/entry_points.txt +5 -0
- fbuild-1.1.0.dist-info/licenses/LICENSE +21 -0
- fbuild-1.1.0.dist-info/top_level.txt +2 -0
- fbuild_lint/__init__.py +0 -0
- fbuild_lint/ruff_plugins/__init__.py +0 -0
- fbuild_lint/ruff_plugins/keyboard_interrupt_checker.py +158 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"""Toolchain management for AVR-GCC.
|
|
2
|
+
|
|
3
|
+
This module handles downloading, extracting, and managing the AVR-GCC
|
|
4
|
+
toolchain required for building Arduino sketches.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
from .cache import Cache
|
|
12
|
+
from .downloader import PackageDownloader
|
|
13
|
+
from .package import IToolchain, PackageError
|
|
14
|
+
from .platform_utils import PlatformDetector, PlatformError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ToolchainError(PackageError):
|
|
18
|
+
"""Raised when toolchain operations fail."""
|
|
19
|
+
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ToolchainAVR(IToolchain):
|
|
24
|
+
"""Manages AVR-GCC toolchain."""
|
|
25
|
+
|
|
26
|
+
# AVR-GCC version used by Arduino
|
|
27
|
+
VERSION = "7.3.0-atmel3.6.1-arduino7"
|
|
28
|
+
|
|
29
|
+
# Base URL for toolchain downloads
|
|
30
|
+
BASE_URL = "https://downloads.arduino.cc/tools"
|
|
31
|
+
|
|
32
|
+
# Platform-specific toolchain packages
|
|
33
|
+
PACKAGES = {
|
|
34
|
+
"windows": {
|
|
35
|
+
"x86_64": "avr-gcc-7.3.0-atmel3.6.1-arduino7-i686-w64-mingw32.zip",
|
|
36
|
+
"checksum": "a54f64755fff4cb792a1495e5defdd789902a2a3503982e81b898299cf39800e",
|
|
37
|
+
},
|
|
38
|
+
"linux": {
|
|
39
|
+
"x86_64": "avr-gcc-7.3.0-atmel3.6.1-arduino7-x86_64-pc-linux-gnu.tar.bz2",
|
|
40
|
+
"checksum": "bd8c37f6952a2130ac9ee32c53f6a660feb79bee8353c8e289eb60fdcefed91e",
|
|
41
|
+
"i686": "avr-gcc-7.3.0-atmel3.6.1-arduino7-i686-pc-linux-gnu.tar.bz2",
|
|
42
|
+
"aarch64": "avr-gcc-7.3.0-atmel3.6.1-arduino7-aarch64-pc-linux-gnu.tar.bz2",
|
|
43
|
+
"armv7l": "avr-gcc-7.3.0-atmel3.6.1-arduino7-arm-linux-gnueabihf.tar.bz2",
|
|
44
|
+
},
|
|
45
|
+
"darwin": {
|
|
46
|
+
"x86_64": "avr-gcc-7.3.0-atmel3.6.1-arduino7-x86_64-apple-darwin14.tar.bz2",
|
|
47
|
+
"checksum": "4c9ca2d87b5c1b5c82f567a9bfc0fdaef57fe8b9f74bae1e32b3e1964612d85e",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Required tools (executables in bin/)
|
|
52
|
+
REQUIRED_TOOLS = [
|
|
53
|
+
"avr-gcc",
|
|
54
|
+
"avr-g++",
|
|
55
|
+
"avr-ar",
|
|
56
|
+
"avr-objcopy",
|
|
57
|
+
"avr-size",
|
|
58
|
+
"avr-nm",
|
|
59
|
+
"avr-objdump",
|
|
60
|
+
"avr-ranlib",
|
|
61
|
+
"avr-strip",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
# Required subdirectories (libs and headers)
|
|
65
|
+
REQUIRED_DIRS = [
|
|
66
|
+
"bin", # Executables
|
|
67
|
+
"avr/include", # AVR C library headers
|
|
68
|
+
"lib/gcc/avr", # GCC AVR libraries
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
# Key header files that must exist
|
|
72
|
+
REQUIRED_HEADERS = [
|
|
73
|
+
"avr/include/avr/io.h",
|
|
74
|
+
"avr/include/avr/interrupt.h",
|
|
75
|
+
"avr/include/stdio.h",
|
|
76
|
+
"avr/include/stdlib.h",
|
|
77
|
+
"avr/include/string.h",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# Key library files patterns
|
|
81
|
+
REQUIRED_LIB_PATTERNS = [
|
|
82
|
+
"lib/gcc/avr/*/libgcc.a",
|
|
83
|
+
"avr/lib/libc.a",
|
|
84
|
+
"avr/lib/libm.a",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
def __init__(self, cache: Cache):
|
|
88
|
+
"""Initialize toolchain manager.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
cache: Cache instance for storing toolchain
|
|
92
|
+
"""
|
|
93
|
+
self.cache = cache
|
|
94
|
+
self.downloader = PackageDownloader()
|
|
95
|
+
self._toolchain_path: Optional[Path] = None
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def detect_platform() -> tuple[str, str]:
|
|
99
|
+
"""Detect host platform and architecture.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Tuple of (platform, architecture)
|
|
103
|
+
Platform: 'windows', 'linux', or 'darwin'
|
|
104
|
+
Architecture: 'x86_64', 'i686', 'aarch64', 'armv7l'
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
ToolchainError: If platform is not supported
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
return PlatformDetector.detect_avr_platform()
|
|
111
|
+
except PlatformError as e:
|
|
112
|
+
raise ToolchainError(str(e))
|
|
113
|
+
|
|
114
|
+
def _get_package_details(self) -> tuple[str, Optional[str]]:
|
|
115
|
+
"""Get package filename and checksum for current platform.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Tuple of (package_filename, checksum)
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ToolchainError: If no package available for platform
|
|
122
|
+
"""
|
|
123
|
+
plat, arch = self.detect_platform()
|
|
124
|
+
|
|
125
|
+
if plat not in self.PACKAGES:
|
|
126
|
+
raise ToolchainError(f"No toolchain package for platform: {plat}")
|
|
127
|
+
|
|
128
|
+
platform_packages = self.PACKAGES[plat]
|
|
129
|
+
|
|
130
|
+
# For Windows and macOS, only x86_64 is available
|
|
131
|
+
if plat in ("windows", "darwin"):
|
|
132
|
+
if arch != "x86_64":
|
|
133
|
+
# Try to use x86_64 package anyway
|
|
134
|
+
arch = "x86_64"
|
|
135
|
+
|
|
136
|
+
if arch not in platform_packages:
|
|
137
|
+
# Try x86_64 as fallback
|
|
138
|
+
if "x86_64" in platform_packages:
|
|
139
|
+
arch = "x86_64"
|
|
140
|
+
else:
|
|
141
|
+
raise ToolchainError(f"No toolchain package for {plat}/{arch}. " + f"Available: {list(platform_packages.keys())}")
|
|
142
|
+
|
|
143
|
+
package_name = platform_packages[arch]
|
|
144
|
+
checksum = platform_packages.get("checksum")
|
|
145
|
+
|
|
146
|
+
return package_name, checksum
|
|
147
|
+
|
|
148
|
+
def ensure_toolchain(self, force_download: bool = False) -> Path:
|
|
149
|
+
"""Ensure toolchain is available, downloading if necessary.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
force_download: Force re-download even if cached
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Path to toolchain root directory
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
ToolchainError: If toolchain cannot be obtained or verified
|
|
159
|
+
"""
|
|
160
|
+
# Check if already loaded
|
|
161
|
+
if self._toolchain_path and not force_download:
|
|
162
|
+
return self._toolchain_path
|
|
163
|
+
|
|
164
|
+
# Get package info
|
|
165
|
+
package_name, checksum = self._get_package_details()
|
|
166
|
+
|
|
167
|
+
# Use URL and version for cache path
|
|
168
|
+
url = f"{self.BASE_URL}/{package_name}"
|
|
169
|
+
toolchain_path = self.cache.get_toolchain_path(self.BASE_URL, self.VERSION)
|
|
170
|
+
package_path = self.cache.get_package_path(self.BASE_URL, self.VERSION, package_name)
|
|
171
|
+
|
|
172
|
+
# Check if already extracted and verified
|
|
173
|
+
if not force_download and self.cache.is_toolchain_cached(self.BASE_URL, self.VERSION):
|
|
174
|
+
# Comprehensive verification
|
|
175
|
+
if self._verify_toolchain(toolchain_path):
|
|
176
|
+
self._toolchain_path = toolchain_path
|
|
177
|
+
return toolchain_path
|
|
178
|
+
else:
|
|
179
|
+
print("Cached toolchain failed validation, re-downloading...")
|
|
180
|
+
|
|
181
|
+
# Need to download and extract
|
|
182
|
+
self.cache.ensure_directories()
|
|
183
|
+
|
|
184
|
+
print(f"Downloading AVR-GCC toolchain ({self.VERSION})...")
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
# Ensure package directory exists
|
|
188
|
+
package_path.parent.mkdir(parents=True, exist_ok=True)
|
|
189
|
+
|
|
190
|
+
# Download if not cached
|
|
191
|
+
if force_download or not package_path.exists():
|
|
192
|
+
self.downloader.download(url, package_path, checksum)
|
|
193
|
+
else:
|
|
194
|
+
print(f"Using cached {package_name}")
|
|
195
|
+
|
|
196
|
+
# Extract
|
|
197
|
+
print("Extracting toolchain...")
|
|
198
|
+
toolchain_path.parent.mkdir(parents=True, exist_ok=True)
|
|
199
|
+
|
|
200
|
+
# Extract to a temporary location first
|
|
201
|
+
import shutil
|
|
202
|
+
import tempfile
|
|
203
|
+
|
|
204
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
205
|
+
temp_path = Path(temp_dir)
|
|
206
|
+
self.downloader.extract_archive(package_path, temp_path, show_progress=False)
|
|
207
|
+
|
|
208
|
+
# Find the actual toolchain directory (may be nested)
|
|
209
|
+
extracted_dirs = list(temp_path.iterdir())
|
|
210
|
+
if len(extracted_dirs) == 1 and extracted_dirs[0].is_dir():
|
|
211
|
+
# Single directory extracted, use it
|
|
212
|
+
src_dir = extracted_dirs[0]
|
|
213
|
+
else:
|
|
214
|
+
# Multiple items extracted, use the temp dir itself
|
|
215
|
+
src_dir = temp_path
|
|
216
|
+
|
|
217
|
+
# Move to final location
|
|
218
|
+
if toolchain_path.exists():
|
|
219
|
+
shutil.rmtree(toolchain_path)
|
|
220
|
+
shutil.move(str(src_dir), str(toolchain_path))
|
|
221
|
+
|
|
222
|
+
# Comprehensive verification
|
|
223
|
+
if not self._verify_toolchain(toolchain_path):
|
|
224
|
+
raise ToolchainError("Toolchain verification failed after extraction")
|
|
225
|
+
|
|
226
|
+
self._toolchain_path = toolchain_path
|
|
227
|
+
print(f"Toolchain ready at {toolchain_path}")
|
|
228
|
+
return toolchain_path
|
|
229
|
+
|
|
230
|
+
except KeyboardInterrupt as ke:
|
|
231
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
232
|
+
|
|
233
|
+
handle_keyboard_interrupt_properly(ke)
|
|
234
|
+
raise # Never reached, but satisfies type checker
|
|
235
|
+
except Exception as e:
|
|
236
|
+
raise ToolchainError(f"Failed to setup toolchain: {e}")
|
|
237
|
+
|
|
238
|
+
def _verify_toolchain(self, toolchain_path: Path) -> bool:
|
|
239
|
+
"""Comprehensively verify toolchain installation.
|
|
240
|
+
|
|
241
|
+
Checks for:
|
|
242
|
+
- All required executables in bin/
|
|
243
|
+
- Required directories (lib, include, etc.)
|
|
244
|
+
- Key header files
|
|
245
|
+
- Key library files
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
toolchain_path: Path to toolchain directory
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
True if toolchain is complete and valid
|
|
252
|
+
"""
|
|
253
|
+
# Check required directories
|
|
254
|
+
for dir_path in self.REQUIRED_DIRS:
|
|
255
|
+
if not (toolchain_path / dir_path).exists():
|
|
256
|
+
print(f"Missing directory: {dir_path}")
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
# Check required executables
|
|
260
|
+
bin_dir = toolchain_path / "bin"
|
|
261
|
+
exe_suffix = ".exe" if sys.platform == "win32" else ""
|
|
262
|
+
|
|
263
|
+
for tool in self.REQUIRED_TOOLS:
|
|
264
|
+
tool_path = bin_dir / f"{tool}{exe_suffix}"
|
|
265
|
+
if not tool_path.exists():
|
|
266
|
+
print(f"Missing tool: {tool}")
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
# Check required headers
|
|
270
|
+
for header in self.REQUIRED_HEADERS:
|
|
271
|
+
header_path = toolchain_path / header
|
|
272
|
+
if not header_path.exists():
|
|
273
|
+
print(f"Missing header: {header}")
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
# Check required libraries (using glob patterns)
|
|
277
|
+
for lib_pattern in self.REQUIRED_LIB_PATTERNS:
|
|
278
|
+
lib_paths = list(toolchain_path.glob(lib_pattern))
|
|
279
|
+
if not lib_paths:
|
|
280
|
+
print(f"Missing library matching: {lib_pattern}")
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
return True
|
|
284
|
+
|
|
285
|
+
def get_tool_path(self, tool_name: str) -> Path:
|
|
286
|
+
"""Get path to a specific tool.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
tool_name: Name of the tool (e.g., 'avr-gcc')
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Path to the tool executable
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
ToolchainError: If toolchain not initialized or tool not found
|
|
296
|
+
"""
|
|
297
|
+
if not self._toolchain_path:
|
|
298
|
+
raise ToolchainError("Toolchain not initialized. Call ensure_toolchain() first.")
|
|
299
|
+
|
|
300
|
+
exe_suffix = ".exe" if sys.platform == "win32" else ""
|
|
301
|
+
tool_path = self._toolchain_path / "bin" / f"{tool_name}{exe_suffix}"
|
|
302
|
+
|
|
303
|
+
if not tool_path.exists():
|
|
304
|
+
raise ToolchainError(f"Tool not found: {tool_name}")
|
|
305
|
+
|
|
306
|
+
return tool_path
|
|
307
|
+
|
|
308
|
+
def get_all_tools(self) -> Dict[str, Path]:
|
|
309
|
+
"""Get paths to all required tools.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Dictionary mapping tool names to their paths
|
|
313
|
+
|
|
314
|
+
Raises:
|
|
315
|
+
ToolchainError: If toolchain not initialized
|
|
316
|
+
"""
|
|
317
|
+
if not self._toolchain_path:
|
|
318
|
+
raise ToolchainError("Toolchain not initialized. Call ensure_toolchain() first.")
|
|
319
|
+
|
|
320
|
+
return {tool: self.get_tool_path(tool) for tool in self.REQUIRED_TOOLS}
|
|
321
|
+
|
|
322
|
+
# Implement BaseToolchain interface
|
|
323
|
+
def get_gcc_path(self) -> Optional[Path]:
|
|
324
|
+
"""Get path to GCC compiler.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Path to gcc binary or None if not found
|
|
328
|
+
"""
|
|
329
|
+
try:
|
|
330
|
+
return self.get_tool_path("avr-gcc")
|
|
331
|
+
except ToolchainError:
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
def get_gxx_path(self) -> Optional[Path]:
|
|
335
|
+
"""Get path to G++ compiler.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Path to g++ binary or None if not found
|
|
339
|
+
"""
|
|
340
|
+
try:
|
|
341
|
+
return self.get_tool_path("avr-g++")
|
|
342
|
+
except ToolchainError:
|
|
343
|
+
return None
|
|
344
|
+
|
|
345
|
+
def get_ar_path(self) -> Optional[Path]:
|
|
346
|
+
"""Get path to archiver (ar).
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Path to ar binary or None if not found
|
|
350
|
+
"""
|
|
351
|
+
try:
|
|
352
|
+
return self.get_tool_path("avr-ar")
|
|
353
|
+
except ToolchainError:
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
def get_objcopy_path(self) -> Optional[Path]:
|
|
357
|
+
"""Get path to objcopy utility.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Path to objcopy binary or None if not found
|
|
361
|
+
"""
|
|
362
|
+
try:
|
|
363
|
+
return self.get_tool_path("avr-objcopy")
|
|
364
|
+
except ToolchainError:
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
def get_size_path(self) -> Optional[Path]:
|
|
368
|
+
"""Get path to size utility.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Path to size binary or None if not found
|
|
372
|
+
"""
|
|
373
|
+
try:
|
|
374
|
+
return self.get_tool_path("avr-size")
|
|
375
|
+
except ToolchainError:
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
def get_bin_dir(self) -> Optional[Path]:
|
|
379
|
+
"""Get path to toolchain bin directory.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Path to bin directory containing compiler binaries
|
|
383
|
+
"""
|
|
384
|
+
if not self._toolchain_path:
|
|
385
|
+
return None
|
|
386
|
+
return self._toolchain_path / "bin"
|
|
387
|
+
|
|
388
|
+
def is_installed(self) -> bool:
|
|
389
|
+
"""Check if toolchain is already installed.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
True if toolchain directory exists and is valid
|
|
393
|
+
"""
|
|
394
|
+
if not self._toolchain_path:
|
|
395
|
+
# Try to get from cache
|
|
396
|
+
toolchain_path = self.cache.get_toolchain_path(self.BASE_URL, self.VERSION)
|
|
397
|
+
if not toolchain_path.exists():
|
|
398
|
+
return False
|
|
399
|
+
# Verify it
|
|
400
|
+
if self._verify_toolchain(toolchain_path):
|
|
401
|
+
self._toolchain_path = toolchain_path
|
|
402
|
+
return True
|
|
403
|
+
return False
|
|
404
|
+
return self._verify_toolchain(self._toolchain_path)
|
|
405
|
+
|
|
406
|
+
# Implement BasePackage interface
|
|
407
|
+
def ensure_package(self) -> Path:
|
|
408
|
+
"""Ensure package is downloaded and extracted.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
Path to the extracted package directory
|
|
412
|
+
|
|
413
|
+
Raises:
|
|
414
|
+
PackageError: If download or extraction fails
|
|
415
|
+
"""
|
|
416
|
+
return self.ensure_toolchain()
|
|
417
|
+
|
|
418
|
+
def get_package_info(self) -> Dict[str, Any]:
|
|
419
|
+
"""Get information about the package.
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
Dictionary with package metadata (version, path, etc.)
|
|
423
|
+
"""
|
|
424
|
+
info = {
|
|
425
|
+
"type": "toolchain",
|
|
426
|
+
"platform": "avr",
|
|
427
|
+
"version": self.VERSION,
|
|
428
|
+
"base_url": self.BASE_URL,
|
|
429
|
+
"installed": self._toolchain_path is not None,
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if self._toolchain_path:
|
|
433
|
+
info["path"] = str(self._toolchain_path)
|
|
434
|
+
info["tools"] = {name: str(path) for name, path in self.get_all_tools().items()}
|
|
435
|
+
|
|
436
|
+
return info
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Toolchain Binary Finder Utilities.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for locating and verifying toolchain binaries
|
|
4
|
+
in the toolchain installation directory.
|
|
5
|
+
|
|
6
|
+
Binary Naming Conventions:
|
|
7
|
+
- AVR: avr-gcc, avr-g++, avr-ar, avr-objcopy, etc.
|
|
8
|
+
- RISC-V ESP32: riscv32-esp-elf-gcc, riscv32-esp-elf-g++, etc.
|
|
9
|
+
- Xtensa ESP32: xtensa-esp32-elf-gcc, xtensa-esp32-elf-g++, etc.
|
|
10
|
+
|
|
11
|
+
Directory Structure:
|
|
12
|
+
The toolchain binaries are typically located in:
|
|
13
|
+
- toolchain_path/bin/bin/ (nested bin directory after extraction)
|
|
14
|
+
- toolchain_path/bin/{toolchain_name}/bin/ (e.g., bin/riscv32-esp-elf/bin/)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BinaryNotFoundError(Exception):
|
|
22
|
+
"""Raised when a required toolchain binary is not found."""
|
|
23
|
+
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ToolchainBinaryFinder:
|
|
28
|
+
"""Finds and verifies toolchain binaries in the installation directory."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, toolchain_path: Path, binary_prefix: str):
|
|
31
|
+
"""Initialize the binary finder.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
toolchain_path: Base path to the toolchain installation
|
|
35
|
+
binary_prefix: Binary name prefix (e.g., "avr", "riscv32-esp-elf", "xtensa-esp32-elf")
|
|
36
|
+
"""
|
|
37
|
+
self.toolchain_path = toolchain_path
|
|
38
|
+
self.binary_prefix = binary_prefix
|
|
39
|
+
|
|
40
|
+
def find_bin_dir(self) -> Optional[Path]:
|
|
41
|
+
"""Find the bin directory containing toolchain binaries.
|
|
42
|
+
|
|
43
|
+
Searches for binaries in common locations:
|
|
44
|
+
1. toolchain_path/bin/bin/ (nested bin, common after extraction)
|
|
45
|
+
2. toolchain_path/bin/{toolchain_name}/bin/ (nested toolchain directory)
|
|
46
|
+
3. toolchain_path/bin/ (direct bin directory)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Path to bin directory, or None if not found
|
|
50
|
+
"""
|
|
51
|
+
# The toolchain structure is: toolchain_path/bin/bin/
|
|
52
|
+
# (after extraction, the toolchain extracts to a subdirectory,
|
|
53
|
+
# and we copy it to toolchain_path/bin/)
|
|
54
|
+
bin_parent = self.toolchain_path.parent / "bin"
|
|
55
|
+
|
|
56
|
+
if not bin_parent.exists():
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
# Check for bin/bin/ (most common after extraction)
|
|
60
|
+
bin_dir = bin_parent / "bin"
|
|
61
|
+
if bin_dir.exists() and bin_dir.is_dir():
|
|
62
|
+
# Verify it has binaries
|
|
63
|
+
binaries = list(bin_dir.glob("*.exe")) or list(bin_dir.glob("*-gcc"))
|
|
64
|
+
if binaries:
|
|
65
|
+
return bin_dir
|
|
66
|
+
|
|
67
|
+
# Look for nested toolchain directory (e.g., bin/riscv32-esp-elf/bin/)
|
|
68
|
+
for item in bin_parent.iterdir():
|
|
69
|
+
if item.is_dir() and "esp" in item.name.lower():
|
|
70
|
+
nested_bin = item / "bin"
|
|
71
|
+
if nested_bin.exists():
|
|
72
|
+
return nested_bin
|
|
73
|
+
|
|
74
|
+
# Check if bin_parent itself has binaries
|
|
75
|
+
binaries = list(bin_parent.glob("*.exe")) or list(bin_parent.glob("*-gcc"))
|
|
76
|
+
if binaries:
|
|
77
|
+
return bin_parent
|
|
78
|
+
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
def find_binary(self, binary_name: str) -> Optional[Path]:
|
|
82
|
+
"""Find a specific binary in the toolchain bin directory.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
binary_name: Name of the binary without prefix (e.g., "gcc", "g++", "ar")
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Path to the binary, or None if not found
|
|
89
|
+
"""
|
|
90
|
+
bin_dir = self.find_bin_dir()
|
|
91
|
+
if bin_dir is None or not bin_dir.exists():
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
# Construct full binary name with prefix
|
|
95
|
+
binary_with_prefix = f"{self.binary_prefix}-{binary_name}"
|
|
96
|
+
|
|
97
|
+
# Check both with and without .exe extension (Windows compatibility)
|
|
98
|
+
for ext in [".exe", ""]:
|
|
99
|
+
binary_path = bin_dir / f"{binary_with_prefix}{ext}"
|
|
100
|
+
if binary_path.exists():
|
|
101
|
+
return binary_path
|
|
102
|
+
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def find_all_binaries(self, binary_names: List[str]) -> Dict[str, Optional[Path]]:
|
|
106
|
+
"""Find multiple binaries at once.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
binary_names: List of binary names without prefix (e.g., ["gcc", "g++", "ar"])
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Dictionary mapping binary names to their paths (None if not found)
|
|
113
|
+
"""
|
|
114
|
+
return {name: self.find_binary(name) for name in binary_names}
|
|
115
|
+
|
|
116
|
+
def get_common_tool_paths(self) -> Dict[str, Optional[Path]]:
|
|
117
|
+
"""Get paths to common toolchain binaries.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Dictionary mapping tool names to their paths
|
|
121
|
+
"""
|
|
122
|
+
common_tools = ["gcc", "g++", "ar", "objcopy", "size", "objdump"]
|
|
123
|
+
return self.find_all_binaries(common_tools)
|
|
124
|
+
|
|
125
|
+
def verify_binary_exists(self, binary_name: str) -> bool:
|
|
126
|
+
"""Verify that a specific binary exists.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
binary_name: Name of the binary without prefix
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
True if binary exists and is a file
|
|
133
|
+
"""
|
|
134
|
+
binary_path = self.find_binary(binary_name)
|
|
135
|
+
return binary_path is not None and binary_path.exists() and binary_path.is_file()
|
|
136
|
+
|
|
137
|
+
def verify_required_binaries(self, required_binaries: List[str]) -> tuple[bool, List[str]]:
|
|
138
|
+
"""Verify that all required binaries exist.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
required_binaries: List of required binary names without prefix
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Tuple of (all_found, missing_binaries)
|
|
145
|
+
- all_found: True if all binaries were found
|
|
146
|
+
- missing_binaries: List of binary names that were not found
|
|
147
|
+
"""
|
|
148
|
+
missing = []
|
|
149
|
+
for binary_name in required_binaries:
|
|
150
|
+
if not self.verify_binary_exists(binary_name):
|
|
151
|
+
missing.append(binary_name)
|
|
152
|
+
|
|
153
|
+
return len(missing) == 0, missing
|
|
154
|
+
|
|
155
|
+
def verify_installation(self) -> bool:
|
|
156
|
+
"""Verify that the toolchain is properly installed.
|
|
157
|
+
|
|
158
|
+
Checks for essential binaries: gcc, g++, ar, objcopy
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
True if all essential binaries are present
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
BinaryNotFoundError: If essential binaries are missing
|
|
165
|
+
"""
|
|
166
|
+
required_tools = ["gcc", "g++", "ar", "objcopy"]
|
|
167
|
+
all_found, missing = self.verify_required_binaries(required_tools)
|
|
168
|
+
|
|
169
|
+
if not all_found:
|
|
170
|
+
raise BinaryNotFoundError(f"Toolchain installation incomplete. Missing binaries: {', '.join(missing)}")
|
|
171
|
+
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
def get_gcc_path(self) -> Optional[Path]:
|
|
175
|
+
"""Get path to GCC compiler."""
|
|
176
|
+
return self.find_binary("gcc")
|
|
177
|
+
|
|
178
|
+
def get_gxx_path(self) -> Optional[Path]:
|
|
179
|
+
"""Get path to G++ compiler."""
|
|
180
|
+
return self.find_binary("g++")
|
|
181
|
+
|
|
182
|
+
def get_ar_path(self) -> Optional[Path]:
|
|
183
|
+
"""Get path to archiver (ar)."""
|
|
184
|
+
return self.find_binary("ar")
|
|
185
|
+
|
|
186
|
+
def get_objcopy_path(self) -> Optional[Path]:
|
|
187
|
+
"""Get path to objcopy utility."""
|
|
188
|
+
return self.find_binary("objcopy")
|
|
189
|
+
|
|
190
|
+
def get_size_path(self) -> Optional[Path]:
|
|
191
|
+
"""Get path to size utility."""
|
|
192
|
+
return self.find_binary("size")
|
|
193
|
+
|
|
194
|
+
def get_objdump_path(self) -> Optional[Path]:
|
|
195
|
+
"""Get path to objdump utility."""
|
|
196
|
+
return self.find_binary("objdump")
|