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,314 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Firmware deployment module for uploading to embedded devices.
|
|
3
|
+
|
|
4
|
+
This module handles flashing firmware to ESP32 devices using esptool.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from fbuild.config import PlatformIOConfig
|
|
14
|
+
from fbuild.packages import Cache
|
|
15
|
+
|
|
16
|
+
from .deployer import DeploymentError, DeploymentResult, IDeployer
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ESP32Deployer(IDeployer):
|
|
20
|
+
"""Handles firmware deployment to embedded devices."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, verbose: bool = False):
|
|
23
|
+
"""Initialize deployer.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
verbose: Whether to show verbose output
|
|
27
|
+
"""
|
|
28
|
+
self.verbose = verbose
|
|
29
|
+
|
|
30
|
+
def deploy(
|
|
31
|
+
self,
|
|
32
|
+
project_dir: Path,
|
|
33
|
+
env_name: str,
|
|
34
|
+
port: Optional[str] = None,
|
|
35
|
+
) -> DeploymentResult:
|
|
36
|
+
"""Deploy firmware to a device.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
project_dir: Path to project directory
|
|
40
|
+
env_name: Environment name to deploy
|
|
41
|
+
port: Serial port to use (auto-detect if None)
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
DeploymentResult with success status and message
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
# Load platformio.ini
|
|
48
|
+
ini_path = project_dir / "platformio.ini"
|
|
49
|
+
if not ini_path.exists():
|
|
50
|
+
raise DeploymentError(f"platformio.ini not found in {project_dir}")
|
|
51
|
+
|
|
52
|
+
config = PlatformIOConfig(ini_path)
|
|
53
|
+
env_config = config.get_env_config(env_name)
|
|
54
|
+
|
|
55
|
+
# Get board and platform
|
|
56
|
+
board_id = env_config.get("board")
|
|
57
|
+
platform_url = env_config.get("platform")
|
|
58
|
+
|
|
59
|
+
if not board_id or not platform_url:
|
|
60
|
+
raise DeploymentError("Board or platform not specified in platformio.ini")
|
|
61
|
+
|
|
62
|
+
# Determine platform type
|
|
63
|
+
if "espressif32" in platform_url or board_id.startswith("esp32"):
|
|
64
|
+
return self._deploy_esp32(project_dir, env_name, board_id, port, platform_url)
|
|
65
|
+
else:
|
|
66
|
+
raise DeploymentError(f"Deployment not supported for board: {board_id}")
|
|
67
|
+
|
|
68
|
+
except DeploymentError as e:
|
|
69
|
+
return DeploymentResult(success=False, message=str(e))
|
|
70
|
+
except KeyboardInterrupt as ke:
|
|
71
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
72
|
+
|
|
73
|
+
handle_keyboard_interrupt_properly(ke)
|
|
74
|
+
raise # Never reached, but satisfies type checker
|
|
75
|
+
except Exception as e:
|
|
76
|
+
return DeploymentResult(success=False, message=f"Unexpected deployment error: {e}")
|
|
77
|
+
|
|
78
|
+
def _deploy_esp32(
|
|
79
|
+
self,
|
|
80
|
+
project_dir: Path,
|
|
81
|
+
env_name: str,
|
|
82
|
+
board_id: str,
|
|
83
|
+
port: Optional[str],
|
|
84
|
+
platform_url: str,
|
|
85
|
+
) -> DeploymentResult:
|
|
86
|
+
"""Deploy firmware to ESP32 device.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
project_dir: Path to project directory
|
|
90
|
+
env_name: Environment name
|
|
91
|
+
board_id: Board identifier
|
|
92
|
+
port: Serial port (auto-detect if None)
|
|
93
|
+
platform_url: Platform package URL
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
DeploymentResult with success status
|
|
97
|
+
"""
|
|
98
|
+
# Get build directory
|
|
99
|
+
build_dir = project_dir / ".fbuild" / "build" / env_name
|
|
100
|
+
firmware_bin = (build_dir / "firmware.bin").absolute()
|
|
101
|
+
bootloader_bin = (build_dir / "bootloader.bin").absolute()
|
|
102
|
+
partitions_bin = (build_dir / "partitions.bin").absolute()
|
|
103
|
+
|
|
104
|
+
if not firmware_bin.exists():
|
|
105
|
+
raise DeploymentError(f"Firmware not found at {firmware_bin}. Run 'fbuild build' first.")
|
|
106
|
+
|
|
107
|
+
# Get cache and ensure platform/toolchain packages
|
|
108
|
+
cache = Cache(project_dir)
|
|
109
|
+
|
|
110
|
+
# Import ESP32 packages
|
|
111
|
+
from fbuild.config import BoardConfig
|
|
112
|
+
from fbuild.packages.framework_esp32 import FrameworkESP32
|
|
113
|
+
from fbuild.packages.platform_esp32 import PlatformESP32
|
|
114
|
+
|
|
115
|
+
# Get board config to determine MCU type
|
|
116
|
+
board_config = BoardConfig.from_board_id(board_id)
|
|
117
|
+
mcu = board_config.mcu
|
|
118
|
+
|
|
119
|
+
# Ensure platform is downloaded
|
|
120
|
+
platform = PlatformESP32(cache, platform_url, show_progress=self.verbose)
|
|
121
|
+
platform.ensure_platform()
|
|
122
|
+
|
|
123
|
+
# Get board JSON to determine required packages
|
|
124
|
+
board_json = platform.get_board_json(board_id)
|
|
125
|
+
packages = platform.get_required_packages(mcu)
|
|
126
|
+
|
|
127
|
+
# Initialize framework
|
|
128
|
+
framework_url = packages.get("framework-arduinoespressif32")
|
|
129
|
+
libs_url = packages.get("framework-arduinoespressif32-libs")
|
|
130
|
+
if not framework_url or not libs_url:
|
|
131
|
+
raise DeploymentError("Framework URLs not found in platform package")
|
|
132
|
+
|
|
133
|
+
framework = FrameworkESP32(cache, framework_url, libs_url, show_progress=self.verbose)
|
|
134
|
+
framework.ensure_framework()
|
|
135
|
+
|
|
136
|
+
# Auto-detect port if not specified
|
|
137
|
+
if not port:
|
|
138
|
+
port = self._detect_serial_port()
|
|
139
|
+
if not port:
|
|
140
|
+
raise DeploymentError("No serial port specified and auto-detection failed. " + "Use --port to specify a port.")
|
|
141
|
+
|
|
142
|
+
if self.verbose:
|
|
143
|
+
print(f"Using port: {port}")
|
|
144
|
+
|
|
145
|
+
# Determine chip type and flash parameters from board JSON
|
|
146
|
+
chip = self._get_chip_type(mcu)
|
|
147
|
+
flash_mode = board_json.get("build", {}).get("flash_mode", "dio")
|
|
148
|
+
|
|
149
|
+
# Get flash frequency and convert to esptool format
|
|
150
|
+
f_flash = board_json.get("build", {}).get("f_flash", "80000000L")
|
|
151
|
+
if isinstance(f_flash, str) and f_flash.endswith("L"):
|
|
152
|
+
freq_value = int(f_flash.rstrip("L"))
|
|
153
|
+
flash_freq = f"{freq_value // 1000000}m"
|
|
154
|
+
elif isinstance(f_flash, (int, float)):
|
|
155
|
+
flash_freq = f"{int(f_flash // 1000000)}m"
|
|
156
|
+
else:
|
|
157
|
+
flash_freq = "80m"
|
|
158
|
+
|
|
159
|
+
flash_size = "detect"
|
|
160
|
+
|
|
161
|
+
# CRITICAL FIX: ESP32-C6/C3/C2/H2 ROM bootloader can only load the second-stage
|
|
162
|
+
# bootloader in DIO mode. Must use DIO for flashing even if app uses QIO.
|
|
163
|
+
# See: https://github.com/espressif/arduino-esp32/discussions/10418
|
|
164
|
+
if mcu in ["esp32c6", "esp32c3", "esp32c2", "esp32h2"]:
|
|
165
|
+
flash_mode = "dio"
|
|
166
|
+
|
|
167
|
+
# Determine bootloader offset based on MCU
|
|
168
|
+
# ESP32/ESP32-S2: 0x1000, ESP32-P4: 0x2000, others: 0x0
|
|
169
|
+
if mcu in ["esp32", "esp32s2"]:
|
|
170
|
+
bootloader_offset = "0x1000"
|
|
171
|
+
elif mcu == "esp32p4":
|
|
172
|
+
bootloader_offset = "0x2000"
|
|
173
|
+
else:
|
|
174
|
+
bootloader_offset = "0x0"
|
|
175
|
+
|
|
176
|
+
# Find boot_app0.bin in framework tools
|
|
177
|
+
boot_app0_bin = framework.framework_path / "tools" / "partitions" / "boot_app0.bin"
|
|
178
|
+
|
|
179
|
+
# Build esptool command to flash multiple binaries at different offsets
|
|
180
|
+
# Flash layout: bootloader @ offset, partition table @ 0x8000, boot_app0 @ 0xe000, app @ 0x10000
|
|
181
|
+
cmd = [
|
|
182
|
+
sys.executable,
|
|
183
|
+
"-m",
|
|
184
|
+
"esptool",
|
|
185
|
+
"--chip",
|
|
186
|
+
chip,
|
|
187
|
+
"--port",
|
|
188
|
+
port,
|
|
189
|
+
"--baud",
|
|
190
|
+
"460800",
|
|
191
|
+
"write_flash",
|
|
192
|
+
"-z", # Compress
|
|
193
|
+
"--flash-mode",
|
|
194
|
+
flash_mode,
|
|
195
|
+
"--flash-freq",
|
|
196
|
+
flash_freq,
|
|
197
|
+
"--flash-size",
|
|
198
|
+
flash_size,
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
# Add bootloader if it exists
|
|
202
|
+
if bootloader_bin.exists():
|
|
203
|
+
cmd.extend([bootloader_offset, str(bootloader_bin)])
|
|
204
|
+
else:
|
|
205
|
+
if self.verbose:
|
|
206
|
+
print("Warning: bootloader.bin not found, skipping")
|
|
207
|
+
|
|
208
|
+
# Add partition table if it exists
|
|
209
|
+
if partitions_bin.exists():
|
|
210
|
+
cmd.extend(["0x8000", str(partitions_bin)])
|
|
211
|
+
else:
|
|
212
|
+
if self.verbose:
|
|
213
|
+
print("Warning: partitions.bin not found, skipping")
|
|
214
|
+
|
|
215
|
+
# Add boot_app0.bin if it exists
|
|
216
|
+
if boot_app0_bin.exists():
|
|
217
|
+
cmd.extend(["0xe000", str(boot_app0_bin)])
|
|
218
|
+
else:
|
|
219
|
+
if self.verbose:
|
|
220
|
+
print("Warning: boot_app0.bin not found, skipping")
|
|
221
|
+
|
|
222
|
+
# Add application firmware at 0x10000
|
|
223
|
+
cmd.extend(["0x10000", str(firmware_bin)])
|
|
224
|
+
|
|
225
|
+
if self.verbose:
|
|
226
|
+
print("Flashing firmware to device...")
|
|
227
|
+
print(f" Bootloader: {bootloader_offset}")
|
|
228
|
+
print(" Partition table: 0x8000")
|
|
229
|
+
print(" Boot app: 0xe000")
|
|
230
|
+
print(" Application: 0x10000")
|
|
231
|
+
print(f"Running: {' '.join(cmd)}")
|
|
232
|
+
|
|
233
|
+
# Execute esptool - must use cmd.exe for ESP32 on Windows
|
|
234
|
+
if sys.platform == "win32":
|
|
235
|
+
# Run via cmd.exe to avoid msys issues
|
|
236
|
+
env = os.environ.copy()
|
|
237
|
+
# Strip MSYS paths that cause issues
|
|
238
|
+
if "PATH" in env:
|
|
239
|
+
paths = env["PATH"].split(os.pathsep)
|
|
240
|
+
filtered_paths = [p for p in paths if "msys" not in p.lower()]
|
|
241
|
+
env["PATH"] = os.pathsep.join(filtered_paths)
|
|
242
|
+
|
|
243
|
+
result = subprocess.run(
|
|
244
|
+
cmd,
|
|
245
|
+
cwd=project_dir,
|
|
246
|
+
capture_output=not self.verbose,
|
|
247
|
+
text=False, # Don't decode as text - esptool may output binary data
|
|
248
|
+
env=env,
|
|
249
|
+
shell=False,
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
result = subprocess.run(
|
|
253
|
+
cmd,
|
|
254
|
+
cwd=project_dir,
|
|
255
|
+
capture_output=not self.verbose,
|
|
256
|
+
text=False, # Don't decode as text - esptool may output binary data
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if result.returncode != 0:
|
|
260
|
+
error_msg = "Upload failed"
|
|
261
|
+
if result.stderr:
|
|
262
|
+
error_msg = result.stderr.decode("utf-8", errors="replace")
|
|
263
|
+
return DeploymentResult(success=False, message=f"Deployment failed: {error_msg}", port=port)
|
|
264
|
+
|
|
265
|
+
return DeploymentResult(success=True, message="Firmware uploaded successfully", port=port)
|
|
266
|
+
|
|
267
|
+
def _get_chip_type(self, mcu: str) -> str:
|
|
268
|
+
"""Get chip type string for esptool from MCU name.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
mcu: MCU type (e.g., "esp32c6", "esp32s3")
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Chip type for esptool (e.g., "esp32c6", "esp32s3")
|
|
275
|
+
"""
|
|
276
|
+
# Map MCU names to esptool chip types
|
|
277
|
+
return mcu # Usually they match directly
|
|
278
|
+
|
|
279
|
+
def _detect_serial_port(self) -> Optional[str]:
|
|
280
|
+
"""Auto-detect serial port for device.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Serial port name or None if not found
|
|
284
|
+
"""
|
|
285
|
+
try:
|
|
286
|
+
import serial.tools.list_ports
|
|
287
|
+
|
|
288
|
+
ports = list(serial.tools.list_ports.comports())
|
|
289
|
+
|
|
290
|
+
# Look for ESP32 or USB-SERIAL devices
|
|
291
|
+
for port in ports:
|
|
292
|
+
description = (port.description or "").lower()
|
|
293
|
+
manufacturer = (port.manufacturer or "").lower()
|
|
294
|
+
|
|
295
|
+
if any(x in description or x in manufacturer for x in ["cp210", "ch340", "usb-serial", "uart", "esp32"]):
|
|
296
|
+
return port.device
|
|
297
|
+
|
|
298
|
+
# If no specific match, return first port
|
|
299
|
+
if ports:
|
|
300
|
+
return ports[0].device
|
|
301
|
+
|
|
302
|
+
except ImportError:
|
|
303
|
+
if self.verbose:
|
|
304
|
+
print("pyserial not installed. Cannot auto-detect port.")
|
|
305
|
+
except KeyboardInterrupt as ke:
|
|
306
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
307
|
+
|
|
308
|
+
handle_keyboard_interrupt_properly(ke)
|
|
309
|
+
raise # Never reached, but satisfies type checker
|
|
310
|
+
except Exception as e:
|
|
311
|
+
if self.verbose:
|
|
312
|
+
print(f"Port detection failed: {e}")
|
|
313
|
+
|
|
314
|
+
return None
|