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.
Files changed (121) hide show
  1. fbuild/__init__.py +390 -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_info_generator.py +624 -0
  8. fbuild/build/build_state.py +325 -0
  9. fbuild/build/build_utils.py +93 -0
  10. fbuild/build/compilation_executor.py +422 -0
  11. fbuild/build/compiler.py +165 -0
  12. fbuild/build/compiler_avr.py +574 -0
  13. fbuild/build/configurable_compiler.py +664 -0
  14. fbuild/build/configurable_linker.py +637 -0
  15. fbuild/build/flag_builder.py +214 -0
  16. fbuild/build/library_dependency_processor.py +185 -0
  17. fbuild/build/linker.py +708 -0
  18. fbuild/build/orchestrator.py +67 -0
  19. fbuild/build/orchestrator_avr.py +651 -0
  20. fbuild/build/orchestrator_esp32.py +878 -0
  21. fbuild/build/orchestrator_rp2040.py +719 -0
  22. fbuild/build/orchestrator_stm32.py +696 -0
  23. fbuild/build/orchestrator_teensy.py +580 -0
  24. fbuild/build/source_compilation_orchestrator.py +218 -0
  25. fbuild/build/source_scanner.py +516 -0
  26. fbuild/cli.py +717 -0
  27. fbuild/cli_utils.py +314 -0
  28. fbuild/config/__init__.py +16 -0
  29. fbuild/config/board_config.py +542 -0
  30. fbuild/config/board_loader.py +92 -0
  31. fbuild/config/ini_parser.py +369 -0
  32. fbuild/config/mcu_specs.py +88 -0
  33. fbuild/daemon/__init__.py +42 -0
  34. fbuild/daemon/async_client.py +531 -0
  35. fbuild/daemon/client.py +1505 -0
  36. fbuild/daemon/compilation_queue.py +293 -0
  37. fbuild/daemon/configuration_lock.py +865 -0
  38. fbuild/daemon/daemon.py +585 -0
  39. fbuild/daemon/daemon_context.py +293 -0
  40. fbuild/daemon/error_collector.py +263 -0
  41. fbuild/daemon/file_cache.py +332 -0
  42. fbuild/daemon/firmware_ledger.py +546 -0
  43. fbuild/daemon/lock_manager.py +508 -0
  44. fbuild/daemon/logging_utils.py +149 -0
  45. fbuild/daemon/messages.py +957 -0
  46. fbuild/daemon/operation_registry.py +288 -0
  47. fbuild/daemon/port_state_manager.py +249 -0
  48. fbuild/daemon/process_tracker.py +366 -0
  49. fbuild/daemon/processors/__init__.py +18 -0
  50. fbuild/daemon/processors/build_processor.py +248 -0
  51. fbuild/daemon/processors/deploy_processor.py +664 -0
  52. fbuild/daemon/processors/install_deps_processor.py +431 -0
  53. fbuild/daemon/processors/locking_processor.py +777 -0
  54. fbuild/daemon/processors/monitor_processor.py +285 -0
  55. fbuild/daemon/request_processor.py +457 -0
  56. fbuild/daemon/shared_serial.py +819 -0
  57. fbuild/daemon/status_manager.py +238 -0
  58. fbuild/daemon/subprocess_manager.py +316 -0
  59. fbuild/deploy/__init__.py +21 -0
  60. fbuild/deploy/deployer.py +67 -0
  61. fbuild/deploy/deployer_esp32.py +310 -0
  62. fbuild/deploy/docker_utils.py +315 -0
  63. fbuild/deploy/monitor.py +519 -0
  64. fbuild/deploy/qemu_runner.py +603 -0
  65. fbuild/interrupt_utils.py +34 -0
  66. fbuild/ledger/__init__.py +52 -0
  67. fbuild/ledger/board_ledger.py +560 -0
  68. fbuild/output.py +352 -0
  69. fbuild/packages/__init__.py +66 -0
  70. fbuild/packages/archive_utils.py +1098 -0
  71. fbuild/packages/arduino_core.py +412 -0
  72. fbuild/packages/cache.py +256 -0
  73. fbuild/packages/concurrent_manager.py +510 -0
  74. fbuild/packages/downloader.py +518 -0
  75. fbuild/packages/fingerprint.py +423 -0
  76. fbuild/packages/framework_esp32.py +538 -0
  77. fbuild/packages/framework_rp2040.py +349 -0
  78. fbuild/packages/framework_stm32.py +459 -0
  79. fbuild/packages/framework_teensy.py +346 -0
  80. fbuild/packages/github_utils.py +96 -0
  81. fbuild/packages/header_trampoline_cache.py +394 -0
  82. fbuild/packages/library_compiler.py +203 -0
  83. fbuild/packages/library_manager.py +549 -0
  84. fbuild/packages/library_manager_esp32.py +725 -0
  85. fbuild/packages/package.py +163 -0
  86. fbuild/packages/platform_esp32.py +383 -0
  87. fbuild/packages/platform_rp2040.py +400 -0
  88. fbuild/packages/platform_stm32.py +581 -0
  89. fbuild/packages/platform_teensy.py +312 -0
  90. fbuild/packages/platform_utils.py +131 -0
  91. fbuild/packages/platformio_registry.py +369 -0
  92. fbuild/packages/sdk_utils.py +231 -0
  93. fbuild/packages/toolchain.py +436 -0
  94. fbuild/packages/toolchain_binaries.py +196 -0
  95. fbuild/packages/toolchain_esp32.py +489 -0
  96. fbuild/packages/toolchain_metadata.py +185 -0
  97. fbuild/packages/toolchain_rp2040.py +436 -0
  98. fbuild/packages/toolchain_stm32.py +417 -0
  99. fbuild/packages/toolchain_teensy.py +404 -0
  100. fbuild/platform_configs/esp32.json +150 -0
  101. fbuild/platform_configs/esp32c2.json +144 -0
  102. fbuild/platform_configs/esp32c3.json +143 -0
  103. fbuild/platform_configs/esp32c5.json +151 -0
  104. fbuild/platform_configs/esp32c6.json +151 -0
  105. fbuild/platform_configs/esp32p4.json +149 -0
  106. fbuild/platform_configs/esp32s3.json +151 -0
  107. fbuild/platform_configs/imxrt1062.json +56 -0
  108. fbuild/platform_configs/rp2040.json +70 -0
  109. fbuild/platform_configs/rp2350.json +76 -0
  110. fbuild/platform_configs/stm32f1.json +59 -0
  111. fbuild/platform_configs/stm32f4.json +63 -0
  112. fbuild/py.typed +0 -0
  113. fbuild-1.2.8.dist-info/METADATA +468 -0
  114. fbuild-1.2.8.dist-info/RECORD +121 -0
  115. fbuild-1.2.8.dist-info/WHEEL +5 -0
  116. fbuild-1.2.8.dist-info/entry_points.txt +5 -0
  117. fbuild-1.2.8.dist-info/licenses/LICENSE +21 -0
  118. fbuild-1.2.8.dist-info/top_level.txt +2 -0
  119. fbuild_lint/__init__.py +0 -0
  120. fbuild_lint/ruff_plugins/__init__.py +0 -0
  121. fbuild_lint/ruff_plugins/keyboard_interrupt_checker.py +158 -0
@@ -0,0 +1,624 @@
1
+ """Build info generator for creating build_info.json after successful builds.
2
+
3
+ This module generates build metadata similar to PlatformIO's idedata.json,
4
+ providing information about the build configuration, firmware paths, memory
5
+ usage, and toolchain information.
6
+
7
+ Design:
8
+ - Stores build info in .fbuild/build/{env_name}/build_info.json
9
+ - Generated automatically after each successful build
10
+ - Contains all information needed for IDE integration and debugging
11
+ """
12
+
13
+ import json
14
+ from dataclasses import dataclass, field
15
+ from datetime import datetime, timezone
16
+ from pathlib import Path
17
+ from typing import Any, Dict, List, Optional
18
+
19
+
20
+ @dataclass
21
+ class BoardInfo:
22
+ """Board configuration information."""
23
+
24
+ id: str
25
+ name: str
26
+ mcu: str
27
+ platform: str
28
+ f_cpu: Optional[int] = None
29
+
30
+ def to_dict(self) -> Dict[str, Any]:
31
+ """Convert to dictionary for JSON serialization."""
32
+ return {
33
+ "id": self.id,
34
+ "name": self.name,
35
+ "mcu": self.mcu,
36
+ "platform": self.platform,
37
+ "f_cpu": self.f_cpu,
38
+ }
39
+
40
+
41
+ @dataclass
42
+ class FirmwareInfo:
43
+ """Firmware file paths and sizes."""
44
+
45
+ elf_path: Optional[str] = None
46
+ hex_path: Optional[str] = None
47
+ bin_path: Optional[str] = None
48
+ elf_size_bytes: Optional[int] = None
49
+ hex_size_bytes: Optional[int] = None
50
+ bin_size_bytes: Optional[int] = None
51
+
52
+ def to_dict(self) -> Dict[str, Any]:
53
+ """Convert to dictionary for JSON serialization."""
54
+ return {
55
+ "elf_path": self.elf_path,
56
+ "hex_path": self.hex_path,
57
+ "bin_path": self.bin_path,
58
+ "elf_size_bytes": self.elf_size_bytes,
59
+ "hex_size_bytes": self.hex_size_bytes,
60
+ "bin_size_bytes": self.bin_size_bytes,
61
+ }
62
+
63
+
64
+ @dataclass
65
+ class MemoryUsage:
66
+ """Memory usage for a specific memory type (flash or RAM)."""
67
+
68
+ used_bytes: int
69
+ max_bytes: Optional[int] = None
70
+ percent: Optional[float] = None
71
+
72
+ def to_dict(self) -> Dict[str, Any]:
73
+ """Convert to dictionary for JSON serialization."""
74
+ return {
75
+ "used_bytes": self.used_bytes,
76
+ "max_bytes": self.max_bytes,
77
+ "percent": self.percent,
78
+ }
79
+
80
+
81
+ @dataclass
82
+ class MemoryInfo:
83
+ """Combined memory usage information."""
84
+
85
+ flash: Optional[MemoryUsage] = None
86
+ ram: Optional[MemoryUsage] = None
87
+
88
+ def to_dict(self) -> Dict[str, Any]:
89
+ """Convert to dictionary for JSON serialization."""
90
+ return {
91
+ "flash": self.flash.to_dict() if self.flash else None,
92
+ "ram": self.ram.to_dict() if self.ram else None,
93
+ }
94
+
95
+
96
+ @dataclass
97
+ class ToolchainInfo:
98
+ """Toolchain version and path information."""
99
+
100
+ version: Optional[str] = None
101
+ cc_path: Optional[str] = None
102
+ cxx_path: Optional[str] = None
103
+ ar_path: Optional[str] = None
104
+ objcopy_path: Optional[str] = None
105
+ size_path: Optional[str] = None
106
+
107
+ def to_dict(self) -> Dict[str, Any]:
108
+ """Convert to dictionary for JSON serialization."""
109
+ return {
110
+ "version": self.version,
111
+ "cc_path": self.cc_path,
112
+ "cxx_path": self.cxx_path,
113
+ "ar_path": self.ar_path,
114
+ "objcopy_path": self.objcopy_path,
115
+ "size_path": self.size_path,
116
+ }
117
+
118
+
119
+ @dataclass
120
+ class FrameworkInfo:
121
+ """Framework version and path information."""
122
+
123
+ name: str
124
+ version: Optional[str] = None
125
+ path: Optional[str] = None
126
+
127
+ def to_dict(self) -> Dict[str, Any]:
128
+ """Convert to dictionary for JSON serialization."""
129
+ return {
130
+ "name": self.name,
131
+ "version": self.version,
132
+ "path": self.path,
133
+ }
134
+
135
+
136
+ @dataclass
137
+ class ESP32SpecificInfo:
138
+ """ESP32-specific build information."""
139
+
140
+ bootloader_path: Optional[str] = None
141
+ partitions_path: Optional[str] = None
142
+ application_offset: Optional[str] = None
143
+ flash_mode: Optional[str] = None
144
+ flash_size: Optional[str] = None
145
+
146
+ def to_dict(self) -> Dict[str, Any]:
147
+ """Convert to dictionary for JSON serialization."""
148
+ return {
149
+ "bootloader_path": self.bootloader_path,
150
+ "partitions_path": self.partitions_path,
151
+ "application_offset": self.application_offset,
152
+ "flash_mode": self.flash_mode,
153
+ "flash_size": self.flash_size,
154
+ }
155
+
156
+
157
+ @dataclass
158
+ class BuildInfo:
159
+ """Complete build information."""
160
+
161
+ version: str = "1.0"
162
+ build_timestamp: str = ""
163
+ build_time_seconds: float = 0.0
164
+ environment: str = ""
165
+ board: Optional[BoardInfo] = None
166
+ firmware: Optional[FirmwareInfo] = None
167
+ memory: Optional[MemoryInfo] = None
168
+ build_flags: List[str] = field(default_factory=list)
169
+ lib_deps: List[str] = field(default_factory=list)
170
+ toolchain: Optional[ToolchainInfo] = None
171
+ framework: Optional[FrameworkInfo] = None
172
+ esp32_specific: Optional[ESP32SpecificInfo] = None
173
+
174
+ def to_dict(self) -> Dict[str, Any]:
175
+ """Convert to dictionary for JSON serialization."""
176
+ return {
177
+ "version": self.version,
178
+ "build_timestamp": self.build_timestamp,
179
+ "build_time_seconds": self.build_time_seconds,
180
+ "environment": self.environment,
181
+ "board": self.board.to_dict() if self.board else None,
182
+ "firmware": self.firmware.to_dict() if self.firmware else None,
183
+ "memory": self.memory.to_dict() if self.memory else None,
184
+ "build_flags": self.build_flags,
185
+ "lib_deps": self.lib_deps,
186
+ "toolchain": self.toolchain.to_dict() if self.toolchain else None,
187
+ "framework": self.framework.to_dict() if self.framework else None,
188
+ "esp32_specific": self.esp32_specific.to_dict() if self.esp32_specific else None,
189
+ }
190
+
191
+
192
+ class BuildInfoGenerator:
193
+ """Generates and saves build_info.json after successful builds."""
194
+
195
+ BUILD_INFO_FILENAME = "build_info.json"
196
+ SCHEMA_VERSION = "1.0"
197
+
198
+ def __init__(self, build_dir: Path):
199
+ """Initialize the build info generator.
200
+
201
+ Args:
202
+ build_dir: Build directory (.fbuild/build/{env_name})
203
+ """
204
+ self.build_dir = Path(build_dir)
205
+ self.build_info_path = self.build_dir / self.BUILD_INFO_FILENAME
206
+
207
+ def generate_avr(
208
+ self,
209
+ env_name: str,
210
+ board_id: str,
211
+ board_name: str,
212
+ mcu: str,
213
+ f_cpu: int,
214
+ build_time: float,
215
+ elf_path: Optional[Path],
216
+ hex_path: Optional[Path],
217
+ size_info: Optional[Any],
218
+ build_flags: List[str],
219
+ lib_deps: List[str],
220
+ toolchain_version: str,
221
+ toolchain_paths: Dict[str, Path],
222
+ framework_version: str,
223
+ core_path: Optional[Path] = None,
224
+ ) -> BuildInfo:
225
+ """Generate build info for AVR platform.
226
+
227
+ Args:
228
+ env_name: Environment name (e.g., 'uno')
229
+ board_id: Board ID (e.g., 'uno')
230
+ board_name: Human-readable board name (e.g., 'Arduino Uno')
231
+ mcu: MCU type (e.g., 'atmega328p')
232
+ f_cpu: CPU frequency in Hz
233
+ build_time: Build duration in seconds
234
+ elf_path: Path to generated .elf file
235
+ hex_path: Path to generated .hex file
236
+ size_info: SizeInfo object from linker
237
+ build_flags: List of build flags
238
+ lib_deps: List of library dependencies
239
+ toolchain_version: Toolchain version string
240
+ toolchain_paths: Dict of toolchain binary paths
241
+ framework_version: Arduino core version
242
+ core_path: Path to Arduino core
243
+
244
+ Returns:
245
+ BuildInfo object with all metadata
246
+ """
247
+ # Board info
248
+ board = BoardInfo(
249
+ id=board_id,
250
+ name=board_name,
251
+ mcu=mcu,
252
+ platform="atmelavr",
253
+ f_cpu=f_cpu,
254
+ )
255
+
256
+ # Firmware info
257
+ firmware = FirmwareInfo()
258
+ if elf_path and elf_path.exists():
259
+ firmware.elf_path = str(elf_path.relative_to(self.build_dir.parent.parent.parent))
260
+ firmware.elf_size_bytes = elf_path.stat().st_size
261
+ if hex_path and hex_path.exists():
262
+ firmware.hex_path = str(hex_path.relative_to(self.build_dir.parent.parent.parent))
263
+ firmware.hex_size_bytes = hex_path.stat().st_size
264
+
265
+ # Memory info from size_info
266
+ memory = None
267
+ if size_info:
268
+ flash_usage = MemoryUsage(
269
+ used_bytes=size_info.total_flash,
270
+ max_bytes=size_info.max_flash,
271
+ percent=size_info.flash_percent,
272
+ )
273
+ ram_usage = MemoryUsage(
274
+ used_bytes=size_info.total_ram,
275
+ max_bytes=size_info.max_ram,
276
+ percent=size_info.ram_percent,
277
+ )
278
+ memory = MemoryInfo(flash=flash_usage, ram=ram_usage)
279
+
280
+ # Toolchain info
281
+ toolchain = ToolchainInfo(
282
+ version=toolchain_version,
283
+ cc_path=str(toolchain_paths.get("gcc")) if toolchain_paths.get("gcc") else None,
284
+ cxx_path=str(toolchain_paths.get("gxx")) if toolchain_paths.get("gxx") else None,
285
+ ar_path=str(toolchain_paths.get("ar")) if toolchain_paths.get("ar") else None,
286
+ objcopy_path=str(toolchain_paths.get("objcopy")) if toolchain_paths.get("objcopy") else None,
287
+ size_path=str(toolchain_paths.get("size")) if toolchain_paths.get("size") else None,
288
+ )
289
+
290
+ # Framework info
291
+ framework = FrameworkInfo(
292
+ name="arduino",
293
+ version=framework_version,
294
+ path=str(core_path) if core_path else None,
295
+ )
296
+
297
+ return BuildInfo(
298
+ version=self.SCHEMA_VERSION,
299
+ build_timestamp=datetime.now(timezone.utc).isoformat(),
300
+ build_time_seconds=round(build_time, 3),
301
+ environment=env_name,
302
+ board=board,
303
+ firmware=firmware,
304
+ memory=memory,
305
+ build_flags=build_flags or [],
306
+ lib_deps=lib_deps or [],
307
+ toolchain=toolchain,
308
+ framework=framework,
309
+ esp32_specific=None,
310
+ )
311
+
312
+ def generate_esp32(
313
+ self,
314
+ env_name: str,
315
+ board_id: str,
316
+ board_name: str,
317
+ mcu: str,
318
+ f_cpu: int,
319
+ build_time: float,
320
+ elf_path: Optional[Path],
321
+ bin_path: Optional[Path],
322
+ size_info: Optional[Any],
323
+ build_flags: List[str],
324
+ lib_deps: List[str],
325
+ toolchain_version: str,
326
+ toolchain_paths: Dict[str, Path],
327
+ framework_version: str,
328
+ core_path: Optional[Path] = None,
329
+ bootloader_path: Optional[Path] = None,
330
+ partitions_path: Optional[Path] = None,
331
+ application_offset: Optional[str] = None,
332
+ flash_mode: Optional[str] = None,
333
+ flash_size: Optional[str] = None,
334
+ ) -> BuildInfo:
335
+ """Generate build info for ESP32 platform.
336
+
337
+ Args:
338
+ env_name: Environment name
339
+ board_id: Board ID
340
+ board_name: Human-readable board name
341
+ mcu: MCU type (e.g., 'esp32c6')
342
+ f_cpu: CPU frequency in Hz
343
+ build_time: Build duration in seconds
344
+ elf_path: Path to generated .elf file
345
+ bin_path: Path to generated .bin file
346
+ size_info: SizeInfo object from linker
347
+ build_flags: List of build flags
348
+ lib_deps: List of library dependencies
349
+ toolchain_version: Toolchain version string
350
+ toolchain_paths: Dict of toolchain binary paths
351
+ framework_version: ESP32 Arduino framework version
352
+ core_path: Path to framework core
353
+ bootloader_path: Path to bootloader.bin
354
+ partitions_path: Path to partitions.bin
355
+ application_offset: Application offset (e.g., '0x10000')
356
+ flash_mode: Flash mode (e.g., 'dio', 'qio')
357
+ flash_size: Flash size (e.g., '4MB')
358
+
359
+ Returns:
360
+ BuildInfo object with all metadata
361
+ """
362
+ # Board info
363
+ board = BoardInfo(
364
+ id=board_id,
365
+ name=board_name,
366
+ mcu=mcu,
367
+ platform="espressif32",
368
+ f_cpu=f_cpu,
369
+ )
370
+
371
+ # Firmware info
372
+ firmware = FirmwareInfo()
373
+ try:
374
+ if elf_path and elf_path.exists():
375
+ firmware.elf_path = str(elf_path.relative_to(self.build_dir.parent.parent.parent))
376
+ firmware.elf_size_bytes = elf_path.stat().st_size
377
+ if bin_path and bin_path.exists():
378
+ firmware.bin_path = str(bin_path.relative_to(self.build_dir.parent.parent.parent))
379
+ firmware.bin_size_bytes = bin_path.stat().st_size
380
+ except ValueError:
381
+ # If relative_to fails, use absolute paths
382
+ if elf_path and elf_path.exists():
383
+ firmware.elf_path = str(elf_path)
384
+ firmware.elf_size_bytes = elf_path.stat().st_size
385
+ if bin_path and bin_path.exists():
386
+ firmware.bin_path = str(bin_path)
387
+ firmware.bin_size_bytes = bin_path.stat().st_size
388
+
389
+ # Memory info from size_info
390
+ memory = None
391
+ if size_info:
392
+ flash_usage = MemoryUsage(
393
+ used_bytes=getattr(size_info, 'total_flash', 0),
394
+ max_bytes=getattr(size_info, 'max_flash', None),
395
+ percent=getattr(size_info, 'flash_percent', None),
396
+ )
397
+ ram_usage = MemoryUsage(
398
+ used_bytes=getattr(size_info, 'total_ram', 0),
399
+ max_bytes=getattr(size_info, 'max_ram', None),
400
+ percent=getattr(size_info, 'ram_percent', None),
401
+ )
402
+ memory = MemoryInfo(flash=flash_usage, ram=ram_usage)
403
+
404
+ # Toolchain info
405
+ toolchain = ToolchainInfo(
406
+ version=toolchain_version,
407
+ cc_path=str(toolchain_paths.get("gcc")) if toolchain_paths.get("gcc") else None,
408
+ cxx_path=str(toolchain_paths.get("gxx")) if toolchain_paths.get("gxx") else None,
409
+ ar_path=str(toolchain_paths.get("ar")) if toolchain_paths.get("ar") else None,
410
+ objcopy_path=str(toolchain_paths.get("objcopy")) if toolchain_paths.get("objcopy") else None,
411
+ size_path=str(toolchain_paths.get("size")) if toolchain_paths.get("size") else None,
412
+ )
413
+
414
+ # Framework info
415
+ framework = FrameworkInfo(
416
+ name="arduino",
417
+ version=framework_version,
418
+ path=str(core_path) if core_path else None,
419
+ )
420
+
421
+ # ESP32-specific info
422
+ esp32_specific = None
423
+ if bootloader_path or partitions_path or application_offset:
424
+ esp32_specific = ESP32SpecificInfo(
425
+ bootloader_path=str(bootloader_path) if bootloader_path else None,
426
+ partitions_path=str(partitions_path) if partitions_path else None,
427
+ application_offset=application_offset,
428
+ flash_mode=flash_mode,
429
+ flash_size=flash_size,
430
+ )
431
+
432
+ return BuildInfo(
433
+ version=self.SCHEMA_VERSION,
434
+ build_timestamp=datetime.now(timezone.utc).isoformat(),
435
+ build_time_seconds=round(build_time, 3),
436
+ environment=env_name,
437
+ board=board,
438
+ firmware=firmware,
439
+ memory=memory,
440
+ build_flags=build_flags or [],
441
+ lib_deps=lib_deps or [],
442
+ toolchain=toolchain,
443
+ framework=framework,
444
+ esp32_specific=esp32_specific,
445
+ )
446
+
447
+ def generate_generic(
448
+ self,
449
+ env_name: str,
450
+ board_id: str,
451
+ board_name: str,
452
+ mcu: str,
453
+ platform: str,
454
+ f_cpu: int,
455
+ build_time: float,
456
+ elf_path: Optional[Path],
457
+ hex_path: Optional[Path] = None,
458
+ bin_path: Optional[Path] = None,
459
+ size_info: Optional[Any] = None,
460
+ build_flags: Optional[List[str]] = None,
461
+ lib_deps: Optional[List[str]] = None,
462
+ toolchain_version: Optional[str] = None,
463
+ toolchain_paths: Optional[Dict[str, Path]] = None,
464
+ framework_name: str = "arduino",
465
+ framework_version: Optional[str] = None,
466
+ core_path: Optional[Path] = None,
467
+ ) -> BuildInfo:
468
+ """Generate build info for any platform (generic method).
469
+
470
+ This is a generic method that can be used for platforms that don't
471
+ have specific generate methods (Teensy, RP2040, STM32).
472
+
473
+ Args:
474
+ env_name: Environment name
475
+ board_id: Board ID
476
+ board_name: Human-readable board name
477
+ mcu: MCU type
478
+ platform: Platform name
479
+ f_cpu: CPU frequency in Hz
480
+ build_time: Build duration in seconds
481
+ elf_path: Path to generated .elf file
482
+ hex_path: Path to generated .hex file (optional)
483
+ bin_path: Path to generated .bin file (optional)
484
+ size_info: SizeInfo object from linker
485
+ build_flags: List of build flags
486
+ lib_deps: List of library dependencies
487
+ toolchain_version: Toolchain version string
488
+ toolchain_paths: Dict of toolchain binary paths
489
+ framework_name: Framework name (default: 'arduino')
490
+ framework_version: Framework version
491
+ core_path: Path to framework core
492
+
493
+ Returns:
494
+ BuildInfo object with all metadata
495
+ """
496
+ # Board info
497
+ board = BoardInfo(
498
+ id=board_id,
499
+ name=board_name,
500
+ mcu=mcu,
501
+ platform=platform,
502
+ f_cpu=f_cpu,
503
+ )
504
+
505
+ # Firmware info
506
+ firmware = FirmwareInfo()
507
+ try:
508
+ base_dir = self.build_dir.parent.parent.parent
509
+ if elf_path and elf_path.exists():
510
+ try:
511
+ firmware.elf_path = str(elf_path.relative_to(base_dir))
512
+ except ValueError:
513
+ firmware.elf_path = str(elf_path)
514
+ firmware.elf_size_bytes = elf_path.stat().st_size
515
+ if hex_path and hex_path.exists():
516
+ try:
517
+ firmware.hex_path = str(hex_path.relative_to(base_dir))
518
+ except ValueError:
519
+ firmware.hex_path = str(hex_path)
520
+ firmware.hex_size_bytes = hex_path.stat().st_size
521
+ if bin_path and bin_path.exists():
522
+ try:
523
+ firmware.bin_path = str(bin_path.relative_to(base_dir))
524
+ except ValueError:
525
+ firmware.bin_path = str(bin_path)
526
+ firmware.bin_size_bytes = bin_path.stat().st_size
527
+ except KeyboardInterrupt as ke:
528
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
529
+
530
+ handle_keyboard_interrupt_properly(ke)
531
+ except Exception:
532
+ pass # Silently ignore path resolution errors
533
+
534
+ # Memory info from size_info
535
+ memory = None
536
+ if size_info:
537
+ flash_usage = MemoryUsage(
538
+ used_bytes=getattr(size_info, 'total_flash', 0),
539
+ max_bytes=getattr(size_info, 'max_flash', None),
540
+ percent=getattr(size_info, 'flash_percent', None),
541
+ )
542
+ ram_usage = MemoryUsage(
543
+ used_bytes=getattr(size_info, 'total_ram', 0),
544
+ max_bytes=getattr(size_info, 'max_ram', None),
545
+ percent=getattr(size_info, 'ram_percent', None),
546
+ )
547
+ memory = MemoryInfo(flash=flash_usage, ram=ram_usage)
548
+
549
+ # Toolchain info
550
+ toolchain = None
551
+ if toolchain_paths:
552
+ toolchain = ToolchainInfo(
553
+ version=toolchain_version,
554
+ cc_path=str(toolchain_paths.get("gcc")) if toolchain_paths.get("gcc") else None,
555
+ cxx_path=str(toolchain_paths.get("gxx")) if toolchain_paths.get("gxx") else None,
556
+ ar_path=str(toolchain_paths.get("ar")) if toolchain_paths.get("ar") else None,
557
+ objcopy_path=str(toolchain_paths.get("objcopy")) if toolchain_paths.get("objcopy") else None,
558
+ size_path=str(toolchain_paths.get("size")) if toolchain_paths.get("size") else None,
559
+ )
560
+
561
+ # Framework info
562
+ framework = FrameworkInfo(
563
+ name=framework_name,
564
+ version=framework_version,
565
+ path=str(core_path) if core_path else None,
566
+ )
567
+
568
+ return BuildInfo(
569
+ version=self.SCHEMA_VERSION,
570
+ build_timestamp=datetime.now(timezone.utc).isoformat(),
571
+ build_time_seconds=round(build_time, 3),
572
+ environment=env_name,
573
+ board=board,
574
+ firmware=firmware,
575
+ memory=memory,
576
+ build_flags=build_flags or [],
577
+ lib_deps=lib_deps or [],
578
+ toolchain=toolchain,
579
+ framework=framework,
580
+ esp32_specific=None,
581
+ )
582
+
583
+ def save(self, build_info: BuildInfo) -> Path:
584
+ """Save build info to JSON file.
585
+
586
+ Args:
587
+ build_info: BuildInfo object to save
588
+
589
+ Returns:
590
+ Path to the saved build_info.json file
591
+ """
592
+ self.build_dir.mkdir(parents=True, exist_ok=True)
593
+
594
+ with open(self.build_info_path, "w", encoding="utf-8") as f:
595
+ json.dump(build_info.to_dict(), f, indent=2)
596
+
597
+ return self.build_info_path
598
+
599
+ def load(self) -> Optional[BuildInfo]:
600
+ """Load build info from JSON file.
601
+
602
+ Returns:
603
+ BuildInfo object or None if file doesn't exist or is corrupted
604
+ """
605
+ if not self.build_info_path.exists():
606
+ return None
607
+
608
+ try:
609
+ with open(self.build_info_path, "r", encoding="utf-8") as f:
610
+ data = json.load(f)
611
+
612
+ # Reconstruct BuildInfo from dict
613
+ # This is a simplified reconstruction - for full fidelity,
614
+ # we'd need from_dict methods on all dataclasses
615
+ return BuildInfo(
616
+ version=data.get("version", "1.0"),
617
+ build_timestamp=data.get("build_timestamp", ""),
618
+ build_time_seconds=data.get("build_time_seconds", 0.0),
619
+ environment=data.get("environment", ""),
620
+ build_flags=data.get("build_flags", []),
621
+ lib_deps=data.get("lib_deps", []),
622
+ )
623
+ except (json.JSONDecodeError, KeyError):
624
+ return None