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,651 @@
1
+ """
2
+ Build orchestration for Fbuild projects.
3
+
4
+ This module coordinates the entire build process, from parsing platformio.ini
5
+ to generating firmware binaries. It integrates all build system components:
6
+ - Configuration parsing (platformio.ini, boards.txt)
7
+ - Package management (toolchain, Arduino core)
8
+ - Source scanning and preprocessing
9
+ - Compilation (avr-gcc/avr-g++)
10
+ - Linking (avr-gcc linker, avr-objcopy)
11
+ """
12
+
13
+ import logging
14
+ import time
15
+ from pathlib import Path
16
+ from typing import Optional, List, Any
17
+
18
+ from ..interrupt_utils import handle_keyboard_interrupt_properly
19
+ from ..config import PlatformIOConfig, BoardConfig, BoardConfigLoader
20
+ from ..config.board_config import BoardConfigError
21
+ from ..packages import Cache, Toolchain, ArduinoCore
22
+ from ..packages.toolchain import ToolchainError
23
+ from ..packages.arduino_core import ArduinoCoreError
24
+ from ..packages.library_manager import LibraryError
25
+ from .source_scanner import SourceScanner, SourceCollection
26
+ from .compiler import CompilerError as CompilerImportError
27
+ from .linker import LinkerError as LinkerImportError
28
+ from .orchestrator_esp32 import OrchestratorESP32
29
+ from .build_utils import SizeInfoPrinter
30
+ from .library_dependency_processor import LibraryDependencyProcessor
31
+ from .source_compilation_orchestrator import (
32
+ SourceCompilationOrchestrator,
33
+ SourceCompilationOrchestratorError
34
+ )
35
+ from .build_component_factory import BuildComponentFactory
36
+ from .orchestrator import IBuildOrchestrator, BuildResult, BuildOrchestratorError
37
+ from .build_state import BuildStateTracker
38
+ from .build_info_generator import BuildInfoGenerator
39
+ from ..output import (
40
+ log, log_phase, log_detail, log_build_complete, log_firmware_path, set_verbose
41
+ )
42
+
43
+ # Import daemon accessor functions for async compilation
44
+ # TODO: Re-enable when get_compilation_queue() is implemented in daemon module
45
+ # try:
46
+ # from ..daemon import daemon
47
+ # DAEMON_AVAILABLE = True
48
+ # except ImportError:
49
+ # DAEMON_AVAILABLE = False
50
+ DAEMON_AVAILABLE = False
51
+
52
+
53
+ class BuildOrchestratorAVR(IBuildOrchestrator):
54
+ """
55
+ Orchestrates the complete build process for embedded projects.
56
+
57
+ This class coordinates all phases of the build:
58
+ 1. Parse platformio.ini configuration
59
+ 2. Load board configuration
60
+ 3. Ensure toolchain is downloaded and validated
61
+ 4. Ensure Arduino core is downloaded and validated
62
+ 5. Setup build directories
63
+ 6. Download and compile library dependencies
64
+ 7. Scan source files (sketch + core + variant)
65
+ 8. Compile all sources to object files
66
+ 9. Link objects (including libraries) into firmware.elf
67
+ 10. Convert to firmware.hex (Intel HEX format)
68
+ 11. Display size information
69
+
70
+ Example usage:
71
+ orchestrator = BuildOrchestrator()
72
+ result = orchestrator.build(
73
+ project_dir=Path("."),
74
+ env_name="uno",
75
+ clean=False,
76
+ verbose=False
77
+ )
78
+ if result.success:
79
+ print(f"Firmware: {result.hex_path}")
80
+ print(f"Flash: {result.size_info.total_flash} bytes")
81
+ """
82
+
83
+ def __init__(
84
+ self,
85
+ cache: Optional[Cache] = None,
86
+ verbose: bool = False
87
+ ):
88
+ """
89
+ Initialize build orchestrator.
90
+
91
+ Args:
92
+ cache: Cache instance for package management (optional)
93
+ verbose: Enable verbose output
94
+ """
95
+ self.cache = cache
96
+ self.verbose = verbose
97
+
98
+ def _log(self, message: str, verbose_only: bool = True) -> None:
99
+ """
100
+ Log a message and optionally print it.
101
+
102
+ Args:
103
+ message: Message to log
104
+ verbose_only: If True, only log if verbose mode is enabled
105
+ """
106
+ if not verbose_only or self.verbose:
107
+ logging.info(message)
108
+
109
+ def build(
110
+ self,
111
+ project_dir: Path,
112
+ env_name: Optional[str] = None,
113
+ clean: bool = False,
114
+ verbose: Optional[bool] = None
115
+ ) -> BuildResult:
116
+ """
117
+ Execute complete build process.
118
+
119
+ Args:
120
+ project_dir: Project root directory containing platformio.ini
121
+ env_name: Environment name to build (defaults to first/default env)
122
+ clean: Clean build (remove all artifacts before building)
123
+ verbose: Override verbose setting
124
+
125
+ Returns:
126
+ BuildResult with build status and output paths
127
+
128
+ Raises:
129
+ BuildOrchestratorError: If build fails at any phase
130
+ """
131
+ start_time = time.time()
132
+ verbose_mode = verbose if verbose is not None else self.verbose
133
+ set_verbose(verbose_mode)
134
+
135
+ try:
136
+ project_dir = Path(project_dir).resolve()
137
+
138
+ # Initialize cache if not provided
139
+ if self.cache is None:
140
+ self.cache = Cache(project_dir)
141
+
142
+ # Phase 1: Parse configuration
143
+ log_phase(1, 9, "Parsing platformio.ini...", verbose_only=not verbose_mode)
144
+
145
+ config = self._parse_config(project_dir)
146
+
147
+ # Determine environment to build
148
+ if env_name is None:
149
+ env_name = config.get_default_environment()
150
+ if env_name is None:
151
+ raise BuildOrchestratorError(
152
+ "No environment specified and no default found in platformio.ini"
153
+ )
154
+
155
+ log_detail(f"Building environment: {env_name}", verbose_only=not verbose_mode)
156
+
157
+ env_config = config.get_env_config(env_name)
158
+
159
+ # Phase 2: Load board configuration
160
+ log_phase(2, 9, "Loading board configuration...", verbose_only=not verbose_mode)
161
+
162
+ board_id = env_config['board']
163
+ board_config = BoardConfigLoader.load_board_config(board_id, env_config)
164
+
165
+ log_detail(f"Board: {board_config.name}", verbose_only=not verbose_mode)
166
+ log_detail(f"MCU: {board_config.mcu}", verbose_only=not verbose_mode)
167
+ log_detail(f"F_CPU: {board_config.f_cpu}", verbose_only=not verbose_mode)
168
+
169
+ # Detect platform and handle accordingly
170
+ if board_config.platform == "esp32":
171
+ log_detail(f"Platform: {board_config.platform} (using native ESP32 build)", verbose_only=not verbose_mode)
172
+ # Get build flags from platformio.ini
173
+ build_flags = config.get_build_flags(env_name)
174
+ return self._build_esp32(
175
+ project_dir, env_name, board_id, env_config, clean, verbose_mode, start_time, build_flags
176
+ )
177
+ elif board_config.platform == "teensy":
178
+ log_detail(f"Platform: {board_config.platform} (using native Teensy build)", verbose_only=not verbose_mode)
179
+ # Get build flags from platformio.ini
180
+ build_flags = config.get_build_flags(env_name)
181
+ return self._build_teensy(
182
+ project_dir, env_name, board_id, board_config, clean, verbose_mode, start_time, build_flags
183
+ )
184
+ elif board_config.platform != "avr":
185
+ # Only AVR, ESP32, and Teensy are supported natively
186
+ return BuildResult(
187
+ success=False,
188
+ hex_path=None,
189
+ elf_path=None,
190
+ size_info=None,
191
+ build_time=time.time() - start_time,
192
+ message=f"Platform '{board_config.platform}' is not supported. " +
193
+ "Fbuild currently supports 'avr', 'esp32', and 'teensy' platforms natively."
194
+ )
195
+
196
+ # Phase 3: Ensure toolchain
197
+ log_phase(3, 9, "Ensuring AVR toolchain...", verbose_only=not verbose_mode)
198
+
199
+ toolchain = self._ensure_toolchain()
200
+
201
+ log_detail("Toolchain ready", verbose_only=not verbose_mode)
202
+
203
+ # Phase 4: Ensure Arduino core
204
+ log_phase(4, 9, "Ensuring Arduino core...", verbose_only=not verbose_mode)
205
+
206
+ arduino_core = self._ensure_arduino_core()
207
+ core_path = arduino_core.ensure_avr_core()
208
+
209
+ log_detail(f"Core ready: version {arduino_core.AVR_VERSION}", verbose_only=not verbose_mode)
210
+
211
+ # Phase 5: Setup build directories
212
+ log_phase(5, 11, "Preparing build directories...", verbose_only=not verbose_mode)
213
+
214
+ if clean:
215
+ self.cache.clean_build(env_name)
216
+
217
+ self.cache.ensure_build_directories(env_name)
218
+ build_dir = self.cache.get_build_dir(env_name)
219
+ core_build_dir = self.cache.get_core_build_dir(env_name)
220
+ src_build_dir = self.cache.get_src_build_dir(env_name)
221
+
222
+ # Phase 5.5: Check build state and invalidate cache if needed
223
+ log_phase(5, 11, "Checking build configuration state...", verbose_only=not verbose_mode)
224
+
225
+ state_tracker = BuildStateTracker(build_dir)
226
+ build_flags = config.get_build_flags(env_name)
227
+ lib_deps = config.get_lib_deps(env_name)
228
+
229
+ needs_rebuild, reasons, current_state = state_tracker.check_invalidation(
230
+ platformio_ini_path=project_dir / "platformio.ini",
231
+ platform=board_config.platform,
232
+ board=board_id,
233
+ framework=env_config.get('framework', 'arduino'),
234
+ toolchain_version=toolchain.VERSION,
235
+ framework_version=arduino_core.AVR_VERSION,
236
+ platform_version=arduino_core.AVR_VERSION, # Using core version as platform version
237
+ build_flags=build_flags,
238
+ lib_deps=lib_deps,
239
+ )
240
+
241
+ if needs_rebuild:
242
+ log_detail("Build cache invalidated:", verbose_only=not verbose_mode)
243
+ for reason in reasons:
244
+ log_detail(f" - {reason}", indent=8, verbose_only=not verbose_mode)
245
+ log_detail("Cleaning build artifacts...", verbose_only=not verbose_mode)
246
+ # Clean build artifacts to force rebuild
247
+ self.cache.clean_build(env_name)
248
+ # Recreate directories
249
+ self.cache.ensure_build_directories(env_name)
250
+ else:
251
+ log_detail("Build configuration unchanged, using cached artifacts", verbose_only=not verbose_mode)
252
+
253
+ # Phase 6: Download and compile library dependencies
254
+ log_phase(6, 11, "Processing library dependencies...", verbose_only=not verbose_mode)
255
+
256
+ lib_deps = config.get_lib_deps(env_name)
257
+
258
+ lib_processor = LibraryDependencyProcessor(
259
+ build_dir=build_dir,
260
+ mode="release",
261
+ verbose=verbose_mode
262
+ )
263
+
264
+ lib_result = lib_processor.process_dependencies(
265
+ lib_deps=lib_deps,
266
+ toolchain=toolchain,
267
+ board_config=board_config,
268
+ core_path=core_path
269
+ )
270
+
271
+ lib_include_paths = lib_result.include_paths
272
+ lib_objects = lib_result.object_files
273
+
274
+ # Phase 7: Scan source files
275
+ log_phase(7, 11, "Scanning source files...", verbose_only=not verbose_mode)
276
+
277
+ sources = self._scan_sources(
278
+ project_dir,
279
+ build_dir,
280
+ board_config,
281
+ core_path
282
+ )
283
+
284
+ total_sources = (
285
+ len(sources.sketch_sources)
286
+ + len(sources.core_sources)
287
+ + len(sources.variant_sources)
288
+ )
289
+
290
+ log_detail(f"Sketch: {len(sources.sketch_sources)} files", verbose_only=not verbose_mode)
291
+ log_detail(f"Core: {len(sources.core_sources)} files", verbose_only=not verbose_mode)
292
+ log_detail(f"Variant: {len(sources.variant_sources)} files", verbose_only=not verbose_mode)
293
+ log_detail(f"Total: {total_sources} files", verbose_only=not verbose_mode)
294
+
295
+ # Phase 8: Compile sources
296
+ log_phase(8, 11, "Compiling sources...", verbose_only=not verbose_mode)
297
+
298
+ # Get compilation queue from daemon if available
299
+ # TODO: Implement get_compilation_queue() in daemon module
300
+ compilation_queue = None
301
+ # if DAEMON_AVAILABLE:
302
+ # try:
303
+ # compilation_queue = daemon.get_compilation_queue()
304
+ # if compilation_queue and verbose_mode:
305
+ # print(f" [async] Using parallel compilation with {compilation_queue.num_workers} workers")
306
+ # self._log(f" [async] Using parallel compilation with {compilation_queue.num_workers} workers")
307
+ # except KeyboardInterrupt as ke:
308
+ # handle_keyboard_interrupt_properly(ke)
309
+ # except Exception:
310
+ # # Daemon not running or queue not initialized - use sync mode
311
+ # pass
312
+
313
+ compiler = BuildComponentFactory.create_compiler(
314
+ toolchain, board_config, core_path, lib_include_paths, compilation_queue
315
+ )
316
+
317
+ compilation_orchestrator = SourceCompilationOrchestrator(verbose=verbose_mode)
318
+ compilation_result = compilation_orchestrator.compile_multiple_groups(
319
+ compiler=compiler,
320
+ sketch_sources=sources.sketch_sources,
321
+ core_sources=sources.core_sources,
322
+ variant_sources=sources.variant_sources,
323
+ src_build_dir=src_build_dir,
324
+ core_build_dir=core_build_dir
325
+ )
326
+
327
+ sketch_objects = compilation_result.sketch_objects
328
+ all_core_objects = compilation_result.all_core_objects
329
+
330
+ # Phase 9: Link firmware
331
+ log_phase(9, 11, "Linking firmware...", verbose_only=not verbose_mode)
332
+
333
+ elf_path = build_dir / 'firmware.elf'
334
+ hex_path = build_dir / 'firmware.hex'
335
+
336
+ linker = BuildComponentFactory.create_linker(toolchain, board_config)
337
+ # For LTO with -fno-fat-lto-objects, we pass library objects separately
338
+ # so they don't get archived (LTO bytecode doesn't work well in archives)
339
+ link_result = linker.link_legacy(
340
+ sketch_objects,
341
+ all_core_objects,
342
+ elf_path,
343
+ hex_path,
344
+ [], # No library archives
345
+ None, # No extra flags
346
+ lib_objects # Library objects passed separately for LTO
347
+ )
348
+
349
+ if not link_result.success:
350
+ raise BuildOrchestratorError(
351
+ f"Linking failed:\n{link_result.stderr}"
352
+ )
353
+
354
+ log_firmware_path(hex_path, verbose_only=not verbose_mode)
355
+
356
+ # Phase 10: Save build state for future cache validation
357
+ log_phase(10, 11, "Saving build state...", verbose_only=not verbose_mode)
358
+ state_tracker.save_state(current_state)
359
+
360
+ # Phase 10.5: Generate build_info.json
361
+ build_time = time.time() - start_time
362
+ build_info_generator = BuildInfoGenerator(build_dir)
363
+ toolchain_tools = toolchain.get_all_tools()
364
+ # Parse f_cpu from string (e.g., "16000000L") to int
365
+ f_cpu_int = int(board_config.f_cpu.rstrip("L"))
366
+ # Build toolchain_paths dict, filtering out None values
367
+ toolchain_paths_raw = {
368
+ "gcc": toolchain_tools.get("avr-gcc"),
369
+ "gxx": toolchain_tools.get("avr-g++"),
370
+ "ar": toolchain_tools.get("avr-ar"),
371
+ "objcopy": toolchain_tools.get("avr-objcopy"),
372
+ "size": toolchain_tools.get("avr-size"),
373
+ }
374
+ toolchain_paths = {k: v for k, v in toolchain_paths_raw.items() if v is not None}
375
+ build_info = build_info_generator.generate_avr(
376
+ env_name=env_name,
377
+ board_id=board_id,
378
+ board_name=board_config.name,
379
+ mcu=board_config.mcu,
380
+ f_cpu=f_cpu_int,
381
+ build_time=build_time,
382
+ elf_path=elf_path,
383
+ hex_path=hex_path,
384
+ size_info=link_result.size_info,
385
+ build_flags=build_flags,
386
+ lib_deps=lib_deps,
387
+ toolchain_version=toolchain.VERSION,
388
+ toolchain_paths=toolchain_paths,
389
+ framework_version=arduino_core.AVR_VERSION,
390
+ core_path=core_path,
391
+ )
392
+ build_info_generator.save(build_info)
393
+ log_detail(f"Build info saved to {build_info_generator.build_info_path}", verbose_only=not verbose_mode)
394
+
395
+ # Phase 11: Display results
396
+
397
+ log_phase(11, 11, "Build complete!", verbose_only=not verbose_mode)
398
+ log("")
399
+ SizeInfoPrinter.print_size_info(link_result.size_info)
400
+ log_build_complete(build_time, verbose_only=not verbose_mode)
401
+
402
+ return BuildResult(
403
+ success=True,
404
+ hex_path=hex_path,
405
+ elf_path=elf_path,
406
+ size_info=link_result.size_info,
407
+ build_time=build_time,
408
+ message="Build successful"
409
+ )
410
+
411
+ except (
412
+ BuildOrchestratorError,
413
+ ToolchainError,
414
+ ArduinoCoreError,
415
+ CompilerImportError,
416
+ LinkerImportError,
417
+ BoardConfigError,
418
+ LibraryError,
419
+ SourceCompilationOrchestratorError
420
+ ) as e:
421
+ build_time = time.time() - start_time
422
+ return BuildResult(
423
+ success=False,
424
+ hex_path=None,
425
+ elf_path=None,
426
+ size_info=None,
427
+ build_time=build_time,
428
+ message=str(e)
429
+ )
430
+ except KeyboardInterrupt as ke:
431
+ handle_keyboard_interrupt_properly(ke)
432
+ except Exception as e:
433
+ build_time = time.time() - start_time
434
+ return BuildResult(
435
+ success=False,
436
+ hex_path=None,
437
+ elf_path=None,
438
+ size_info=None,
439
+ build_time=build_time,
440
+ message=f"Unexpected error: {e}"
441
+ )
442
+
443
+ def _build_esp32(
444
+ self,
445
+ project_dir: Path,
446
+ env_name: str,
447
+ board_id: str,
448
+ env_config: dict[str, Any],
449
+ clean: bool,
450
+ verbose: bool,
451
+ start_time: float,
452
+ build_flags: List[str]
453
+ ) -> BuildResult:
454
+ """
455
+ Build ESP32 project using native build system.
456
+
457
+ Delegates to ESP32Orchestrator for ESP32-specific build logic.
458
+
459
+ Args:
460
+ project_dir: Project directory
461
+ env_name: Environment name
462
+ board_id: Board ID (e.g., esp32-c6-devkitm-1)
463
+ env_config: Environment configuration dict
464
+ clean: Whether to clean before build
465
+ verbose: Verbose output
466
+ start_time: Build start time
467
+ build_flags: User build flags from platformio.ini
468
+
469
+ Returns:
470
+ BuildResult
471
+ """
472
+ if self.cache is None:
473
+ return BuildResult(
474
+ success=False,
475
+ hex_path=None,
476
+ elf_path=None,
477
+ size_info=None,
478
+ build_time=time.time() - start_time,
479
+ message="Cache is required for ESP32 builds"
480
+ )
481
+
482
+ esp32_orchestrator = OrchestratorESP32(self.cache, verbose)
483
+ # Use the new BaseBuildOrchestrator-compliant interface
484
+ result = esp32_orchestrator.build(
485
+ project_dir=project_dir,
486
+ env_name=env_name,
487
+ clean=clean,
488
+ verbose=verbose
489
+ )
490
+ return result
491
+
492
+ def _build_teensy(
493
+ self,
494
+ project_dir: Path,
495
+ env_name: str,
496
+ board_id: str,
497
+ board_config: BoardConfig,
498
+ clean: bool,
499
+ verbose: bool,
500
+ start_time: float,
501
+ build_flags: List[str]
502
+ ) -> BuildResult:
503
+ """
504
+ Build Teensy project using native build system.
505
+
506
+ Args:
507
+ project_dir: Project directory
508
+ env_name: Environment name
509
+ board_id: Board ID (e.g., teensy41)
510
+ board_config: Board configuration
511
+ clean: Whether to clean before build
512
+ verbose: Verbose output
513
+ start_time: Build start time
514
+ build_flags: User build flags from platformio.ini
515
+
516
+ Returns:
517
+ BuildResult
518
+ """
519
+ if self.cache is None:
520
+ return BuildResult(
521
+ success=False,
522
+ hex_path=None,
523
+ elf_path=None,
524
+ size_info=None,
525
+ build_time=time.time() - start_time,
526
+ message="Cache not initialized"
527
+ )
528
+
529
+ # Delegate to OrchestratorTeensy for native Teensy build
530
+ from .orchestrator_teensy import OrchestratorTeensy
531
+
532
+ teensy_orchestrator = OrchestratorTeensy(self.cache, verbose)
533
+ result = teensy_orchestrator.build(
534
+ project_dir=project_dir,
535
+ env_name=env_name,
536
+ clean=clean,
537
+ verbose=verbose
538
+ )
539
+
540
+ return result
541
+
542
+ def _parse_config(self, project_dir: Path) -> PlatformIOConfig:
543
+ """
544
+ Parse platformio.ini configuration file.
545
+
546
+ Args:
547
+ project_dir: Project directory
548
+
549
+ Returns:
550
+ PlatformIOConfig instance
551
+
552
+ Raises:
553
+ BuildOrchestratorError: If platformio.ini not found or invalid
554
+ """
555
+ ini_path = project_dir / 'platformio.ini'
556
+
557
+ if not ini_path.exists():
558
+ raise BuildOrchestratorError(
559
+ f"platformio.ini not found in {project_dir}\n" +
560
+ "Make sure you're in a valid project directory."
561
+ )
562
+
563
+ try:
564
+ return PlatformIOConfig(ini_path)
565
+ except KeyboardInterrupt as ke:
566
+ handle_keyboard_interrupt_properly(ke)
567
+ except Exception as e:
568
+ raise BuildOrchestratorError(
569
+ f"Failed to parse platformio.ini: {e}"
570
+ )
571
+
572
+ def _ensure_toolchain(self) -> Toolchain:
573
+ """
574
+ Ensure AVR toolchain is available.
575
+
576
+ Returns:
577
+ Toolchain instance with toolchain ready
578
+
579
+ Raises:
580
+ BuildOrchestratorError: If toolchain cannot be obtained
581
+ """
582
+ try:
583
+ cache = self.cache if self.cache else Cache()
584
+ toolchain = Toolchain(cache)
585
+ toolchain.ensure_toolchain()
586
+ return toolchain
587
+ except KeyboardInterrupt as ke:
588
+ handle_keyboard_interrupt_properly(ke)
589
+ except Exception as e:
590
+ raise BuildOrchestratorError(
591
+ f"Failed to setup toolchain: {e}"
592
+ )
593
+
594
+ def _ensure_arduino_core(self) -> ArduinoCore:
595
+ """
596
+ Ensure Arduino core is available.
597
+
598
+ Returns:
599
+ ArduinoCore instance with core ready
600
+
601
+ Raises:
602
+ BuildOrchestratorError: If core cannot be obtained
603
+ """
604
+ try:
605
+ cache = self.cache if self.cache else Cache()
606
+ arduino_core = ArduinoCore(cache)
607
+ arduino_core.ensure_avr_core()
608
+ return arduino_core
609
+ except KeyboardInterrupt as ke:
610
+ handle_keyboard_interrupt_properly(ke)
611
+ except Exception as e:
612
+ raise BuildOrchestratorError(
613
+ f"Failed to setup Arduino core: {e}"
614
+ )
615
+
616
+ def _scan_sources(
617
+ self,
618
+ project_dir: Path,
619
+ build_dir: Path,
620
+ board_config: BoardConfig,
621
+ core_path: Path
622
+ ) -> "SourceCollection":
623
+ """
624
+ Scan for all source files.
625
+
626
+ Args:
627
+ project_dir: Project directory
628
+ build_dir: Build output directory
629
+ board_config: Board configuration
630
+ core_path: Arduino core installation path
631
+
632
+ Returns:
633
+ SourceCollection with all sources
634
+ """
635
+ scanner = SourceScanner(project_dir, build_dir)
636
+
637
+ # Determine source directories
638
+ # Check if 'src' directory exists, otherwise use project root
639
+ src_dir = project_dir / 'src'
640
+ if not src_dir.exists():
641
+ src_dir = project_dir
642
+
643
+ core_dir = board_config.get_core_sources_dir(core_path)
644
+ variant_dir = board_config.get_variant_dir(core_path)
645
+
646
+ return scanner.scan(
647
+ src_dir=src_dir,
648
+ core_dir=core_dir,
649
+ variant_dir=variant_dir
650
+ )
651
+