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