fbuild 1.2.8__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.
- fbuild/__init__.py +390 -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_info_generator.py +624 -0
- fbuild/build/build_state.py +325 -0
- fbuild/build/build_utils.py +93 -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 +664 -0
- fbuild/build/configurable_linker.py +637 -0
- fbuild/build/flag_builder.py +214 -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 +651 -0
- fbuild/build/orchestrator_esp32.py +878 -0
- fbuild/build/orchestrator_rp2040.py +719 -0
- fbuild/build/orchestrator_stm32.py +696 -0
- fbuild/build/orchestrator_teensy.py +580 -0
- fbuild/build/source_compilation_orchestrator.py +218 -0
- fbuild/build/source_scanner.py +516 -0
- fbuild/cli.py +717 -0
- fbuild/cli_utils.py +314 -0
- fbuild/config/__init__.py +16 -0
- fbuild/config/board_config.py +542 -0
- fbuild/config/board_loader.py +92 -0
- fbuild/config/ini_parser.py +369 -0
- fbuild/config/mcu_specs.py +88 -0
- fbuild/daemon/__init__.py +42 -0
- fbuild/daemon/async_client.py +531 -0
- fbuild/daemon/client.py +1505 -0
- fbuild/daemon/compilation_queue.py +293 -0
- fbuild/daemon/configuration_lock.py +865 -0
- fbuild/daemon/daemon.py +585 -0
- fbuild/daemon/daemon_context.py +293 -0
- fbuild/daemon/error_collector.py +263 -0
- fbuild/daemon/file_cache.py +332 -0
- fbuild/daemon/firmware_ledger.py +546 -0
- fbuild/daemon/lock_manager.py +508 -0
- fbuild/daemon/logging_utils.py +149 -0
- fbuild/daemon/messages.py +957 -0
- fbuild/daemon/operation_registry.py +288 -0
- fbuild/daemon/port_state_manager.py +249 -0
- fbuild/daemon/process_tracker.py +366 -0
- fbuild/daemon/processors/__init__.py +18 -0
- fbuild/daemon/processors/build_processor.py +248 -0
- fbuild/daemon/processors/deploy_processor.py +664 -0
- fbuild/daemon/processors/install_deps_processor.py +431 -0
- fbuild/daemon/processors/locking_processor.py +777 -0
- fbuild/daemon/processors/monitor_processor.py +285 -0
- fbuild/daemon/request_processor.py +457 -0
- fbuild/daemon/shared_serial.py +819 -0
- fbuild/daemon/status_manager.py +238 -0
- fbuild/daemon/subprocess_manager.py +316 -0
- fbuild/deploy/__init__.py +21 -0
- fbuild/deploy/deployer.py +67 -0
- fbuild/deploy/deployer_esp32.py +310 -0
- fbuild/deploy/docker_utils.py +315 -0
- fbuild/deploy/monitor.py +519 -0
- fbuild/deploy/qemu_runner.py +603 -0
- fbuild/interrupt_utils.py +34 -0
- fbuild/ledger/__init__.py +52 -0
- fbuild/ledger/board_ledger.py +560 -0
- fbuild/output.py +352 -0
- fbuild/packages/__init__.py +66 -0
- fbuild/packages/archive_utils.py +1098 -0
- fbuild/packages/arduino_core.py +412 -0
- fbuild/packages/cache.py +256 -0
- fbuild/packages/concurrent_manager.py +510 -0
- fbuild/packages/downloader.py +518 -0
- fbuild/packages/fingerprint.py +423 -0
- fbuild/packages/framework_esp32.py +538 -0
- fbuild/packages/framework_rp2040.py +349 -0
- fbuild/packages/framework_stm32.py +459 -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 +725 -0
- fbuild/packages/package.py +163 -0
- fbuild/packages/platform_esp32.py +383 -0
- fbuild/packages/platform_rp2040.py +400 -0
- fbuild/packages/platform_stm32.py +581 -0
- fbuild/packages/platform_teensy.py +312 -0
- fbuild/packages/platform_utils.py +131 -0
- fbuild/packages/platformio_registry.py +369 -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 +489 -0
- fbuild/packages/toolchain_metadata.py +185 -0
- fbuild/packages/toolchain_rp2040.py +436 -0
- fbuild/packages/toolchain_stm32.py +417 -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/platform_configs/rp2040.json +70 -0
- fbuild/platform_configs/rp2350.json +76 -0
- fbuild/platform_configs/stm32f1.json +59 -0
- fbuild/platform_configs/stm32f4.json +63 -0
- fbuild/py.typed +0 -0
- fbuild-1.2.8.dist-info/METADATA +468 -0
- fbuild-1.2.8.dist-info/RECORD +121 -0
- fbuild-1.2.8.dist-info/WHEEL +5 -0
- fbuild-1.2.8.dist-info/entry_points.txt +5 -0
- fbuild-1.2.8.dist-info/licenses/LICENSE +21 -0
- fbuild-1.2.8.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,444 @@
|
|
|
1
|
+
"""Binary Generation Utilities.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for generating firmware binaries from ELF files,
|
|
4
|
+
including ESP32-specific bootloader and partition table generation.
|
|
5
|
+
|
|
6
|
+
Design:
|
|
7
|
+
- Separates binary generation logic from linker
|
|
8
|
+
- Supports both objcopy (AVR) and esptool (ESP32) workflows
|
|
9
|
+
- Handles ESP32 bootloader and partition table generation
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BinaryGeneratorError(Exception):
|
|
19
|
+
"""Raised when binary generation operations fail."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BinaryGenerator:
|
|
24
|
+
"""Handles firmware binary generation from ELF files.
|
|
25
|
+
|
|
26
|
+
This class provides:
|
|
27
|
+
- ELF to BIN conversion using objcopy or esptool
|
|
28
|
+
- ESP32 bootloader.bin generation
|
|
29
|
+
- ESP32 partitions.bin generation
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
mcu: str,
|
|
35
|
+
board_config: Dict[str, Any],
|
|
36
|
+
build_dir: Path,
|
|
37
|
+
toolchain: Any = None,
|
|
38
|
+
framework: Any = None,
|
|
39
|
+
show_progress: bool = True
|
|
40
|
+
):
|
|
41
|
+
"""Initialize binary generator.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
mcu: MCU type (e.g., "esp32c6", "atmega328p")
|
|
45
|
+
board_config: Board configuration dictionary
|
|
46
|
+
build_dir: Directory for build artifacts
|
|
47
|
+
toolchain: Toolchain instance (required for objcopy)
|
|
48
|
+
framework: Framework instance (required for ESP32 bootloader/partitions)
|
|
49
|
+
show_progress: Whether to show generation progress
|
|
50
|
+
"""
|
|
51
|
+
self.mcu = mcu
|
|
52
|
+
self.board_config = board_config
|
|
53
|
+
self.build_dir = build_dir
|
|
54
|
+
self.toolchain = toolchain
|
|
55
|
+
self.framework = framework
|
|
56
|
+
self.show_progress = show_progress
|
|
57
|
+
|
|
58
|
+
def generate_bin(self, elf_path: Path, output_bin: Optional[Path] = None) -> Path:
|
|
59
|
+
"""Generate firmware.bin from firmware.elf.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
elf_path: Path to firmware.elf
|
|
63
|
+
output_bin: Optional path for output .bin file
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Path to generated firmware.bin
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
BinaryGeneratorError: If conversion fails
|
|
70
|
+
"""
|
|
71
|
+
if not elf_path.exists():
|
|
72
|
+
raise BinaryGeneratorError(f"ELF file not found: {elf_path}")
|
|
73
|
+
|
|
74
|
+
# Generate output path if not provided
|
|
75
|
+
if output_bin is None:
|
|
76
|
+
output_bin = self.build_dir / "firmware.bin"
|
|
77
|
+
|
|
78
|
+
# For ESP32 platforms, use esptool.py elf2image instead of objcopy
|
|
79
|
+
# This generates a properly formatted ESP32 flash image without memory gaps
|
|
80
|
+
if self.mcu.startswith("esp32"):
|
|
81
|
+
return self._generate_bin_esp32(elf_path, output_bin)
|
|
82
|
+
else:
|
|
83
|
+
return self._generate_bin_objcopy(elf_path, output_bin)
|
|
84
|
+
|
|
85
|
+
def _generate_bin_esp32(self, elf_path: Path, output_bin: Path) -> Path:
|
|
86
|
+
"""Generate firmware.bin for ESP32 using esptool.py elf2image.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
elf_path: Path to firmware.elf
|
|
90
|
+
output_bin: Path for output .bin file
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Path to generated firmware.bin
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
BinaryGeneratorError: If conversion fails
|
|
97
|
+
"""
|
|
98
|
+
# Get chip type from MCU
|
|
99
|
+
chip = self.mcu # e.g., "esp32c6", "esp32s3"
|
|
100
|
+
|
|
101
|
+
# Get flash parameters from board config
|
|
102
|
+
flash_mode = self.board_config.get("build", {}).get("flash_mode", "dio")
|
|
103
|
+
flash_freq = self.board_config.get("build", {}).get("f_flash", "80m")
|
|
104
|
+
flash_size = self.board_config.get("build", {}).get("flash_size", "4MB")
|
|
105
|
+
|
|
106
|
+
# Convert frequency to esptool format if needed
|
|
107
|
+
flash_freq = self._normalize_flash_freq(flash_freq)
|
|
108
|
+
|
|
109
|
+
# Build esptool.py elf2image command
|
|
110
|
+
cmd = [
|
|
111
|
+
sys.executable,
|
|
112
|
+
"-m",
|
|
113
|
+
"esptool",
|
|
114
|
+
"--chip",
|
|
115
|
+
chip,
|
|
116
|
+
"elf2image",
|
|
117
|
+
"--flash-mode",
|
|
118
|
+
flash_mode,
|
|
119
|
+
"--flash-freq",
|
|
120
|
+
flash_freq,
|
|
121
|
+
"--flash-size",
|
|
122
|
+
flash_size,
|
|
123
|
+
"--elf-sha256-offset",
|
|
124
|
+
"0xb0",
|
|
125
|
+
"-o",
|
|
126
|
+
str(output_bin),
|
|
127
|
+
str(elf_path)
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
if self.show_progress:
|
|
131
|
+
print("Generating firmware.bin using esptool.py elf2image...")
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
result = subprocess.run(
|
|
135
|
+
cmd,
|
|
136
|
+
capture_output=True,
|
|
137
|
+
text=False, # Don't decode as text - esptool may output binary data
|
|
138
|
+
timeout=60
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if result.returncode != 0:
|
|
142
|
+
error_msg = "Binary generation failed\n"
|
|
143
|
+
stderr = result.stderr.decode('utf-8', errors='replace') if result.stderr else ""
|
|
144
|
+
stdout = result.stdout.decode('utf-8', errors='replace') if result.stdout else ""
|
|
145
|
+
error_msg += f"stderr: {stderr}\n"
|
|
146
|
+
error_msg += f"stdout: {stdout}"
|
|
147
|
+
raise BinaryGeneratorError(error_msg)
|
|
148
|
+
|
|
149
|
+
if not output_bin.exists():
|
|
150
|
+
raise BinaryGeneratorError(f"firmware.bin was not created: {output_bin}")
|
|
151
|
+
|
|
152
|
+
if self.show_progress:
|
|
153
|
+
size = output_bin.stat().st_size
|
|
154
|
+
print(f"✓ Created firmware.bin: {size:,} bytes ({size / 1024:.2f} KB)")
|
|
155
|
+
|
|
156
|
+
return output_bin
|
|
157
|
+
|
|
158
|
+
except subprocess.TimeoutExpired as e:
|
|
159
|
+
raise BinaryGeneratorError("Binary generation timeout") from e
|
|
160
|
+
except KeyboardInterrupt as ke:
|
|
161
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
162
|
+
handle_keyboard_interrupt_properly(ke)
|
|
163
|
+
raise # Never reached, but satisfies type checker
|
|
164
|
+
except Exception as e:
|
|
165
|
+
raise BinaryGeneratorError(f"Failed to generate binary: {e}") from e
|
|
166
|
+
|
|
167
|
+
def _generate_bin_objcopy(self, elf_path: Path, output_bin: Path) -> Path:
|
|
168
|
+
"""Generate firmware.bin using objcopy (for non-ESP32 platforms).
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
elf_path: Path to firmware.elf
|
|
172
|
+
output_bin: Path for output .bin file
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Path to generated firmware.bin
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
BinaryGeneratorError: If conversion fails
|
|
179
|
+
"""
|
|
180
|
+
if self.toolchain is None:
|
|
181
|
+
raise BinaryGeneratorError("Toolchain required for objcopy binary generation")
|
|
182
|
+
|
|
183
|
+
# Get objcopy tool
|
|
184
|
+
objcopy_path = self.toolchain.get_objcopy_path()
|
|
185
|
+
if objcopy_path is None or not objcopy_path.exists():
|
|
186
|
+
raise BinaryGeneratorError(
|
|
187
|
+
f"objcopy not found: {objcopy_path}. " +
|
|
188
|
+
"Ensure toolchain is installed."
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Build objcopy command
|
|
192
|
+
cmd = [
|
|
193
|
+
str(objcopy_path),
|
|
194
|
+
"-O", "binary",
|
|
195
|
+
str(elf_path),
|
|
196
|
+
str(output_bin)
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
# Execute objcopy
|
|
200
|
+
if self.show_progress:
|
|
201
|
+
print("Generating firmware.bin...")
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
result = subprocess.run(
|
|
205
|
+
cmd,
|
|
206
|
+
capture_output=True,
|
|
207
|
+
text=True,
|
|
208
|
+
timeout=30
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if result.returncode != 0:
|
|
212
|
+
error_msg = "Binary generation failed\n"
|
|
213
|
+
error_msg += f"stderr: {result.stderr}\n"
|
|
214
|
+
error_msg += f"stdout: {result.stdout}"
|
|
215
|
+
raise BinaryGeneratorError(error_msg)
|
|
216
|
+
|
|
217
|
+
if not output_bin.exists():
|
|
218
|
+
raise BinaryGeneratorError(f"firmware.bin was not created: {output_bin}")
|
|
219
|
+
|
|
220
|
+
if self.show_progress:
|
|
221
|
+
size = output_bin.stat().st_size
|
|
222
|
+
print(f"✓ Created firmware.bin: {size:,} bytes ({size / 1024 / 1024:.2f} MB)")
|
|
223
|
+
|
|
224
|
+
return output_bin
|
|
225
|
+
|
|
226
|
+
except subprocess.TimeoutExpired as e:
|
|
227
|
+
raise BinaryGeneratorError("Binary generation timeout") from e
|
|
228
|
+
except KeyboardInterrupt as ke:
|
|
229
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
230
|
+
handle_keyboard_interrupt_properly(ke)
|
|
231
|
+
raise # Never reached, but satisfies type checker
|
|
232
|
+
except Exception as e:
|
|
233
|
+
raise BinaryGeneratorError(f"Failed to generate binary: {e}") from e
|
|
234
|
+
|
|
235
|
+
def generate_bootloader(self, output_bin: Optional[Path] = None) -> Path:
|
|
236
|
+
"""Generate bootloader.bin from bootloader ELF file.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
output_bin: Optional path for output bootloader.bin
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Path to generated bootloader.bin
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
BinaryGeneratorError: If generation fails
|
|
246
|
+
"""
|
|
247
|
+
if not self.mcu.startswith("esp32"):
|
|
248
|
+
raise BinaryGeneratorError(
|
|
249
|
+
f"Bootloader generation only supported for ESP32 platforms, not {self.mcu}"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if self.framework is None:
|
|
253
|
+
raise BinaryGeneratorError("Framework required for bootloader generation")
|
|
254
|
+
|
|
255
|
+
# Generate output path if not provided
|
|
256
|
+
if output_bin is None:
|
|
257
|
+
output_bin = self.build_dir / "bootloader.bin"
|
|
258
|
+
|
|
259
|
+
# Get flash parameters from board config
|
|
260
|
+
flash_mode = self.board_config.get("build", {}).get("flash_mode", "dio")
|
|
261
|
+
flash_freq = self.board_config.get("build", {}).get("f_flash", "80m")
|
|
262
|
+
flash_size = self.board_config.get("build", {}).get("flash_size", "4MB")
|
|
263
|
+
|
|
264
|
+
# Convert frequency to esptool format if needed
|
|
265
|
+
flash_freq = self._normalize_flash_freq(flash_freq)
|
|
266
|
+
|
|
267
|
+
# Find bootloader ELF file in framework SDK
|
|
268
|
+
bootloader_name = f"bootloader_{flash_mode}_{flash_freq.replace('m', 'm')}.elf"
|
|
269
|
+
sdk_bin_dir = self.framework.get_sdk_dir() / self.mcu / "bin"
|
|
270
|
+
bootloader_elf = sdk_bin_dir / bootloader_name
|
|
271
|
+
|
|
272
|
+
if not bootloader_elf.exists():
|
|
273
|
+
raise BinaryGeneratorError(
|
|
274
|
+
f"Bootloader ELF not found: {bootloader_elf}"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# CRITICAL FIX: ESP32-C6/C3/C2/H2 bootloaders MUST be generated in DIO mode
|
|
278
|
+
# even if the application uses QIO. The ROM bootloader can only load the
|
|
279
|
+
# second-stage bootloader in DIO mode. QIO is enabled later by the second-stage
|
|
280
|
+
# bootloader for the application. This is a known issue with esptool v4.7+.
|
|
281
|
+
# See: https://github.com/espressif/arduino-esp32/discussions/10418
|
|
282
|
+
bootloader_flash_mode = flash_mode
|
|
283
|
+
if self.mcu in ["esp32c6", "esp32c3", "esp32c2", "esp32h2"]:
|
|
284
|
+
bootloader_flash_mode = "dio"
|
|
285
|
+
|
|
286
|
+
# Generate bootloader.bin using esptool.py elf2image
|
|
287
|
+
cmd = [
|
|
288
|
+
sys.executable,
|
|
289
|
+
"-m",
|
|
290
|
+
"esptool",
|
|
291
|
+
"--chip",
|
|
292
|
+
self.mcu,
|
|
293
|
+
"elf2image",
|
|
294
|
+
"--flash-mode",
|
|
295
|
+
bootloader_flash_mode,
|
|
296
|
+
"--flash-freq",
|
|
297
|
+
flash_freq,
|
|
298
|
+
"--flash-size",
|
|
299
|
+
flash_size,
|
|
300
|
+
"-o",
|
|
301
|
+
str(output_bin),
|
|
302
|
+
str(bootloader_elf)
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
if self.show_progress:
|
|
306
|
+
print("Generating bootloader.bin...")
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
result = subprocess.run(
|
|
310
|
+
cmd,
|
|
311
|
+
capture_output=True,
|
|
312
|
+
text=False,
|
|
313
|
+
timeout=60
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if result.returncode != 0:
|
|
317
|
+
error_msg = "Bootloader generation failed\n"
|
|
318
|
+
stderr = result.stderr.decode('utf-8', errors='replace') if result.stderr else ""
|
|
319
|
+
stdout = result.stdout.decode('utf-8', errors='replace') if result.stdout else ""
|
|
320
|
+
error_msg += f"stderr: {stderr}\n"
|
|
321
|
+
error_msg += f"stdout: {stdout}"
|
|
322
|
+
raise BinaryGeneratorError(error_msg)
|
|
323
|
+
|
|
324
|
+
if not output_bin.exists():
|
|
325
|
+
raise BinaryGeneratorError(f"bootloader.bin was not created: {output_bin}")
|
|
326
|
+
|
|
327
|
+
if self.show_progress:
|
|
328
|
+
size = output_bin.stat().st_size
|
|
329
|
+
print(f"✓ Created bootloader.bin: {size:,} bytes ({size / 1024:.2f} KB)")
|
|
330
|
+
|
|
331
|
+
return output_bin
|
|
332
|
+
|
|
333
|
+
except subprocess.TimeoutExpired as e:
|
|
334
|
+
raise BinaryGeneratorError("Bootloader generation timeout") from e
|
|
335
|
+
except KeyboardInterrupt as ke:
|
|
336
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
337
|
+
handle_keyboard_interrupt_properly(ke)
|
|
338
|
+
raise # Never reached, but satisfies type checker
|
|
339
|
+
except Exception as e:
|
|
340
|
+
raise BinaryGeneratorError(f"Failed to generate bootloader: {e}") from e
|
|
341
|
+
|
|
342
|
+
def generate_partition_table(self, output_bin: Optional[Path] = None) -> Path:
|
|
343
|
+
"""Generate partitions.bin from partition CSV file.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
output_bin: Optional path for output partitions.bin
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Path to generated partitions.bin
|
|
350
|
+
|
|
351
|
+
Raises:
|
|
352
|
+
BinaryGeneratorError: If generation fails
|
|
353
|
+
"""
|
|
354
|
+
if not self.mcu.startswith("esp32"):
|
|
355
|
+
raise BinaryGeneratorError(
|
|
356
|
+
f"Partition table generation only supported for ESP32 platforms, not {self.mcu}"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if self.framework is None:
|
|
360
|
+
raise BinaryGeneratorError("Framework required for partition table generation")
|
|
361
|
+
|
|
362
|
+
# Generate output path if not provided
|
|
363
|
+
if output_bin is None:
|
|
364
|
+
output_bin = self.build_dir / "partitions.bin"
|
|
365
|
+
|
|
366
|
+
# Find partition CSV file - use default.csv from framework
|
|
367
|
+
partitions_csv = self.framework.framework_path / "tools" / "partitions" / "default.csv"
|
|
368
|
+
|
|
369
|
+
if not partitions_csv.exists():
|
|
370
|
+
raise BinaryGeneratorError(
|
|
371
|
+
f"Partition CSV not found: {partitions_csv}"
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Find gen_esp32part.py tool - also in framework
|
|
375
|
+
gen_tool = self.framework.framework_path / "tools" / "gen_esp32part.py"
|
|
376
|
+
|
|
377
|
+
if not gen_tool.exists():
|
|
378
|
+
raise BinaryGeneratorError(
|
|
379
|
+
f"Partition generation tool not found: {gen_tool}"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Generate partition table using gen_esp32part.py
|
|
383
|
+
cmd = [
|
|
384
|
+
sys.executable,
|
|
385
|
+
str(gen_tool),
|
|
386
|
+
"-q",
|
|
387
|
+
str(partitions_csv),
|
|
388
|
+
str(output_bin)
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
if self.show_progress:
|
|
392
|
+
print("Generating partitions.bin...")
|
|
393
|
+
|
|
394
|
+
try:
|
|
395
|
+
result = subprocess.run(
|
|
396
|
+
cmd,
|
|
397
|
+
capture_output=True,
|
|
398
|
+
text=True,
|
|
399
|
+
timeout=30
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
if result.returncode != 0:
|
|
403
|
+
error_msg = "Partition table generation failed\n"
|
|
404
|
+
error_msg += f"stderr: {result.stderr}\n"
|
|
405
|
+
error_msg += f"stdout: {result.stdout}"
|
|
406
|
+
raise BinaryGeneratorError(error_msg)
|
|
407
|
+
|
|
408
|
+
if not output_bin.exists():
|
|
409
|
+
raise BinaryGeneratorError(f"partitions.bin was not created: {output_bin}")
|
|
410
|
+
|
|
411
|
+
if self.show_progress:
|
|
412
|
+
size = output_bin.stat().st_size
|
|
413
|
+
print(f"✓ Created partitions.bin: {size:,} bytes")
|
|
414
|
+
|
|
415
|
+
return output_bin
|
|
416
|
+
|
|
417
|
+
except subprocess.TimeoutExpired as e:
|
|
418
|
+
raise BinaryGeneratorError("Partition table generation timeout") from e
|
|
419
|
+
except KeyboardInterrupt as ke:
|
|
420
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
421
|
+
handle_keyboard_interrupt_properly(ke)
|
|
422
|
+
raise # Never reached, but satisfies type checker
|
|
423
|
+
except Exception as e:
|
|
424
|
+
raise BinaryGeneratorError(f"Failed to generate partition table: {e}") from e
|
|
425
|
+
|
|
426
|
+
@staticmethod
|
|
427
|
+
def _normalize_flash_freq(flash_freq: Any) -> str:
|
|
428
|
+
"""Normalize flash frequency to esptool format.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
flash_freq: Flash frequency (int/float in Hz, or string like "80m" or "80000000L")
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
Normalized frequency string (e.g., "80m")
|
|
435
|
+
"""
|
|
436
|
+
if isinstance(flash_freq, (int, float)):
|
|
437
|
+
# Convert Hz to MHz format like "80m"
|
|
438
|
+
return f"{int(flash_freq // 1000000)}m"
|
|
439
|
+
elif isinstance(flash_freq, str) and flash_freq.endswith('L'):
|
|
440
|
+
# Handle string representation of long integers like "80000000L"
|
|
441
|
+
freq_value = int(flash_freq.rstrip('L'))
|
|
442
|
+
return f"{freq_value // 1000000}m"
|
|
443
|
+
else:
|
|
444
|
+
return str(flash_freq)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Build component factory for Fbuild build system.
|
|
3
|
+
|
|
4
|
+
This module provides factory methods for creating build components
|
|
5
|
+
(Compiler, Linker) with appropriate configurations. It centralizes
|
|
6
|
+
the logic for setting up these components with correct parameters.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Optional, TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from ..config.board_config import BoardConfig
|
|
13
|
+
from ..config.mcu_specs import get_max_flash, get_max_ram
|
|
14
|
+
from ..packages.package import IToolchain
|
|
15
|
+
from .compiler_avr import CompilerAVR
|
|
16
|
+
from .linker import LinkerAVR
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from ..daemon.compilation_queue import CompilationJobQueue
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BuildComponentFactory:
|
|
23
|
+
"""
|
|
24
|
+
Factory for creating build components with proper configurations.
|
|
25
|
+
|
|
26
|
+
This class centralizes the creation of build system components (Compiler, Linker)
|
|
27
|
+
with appropriate settings derived from board configuration and toolchain.
|
|
28
|
+
|
|
29
|
+
Example usage:
|
|
30
|
+
factory = BuildComponentFactory()
|
|
31
|
+
compiler = factory.create_compiler(
|
|
32
|
+
toolchain=toolchain,
|
|
33
|
+
board_config=board_config,
|
|
34
|
+
core_path=core_path,
|
|
35
|
+
lib_include_paths=[Path("lib1/include"), Path("lib2/include")]
|
|
36
|
+
)
|
|
37
|
+
linker = factory.create_linker(
|
|
38
|
+
toolchain=toolchain,
|
|
39
|
+
board_config=board_config
|
|
40
|
+
)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def create_compiler(
|
|
45
|
+
toolchain: IToolchain,
|
|
46
|
+
board_config: BoardConfig,
|
|
47
|
+
core_path: Path,
|
|
48
|
+
lib_include_paths: Optional[List[Path]] = None,
|
|
49
|
+
compilation_queue: Optional['CompilationJobQueue'] = None
|
|
50
|
+
) -> CompilerAVR:
|
|
51
|
+
"""
|
|
52
|
+
Create compiler instance with appropriate settings.
|
|
53
|
+
|
|
54
|
+
Configures the compiler with:
|
|
55
|
+
- Toolchain binaries (avr-gcc, avr-g++)
|
|
56
|
+
- MCU and F_CPU from board configuration
|
|
57
|
+
- Include paths (core + variant + libraries)
|
|
58
|
+
- Defines (Arduino version, board-specific defines)
|
|
59
|
+
- Optional compilation queue for async/parallel compilation
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
toolchain: Toolchain instance
|
|
63
|
+
board_config: Board configuration
|
|
64
|
+
core_path: Arduino core path
|
|
65
|
+
lib_include_paths: Optional library include paths
|
|
66
|
+
compilation_queue: Optional compilation queue for async compilation
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Configured Compiler instance
|
|
70
|
+
"""
|
|
71
|
+
# Get toolchain binaries
|
|
72
|
+
tools = toolchain.get_all_tools()
|
|
73
|
+
|
|
74
|
+
# Get include paths from board config
|
|
75
|
+
include_paths = board_config.get_include_paths(core_path)
|
|
76
|
+
|
|
77
|
+
# Add library include paths
|
|
78
|
+
if lib_include_paths:
|
|
79
|
+
include_paths = list(include_paths) + list(lib_include_paths)
|
|
80
|
+
|
|
81
|
+
# Get defines from board config
|
|
82
|
+
defines = board_config.get_defines()
|
|
83
|
+
|
|
84
|
+
# Create and return compiler
|
|
85
|
+
return CompilerAVR(
|
|
86
|
+
avr_gcc=tools['avr-gcc'],
|
|
87
|
+
avr_gpp=tools['avr-g++'],
|
|
88
|
+
mcu=board_config.mcu,
|
|
89
|
+
f_cpu=board_config.f_cpu,
|
|
90
|
+
includes=include_paths,
|
|
91
|
+
defines=defines,
|
|
92
|
+
compilation_queue=compilation_queue
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def create_linker(
|
|
97
|
+
toolchain: IToolchain,
|
|
98
|
+
board_config: BoardConfig
|
|
99
|
+
) -> LinkerAVR:
|
|
100
|
+
"""
|
|
101
|
+
Create linker instance with appropriate settings.
|
|
102
|
+
|
|
103
|
+
Configures the linker with:
|
|
104
|
+
- Toolchain binaries (avr-gcc, avr-ar, avr-objcopy, avr-size)
|
|
105
|
+
- MCU from board configuration
|
|
106
|
+
- Flash and RAM limits from MCU specifications
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
toolchain: Toolchain instance
|
|
110
|
+
board_config: Board configuration
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Configured Linker instance
|
|
114
|
+
"""
|
|
115
|
+
# Get toolchain binaries
|
|
116
|
+
tools = toolchain.get_all_tools()
|
|
117
|
+
|
|
118
|
+
# Get MCU specifications from centralized module
|
|
119
|
+
max_flash = get_max_flash(board_config.mcu)
|
|
120
|
+
max_ram = get_max_ram(board_config.mcu)
|
|
121
|
+
|
|
122
|
+
# Create and return linker
|
|
123
|
+
return LinkerAVR(
|
|
124
|
+
avr_gcc=tools['avr-gcc'],
|
|
125
|
+
avr_ar=tools['avr-ar'],
|
|
126
|
+
avr_objcopy=tools['avr-objcopy'],
|
|
127
|
+
avr_size=tools['avr-size'],
|
|
128
|
+
mcu=board_config.mcu,
|
|
129
|
+
max_flash=max_flash,
|
|
130
|
+
max_ram=max_ram
|
|
131
|
+
)
|