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,431 @@
1
+ """
2
+ Install Dependencies Processor - Handles dependency installation operations.
3
+
4
+ This module implements the InstallDependenciesProcessor which downloads and
5
+ caches all dependencies (toolchain, platform, framework, libraries) without
6
+ performing actual compilation. Useful for:
7
+ - Pre-warming the cache before builds
8
+ - Ensuring dependencies are available offline
9
+ - Separating dependency installation from compilation
10
+ """
11
+
12
+ import importlib
13
+ import logging
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import TYPE_CHECKING
17
+
18
+ from fbuild.daemon.messages import OperationType
19
+ from fbuild.daemon.request_processor import RequestProcessor
20
+
21
+ if TYPE_CHECKING:
22
+ from fbuild.daemon.daemon_context import DaemonContext
23
+ from fbuild.daemon.messages import DaemonState, InstallDependenciesRequest
24
+ from fbuild.packages.cache import Cache
25
+
26
+
27
+ class InstallDependenciesProcessor(RequestProcessor):
28
+ """Processor for install dependencies requests.
29
+
30
+ This processor handles downloading and caching of build dependencies
31
+ without performing actual compilation. It:
32
+ 1. Reloads build modules to pick up code changes (for development)
33
+ 2. Detects platform type from platformio.ini
34
+ 3. Downloads and caches platform, toolchain, framework
35
+ 4. Installs library dependencies
36
+ 5. Returns success/failure based on installation result
37
+
38
+ Example:
39
+ >>> processor = InstallDependenciesProcessor()
40
+ >>> success = processor.process_request(install_deps_request, daemon_context)
41
+ """
42
+
43
+ def get_operation_type(self) -> OperationType:
44
+ """Return BUILD operation type (dependencies are part of build)."""
45
+ return OperationType.BUILD
46
+
47
+ def get_required_locks(self, request: "InstallDependenciesRequest", context: "DaemonContext") -> dict[str, str]:
48
+ """Install dependencies requires only a project lock.
49
+
50
+ Args:
51
+ request: The install dependencies request
52
+ context: The daemon context
53
+
54
+ Returns:
55
+ Dictionary with project lock requirement
56
+ """
57
+ return {"project": request.project_dir}
58
+
59
+ def get_starting_state(self) -> "DaemonState":
60
+ """Get the daemon state when operation starts."""
61
+ from fbuild.daemon.messages import DaemonState
62
+
63
+ return DaemonState.BUILDING
64
+
65
+ def get_starting_message(self, request: "InstallDependenciesRequest") -> str:
66
+ """Get the status message when operation starts."""
67
+ return f"Installing dependencies for {request.environment}"
68
+
69
+ def get_success_message(self, request: "InstallDependenciesRequest") -> str:
70
+ """Get the status message on success."""
71
+ return "Dependencies installed successfully"
72
+
73
+ def get_failure_message(self, request: "InstallDependenciesRequest") -> str:
74
+ """Get the status message on failure."""
75
+ return "Dependency installation failed"
76
+
77
+ def execute_operation(self, request: "InstallDependenciesRequest", context: "DaemonContext") -> bool:
78
+ """Execute the dependency installation operation.
79
+
80
+ This installs all build dependencies without compiling:
81
+ - Platform package
82
+ - Toolchain (compiler, linker, etc.)
83
+ - Framework (Arduino core, etc.)
84
+ - Library dependencies from lib_deps
85
+
86
+ Args:
87
+ request: The install dependencies request
88
+ context: The daemon context with all subsystems
89
+
90
+ Returns:
91
+ True if all dependencies installed successfully, False otherwise
92
+ """
93
+ logging.info(f"Installing dependencies for project: {request.project_dir}")
94
+
95
+ # Reload build modules to pick up code changes
96
+ self._reload_build_modules()
97
+
98
+ # Detect platform type from platformio.ini
99
+ try:
100
+ from fbuild.config.ini_parser import PlatformIOConfig
101
+
102
+ project_path = Path(request.project_dir)
103
+ ini_path = project_path / "platformio.ini"
104
+
105
+ if not ini_path.exists():
106
+ logging.error(f"platformio.ini not found at {ini_path}")
107
+ return False
108
+
109
+ config = PlatformIOConfig(ini_path)
110
+ env_config = config.get_env_config(request.environment)
111
+ platform = env_config.get("platform", "").lower()
112
+ board_id = env_config.get("board", "")
113
+ lib_deps = config.get_lib_deps(request.environment)
114
+
115
+ logging.info(f"Detected platform: {platform}, board: {board_id}")
116
+
117
+ except KeyboardInterrupt as ke:
118
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
119
+
120
+ handle_keyboard_interrupt_properly(ke)
121
+ raise
122
+ except Exception as e:
123
+ logging.error(f"Failed to parse platformio.ini: {e}")
124
+ return False
125
+
126
+ # Normalize platform name
127
+ platform_name = self._normalize_platform_name(platform)
128
+ logging.info(f"Normalized platform: {platform_name}")
129
+
130
+ # Install dependencies based on platform
131
+ try:
132
+ from fbuild.packages.cache import Cache
133
+
134
+ cache = Cache(project_dir=Path(request.project_dir))
135
+
136
+ if platform_name == "espressif32":
137
+ return self._install_esp32_dependencies(cache, env_config, board_id, lib_deps, project_path, request.verbose)
138
+ elif platform_name == "atmelavr":
139
+ return self._install_avr_dependencies(cache, env_config, request.verbose)
140
+ elif platform_name == "raspberrypi":
141
+ return self._install_rp2040_dependencies(cache, env_config, request.verbose)
142
+ elif platform_name == "ststm32":
143
+ return self._install_stm32_dependencies(cache, env_config, request.verbose)
144
+ else:
145
+ logging.error(f"Unsupported platform: {platform_name}")
146
+ return False
147
+
148
+ except KeyboardInterrupt as ke:
149
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
150
+
151
+ handle_keyboard_interrupt_properly(ke)
152
+ raise
153
+ except Exception as e:
154
+ logging.error(f"Failed to install dependencies: {e}")
155
+ import traceback
156
+
157
+ logging.error(f"Traceback:\n{traceback.format_exc()}")
158
+ return False
159
+
160
+ def _normalize_platform_name(self, platform: str) -> str:
161
+ """Normalize platform name from various formats.
162
+
163
+ Args:
164
+ platform: Raw platform string from platformio.ini
165
+
166
+ Returns:
167
+ Normalized platform name
168
+ """
169
+ if "platform-espressif32" in platform or "platformio/espressif32" in platform or platform == "espressif32":
170
+ return "espressif32"
171
+ elif "platform-atmelavr" in platform or "platformio/atmelavr" in platform or platform == "atmelavr":
172
+ return "atmelavr"
173
+ elif "platform-raspberrypi" in platform or "platformio/raspberrypi" in platform or platform == "raspberrypi":
174
+ return "raspberrypi"
175
+ elif "platform-ststm32" in platform or "platformio/ststm32" in platform or platform == "ststm32":
176
+ return "ststm32"
177
+ return platform
178
+
179
+ def _install_esp32_dependencies(
180
+ self,
181
+ cache: "Cache",
182
+ env_config: dict,
183
+ board_id: str,
184
+ lib_deps: list[str],
185
+ project_dir: Path,
186
+ verbose: bool,
187
+ ) -> bool:
188
+ """Install ESP32 platform dependencies.
189
+
190
+ Args:
191
+ cache: Cache instance for package management
192
+ env_config: Environment configuration from platformio.ini
193
+ board_id: Board identifier
194
+ lib_deps: Library dependencies
195
+ project_dir: Project directory
196
+ verbose: Enable verbose output
197
+
198
+ Returns:
199
+ True if successful, False otherwise
200
+ """
201
+ from fbuild.packages.framework_esp32 import FrameworkESP32
202
+ from fbuild.packages.library_manager_esp32 import LibraryManagerESP32
203
+ from fbuild.packages.platform_esp32 import PlatformESP32
204
+ from fbuild.packages.toolchain_esp32 import ToolchainESP32
205
+
206
+ # Get platform URL
207
+ platform_url = env_config.get("platform")
208
+ if not platform_url:
209
+ logging.error("No platform URL specified in platformio.ini")
210
+ return False
211
+
212
+ # Resolve platform shorthand to actual URL
213
+ platform_url = self._resolve_platform_url(platform_url)
214
+
215
+ # 1. Initialize platform
216
+ logging.info("Installing ESP32 platform...")
217
+ platform = PlatformESP32(cache, platform_url, show_progress=True)
218
+ platform.ensure_platform()
219
+ logging.info(f"Platform installed: version {platform.version}")
220
+
221
+ # Get board configuration
222
+ board_json = platform.get_board_json(board_id)
223
+ mcu = board_json.get("build", {}).get("mcu", "esp32c6")
224
+
225
+ # Get required packages (returns Dict[str, str] of package_name -> url)
226
+ packages = platform.get_required_packages(mcu)
227
+
228
+ # 2. Initialize toolchain
229
+ logging.info("Installing ESP32 toolchain...")
230
+ # Determine toolchain type based on MCU
231
+ toolchain_type: str | None = None
232
+ toolchain_url: str | None = None
233
+ if "toolchain-riscv32-esp" in packages:
234
+ toolchain_url = packages["toolchain-riscv32-esp"]
235
+ toolchain_type = "riscv32-esp"
236
+ elif "toolchain-xtensa-esp-elf" in packages:
237
+ toolchain_url = packages["toolchain-xtensa-esp-elf"]
238
+ toolchain_type = "xtensa-esp-elf"
239
+
240
+ if toolchain_url and toolchain_type:
241
+ toolchain = ToolchainESP32(cache, toolchain_url, toolchain_type, show_progress=True)
242
+ toolchain.ensure_toolchain()
243
+ logging.info(f"Toolchain installed: version {toolchain.version}")
244
+ else:
245
+ logging.warning("No toolchain package found for MCU")
246
+
247
+ # 3. Initialize framework
248
+ logging.info("Installing Arduino framework...")
249
+ framework_url = packages.get("framework-arduinoespressif32")
250
+ libs_url = packages.get("framework-arduinoespressif32-libs")
251
+ if framework_url and libs_url:
252
+ # Check for MCU-specific skeleton library
253
+ mcu_suffix = mcu.replace("esp32", "")
254
+ skeleton_lib_name = f"framework-arduino-{mcu_suffix}-skeleton-lib"
255
+ skeleton_lib_url = packages.get(skeleton_lib_name)
256
+ framework = FrameworkESP32(cache, framework_url, libs_url, skeleton_lib_url=skeleton_lib_url, show_progress=True)
257
+ framework.ensure_framework()
258
+ logging.info(f"Framework installed: version {framework.version}")
259
+ else:
260
+ logging.warning("No framework package found or missing libs URL")
261
+
262
+ # 4. Install library dependencies
263
+ if lib_deps:
264
+ logging.info(f"Installing {len(lib_deps)} library dependencies...")
265
+ from fbuild.packages.platformio_registry import LibrarySpec
266
+
267
+ build_dir = cache.project_dir / ".fbuild" / "build" / board_id
268
+ lib_manager = LibraryManagerESP32(build_dir, project_dir=project_dir)
269
+ for lib_dep in lib_deps:
270
+ logging.info(f" Installing: {lib_dep}")
271
+ try:
272
+ spec = LibrarySpec.parse(lib_dep)
273
+ lib_manager.download_library(spec, show_progress=True)
274
+ except KeyboardInterrupt as ke:
275
+ from fbuild.interrupt_utils import (
276
+ handle_keyboard_interrupt_properly,
277
+ )
278
+
279
+ handle_keyboard_interrupt_properly(ke)
280
+ raise
281
+ except Exception as e:
282
+ logging.error(f"Failed to install library {lib_dep}: {e}")
283
+ return False
284
+ logging.info("All libraries installed successfully")
285
+ else:
286
+ logging.info("No library dependencies to install")
287
+
288
+ return True
289
+
290
+ def _install_avr_dependencies(
291
+ self,
292
+ cache: "Cache",
293
+ env_config: dict,
294
+ verbose: bool,
295
+ ) -> bool:
296
+ """Install AVR platform dependencies.
297
+
298
+ Args:
299
+ cache: Cache instance
300
+ env_config: Environment configuration
301
+ verbose: Enable verbose output
302
+
303
+ Returns:
304
+ True if successful, False otherwise
305
+ """
306
+ # AVR platform support - minimal implementation
307
+ # TODO: Implement full AVR dependency installation
308
+ logging.info("AVR dependency installation not yet fully implemented")
309
+ logging.info("AVR builds will install dependencies on first build")
310
+ return True
311
+
312
+ def _install_rp2040_dependencies(
313
+ self,
314
+ cache: "Cache",
315
+ env_config: dict,
316
+ verbose: bool,
317
+ ) -> bool:
318
+ """Install RP2040 platform dependencies.
319
+
320
+ Args:
321
+ cache: Cache instance
322
+ env_config: Environment configuration
323
+ verbose: Enable verbose output
324
+
325
+ Returns:
326
+ True if successful, False otherwise
327
+ """
328
+ # RP2040 platform support - minimal implementation
329
+ # TODO: Implement full RP2040 dependency installation
330
+ logging.info("RP2040 dependency installation not yet fully implemented")
331
+ logging.info("RP2040 builds will install dependencies on first build")
332
+ return True
333
+
334
+ def _install_stm32_dependencies(
335
+ self,
336
+ cache: "Cache",
337
+ env_config: dict,
338
+ verbose: bool,
339
+ ) -> bool:
340
+ """Install STM32 platform dependencies.
341
+
342
+ Args:
343
+ cache: Cache instance
344
+ env_config: Environment configuration
345
+ verbose: Enable verbose output
346
+
347
+ Returns:
348
+ True if successful, False otherwise
349
+ """
350
+ # STM32 platform support - minimal implementation
351
+ # TODO: Implement full STM32 dependency installation
352
+ logging.info("STM32 dependency installation not yet fully implemented")
353
+ logging.info("STM32 builds will install dependencies on first build")
354
+ return True
355
+
356
+ def _resolve_platform_url(self, platform_url: str) -> str:
357
+ """Resolve platform shorthand to actual download URL.
358
+
359
+ Args:
360
+ platform_url: Platform URL or shorthand from platformio.ini
361
+
362
+ Returns:
363
+ Resolved platform URL
364
+ """
365
+ # If it's already a full URL, return as-is
366
+ if platform_url.startswith("http://") or platform_url.startswith("https://"):
367
+ return platform_url
368
+
369
+ # Handle PlatformIO shorthand formats - use default GitHub URLs
370
+ # PlatformIO registry doesn't provide platform URLs, so we use known defaults
371
+ platform_defaults = {
372
+ "espressif32": "https://github.com/platformio/platform-espressif32.git",
373
+ "atmelavr": "https://github.com/platformio/platform-atmelavr.git",
374
+ "raspberrypi": "https://github.com/platformio/platform-raspberrypi.git",
375
+ "ststm32": "https://github.com/platformio/platform-ststm32.git",
376
+ }
377
+
378
+ # Normalize the platform name
379
+ normalized = platform_url.lower()
380
+ if normalized.startswith("platformio/"):
381
+ normalized = normalized.replace("platformio/", "")
382
+
383
+ if normalized in platform_defaults:
384
+ return platform_defaults[normalized]
385
+
386
+ # If we can't resolve, return as-is and let the caller handle the error
387
+ return platform_url
388
+
389
+ def _reload_build_modules(self) -> None:
390
+ """Reload build-related modules to pick up code changes.
391
+
392
+ This is critical for development on Windows where daemon caching prevents
393
+ testing code changes.
394
+ """
395
+ modules_to_reload = [
396
+ # Package modules (reload first - no dependencies)
397
+ "fbuild.packages.cache",
398
+ "fbuild.packages.downloader",
399
+ "fbuild.packages.archive_utils",
400
+ "fbuild.packages.platformio_registry",
401
+ "fbuild.packages.toolchain",
402
+ "fbuild.packages.toolchain_esp32",
403
+ "fbuild.packages.arduino_core",
404
+ "fbuild.packages.framework_esp32",
405
+ "fbuild.packages.platform_esp32",
406
+ "fbuild.packages.library_manager",
407
+ "fbuild.packages.library_manager_esp32",
408
+ # Config system
409
+ "fbuild.config.ini_parser",
410
+ "fbuild.config.board_config",
411
+ "fbuild.config.board_loader",
412
+ ]
413
+
414
+ reloaded_count = 0
415
+ for module_name in modules_to_reload:
416
+ try:
417
+ if module_name in sys.modules:
418
+ importlib.reload(sys.modules[module_name])
419
+ reloaded_count += 1
420
+ else:
421
+ __import__(module_name)
422
+ reloaded_count += 1
423
+ except KeyboardInterrupt as ke:
424
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
425
+
426
+ handle_keyboard_interrupt_properly(ke)
427
+ except Exception as e:
428
+ logging.warning(f"Failed to reload/import module {module_name}: {e}")
429
+
430
+ if reloaded_count > 0:
431
+ logging.info(f"Loaded/reloaded {reloaded_count} package modules")