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,797 @@
1
+ """
2
+ ESP32-specific build orchestration for Fbuild projects.
3
+
4
+ This module handles ESP32 platform builds separately from AVR builds,
5
+ providing cleaner separation of concerns and better maintainability.
6
+ """
7
+
8
+ import _thread
9
+ import time
10
+ from pathlib import Path
11
+ from typing import List, Optional
12
+ from dataclasses import dataclass
13
+
14
+ from ..packages import Cache
15
+ from ..packages.platform_esp32 import PlatformESP32
16
+ from ..packages.toolchain_esp32 import ToolchainESP32
17
+ from ..packages.framework_esp32 import FrameworkESP32
18
+ from ..packages.library_manager_esp32 import LibraryManagerESP32
19
+ from ..cli_utils import BannerFormatter
20
+ from .configurable_compiler import ConfigurableCompiler
21
+ from .configurable_linker import ConfigurableLinker
22
+ from .linker import SizeInfo
23
+ from .orchestrator import IBuildOrchestrator, BuildResult
24
+ from .build_utils import safe_rmtree
25
+ from .build_state import BuildStateTracker
26
+
27
+
28
+ @dataclass
29
+ class BuildResultESP32:
30
+ """Result of an ESP32 build operation (internal use)."""
31
+
32
+ success: bool
33
+ firmware_bin: Optional[Path]
34
+ firmware_elf: Optional[Path]
35
+ bootloader_bin: Optional[Path]
36
+ partitions_bin: Optional[Path]
37
+ size_info: Optional[SizeInfo]
38
+ build_time: float
39
+ message: str
40
+
41
+
42
+ class OrchestratorESP32(IBuildOrchestrator):
43
+ """
44
+ Orchestrates ESP32-specific build process.
45
+
46
+ Handles platform initialization, toolchain setup, framework preparation,
47
+ library compilation, and firmware generation for ESP32 targets.
48
+ """
49
+
50
+ def __init__(self, cache: Cache, verbose: bool = False):
51
+ """
52
+ Initialize ESP32 orchestrator.
53
+
54
+ Args:
55
+ cache: Cache instance for package management
56
+ verbose: Enable verbose output
57
+ """
58
+ self.cache = cache
59
+ self.verbose = verbose
60
+
61
+ def build(
62
+ self,
63
+ project_dir: Path,
64
+ env_name: Optional[str] = None,
65
+ clean: bool = False,
66
+ verbose: Optional[bool] = None
67
+ ) -> BuildResult:
68
+ """Execute complete build process (BaseBuildOrchestrator interface).
69
+
70
+ Args:
71
+ project_dir: Project root directory containing platformio.ini
72
+ env_name: Environment name to build (defaults to first/default env)
73
+ clean: Clean build (remove all artifacts before building)
74
+ verbose: Override verbose setting
75
+
76
+ Returns:
77
+ BuildResult with build status and output paths
78
+
79
+ Raises:
80
+ BuildOrchestratorError: If build fails at any phase
81
+ """
82
+ from ..config import PlatformIOConfig
83
+
84
+ verbose_mode = verbose if verbose is not None else self.verbose
85
+
86
+ # Parse platformio.ini to get environment configuration
87
+ ini_path = project_dir / "platformio.ini"
88
+ if not ini_path.exists():
89
+ return BuildResult(
90
+ success=False,
91
+ hex_path=None,
92
+ elf_path=None,
93
+ size_info=None,
94
+ build_time=0.0,
95
+ message=f"platformio.ini not found in {project_dir}"
96
+ )
97
+
98
+ try:
99
+ config = PlatformIOConfig(ini_path)
100
+
101
+ # Determine environment to build
102
+ if env_name is None:
103
+ env_name = config.get_default_environment()
104
+ if env_name is None:
105
+ return BuildResult(
106
+ success=False,
107
+ hex_path=None,
108
+ elf_path=None,
109
+ size_info=None,
110
+ build_time=0.0,
111
+ message="No environment specified and no default found in platformio.ini"
112
+ )
113
+
114
+ env_config = config.get_env_config(env_name)
115
+ board_id = env_config.get("board", "")
116
+ build_flags = config.get_build_flags(env_name)
117
+ lib_deps = config.get_lib_deps(env_name)
118
+
119
+ # Call internal build method
120
+ esp32_result = self._build_esp32(
121
+ project_dir, env_name, board_id, env_config, build_flags, lib_deps, clean, verbose_mode
122
+ )
123
+
124
+ # Convert BuildResultESP32 to BuildResult
125
+ return BuildResult(
126
+ success=esp32_result.success,
127
+ hex_path=esp32_result.firmware_bin,
128
+ elf_path=esp32_result.firmware_elf,
129
+ size_info=esp32_result.size_info,
130
+ build_time=esp32_result.build_time,
131
+ message=esp32_result.message
132
+ )
133
+
134
+ except KeyboardInterrupt:
135
+ _thread.interrupt_main()
136
+ raise
137
+ except Exception as e:
138
+ return BuildResult(
139
+ success=False,
140
+ hex_path=None,
141
+ elf_path=None,
142
+ size_info=None,
143
+ build_time=0.0,
144
+ message=f"Failed to parse configuration: {e}"
145
+ )
146
+
147
+ def _build_esp32(
148
+ self,
149
+ project_dir: Path,
150
+ env_name: str,
151
+ board_id: str,
152
+ env_config: dict,
153
+ build_flags: List[str],
154
+ lib_deps: List[str],
155
+ clean: bool = False,
156
+ verbose: bool = False
157
+ ) -> BuildResultESP32:
158
+ """
159
+ Execute complete ESP32 build process (internal implementation).
160
+
161
+ Args:
162
+ project_dir: Project directory
163
+ env_name: Environment name
164
+ board_id: Board ID (e.g., esp32-c6-devkitm-1)
165
+ env_config: Environment configuration dict
166
+ build_flags: User build flags from platformio.ini
167
+ lib_deps: Library dependencies from platformio.ini
168
+ clean: Whether to clean before build
169
+ verbose: Verbose output mode
170
+
171
+ Returns:
172
+ BuildResultESP32 with build status and output paths
173
+ """
174
+ start_time = time.time()
175
+
176
+ try:
177
+ # Get platform URL from env_config
178
+ platform_url = env_config.get('platform')
179
+ if not platform_url:
180
+ return self._error_result(
181
+ start_time,
182
+ "No platform URL specified in platformio.ini"
183
+ )
184
+
185
+ # Initialize platform
186
+ if verbose:
187
+ print("[3/10] Initializing ESP32 platform...")
188
+ else:
189
+ print("Initializing ESP32 platform...")
190
+
191
+ platform = PlatformESP32(self.cache, platform_url, show_progress=True)
192
+ platform.ensure_platform()
193
+
194
+ # Get board configuration
195
+ board_json = platform.get_board_json(board_id)
196
+ mcu = board_json.get("build", {}).get("mcu", "esp32c6")
197
+
198
+ if verbose:
199
+ print(f" Board: {board_id}")
200
+ print(f" MCU: {mcu}")
201
+
202
+ # Get required packages
203
+ packages = platform.get_required_packages(mcu)
204
+
205
+ # Initialize toolchain
206
+ toolchain = self._setup_toolchain(packages, start_time, verbose)
207
+ if toolchain is None:
208
+ return self._error_result(
209
+ start_time,
210
+ "Failed to initialize toolchain"
211
+ )
212
+
213
+ # Initialize framework
214
+ framework = self._setup_framework(packages, start_time, verbose)
215
+ if framework is None:
216
+ return self._error_result(
217
+ start_time,
218
+ "Failed to initialize framework"
219
+ )
220
+
221
+ # Setup build directory
222
+ build_dir = self._setup_build_directory(env_name, clean, verbose)
223
+
224
+ # Check build state and invalidate cache if needed
225
+ if verbose:
226
+ print("[6.5/10] Checking build configuration state...")
227
+
228
+ state_tracker = BuildStateTracker(build_dir)
229
+ needs_rebuild, reasons, current_state = state_tracker.check_invalidation(
230
+ platformio_ini_path=project_dir / "platformio.ini",
231
+ platform="esp32",
232
+ board=board_id,
233
+ framework=env_config.get('framework', 'arduino'),
234
+ toolchain_version=toolchain.version,
235
+ framework_version=framework.version,
236
+ platform_version=platform.version,
237
+ build_flags=build_flags,
238
+ lib_deps=lib_deps,
239
+ )
240
+
241
+ if needs_rebuild:
242
+ if verbose:
243
+ print(" Build cache invalidated:")
244
+ for reason in reasons:
245
+ print(f" - {reason}")
246
+ print(" Cleaning build artifacts...")
247
+ # Clean build artifacts to force rebuild
248
+ from .build_utils import safe_rmtree
249
+ if build_dir.exists():
250
+ safe_rmtree(build_dir)
251
+ # Recreate build directory
252
+ build_dir.mkdir(parents=True, exist_ok=True)
253
+ else:
254
+ if verbose:
255
+ print(" Build configuration unchanged, using cached artifacts")
256
+
257
+ # Initialize compilation executor early to show sccache status
258
+ from .compilation_executor import CompilationExecutor
259
+ compilation_executor = CompilationExecutor(
260
+ build_dir=build_dir,
261
+ show_progress=verbose
262
+ )
263
+
264
+ # Try to get compilation queue from daemon for async compilation
265
+ # TODO: Implement get_compilation_queue() in daemon module
266
+ compilation_queue = None
267
+ # try:
268
+ # from ..daemon import daemon
269
+ # compilation_queue = daemon.get_compilation_queue()
270
+ # if compilation_queue and verbose:
271
+ # num_workers = getattr(compilation_queue, 'num_workers', 'unknown')
272
+ # print(f"[Async Mode] Using daemon compilation queue with {num_workers} workers")
273
+ # except (ImportError, AttributeError):
274
+ # # Daemon not available or not running - use synchronous compilation
275
+ # if verbose:
276
+ # print("[Sync Mode] Daemon not available, using synchronous compilation")
277
+
278
+ # Initialize compiler
279
+ if verbose:
280
+ print("[7/10] Compiling Arduino core...")
281
+ else:
282
+ print("Compiling Arduino core...")
283
+
284
+ compiler = ConfigurableCompiler(
285
+ platform,
286
+ toolchain,
287
+ framework,
288
+ board_id,
289
+ build_dir,
290
+ platform_config=None,
291
+ show_progress=verbose,
292
+ user_build_flags=build_flags,
293
+ compilation_executor=compilation_executor,
294
+ compilation_queue=compilation_queue
295
+ )
296
+
297
+ # Compile Arduino core with progress bar
298
+ if verbose:
299
+ core_obj_files = compiler.compile_core()
300
+ else:
301
+ # Use tqdm progress bar for non-verbose mode
302
+ from tqdm import tqdm
303
+
304
+ # Get number of core source files for progress tracking
305
+ core_sources = framework.get_core_sources(compiler.core)
306
+ total_files = len(core_sources)
307
+
308
+ # Create progress bar
309
+ with tqdm(
310
+ total=total_files,
311
+ desc='Compiling Arduino core',
312
+ unit='file',
313
+ ncols=80,
314
+ leave=False
315
+ ) as pbar:
316
+ core_obj_files = compiler.compile_core(progress_bar=pbar)
317
+
318
+ # Print completion message
319
+ print(f"Compiled {len(core_obj_files)} core files")
320
+
321
+ # Add Bluetooth stub for non-ESP32 targets (ESP32-C6, ESP32-S3, etc.)
322
+ # where esp32-hal-bt.c fails to compile but btInUse() is still referenced
323
+ bt_stub_obj = self._create_bt_stub(build_dir, compiler, verbose)
324
+ if bt_stub_obj:
325
+ core_obj_files.append(bt_stub_obj)
326
+
327
+ core_archive = compiler.create_core_archive(core_obj_files)
328
+
329
+ if verbose:
330
+ print(f" Compiled {len(core_obj_files)} core source files")
331
+
332
+ # Handle library dependencies
333
+ library_archives, library_include_paths = self._process_libraries(
334
+ env_config, build_dir, compiler, toolchain, verbose
335
+ )
336
+
337
+ # Add library include paths to compiler
338
+ if library_include_paths:
339
+ compiler.add_library_includes(library_include_paths)
340
+
341
+ # Find and compile sketch
342
+ sketch_obj_files = self._compile_sketch(project_dir, compiler, start_time, verbose)
343
+ if sketch_obj_files is None:
344
+ return self._error_result(
345
+ start_time,
346
+ f"No .ino sketch file found in {project_dir}"
347
+ )
348
+
349
+ # Initialize linker
350
+ if verbose:
351
+ print("[9/10] Linking firmware...")
352
+ else:
353
+ print("Linking firmware...")
354
+
355
+ linker = ConfigurableLinker(
356
+ platform,
357
+ toolchain,
358
+ framework,
359
+ board_id,
360
+ build_dir,
361
+ platform_config=None,
362
+ show_progress=verbose
363
+ )
364
+
365
+ # Link firmware
366
+ firmware_elf = linker.link(sketch_obj_files, core_archive, library_archives=library_archives)
367
+
368
+ # Generate binary
369
+ if verbose:
370
+ print("[10/10] Generating firmware binary...")
371
+ else:
372
+ print("Generating firmware binary...")
373
+
374
+ firmware_bin = linker.generate_bin(firmware_elf)
375
+
376
+ # Generate bootloader and partition table
377
+ bootloader_bin, partitions_bin = self._generate_boot_components(
378
+ linker, mcu, verbose
379
+ )
380
+
381
+ build_time = time.time() - start_time
382
+
383
+ if verbose:
384
+ self._print_success(
385
+ build_time, firmware_elf, firmware_bin,
386
+ bootloader_bin, partitions_bin
387
+ )
388
+
389
+ # Save build state for future cache validation
390
+ if verbose:
391
+ print("[10.5/10] Saving build state...")
392
+ state_tracker.save_state(current_state)
393
+
394
+ return BuildResultESP32(
395
+ success=True,
396
+ firmware_bin=firmware_bin,
397
+ firmware_elf=firmware_elf,
398
+ bootloader_bin=bootloader_bin,
399
+ partitions_bin=partitions_bin,
400
+ size_info=None, # TODO: Add size info for ESP32
401
+ build_time=build_time,
402
+ message="Build successful (native ESP32 build)"
403
+ )
404
+
405
+ except KeyboardInterrupt as ke:
406
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
407
+ handle_keyboard_interrupt_properly(ke)
408
+ raise # Never reached, but satisfies type checker
409
+ except Exception as e:
410
+ build_time = time.time() - start_time
411
+ import traceback
412
+ error_trace = traceback.format_exc()
413
+ return BuildResultESP32(
414
+ success=False,
415
+ firmware_bin=None,
416
+ firmware_elf=None,
417
+ bootloader_bin=None,
418
+ partitions_bin=None,
419
+ size_info=None,
420
+ build_time=build_time,
421
+ message=f"ESP32 native build failed: {e}\n\n{error_trace}"
422
+ )
423
+
424
+ def _setup_toolchain(
425
+ self,
426
+ packages: dict,
427
+ start_time: float,
428
+ verbose: bool
429
+ ) -> Optional['ToolchainESP32']:
430
+ """
431
+ Initialize ESP32 toolchain.
432
+
433
+ Args:
434
+ packages: Package URLs dictionary
435
+ start_time: Build start time for error reporting
436
+ verbose: Verbose output mode
437
+
438
+ Returns:
439
+ ToolchainESP32 instance or None on failure
440
+ """
441
+ if verbose:
442
+ print("[4/10] Initializing ESP32 toolchain...")
443
+ else:
444
+ print("Initializing ESP32 toolchain...")
445
+
446
+ toolchain_url = packages.get("toolchain-riscv32-esp") or packages.get("toolchain-xtensa-esp-elf")
447
+ if not toolchain_url:
448
+ return None
449
+
450
+ # Determine toolchain type
451
+ toolchain_type = "riscv32-esp" if "riscv32" in toolchain_url else "xtensa-esp-elf"
452
+ toolchain = ToolchainESP32(
453
+ self.cache,
454
+ toolchain_url,
455
+ toolchain_type,
456
+ show_progress=True
457
+ )
458
+ toolchain.ensure_toolchain()
459
+ return toolchain
460
+
461
+ def _setup_framework(
462
+ self,
463
+ packages: dict,
464
+ start_time: float,
465
+ verbose: bool
466
+ ) -> Optional[FrameworkESP32]:
467
+ """
468
+ Initialize ESP32 framework.
469
+
470
+ Args:
471
+ packages: Package URLs dictionary
472
+ start_time: Build start time for error reporting
473
+ verbose: Verbose output mode
474
+
475
+ Returns:
476
+ FrameworkESP32 instance or None on failure
477
+ """
478
+ if verbose:
479
+ print("[5/10] Initializing ESP32 framework...")
480
+ else:
481
+ print("Initializing ESP32 framework...")
482
+
483
+ framework_url = packages.get("framework-arduinoespressif32")
484
+ libs_url = packages.get("framework-arduinoespressif32-libs", "")
485
+
486
+ if not framework_url:
487
+ return None
488
+
489
+ # Find skeleton library if present (e.g., framework-arduino-esp32c2-skeleton-lib)
490
+ skeleton_lib_url = None
491
+ for package_name, package_url in packages.items():
492
+ if package_name.startswith("framework-arduino-") and package_name.endswith("-skeleton-lib"):
493
+ skeleton_lib_url = package_url
494
+ break
495
+
496
+ framework = FrameworkESP32(
497
+ self.cache,
498
+ framework_url,
499
+ libs_url,
500
+ skeleton_lib_url=skeleton_lib_url,
501
+ show_progress=True
502
+ )
503
+ framework.ensure_framework()
504
+ return framework
505
+
506
+ def _setup_build_directory(self, env_name: str, clean: bool, verbose: bool) -> Path:
507
+ """
508
+ Setup build directory with optional cleaning.
509
+
510
+ Args:
511
+ env_name: Environment name
512
+ clean: Whether to clean before build
513
+ verbose: Verbose output mode
514
+
515
+ Returns:
516
+ Build directory path
517
+ """
518
+ build_dir = self.cache.get_build_dir(env_name)
519
+
520
+ if clean and build_dir.exists():
521
+ if verbose:
522
+ print("[6/10] Cleaning build directory...")
523
+ else:
524
+ print("Cleaning build directory...")
525
+ safe_rmtree(build_dir)
526
+
527
+ build_dir.mkdir(parents=True, exist_ok=True)
528
+ return build_dir
529
+
530
+ def _process_libraries(
531
+ self,
532
+ env_config: dict,
533
+ build_dir: Path,
534
+ compiler: ConfigurableCompiler,
535
+ toolchain: ToolchainESP32,
536
+ verbose: bool
537
+ ) -> tuple[List[Path], List[Path]]:
538
+ """
539
+ Process and compile library dependencies.
540
+
541
+ Args:
542
+ env_config: Environment configuration
543
+ build_dir: Build directory
544
+ compiler: Configured compiler instance
545
+ toolchain: ESP32 toolchain instance
546
+ verbose: Verbose output mode
547
+
548
+ Returns:
549
+ Tuple of (library_archives, library_include_paths)
550
+ """
551
+ lib_deps = env_config.get('lib_deps', '')
552
+ library_archives = []
553
+ library_include_paths = []
554
+
555
+ if not lib_deps:
556
+ return library_archives, library_include_paths
557
+
558
+ if verbose:
559
+ print("[7.5/10] Processing library dependencies...")
560
+
561
+ # Parse lib_deps (can be string or list)
562
+ if isinstance(lib_deps, str):
563
+ lib_specs = [dep.strip() for dep in lib_deps.split('\n') if dep.strip()]
564
+ else:
565
+ lib_specs = lib_deps
566
+
567
+ if not lib_specs:
568
+ return library_archives, library_include_paths
569
+
570
+ # Initialize library manager
571
+ lib_manager = LibraryManagerESP32(build_dir)
572
+
573
+ # Get compiler flags for library compilation
574
+ lib_compiler_flags = compiler.get_base_flags()
575
+
576
+ # Get include paths for library compilation
577
+ lib_include_paths = compiler.get_include_paths()
578
+
579
+ # Get toolchain bin path
580
+ toolchain_bin_path = toolchain.get_bin_path()
581
+ if toolchain_bin_path is None:
582
+ if verbose:
583
+ print(" Warning: Toolchain bin directory not found, skipping libraries")
584
+ return library_archives, library_include_paths
585
+
586
+ # Ensure libraries are downloaded and compiled
587
+ libraries = lib_manager.ensure_libraries(
588
+ lib_specs,
589
+ toolchain_bin_path,
590
+ lib_compiler_flags,
591
+ lib_include_paths,
592
+ show_progress=verbose
593
+ )
594
+
595
+ # Get library archives and include paths
596
+ library_archives = [lib.archive_file for lib in libraries if lib.is_compiled]
597
+ library_include_paths = lib_manager.get_library_include_paths()
598
+
599
+ if verbose:
600
+ print(f" Compiled {len(libraries)} library dependencies")
601
+
602
+ return library_archives, library_include_paths
603
+
604
+ def _compile_sketch(
605
+ self,
606
+ project_dir: Path,
607
+ compiler: ConfigurableCompiler,
608
+ start_time: float,
609
+ verbose: bool
610
+ ) -> Optional[List[Path]]:
611
+ """
612
+ Find and compile sketch files.
613
+
614
+ Args:
615
+ project_dir: Project directory
616
+ compiler: Configured compiler instance
617
+ start_time: Build start time for error reporting
618
+ verbose: Verbose output mode
619
+
620
+ Returns:
621
+ List of compiled object files or None if no sketch found
622
+ """
623
+ if verbose:
624
+ print("[8/10] Compiling sketch...")
625
+
626
+ # Look for .ino files in the project directory
627
+ sketch_files = list(project_dir.glob("*.ino"))
628
+ if not sketch_files:
629
+ return None
630
+
631
+ sketch_path = sketch_files[0]
632
+ sketch_obj_files = compiler.compile_sketch(sketch_path)
633
+
634
+ if verbose:
635
+ print(f" Compiled {len(sketch_obj_files)} sketch file(s)")
636
+
637
+ return sketch_obj_files
638
+
639
+ def _create_bt_stub(
640
+ self,
641
+ build_dir: Path,
642
+ compiler: ConfigurableCompiler,
643
+ verbose: bool
644
+ ) -> Optional[Path]:
645
+ """
646
+ Create a Bluetooth stub for ESP32 targets where esp32-hal-bt.c fails to compile.
647
+
648
+ On non-ESP32 targets (ESP32-C6, ESP32-S3, etc.), the esp32-hal-bt.c file may
649
+ fail to compile due to SDK incompatibilities, but initArduino() still references
650
+ btInUse(). This creates a stub implementation that returns false.
651
+
652
+ Args:
653
+ build_dir: Build directory
654
+ compiler: Configured compiler instance
655
+ verbose: Whether to print verbose output
656
+
657
+ Returns:
658
+ Path to compiled stub object file, or None on error
659
+ """
660
+ try:
661
+ # Create stub source file
662
+ stub_dir = build_dir / "stubs"
663
+ stub_dir.mkdir(parents=True, exist_ok=True)
664
+ stub_file = stub_dir / "bt_stub.c"
665
+
666
+ # Write minimal btInUse() implementation
667
+ stub_content = """// Bluetooth stub for ESP32 targets where esp32-hal-bt.c fails to compile
668
+ // This provides a fallback implementation of btInUse() that always returns false
669
+
670
+ #include <stdbool.h>
671
+
672
+ // Weak attribute allows this to be overridden if the real implementation links
673
+ __attribute__((weak)) bool btInUse(void) {
674
+ return false;
675
+ }
676
+ """
677
+ stub_file.write_text(stub_content)
678
+
679
+ # Compile the stub
680
+ stub_obj = stub_dir / "bt_stub.o"
681
+ compiled_obj = compiler.compile_source(stub_file, stub_obj)
682
+
683
+ if verbose:
684
+ print(f" Created Bluetooth stub: {compiled_obj.name}")
685
+
686
+ return compiled_obj
687
+
688
+ except KeyboardInterrupt as ke:
689
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
690
+
691
+ handle_keyboard_interrupt_properly(ke)
692
+ raise # Never reached, but satisfies type checker
693
+ except Exception as e:
694
+ if verbose:
695
+ print(f"Warning: Failed to create Bluetooth stub: {e}")
696
+ return None
697
+
698
+ def _generate_boot_components(
699
+ self,
700
+ linker: ConfigurableLinker,
701
+ mcu: str,
702
+ verbose: bool
703
+ ) -> tuple[Optional[Path], Optional[Path]]:
704
+ """
705
+ Generate bootloader and partition table for ESP32.
706
+
707
+ Args:
708
+ linker: Configured linker instance
709
+ mcu: MCU identifier
710
+ verbose: Verbose output mode
711
+
712
+ Returns:
713
+ Tuple of (bootloader_bin, partitions_bin)
714
+ """
715
+ bootloader_bin = None
716
+ partitions_bin = None
717
+
718
+ if not mcu.startswith("esp32"):
719
+ return bootloader_bin, partitions_bin
720
+
721
+ if verbose:
722
+ print("[11/12] Generating bootloader...")
723
+ try:
724
+ bootloader_bin = linker.generate_bootloader()
725
+ except KeyboardInterrupt as ke:
726
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
727
+ handle_keyboard_interrupt_properly(ke)
728
+ raise # Never reached, but satisfies type checker
729
+ except Exception as e:
730
+ if verbose:
731
+ print(f"Warning: Could not generate bootloader: {e}")
732
+
733
+ if verbose:
734
+ print("[12/12] Generating partition table...")
735
+ try:
736
+ partitions_bin = linker.generate_partition_table()
737
+ except KeyboardInterrupt as ke:
738
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
739
+ handle_keyboard_interrupt_properly(ke)
740
+ raise # Never reached, but satisfies type checker
741
+ except Exception as e:
742
+ if verbose:
743
+ print(f"Warning: Could not generate partition table: {e}")
744
+
745
+ return bootloader_bin, partitions_bin
746
+
747
+ def _print_success(
748
+ self,
749
+ build_time: float,
750
+ firmware_elf: Path,
751
+ firmware_bin: Path,
752
+ bootloader_bin: Optional[Path],
753
+ partitions_bin: Optional[Path]
754
+ ) -> None:
755
+ """
756
+ Print build success message.
757
+
758
+ Args:
759
+ build_time: Total build time
760
+ firmware_elf: Path to firmware ELF
761
+ firmware_bin: Path to firmware binary
762
+ bootloader_bin: Optional path to bootloader
763
+ partitions_bin: Optional path to partition table
764
+ """
765
+ # Build success message
766
+ message_lines = ["BUILD SUCCESSFUL!"]
767
+ message_lines.append(f"Build time: {build_time:.2f}s")
768
+ message_lines.append(f"Firmware ELF: {firmware_elf}")
769
+ message_lines.append(f"Firmware BIN: {firmware_bin}")
770
+ if bootloader_bin:
771
+ message_lines.append(f"Bootloader: {bootloader_bin}")
772
+ if partitions_bin:
773
+ message_lines.append(f"Partitions: {partitions_bin}")
774
+
775
+ BannerFormatter.print_banner("\n".join(message_lines), width=60, center=False)
776
+
777
+ def _error_result(self, start_time: float, message: str) -> BuildResultESP32:
778
+ """
779
+ Create an error result.
780
+
781
+ Args:
782
+ start_time: Build start time
783
+ message: Error message
784
+
785
+ Returns:
786
+ BuildResultESP32 indicating failure
787
+ """
788
+ return BuildResultESP32(
789
+ success=False,
790
+ firmware_bin=None,
791
+ firmware_elf=None,
792
+ bootloader_bin=None,
793
+ partitions_bin=None,
794
+ size_info=None,
795
+ build_time=time.time() - start_time,
796
+ message=message
797
+ )