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,696 @@
1
+ """
2
+ STM32-specific build orchestration for Fbuild projects.
3
+
4
+ This module handles STM32 platform builds separately from other platforms,
5
+ providing cleaner separation of concerns and better maintainability.
6
+ """
7
+
8
+ import _thread
9
+ import logging
10
+ import time
11
+ from pathlib import Path
12
+ from typing import List, Optional
13
+ from dataclasses import dataclass
14
+
15
+ from ..packages import Cache
16
+ from ..packages.platform_stm32 import PlatformSTM32
17
+ from ..packages.toolchain_stm32 import ToolchainSTM32
18
+ from ..packages.library_manager import LibraryManager, LibraryError
19
+ from ..config.board_config import BoardConfig
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
+ from .build_info_generator import BuildInfoGenerator
27
+
28
+ # Module-level logger
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ @dataclass
33
+ class BuildResultSTM32:
34
+ """Result of an STM32 build operation (internal use)."""
35
+
36
+ success: bool
37
+ firmware_hex: Optional[Path]
38
+ firmware_bin: Optional[Path]
39
+ firmware_elf: Optional[Path]
40
+ size_info: Optional[SizeInfo]
41
+ build_time: float
42
+ message: str
43
+
44
+
45
+ class OrchestratorSTM32(IBuildOrchestrator):
46
+ """
47
+ Orchestrates STM32-specific build process.
48
+
49
+ Handles platform initialization, toolchain setup, framework preparation,
50
+ and firmware generation for STM32 targets.
51
+ """
52
+
53
+ def __init__(self, cache: Cache, verbose: bool = False):
54
+ """
55
+ Initialize STM32 orchestrator.
56
+
57
+ Args:
58
+ cache: Cache instance for package management
59
+ verbose: Enable verbose output
60
+ """
61
+ self.cache = cache
62
+ self.verbose = verbose
63
+
64
+ def build(
65
+ self,
66
+ project_dir: Path,
67
+ env_name: Optional[str] = None,
68
+ clean: bool = False,
69
+ verbose: Optional[bool] = None
70
+ ) -> BuildResult:
71
+ """Execute complete build process (IBuildOrchestrator interface).
72
+
73
+ Args:
74
+ project_dir: Project root directory containing platformio.ini
75
+ env_name: Environment name to build (defaults to first/default env)
76
+ clean: Clean build (remove all artifacts before building)
77
+ verbose: Override verbose setting
78
+
79
+ Returns:
80
+ BuildResult with build status and output paths
81
+
82
+ Raises:
83
+ BuildOrchestratorError: If build fails at any phase
84
+ """
85
+ from ..config import PlatformIOConfig
86
+
87
+ verbose_mode = verbose if verbose is not None else self.verbose
88
+
89
+ # Parse platformio.ini to get environment configuration
90
+ ini_path = project_dir / "platformio.ini"
91
+ if not ini_path.exists():
92
+ return BuildResult(
93
+ success=False,
94
+ hex_path=None,
95
+ elf_path=None,
96
+ size_info=None,
97
+ build_time=0.0,
98
+ message=f"platformio.ini not found in {project_dir}"
99
+ )
100
+
101
+ try:
102
+ config = PlatformIOConfig(ini_path)
103
+
104
+ # Determine environment to build
105
+ if env_name is None:
106
+ env_name = config.get_default_environment()
107
+ if env_name is None:
108
+ return BuildResult(
109
+ success=False,
110
+ hex_path=None,
111
+ elf_path=None,
112
+ size_info=None,
113
+ build_time=0.0,
114
+ message="No environment specified and no default found in platformio.ini"
115
+ )
116
+
117
+ env_config = config.get_env_config(env_name)
118
+ board_id = env_config.get("board", "nucleo_f446re")
119
+ build_flags = config.get_build_flags(env_name)
120
+ lib_deps = config.get_lib_deps(env_name)
121
+
122
+ # Call internal build method
123
+ stm32_result = self._build_stm32(
124
+ project_dir, env_name, board_id, env_config, build_flags, lib_deps, clean, verbose_mode
125
+ )
126
+
127
+ # Convert BuildResultSTM32 to BuildResult
128
+ return BuildResult(
129
+ success=stm32_result.success,
130
+ hex_path=stm32_result.firmware_hex,
131
+ elf_path=stm32_result.firmware_elf,
132
+ size_info=stm32_result.size_info,
133
+ build_time=stm32_result.build_time,
134
+ message=stm32_result.message
135
+ )
136
+
137
+ except KeyboardInterrupt:
138
+ _thread.interrupt_main()
139
+ raise
140
+ except Exception as e:
141
+ return BuildResult(
142
+ success=False,
143
+ hex_path=None,
144
+ elf_path=None,
145
+ size_info=None,
146
+ build_time=0.0,
147
+ message=f"Failed to parse configuration: {e}"
148
+ )
149
+
150
+ def _build_stm32(
151
+ self,
152
+ project_dir: Path,
153
+ env_name: str,
154
+ board_id: str,
155
+ env_config: dict,
156
+ build_flags: List[str],
157
+ lib_deps: List[str],
158
+ clean: bool = False,
159
+ verbose: bool = False
160
+ ) -> BuildResultSTM32:
161
+ """
162
+ Execute complete STM32 build process (internal implementation).
163
+
164
+ Args:
165
+ project_dir: Project directory
166
+ env_name: Environment name
167
+ board_id: Board ID (e.g., nucleo_f446re, bluepill_f103c8)
168
+ env_config: Environment configuration dict
169
+ build_flags: User build flags from platformio.ini
170
+ lib_deps: Library dependencies from platformio.ini
171
+ clean: Whether to clean before build
172
+ verbose: Verbose output mode
173
+
174
+ Returns:
175
+ BuildResultSTM32 with build status and output paths
176
+ """
177
+ start_time = time.time()
178
+
179
+ try:
180
+ # Get board configuration
181
+ from ..config.board_config import BoardConfig
182
+
183
+ if verbose:
184
+ logger.info("[2/7] Loading board configuration...")
185
+ else:
186
+ logger.info("Loading board configuration...")
187
+
188
+ board_config = BoardConfig.from_board_id(board_id)
189
+
190
+ # Initialize platform
191
+ if verbose:
192
+ logger.info("[3/7] Initializing STM32 platform...")
193
+ else:
194
+ logger.info("Initializing STM32 platform...")
195
+
196
+ platform = PlatformSTM32(
197
+ self.cache,
198
+ board_config.mcu,
199
+ show_progress=True
200
+ )
201
+ platform.ensure_package()
202
+
203
+ if verbose:
204
+ logger.info(f" Board: {board_id}")
205
+ logger.info(f" MCU: {board_config.mcu}")
206
+ logger.info(f" CPU Frequency: {board_config.f_cpu}")
207
+
208
+ # Setup build directory
209
+ build_dir = self._setup_build_directory(env_name, clean, verbose)
210
+
211
+ # Check build state and invalidate cache if needed
212
+ if verbose:
213
+ logger.info("[3.5/7] Checking build configuration state...")
214
+
215
+ state_tracker = BuildStateTracker(build_dir)
216
+ needs_rebuild, reasons, current_state = state_tracker.check_invalidation(
217
+ platformio_ini_path=project_dir / "platformio.ini",
218
+ platform="ststm32",
219
+ board=board_id,
220
+ framework=env_config.get('framework', 'arduino'),
221
+ toolchain_version=platform.toolchain.version,
222
+ framework_version=platform.framework.version,
223
+ platform_version=f"stm32-{platform.framework.version}",
224
+ build_flags=build_flags,
225
+ lib_deps=lib_deps,
226
+ )
227
+
228
+ if needs_rebuild:
229
+ if verbose:
230
+ logger.info(" Build cache invalidated:")
231
+ for reason in reasons:
232
+ logger.info(f" - {reason}")
233
+ logger.info(" Cleaning build artifacts...")
234
+ # Clean build artifacts to force rebuild
235
+ if build_dir.exists():
236
+ safe_rmtree(build_dir)
237
+ # Recreate build directory
238
+ build_dir.mkdir(parents=True, exist_ok=True)
239
+ else:
240
+ if verbose:
241
+ logger.info(" Build configuration unchanged, using cached artifacts")
242
+
243
+ # Load platform configuration JSON for MCU-specific settings
244
+ import json
245
+ mcu_family = platform._get_mcu_family(board_config.mcu).lower().replace("xx", "")
246
+ platform_config_path = Path(__file__).parent.parent / "platform_configs" / f"{mcu_family}.json"
247
+ platform_config = None
248
+ if platform_config_path.exists():
249
+ with open(platform_config_path, 'r') as f:
250
+ platform_config = json.load(f)
251
+
252
+ # Initialize compiler
253
+ if verbose:
254
+ logger.info("[4/7] Compiling Arduino core...")
255
+ else:
256
+ logger.info("Compiling Arduino core...")
257
+
258
+ compiler = ConfigurableCompiler(
259
+ platform,
260
+ platform.toolchain,
261
+ platform.framework,
262
+ board_id,
263
+ build_dir,
264
+ platform_config=platform_config,
265
+ show_progress=verbose,
266
+ user_build_flags=build_flags
267
+ )
268
+
269
+ # Compile Arduino core with progress bar
270
+ if verbose:
271
+ core_obj_files = compiler.compile_core()
272
+ else:
273
+ # Use tqdm progress bar for non-verbose mode
274
+ from tqdm import tqdm
275
+
276
+ # Get number of core source files for progress tracking
277
+ core_sources = platform.framework.get_core_sources("arduino")
278
+ total_files = len(core_sources)
279
+
280
+ # Create progress bar
281
+ with tqdm(
282
+ total=total_files,
283
+ desc='Compiling Arduino core',
284
+ unit='file',
285
+ ncols=80,
286
+ leave=False
287
+ ) as pbar:
288
+ core_obj_files = compiler.compile_core(progress_bar=pbar)
289
+
290
+ # Print completion message
291
+ logger.info(f"Compiled {len(core_obj_files)} core files")
292
+
293
+ core_archive = compiler.create_core_archive(core_obj_files)
294
+
295
+ if verbose:
296
+ logger.info(f" Compiled {len(core_obj_files)} core source files")
297
+
298
+ # Handle library dependencies (if any)
299
+ library_archives, library_include_paths = self._process_libraries(
300
+ env_config, build_dir, compiler, platform.toolchain, board_config, verbose, project_dir=project_dir
301
+ )
302
+
303
+ # Add library include paths to compiler
304
+ if library_include_paths:
305
+ compiler.add_library_includes(library_include_paths)
306
+
307
+ # Get src_dir override from platformio.ini
308
+ from ..config import PlatformIOConfig
309
+ config_for_src_dir = PlatformIOConfig(project_dir / "platformio.ini")
310
+ src_dir_override = config_for_src_dir.get_src_dir()
311
+
312
+ # Find and compile sketch
313
+ sketch_obj_files = self._compile_sketch(project_dir, compiler, start_time, verbose, src_dir_override)
314
+ if sketch_obj_files is None:
315
+ search_dir = project_dir / src_dir_override if src_dir_override else project_dir
316
+ return self._error_result(
317
+ start_time,
318
+ f"No .ino sketch file found in {search_dir}"
319
+ )
320
+
321
+ # Initialize linker
322
+ if verbose:
323
+ logger.info("[6/7] Linking firmware...")
324
+ else:
325
+ logger.info("Linking firmware...")
326
+
327
+ linker = ConfigurableLinker(
328
+ platform,
329
+ platform.toolchain,
330
+ platform.framework,
331
+ board_id,
332
+ build_dir,
333
+ platform_config=platform_config,
334
+ show_progress=verbose
335
+ )
336
+
337
+ # Link firmware
338
+ firmware_elf = linker.link(sketch_obj_files, core_archive, library_archives=library_archives)
339
+
340
+ # Generate bin and hex files
341
+ if verbose:
342
+ logger.info("[7/7] Generating firmware...")
343
+ else:
344
+ logger.info("Generating firmware...")
345
+
346
+ firmware_bin = linker.generate_bin(firmware_elf)
347
+ firmware_hex = self._generate_hex(firmware_elf, platform.toolchain, verbose)
348
+
349
+ # Get size info
350
+ size_info = linker.get_size_info(firmware_elf)
351
+
352
+ build_time = time.time() - start_time
353
+
354
+ if verbose:
355
+ self._print_success(
356
+ build_time, firmware_elf, firmware_hex, size_info
357
+ )
358
+
359
+ # Save build state for future cache validation
360
+ if verbose:
361
+ logger.info("[7.5/7] Saving build state...")
362
+ state_tracker.save_state(current_state)
363
+
364
+ # Generate build_info.json
365
+ build_info_generator = BuildInfoGenerator(build_dir)
366
+ # Parse f_cpu from string (e.g., "180000000L") to int
367
+ f_cpu_int = int(board_config.f_cpu.rstrip("L"))
368
+ # Build toolchain_paths dict, filtering out None values
369
+ toolchain_paths_raw = {
370
+ "gcc": platform.toolchain.get_gcc_path(),
371
+ "gxx": platform.toolchain.get_gxx_path(),
372
+ "ar": platform.toolchain.get_ar_path(),
373
+ "objcopy": platform.toolchain.get_objcopy_path(),
374
+ "size": platform.toolchain.get_size_path(),
375
+ }
376
+ toolchain_paths = {k: v for k, v in toolchain_paths_raw.items() if v is not None}
377
+ build_info = build_info_generator.generate_generic(
378
+ env_name=env_name,
379
+ board_id=board_id,
380
+ board_name=board_config.name,
381
+ mcu=board_config.mcu,
382
+ platform="ststm32",
383
+ f_cpu=f_cpu_int,
384
+ build_time=build_time,
385
+ elf_path=firmware_elf,
386
+ hex_path=firmware_hex,
387
+ bin_path=firmware_bin,
388
+ size_info=size_info,
389
+ build_flags=build_flags,
390
+ lib_deps=lib_deps,
391
+ toolchain_version=platform.toolchain.version,
392
+ toolchain_paths=toolchain_paths,
393
+ framework_name="arduino",
394
+ framework_version=platform.framework.version,
395
+ core_path=platform.framework.get_cores_dir(),
396
+ )
397
+ build_info_generator.save(build_info)
398
+ if verbose:
399
+ logger.info(f" Build info saved to {build_info_generator.build_info_path}")
400
+
401
+ return BuildResultSTM32(
402
+ success=True,
403
+ firmware_hex=firmware_hex,
404
+ firmware_bin=firmware_bin,
405
+ firmware_elf=firmware_elf,
406
+ size_info=size_info,
407
+ build_time=build_time,
408
+ message="Build successful (native STM32 build)"
409
+ )
410
+
411
+ except KeyboardInterrupt as ke:
412
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
413
+ handle_keyboard_interrupt_properly(ke)
414
+ raise # Never reached, but satisfies type checker
415
+ except Exception as e:
416
+ build_time = time.time() - start_time
417
+ import traceback
418
+ error_trace = traceback.format_exc()
419
+ return BuildResultSTM32(
420
+ success=False,
421
+ firmware_hex=None,
422
+ firmware_bin=None,
423
+ firmware_elf=None,
424
+ size_info=None,
425
+ build_time=build_time,
426
+ message=f"STM32 native build failed: {e}\n\n{error_trace}"
427
+ )
428
+
429
+ def _generate_hex(self, elf_path: Path, toolchain: ToolchainSTM32, verbose: bool = False) -> Path:
430
+ """Generate HEX file from ELF file.
431
+
432
+ Args:
433
+ elf_path: Path to input ELF file
434
+ toolchain: STM32 toolchain instance
435
+ verbose: Verbose output mode
436
+
437
+ Returns:
438
+ Path to generated HEX file
439
+
440
+ Raises:
441
+ Exception: If HEX generation fails
442
+ """
443
+ import subprocess
444
+
445
+ hex_path = elf_path.parent / f"{elf_path.stem}.hex"
446
+
447
+ if verbose:
448
+ logger.info(f" Generating HEX file: {hex_path.name}")
449
+
450
+ objcopy = toolchain.get_objcopy_path()
451
+ if objcopy is None:
452
+ raise Exception("objcopy not found in toolchain")
453
+
454
+ cmd = [
455
+ str(objcopy),
456
+ "-O", "ihex",
457
+ str(elf_path),
458
+ str(hex_path)
459
+ ]
460
+
461
+ result = subprocess.run(cmd, capture_output=True, text=True)
462
+ if result.returncode != 0:
463
+ raise Exception(f"objcopy failed: {result.stderr}")
464
+
465
+ if verbose:
466
+ logger.info(f" HEX file generated: {hex_path}")
467
+
468
+ return hex_path
469
+
470
+ def _setup_build_directory(self, env_name: str, clean: bool, verbose: bool) -> Path:
471
+ """
472
+ Setup build directory with optional cleaning.
473
+
474
+ Args:
475
+ env_name: Environment name
476
+ clean: Whether to clean before build
477
+ verbose: Verbose output mode
478
+
479
+ Returns:
480
+ Build directory path
481
+ """
482
+ build_dir = self.cache.get_build_dir(env_name)
483
+
484
+ if clean and build_dir.exists():
485
+ if verbose:
486
+ logger.info("[1/7] Cleaning build directory...")
487
+ else:
488
+ logger.info("Cleaning build directory...")
489
+ safe_rmtree(build_dir)
490
+
491
+ build_dir.mkdir(parents=True, exist_ok=True)
492
+ return build_dir
493
+
494
+ def _process_libraries(
495
+ self,
496
+ env_config: dict,
497
+ build_dir: Path,
498
+ compiler: ConfigurableCompiler,
499
+ toolchain: ToolchainSTM32,
500
+ board_config: BoardConfig,
501
+ verbose: bool,
502
+ project_dir: Optional[Path] = None
503
+ ) -> tuple[List[Path], List[Path]]:
504
+ """
505
+ Process and compile library dependencies.
506
+
507
+ Args:
508
+ env_config: Environment configuration
509
+ build_dir: Build directory
510
+ compiler: Configured compiler instance
511
+ toolchain: STM32 toolchain instance
512
+ board_config: Board configuration instance
513
+ verbose: Verbose output mode
514
+ project_dir: Optional project directory for resolving relative library paths
515
+
516
+ Returns:
517
+ Tuple of (library_archives, library_include_paths)
518
+ """
519
+ lib_deps = env_config.get('lib_deps', '')
520
+ library_archives = []
521
+ library_include_paths = []
522
+
523
+ if not lib_deps:
524
+ return library_archives, library_include_paths
525
+
526
+ if verbose:
527
+ logger.info("[4.5/7] Processing library dependencies...")
528
+
529
+ # Parse lib_deps (can be string or list)
530
+ if isinstance(lib_deps, str):
531
+ lib_specs = [dep.strip() for dep in lib_deps.split('\n') if dep.strip()]
532
+ else:
533
+ lib_specs = lib_deps
534
+
535
+ if not lib_specs:
536
+ return library_archives, library_include_paths
537
+
538
+ try:
539
+ # Initialize library manager
540
+ library_manager = LibraryManager(build_dir, mode="release")
541
+
542
+ # Prepare compilation parameters
543
+ lib_defines = []
544
+ defines_dict = board_config.get_defines()
545
+ for key, value in defines_dict.items():
546
+ if value:
547
+ lib_defines.append(f"{key}={value}")
548
+ else:
549
+ lib_defines.append(key)
550
+
551
+ # Get include paths from compiler configuration
552
+ lib_includes = compiler.get_include_paths()
553
+
554
+ # Get compiler path from toolchain (use C++ compiler for libraries)
555
+ compiler_path = toolchain.get_gxx_path()
556
+ if compiler_path is None:
557
+ raise LibraryError("C++ compiler not found in toolchain")
558
+
559
+ if verbose:
560
+ logger.info(f" Found {len(lib_specs)} library dependencies")
561
+
562
+ # Ensure all libraries are downloaded and compiled
563
+ libraries = library_manager.ensure_libraries(
564
+ lib_deps=lib_specs,
565
+ compiler_path=compiler_path,
566
+ mcu=board_config.mcu,
567
+ f_cpu=board_config.f_cpu,
568
+ defines=lib_defines,
569
+ include_paths=lib_includes,
570
+ extra_flags=[],
571
+ show_progress=verbose
572
+ )
573
+
574
+ # Get library artifacts
575
+ library_include_paths = library_manager.get_library_include_paths()
576
+ library_archives = library_manager.get_library_objects()
577
+
578
+ if verbose:
579
+ logger.info(f" Compiled {len(libraries)} libraries")
580
+
581
+ except LibraryError as e:
582
+ logger.warning(f" Error processing libraries: {e}")
583
+ # Continue build without libraries
584
+ library_archives = []
585
+ library_include_paths = []
586
+
587
+ return library_archives, library_include_paths
588
+
589
+ def _compile_sketch(
590
+ self,
591
+ project_dir: Path,
592
+ compiler: ConfigurableCompiler,
593
+ start_time: float,
594
+ verbose: bool,
595
+ src_dir_override: Optional[str] = None
596
+ ) -> Optional[List[Path]]:
597
+ """
598
+ Find and compile sketch files.
599
+
600
+ Args:
601
+ project_dir: Project directory
602
+ compiler: Configured compiler instance
603
+ start_time: Build start time
604
+ verbose: Verbose output mode
605
+ src_dir_override: Optional src_dir override from platformio.ini
606
+
607
+ Returns:
608
+ List of compiled object files, or None if no sketch found
609
+ """
610
+ if verbose:
611
+ logger.info("[5/7] Compiling sketch...")
612
+ else:
613
+ logger.info("Compiling sketch...")
614
+
615
+ # Determine source directory
616
+ if src_dir_override:
617
+ src_dir = project_dir / src_dir_override
618
+ else:
619
+ src_dir = project_dir / "src"
620
+ if not src_dir.exists():
621
+ src_dir = project_dir
622
+
623
+ # Find sketch file (.ino or .cpp)
624
+ sketch_files = list(src_dir.glob("*.ino"))
625
+ if not sketch_files:
626
+ sketch_files = list(src_dir.glob("*.cpp"))
627
+
628
+ if not sketch_files:
629
+ return None
630
+
631
+ # Also find additional source files
632
+ cpp_files = list(src_dir.glob("*.cpp"))
633
+ c_files = list(src_dir.glob("*.c"))
634
+ all_source_files = sketch_files + [f for f in cpp_files if f not in sketch_files] + c_files
635
+
636
+ if verbose:
637
+ logger.info(f" Found {len(all_source_files)} source files")
638
+
639
+ # Compile sketch files - compile each file individually
640
+ obj_files = []
641
+ for source_file in all_source_files:
642
+ if source_file.suffix == '.ino':
643
+ # .ino files need preprocessing
644
+ compiled = compiler.compile_sketch(source_file)
645
+ obj_files.extend(compiled)
646
+ else:
647
+ # .c and .cpp files can be compiled directly
648
+ obj_dir = compiler.build_dir / "obj"
649
+ obj_dir.mkdir(parents=True, exist_ok=True)
650
+ obj_path = obj_dir / f"{source_file.stem}.o"
651
+
652
+ # Skip if up-to-date
653
+ if not compiler.needs_rebuild(source_file, obj_path):
654
+ obj_files.append(obj_path)
655
+ continue
656
+
657
+ compiled_obj = compiler.compile_source(source_file, obj_path)
658
+ obj_files.append(compiled_obj)
659
+
660
+ if verbose:
661
+ logger.info(f" Compiled {len(obj_files)} sketch files")
662
+
663
+ return obj_files
664
+
665
+ def _error_result(self, start_time: float, message: str) -> BuildResultSTM32:
666
+ """Create an error result."""
667
+ return BuildResultSTM32(
668
+ success=False,
669
+ firmware_hex=None,
670
+ firmware_bin=None,
671
+ firmware_elf=None,
672
+ size_info=None,
673
+ build_time=time.time() - start_time,
674
+ message=message
675
+ )
676
+
677
+ def _print_success(
678
+ self,
679
+ build_time: float,
680
+ firmware_elf: Path,
681
+ firmware_hex: Path,
682
+ size_info: Optional[SizeInfo]
683
+ ) -> None:
684
+ """Print success message with build details."""
685
+ logger.info("")
686
+ logger.info("=" * 60)
687
+ logger.info("STM32 BUILD SUCCESSFUL")
688
+ logger.info("=" * 60)
689
+ logger.info(f"Build time: {build_time:.2f}s")
690
+ logger.info(f"Output: {firmware_hex}")
691
+
692
+ if size_info:
693
+ logger.info(f"Flash: {size_info.total_flash:,} bytes")
694
+ logger.info(f"RAM: {size_info.total_ram:,} bytes")
695
+
696
+ logger.info("=" * 60)