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.

Files changed (93) hide show
  1. fbuild/__init__.py +0 -0
  2. fbuild/assets/example.txt +1 -0
  3. fbuild/build/__init__.py +117 -0
  4. fbuild/build/archive_creator.py +186 -0
  5. fbuild/build/binary_generator.py +444 -0
  6. fbuild/build/build_component_factory.py +131 -0
  7. fbuild/build/build_state.py +325 -0
  8. fbuild/build/build_utils.py +98 -0
  9. fbuild/build/compilation_executor.py +422 -0
  10. fbuild/build/compiler.py +165 -0
  11. fbuild/build/compiler_avr.py +574 -0
  12. fbuild/build/configurable_compiler.py +612 -0
  13. fbuild/build/configurable_linker.py +637 -0
  14. fbuild/build/flag_builder.py +186 -0
  15. fbuild/build/library_dependency_processor.py +185 -0
  16. fbuild/build/linker.py +708 -0
  17. fbuild/build/orchestrator.py +67 -0
  18. fbuild/build/orchestrator_avr.py +656 -0
  19. fbuild/build/orchestrator_esp32.py +797 -0
  20. fbuild/build/orchestrator_teensy.py +543 -0
  21. fbuild/build/source_compilation_orchestrator.py +220 -0
  22. fbuild/build/source_scanner.py +516 -0
  23. fbuild/cli.py +566 -0
  24. fbuild/cli_utils.py +312 -0
  25. fbuild/config/__init__.py +16 -0
  26. fbuild/config/board_config.py +457 -0
  27. fbuild/config/board_loader.py +92 -0
  28. fbuild/config/ini_parser.py +209 -0
  29. fbuild/config/mcu_specs.py +88 -0
  30. fbuild/daemon/__init__.py +34 -0
  31. fbuild/daemon/client.py +929 -0
  32. fbuild/daemon/compilation_queue.py +293 -0
  33. fbuild/daemon/daemon.py +474 -0
  34. fbuild/daemon/daemon_context.py +196 -0
  35. fbuild/daemon/error_collector.py +263 -0
  36. fbuild/daemon/file_cache.py +332 -0
  37. fbuild/daemon/lock_manager.py +270 -0
  38. fbuild/daemon/logging_utils.py +149 -0
  39. fbuild/daemon/messages.py +301 -0
  40. fbuild/daemon/operation_registry.py +288 -0
  41. fbuild/daemon/process_tracker.py +366 -0
  42. fbuild/daemon/processors/__init__.py +12 -0
  43. fbuild/daemon/processors/build_processor.py +157 -0
  44. fbuild/daemon/processors/deploy_processor.py +327 -0
  45. fbuild/daemon/processors/monitor_processor.py +146 -0
  46. fbuild/daemon/request_processor.py +401 -0
  47. fbuild/daemon/status_manager.py +216 -0
  48. fbuild/daemon/subprocess_manager.py +316 -0
  49. fbuild/deploy/__init__.py +17 -0
  50. fbuild/deploy/deployer.py +67 -0
  51. fbuild/deploy/deployer_esp32.py +314 -0
  52. fbuild/deploy/monitor.py +495 -0
  53. fbuild/interrupt_utils.py +34 -0
  54. fbuild/packages/__init__.py +53 -0
  55. fbuild/packages/archive_utils.py +1098 -0
  56. fbuild/packages/arduino_core.py +412 -0
  57. fbuild/packages/cache.py +249 -0
  58. fbuild/packages/downloader.py +366 -0
  59. fbuild/packages/framework_esp32.py +538 -0
  60. fbuild/packages/framework_teensy.py +346 -0
  61. fbuild/packages/github_utils.py +96 -0
  62. fbuild/packages/header_trampoline_cache.py +394 -0
  63. fbuild/packages/library_compiler.py +203 -0
  64. fbuild/packages/library_manager.py +549 -0
  65. fbuild/packages/library_manager_esp32.py +413 -0
  66. fbuild/packages/package.py +163 -0
  67. fbuild/packages/platform_esp32.py +383 -0
  68. fbuild/packages/platform_teensy.py +312 -0
  69. fbuild/packages/platform_utils.py +131 -0
  70. fbuild/packages/platformio_registry.py +325 -0
  71. fbuild/packages/sdk_utils.py +231 -0
  72. fbuild/packages/toolchain.py +436 -0
  73. fbuild/packages/toolchain_binaries.py +196 -0
  74. fbuild/packages/toolchain_esp32.py +484 -0
  75. fbuild/packages/toolchain_metadata.py +185 -0
  76. fbuild/packages/toolchain_teensy.py +404 -0
  77. fbuild/platform_configs/esp32.json +150 -0
  78. fbuild/platform_configs/esp32c2.json +144 -0
  79. fbuild/platform_configs/esp32c3.json +143 -0
  80. fbuild/platform_configs/esp32c5.json +151 -0
  81. fbuild/platform_configs/esp32c6.json +151 -0
  82. fbuild/platform_configs/esp32p4.json +149 -0
  83. fbuild/platform_configs/esp32s3.json +151 -0
  84. fbuild/platform_configs/imxrt1062.json +56 -0
  85. fbuild-1.1.0.dist-info/METADATA +447 -0
  86. fbuild-1.1.0.dist-info/RECORD +93 -0
  87. fbuild-1.1.0.dist-info/WHEEL +5 -0
  88. fbuild-1.1.0.dist-info/entry_points.txt +5 -0
  89. fbuild-1.1.0.dist-info/licenses/LICENSE +21 -0
  90. fbuild-1.1.0.dist-info/top_level.txt +2 -0
  91. fbuild_lint/__init__.py +0 -0
  92. fbuild_lint/ruff_plugins/__init__.py +0 -0
  93. 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