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