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,580 @@
1
+ """
2
+ Teensy-specific build orchestration for Fbuild projects.
3
+
4
+ This module handles Teensy platform builds separately from AVR/ESP32 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_teensy import PlatformTeensy
16
+ from ..packages.toolchain_teensy import ToolchainTeensy
17
+ from ..packages.library_manager import LibraryManager, LibraryError
18
+ from ..config.board_config import BoardConfig
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
+ from .build_info_generator import BuildInfoGenerator
27
+
28
+
29
+ @dataclass
30
+ class BuildResultTeensy:
31
+ """Result of a Teensy build operation (internal use)."""
32
+
33
+ success: bool
34
+ firmware_hex: Optional[Path]
35
+ firmware_elf: Optional[Path]
36
+ size_info: Optional[SizeInfo]
37
+ build_time: float
38
+ message: str
39
+
40
+
41
+ class OrchestratorTeensy(IBuildOrchestrator):
42
+ """
43
+ Orchestrates Teensy-specific build process.
44
+
45
+ Handles platform initialization, toolchain setup, framework preparation,
46
+ and firmware generation for Teensy 4.x targets.
47
+ """
48
+
49
+ def __init__(self, cache: Cache, verbose: bool = False):
50
+ """
51
+ Initialize Teensy orchestrator.
52
+
53
+ Args:
54
+ cache: Cache instance for package management
55
+ verbose: Enable verbose output
56
+ """
57
+ self.cache = cache
58
+ self.verbose = verbose
59
+
60
+ def build(
61
+ self,
62
+ project_dir: Path,
63
+ env_name: Optional[str] = None,
64
+ clean: bool = False,
65
+ verbose: Optional[bool] = None
66
+ ) -> BuildResult:
67
+ """Execute complete build process (IBuildOrchestrator interface).
68
+
69
+ Args:
70
+ project_dir: Project root directory containing platformio.ini
71
+ env_name: Environment name to build (defaults to first/default env)
72
+ clean: Clean build (remove all artifacts before building)
73
+ verbose: Override verbose setting
74
+
75
+ Returns:
76
+ BuildResult with build status and output paths
77
+
78
+ Raises:
79
+ BuildOrchestratorError: If build fails at any phase
80
+ """
81
+ from ..config import PlatformIOConfig
82
+
83
+ verbose_mode = verbose if verbose is not None else self.verbose
84
+
85
+ # Parse platformio.ini to get environment configuration
86
+ ini_path = project_dir / "platformio.ini"
87
+ if not ini_path.exists():
88
+ return BuildResult(
89
+ success=False,
90
+ hex_path=None,
91
+ elf_path=None,
92
+ size_info=None,
93
+ build_time=0.0,
94
+ message=f"platformio.ini not found in {project_dir}"
95
+ )
96
+
97
+ try:
98
+ config = PlatformIOConfig(ini_path)
99
+
100
+ # Determine environment to build
101
+ if env_name is None:
102
+ env_name = config.get_default_environment()
103
+ if env_name is None:
104
+ return BuildResult(
105
+ success=False,
106
+ hex_path=None,
107
+ elf_path=None,
108
+ size_info=None,
109
+ build_time=0.0,
110
+ message="No environment specified and no default found in platformio.ini"
111
+ )
112
+
113
+ env_config = config.get_env_config(env_name)
114
+ board_id = env_config.get("board", "teensy41")
115
+ build_flags = config.get_build_flags(env_name)
116
+ lib_deps = config.get_lib_deps(env_name)
117
+
118
+ # Call internal build method
119
+ teensy_result = self._build_teensy(
120
+ project_dir, env_name, board_id, env_config, build_flags, lib_deps, clean, verbose_mode
121
+ )
122
+
123
+ # Convert BuildResultTeensy to BuildResult
124
+ return BuildResult(
125
+ success=teensy_result.success,
126
+ hex_path=teensy_result.firmware_hex,
127
+ elf_path=teensy_result.firmware_elf,
128
+ size_info=teensy_result.size_info,
129
+ build_time=teensy_result.build_time,
130
+ message=teensy_result.message
131
+ )
132
+
133
+ except KeyboardInterrupt:
134
+ _thread.interrupt_main()
135
+ raise
136
+ except Exception as e:
137
+ return BuildResult(
138
+ success=False,
139
+ hex_path=None,
140
+ elf_path=None,
141
+ size_info=None,
142
+ build_time=0.0,
143
+ message=f"Failed to parse configuration: {e}"
144
+ )
145
+
146
+ def _build_teensy(
147
+ self,
148
+ project_dir: Path,
149
+ env_name: str,
150
+ board_id: str,
151
+ env_config: dict,
152
+ build_flags: List[str],
153
+ lib_deps: List[str],
154
+ clean: bool = False,
155
+ verbose: bool = False
156
+ ) -> BuildResultTeensy:
157
+ """
158
+ Execute complete Teensy build process (internal implementation).
159
+
160
+ Args:
161
+ project_dir: Project directory
162
+ env_name: Environment name
163
+ board_id: Board ID (e.g., teensy41)
164
+ env_config: Environment configuration dict
165
+ build_flags: User build flags from platformio.ini
166
+ lib_deps: Library dependencies from platformio.ini
167
+ clean: Whether to clean before build
168
+ verbose: Verbose output mode
169
+
170
+ Returns:
171
+ BuildResultTeensy with build status and output paths
172
+ """
173
+ start_time = time.time()
174
+
175
+ try:
176
+ # Get board configuration
177
+ from ..config.board_config import BoardConfig
178
+
179
+ if verbose:
180
+ print("[2/7] Loading board configuration...")
181
+
182
+ board_config = BoardConfig.from_board_id(board_id)
183
+
184
+ # Initialize platform
185
+ if verbose:
186
+ print("[3/7] Initializing Teensy platform...")
187
+
188
+ platform = PlatformTeensy(
189
+ self.cache,
190
+ board_config.mcu,
191
+ show_progress=verbose
192
+ )
193
+ platform.ensure_package()
194
+
195
+ if verbose:
196
+ print(f" Board: {board_id}")
197
+ print(f" MCU: {board_config.mcu}")
198
+ print(f" CPU Frequency: {board_config.f_cpu}")
199
+
200
+ # Setup build directory
201
+ build_dir = self._setup_build_directory(env_name, clean, verbose)
202
+
203
+ # Check build state and invalidate cache if needed
204
+ if verbose:
205
+ print("[3.5/7] Checking build configuration state...")
206
+
207
+ state_tracker = BuildStateTracker(build_dir)
208
+ needs_rebuild, reasons, current_state = state_tracker.check_invalidation(
209
+ platformio_ini_path=project_dir / "platformio.ini",
210
+ platform="teensy",
211
+ board=board_id,
212
+ framework=env_config.get('framework', 'arduino'),
213
+ toolchain_version=platform.toolchain.version,
214
+ framework_version=platform.framework.version,
215
+ platform_version=f"teensy-{platform.framework.version}",
216
+ build_flags=build_flags,
217
+ lib_deps=lib_deps,
218
+ )
219
+
220
+ if needs_rebuild:
221
+ if verbose:
222
+ print(" Build cache invalidated:")
223
+ for reason in reasons:
224
+ print(f" - {reason}")
225
+ print(" Cleaning build artifacts...")
226
+ # Clean build artifacts to force rebuild
227
+ if build_dir.exists():
228
+ safe_rmtree(build_dir)
229
+ # Recreate build directory
230
+ build_dir.mkdir(parents=True, exist_ok=True)
231
+ else:
232
+ if verbose:
233
+ print(" Build configuration unchanged, using cached artifacts")
234
+
235
+ # Initialize compiler
236
+ if verbose:
237
+ print("[4/7] Compiling Arduino core...")
238
+
239
+ compiler = ConfigurableCompiler(
240
+ platform,
241
+ platform.toolchain,
242
+ platform.framework,
243
+ board_id,
244
+ build_dir,
245
+ platform_config=None,
246
+ show_progress=verbose,
247
+ user_build_flags=build_flags
248
+ )
249
+
250
+ # Compile Arduino core
251
+ core_obj_files = compiler.compile_core()
252
+ core_archive = compiler.create_core_archive(core_obj_files)
253
+
254
+ if verbose:
255
+ print(f" Compiled {len(core_obj_files)} core source files")
256
+
257
+ # Handle library dependencies (if any)
258
+ library_archives, library_include_paths = self._process_libraries(
259
+ env_config, build_dir, compiler, platform.toolchain, board_config, verbose
260
+ )
261
+
262
+ # Add library include paths to compiler
263
+ if library_include_paths:
264
+ compiler.add_library_includes(library_include_paths)
265
+
266
+ # Find and compile sketch
267
+ sketch_obj_files = self._compile_sketch(project_dir, compiler, start_time, verbose)
268
+ if sketch_obj_files is None:
269
+ return self._error_result(
270
+ start_time,
271
+ f"No .ino sketch file found in {project_dir}"
272
+ )
273
+
274
+ # Initialize linker
275
+ if verbose:
276
+ print("[6/7] Linking firmware...")
277
+
278
+ linker = ConfigurableLinker(
279
+ platform,
280
+ platform.toolchain,
281
+ platform.framework,
282
+ board_id,
283
+ build_dir,
284
+ platform_config=None,
285
+ show_progress=verbose
286
+ )
287
+
288
+ # Link firmware
289
+ firmware_elf = linker.link(sketch_obj_files, core_archive, library_archives=library_archives)
290
+
291
+ # Generate hex file
292
+ if verbose:
293
+ print("[7/7] Generating firmware hex...")
294
+
295
+ firmware_hex = linker.generate_hex(firmware_elf)
296
+
297
+ # Get size info
298
+ size_info = linker.get_size_info(firmware_elf)
299
+
300
+ build_time = time.time() - start_time
301
+
302
+ if verbose:
303
+ self._print_success(
304
+ build_time, firmware_elf, firmware_hex, size_info
305
+ )
306
+
307
+ # Save build state for future cache validation
308
+ if verbose:
309
+ print("[7.5/7] Saving build state...")
310
+ state_tracker.save_state(current_state)
311
+
312
+ # Generate build_info.json
313
+ build_info_generator = BuildInfoGenerator(build_dir)
314
+ # Parse f_cpu from string (e.g., "600000000L") to int
315
+ f_cpu_int = int(board_config.f_cpu.rstrip("L"))
316
+ # Build toolchain_paths dict, filtering out None values
317
+ toolchain_paths_raw = {
318
+ "gcc": platform.toolchain.get_gcc_path(),
319
+ "gxx": platform.toolchain.get_gxx_path(),
320
+ "ar": platform.toolchain.get_ar_path(),
321
+ "objcopy": platform.toolchain.get_objcopy_path(),
322
+ "size": platform.toolchain.get_size_path(),
323
+ }
324
+ toolchain_paths = {k: v for k, v in toolchain_paths_raw.items() if v is not None}
325
+ build_info = build_info_generator.generate_generic(
326
+ env_name=env_name,
327
+ board_id=board_id,
328
+ board_name=board_config.name,
329
+ mcu=board_config.mcu,
330
+ platform="teensy",
331
+ f_cpu=f_cpu_int,
332
+ build_time=build_time,
333
+ elf_path=firmware_elf,
334
+ hex_path=firmware_hex,
335
+ size_info=size_info,
336
+ build_flags=build_flags,
337
+ lib_deps=lib_deps,
338
+ toolchain_version=platform.toolchain.version,
339
+ toolchain_paths=toolchain_paths,
340
+ framework_name="arduino",
341
+ framework_version=platform.framework.version,
342
+ core_path=platform.framework.get_cores_dir(),
343
+ )
344
+ build_info_generator.save(build_info)
345
+ if verbose:
346
+ print(f" Build info saved to {build_info_generator.build_info_path}")
347
+
348
+ return BuildResultTeensy(
349
+ success=True,
350
+ firmware_hex=firmware_hex,
351
+ firmware_elf=firmware_elf,
352
+ size_info=size_info,
353
+ build_time=build_time,
354
+ message="Build successful (native Teensy build)"
355
+ )
356
+
357
+ except KeyboardInterrupt as ke:
358
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
359
+ handle_keyboard_interrupt_properly(ke)
360
+ raise # Never reached, but satisfies type checker
361
+ except Exception as e:
362
+ build_time = time.time() - start_time
363
+ import traceback
364
+ error_trace = traceback.format_exc()
365
+ return BuildResultTeensy(
366
+ success=False,
367
+ firmware_hex=None,
368
+ firmware_elf=None,
369
+ size_info=None,
370
+ build_time=build_time,
371
+ message=f"Teensy native build failed: {e}\n\n{error_trace}"
372
+ )
373
+
374
+ def _setup_build_directory(self, env_name: str, clean: bool, verbose: bool) -> Path:
375
+ """
376
+ Setup build directory with optional cleaning.
377
+
378
+ Args:
379
+ env_name: Environment name
380
+ clean: Whether to clean before build
381
+ verbose: Verbose output mode
382
+
383
+ Returns:
384
+ Build directory path
385
+ """
386
+ build_dir = self.cache.get_build_dir(env_name)
387
+
388
+ if clean and build_dir.exists():
389
+ if verbose:
390
+ print("[1/7] Cleaning build directory...")
391
+ safe_rmtree(build_dir)
392
+
393
+ build_dir.mkdir(parents=True, exist_ok=True)
394
+ return build_dir
395
+
396
+ def _process_libraries(
397
+ self,
398
+ env_config: dict,
399
+ build_dir: Path,
400
+ compiler: ConfigurableCompiler,
401
+ toolchain: ToolchainTeensy,
402
+ board_config: BoardConfig,
403
+ verbose: bool
404
+ ) -> tuple[List[Path], List[Path]]:
405
+ """
406
+ Process and compile library dependencies.
407
+
408
+ Args:
409
+ env_config: Environment configuration
410
+ build_dir: Build directory
411
+ compiler: Configured compiler instance
412
+ toolchain: Teensy toolchain instance
413
+ board_config: Board configuration instance
414
+ verbose: Verbose output mode
415
+
416
+ Returns:
417
+ Tuple of (library_archives, library_include_paths)
418
+ """
419
+ lib_deps = env_config.get('lib_deps', '')
420
+ library_archives = []
421
+ library_include_paths = []
422
+
423
+ if not lib_deps:
424
+ return library_archives, library_include_paths
425
+
426
+ if verbose:
427
+ print("[4.5/7] Processing library dependencies...")
428
+
429
+ # Parse lib_deps (can be string or list)
430
+ if isinstance(lib_deps, str):
431
+ lib_specs = [dep.strip() for dep in lib_deps.split('\n') if dep.strip()]
432
+ else:
433
+ lib_specs = lib_deps
434
+
435
+ if not lib_specs:
436
+ return library_archives, library_include_paths
437
+
438
+ try:
439
+ # Initialize library manager
440
+ library_manager = LibraryManager(build_dir, mode="release")
441
+
442
+ # Prepare compilation parameters
443
+ lib_defines = []
444
+ defines_dict = board_config.get_defines()
445
+ for key, value in defines_dict.items():
446
+ if value:
447
+ lib_defines.append(f"{key}={value}")
448
+ else:
449
+ lib_defines.append(key)
450
+
451
+ # Get include paths from compiler configuration
452
+ lib_includes = compiler.get_include_paths()
453
+
454
+ # Get compiler path from toolchain (use C++ compiler for libraries)
455
+ compiler_path = toolchain.get_gxx_path()
456
+ if compiler_path is None:
457
+ raise LibraryError("C++ compiler not found in toolchain")
458
+
459
+ if verbose:
460
+ print(f" Found {len(lib_specs)} library dependencies")
461
+ print(f" Compiler path: {compiler_path}")
462
+
463
+ # Ensure all libraries are downloaded and compiled
464
+ libraries = library_manager.ensure_libraries(
465
+ lib_deps=lib_specs,
466
+ compiler_path=compiler_path,
467
+ mcu=board_config.mcu,
468
+ f_cpu=board_config.f_cpu,
469
+ defines=lib_defines,
470
+ include_paths=lib_includes,
471
+ extra_flags=[],
472
+ show_progress=verbose
473
+ )
474
+
475
+ # Get library artifacts
476
+ library_include_paths = library_manager.get_library_include_paths()
477
+ library_archives = library_manager.get_library_objects()
478
+
479
+ if verbose:
480
+ print(f" Compiled {len(libraries)} libraries")
481
+ print(f" Library objects: {len(library_archives)}")
482
+
483
+ except LibraryError as e:
484
+ print(f" Error processing libraries: {e}")
485
+ # Continue build without libraries
486
+ library_archives = []
487
+ library_include_paths = []
488
+
489
+ return library_archives, library_include_paths
490
+
491
+ def _compile_sketch(
492
+ self,
493
+ project_dir: Path,
494
+ compiler: ConfigurableCompiler,
495
+ start_time: float,
496
+ verbose: bool
497
+ ) -> Optional[List[Path]]:
498
+ """
499
+ Find and compile sketch files.
500
+
501
+ Args:
502
+ project_dir: Project directory
503
+ compiler: Configured compiler instance
504
+ start_time: Build start time for error reporting
505
+ verbose: Verbose output mode
506
+
507
+ Returns:
508
+ List of compiled object files or None if no sketch found
509
+ """
510
+ if verbose:
511
+ print("[5/7] Compiling sketch...")
512
+
513
+ # Look for .ino files in the project directory
514
+ sketch_files = list(project_dir.glob("*.ino"))
515
+ if not sketch_files:
516
+ # Also check src/ directory
517
+ src_dir = project_dir / "src"
518
+ if src_dir.exists():
519
+ sketch_files = list(src_dir.glob("*.ino"))
520
+
521
+ if not sketch_files:
522
+ return None
523
+
524
+ sketch_path = sketch_files[0]
525
+ sketch_obj_files = compiler.compile_sketch(sketch_path)
526
+
527
+ if verbose:
528
+ print(f" Compiled {len(sketch_obj_files)} sketch file(s)")
529
+
530
+ return sketch_obj_files
531
+
532
+ def _print_success(
533
+ self,
534
+ build_time: float,
535
+ firmware_elf: Path,
536
+ firmware_hex: Path,
537
+ size_info: Optional[SizeInfo]
538
+ ) -> None:
539
+ """
540
+ Print build success message.
541
+
542
+ Args:
543
+ build_time: Total build time
544
+ firmware_elf: Path to firmware ELF
545
+ firmware_hex: Path to firmware hex
546
+ size_info: Size information
547
+ """
548
+ # Build success message
549
+ message_lines = ["BUILD SUCCESSFUL!"]
550
+ message_lines.append(f"Build time: {build_time:.2f}s")
551
+ message_lines.append(f"Firmware ELF: {firmware_elf}")
552
+ message_lines.append(f"Firmware HEX: {firmware_hex}")
553
+
554
+ if size_info:
555
+ message_lines.append(
556
+ f"Program size: {size_info.text + size_info.data} bytes"
557
+ )
558
+ message_lines.append(f"Data size: {size_info.bss + size_info.data} bytes")
559
+
560
+ BannerFormatter.print_banner("\n".join(message_lines), width=60, center=False)
561
+
562
+ def _error_result(self, start_time: float, message: str) -> BuildResultTeensy:
563
+ """
564
+ Create an error result.
565
+
566
+ Args:
567
+ start_time: Build start time
568
+ message: Error message
569
+
570
+ Returns:
571
+ BuildResultTeensy indicating failure
572
+ """
573
+ return BuildResultTeensy(
574
+ success=False,
575
+ firmware_hex=None,
576
+ firmware_elf=None,
577
+ size_info=None,
578
+ build_time=time.time() - start_time,
579
+ message=message
580
+ )