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,436 @@
1
+ """RP2040/RP2350 Toolchain Management.
2
+
3
+ This module handles downloading, extracting, and managing ARM GCC toolchain
4
+ needed for Raspberry Pi Pico (RP2040) and Pico 2 (RP2350) builds.
5
+
6
+ Toolchain Download Process:
7
+ 1. Download ARM GCC toolchain for ARM Cortex-M
8
+ 2. Extract to cache directory
9
+ 3. Provide access to compiler binaries
10
+
11
+ Toolchain Structure (after extraction):
12
+ arm-none-eabi/
13
+ ├── bin/
14
+ │ ├── arm-none-eabi-gcc.exe
15
+ │ ├── arm-none-eabi-g++.exe
16
+ │ ├── arm-none-eabi-ar.exe
17
+ │ ├── arm-none-eabi-objcopy.exe
18
+ │ └── ...
19
+ ├── lib/
20
+ └── include/
21
+
22
+ Supported Boards:
23
+ - Raspberry Pi Pico (RP2040, ARM Cortex-M0+ @ 133MHz)
24
+ - Raspberry Pi Pico W (RP2040, ARM Cortex-M0+ @ 133MHz)
25
+ - Raspberry Pi Pico 2 (RP2350, ARM Cortex-M33 @ 150MHz)
26
+ - Raspberry Pi Pico 2 W (RP2350, ARM Cortex-M33 @ 150MHz)
27
+ """
28
+
29
+ import platform
30
+ from pathlib import Path
31
+ from typing import Any, Dict, Optional
32
+
33
+ from .cache import Cache
34
+ from .downloader import DownloadError, ExtractionError, PackageDownloader
35
+ from .package import IToolchain, PackageError
36
+
37
+
38
+ class ToolchainErrorRP2040(PackageError):
39
+ """Raised when RP2040/RP2350 toolchain operations fail."""
40
+
41
+ pass
42
+
43
+
44
+ class ToolchainRP2040(IToolchain):
45
+ """Manages RP2040/RP2350 toolchain download, extraction, and access.
46
+
47
+ This class handles downloading and managing ARM GCC toolchain for RP2040/RP2350:
48
+ - ARM GCC for RP2040 (ARM Cortex-M0+)
49
+ - ARM GCC for RP2350 (ARM Cortex-M33)
50
+ """
51
+
52
+ # Binary prefix for ARM GCC toolchain
53
+ BINARY_PREFIX = "arm-none-eabi-"
54
+
55
+ # Toolchain download URLs for different platforms
56
+ # Source: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
57
+ # Version 15.2.Rel1 (released December 17, 2025)
58
+ TOOLCHAIN_URLS = {
59
+ "Windows": "https://developer.arm.com/-/media/Files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-mingw-w64-x86_64-arm-none-eabi.zip",
60
+ "Linux": "https://developer.arm.com/-/media/Files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-eabi.tar.xz",
61
+ "Darwin": "https://developer.arm.com/-/media/Files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-darwin-x86_64-arm-none-eabi.tar.xz",
62
+ }
63
+
64
+ def __init__(
65
+ self,
66
+ cache: Cache,
67
+ show_progress: bool = True,
68
+ ):
69
+ """Initialize RP2040/RP2350 toolchain manager.
70
+
71
+ Args:
72
+ cache: Cache manager instance
73
+ show_progress: Whether to show download/extraction progress
74
+ """
75
+ self.cache = cache
76
+ self.show_progress = show_progress
77
+ self.downloader = PackageDownloader()
78
+
79
+ # Get platform-specific toolchain URL
80
+ self.toolchain_url = self._get_platform_toolchain_url()
81
+
82
+ # Extract version from URL
83
+ self.version = self._extract_version_from_url(self.toolchain_url)
84
+
85
+ # Get toolchain path from cache
86
+ self.toolchain_path = cache.get_toolchain_path(self.toolchain_url, self.version)
87
+
88
+ @staticmethod
89
+ def _get_platform_toolchain_url() -> str:
90
+ """Get toolchain URL for the current platform.
91
+
92
+ Returns:
93
+ URL to platform-specific toolchain archive
94
+
95
+ Raises:
96
+ ToolchainErrorRP2040: If platform is not supported
97
+ """
98
+ system = platform.system()
99
+ if system not in ToolchainRP2040.TOOLCHAIN_URLS:
100
+ raise ToolchainErrorRP2040(f"Unsupported platform: {system}. " + f"Supported platforms: {', '.join(ToolchainRP2040.TOOLCHAIN_URLS.keys())}")
101
+ return ToolchainRP2040.TOOLCHAIN_URLS[system]
102
+
103
+ @staticmethod
104
+ def _extract_version_from_url(url: str) -> str:
105
+ """Extract version string from toolchain URL.
106
+
107
+ Args:
108
+ url: Toolchain URL
109
+
110
+ Returns:
111
+ Version string (e.g., "15.2.rel1")
112
+ """
113
+ # Extract version from URL pattern: .../15.2.rel1/...
114
+ parts = url.split("/")
115
+ for part in parts:
116
+ if "rel" in part.lower() and part[0].isdigit():
117
+ return part
118
+
119
+ # Fallback: use URL hash if version extraction fails
120
+ from .cache import Cache
121
+
122
+ return Cache.hash_url(url)[:8]
123
+
124
+ def ensure_toolchain(self) -> Path:
125
+ """Ensure toolchain is downloaded and extracted.
126
+
127
+ Returns:
128
+ Path to the extracted toolchain directory
129
+
130
+ Raises:
131
+ ToolchainErrorRP2040: If download or extraction fails
132
+ """
133
+ if self.is_installed():
134
+ if self.show_progress:
135
+ print(f"Using cached ARM GCC toolchain {self.version}")
136
+ return self.toolchain_path
137
+
138
+ try:
139
+ if self.show_progress:
140
+ print(f"Downloading ARM GCC toolchain {self.version}...")
141
+
142
+ # Download and extract toolchain package
143
+ self.cache.ensure_directories()
144
+
145
+ # Use downloader to handle download and extraction
146
+ archive_name = Path(self.toolchain_url).name
147
+ toolchain_cache_dir = self.toolchain_path.parent / "bin"
148
+ toolchain_cache_dir.mkdir(parents=True, exist_ok=True)
149
+ archive_path = toolchain_cache_dir / archive_name
150
+
151
+ # Download if not cached
152
+ if not archive_path.exists():
153
+ self.downloader.download(self.toolchain_url, archive_path, show_progress=self.show_progress)
154
+ else:
155
+ if self.show_progress:
156
+ print("Using cached toolchain archive")
157
+
158
+ # Extract to toolchain directory
159
+ if self.show_progress:
160
+ print("Extracting toolchain...")
161
+
162
+ # Create temp extraction directory
163
+ temp_extract = toolchain_cache_dir / "temp_extract"
164
+ temp_extract.mkdir(parents=True, exist_ok=True)
165
+
166
+ self.downloader.extract_archive(archive_path, temp_extract, show_progress=self.show_progress)
167
+
168
+ # Find the toolchain directory in the extracted content
169
+ # Usually it's a subdirectory like "arm-gnu-toolchain-15.2.rel1-..."
170
+ extracted_dirs = list(temp_extract.glob("arm-gnu-toolchain-*"))
171
+ if not extracted_dirs:
172
+ # Maybe it extracted directly
173
+ extracted_dirs = [temp_extract]
174
+
175
+ source_dir = extracted_dirs[0]
176
+
177
+ # Move to final location (toolchain_path/bin)
178
+ final_bin_path = toolchain_cache_dir
179
+ if final_bin_path.exists() and final_bin_path != temp_extract:
180
+ # Remove old installation
181
+ import shutil
182
+
183
+ for item in final_bin_path.iterdir():
184
+ if item.name != "temp_extract" and not item.name.endswith((".zip", ".tar", ".xz", ".gz")):
185
+ if item.is_dir():
186
+ shutil.rmtree(item)
187
+ else:
188
+ item.unlink()
189
+
190
+ # Copy contents from source_dir to final_bin_path
191
+ import shutil
192
+
193
+ for item in source_dir.iterdir():
194
+ dest = final_bin_path / item.name
195
+ if item.is_dir():
196
+ if dest.exists():
197
+ shutil.rmtree(dest)
198
+ shutil.copytree(item, dest)
199
+ else:
200
+ if dest.exists():
201
+ dest.unlink()
202
+ shutil.copy2(item, dest)
203
+
204
+ # Clean up temp directory
205
+ if temp_extract.exists():
206
+ import shutil
207
+
208
+ shutil.rmtree(temp_extract, ignore_errors=True)
209
+
210
+ # Update toolchain_path to point to the actual installation location
211
+ self.toolchain_path = toolchain_cache_dir
212
+
213
+ if self.show_progress:
214
+ print("ARM GCC toolchain installed successfully")
215
+
216
+ return self.toolchain_path
217
+
218
+ except (DownloadError, ExtractionError) as e:
219
+ raise ToolchainErrorRP2040(f"Failed to install ARM GCC toolchain: {e}")
220
+ except KeyboardInterrupt as ke:
221
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
222
+
223
+ handle_keyboard_interrupt_properly(ke)
224
+ raise # Never reached, but satisfies type checker
225
+ except Exception as e:
226
+ raise ToolchainErrorRP2040(f"Unexpected error installing toolchain: {e}")
227
+
228
+ def is_installed(self) -> bool:
229
+ """Check if toolchain is already installed.
230
+
231
+ Returns:
232
+ True if toolchain directory exists with key binaries
233
+ """
234
+ if not self.toolchain_path.exists():
235
+ return False
236
+
237
+ # Verify essential toolchain binaries exist
238
+ gcc_path = self._find_binary("gcc")
239
+ return gcc_path is not None and gcc_path.exists()
240
+
241
+ def get_bin_dir(self) -> Optional[Path]:
242
+ """Get path to toolchain bin directory.
243
+
244
+ Returns:
245
+ Path to bin directory containing compiler binaries, or None if not found
246
+ """
247
+ # Check common bin directory locations
248
+ possible_paths = [
249
+ self.toolchain_path / "bin" / "bin", # Nested bin
250
+ self.toolchain_path / "bin", # Direct bin
251
+ ]
252
+
253
+ for path in possible_paths:
254
+ if path.exists() and path.is_dir():
255
+ # Verify it contains toolchain binaries
256
+ if list(path.glob(f"{self.BINARY_PREFIX}gcc*")):
257
+ return path
258
+
259
+ return None
260
+
261
+ def _find_binary(self, binary_name: str) -> Optional[Path]:
262
+ """Find a binary in the toolchain bin directory.
263
+
264
+ Args:
265
+ binary_name: Name of the binary (e.g., "gcc", "g++")
266
+
267
+ Returns:
268
+ Path to binary or None if not found
269
+ """
270
+ bin_dir = self.get_bin_dir()
271
+ if not bin_dir:
272
+ return None
273
+
274
+ # Try with and without .exe extension
275
+ binary_path = bin_dir / f"{self.BINARY_PREFIX}{binary_name}"
276
+ if binary_path.exists():
277
+ return binary_path
278
+
279
+ binary_path_exe = bin_dir / f"{self.BINARY_PREFIX}{binary_name}.exe"
280
+ if binary_path_exe.exists():
281
+ return binary_path_exe
282
+
283
+ return None
284
+
285
+ def get_gcc_path(self) -> Optional[Path]:
286
+ """Get path to GCC compiler.
287
+
288
+ Returns:
289
+ Path to gcc binary or None if not found
290
+ """
291
+ return self._find_binary("gcc")
292
+
293
+ def get_gxx_path(self) -> Optional[Path]:
294
+ """Get path to G++ compiler.
295
+
296
+ Returns:
297
+ Path to g++ binary or None if not found
298
+ """
299
+ return self._find_binary("g++")
300
+
301
+ def get_ar_path(self) -> Optional[Path]:
302
+ """Get path to archiver (ar).
303
+
304
+ Returns:
305
+ Path to ar binary or None if not found
306
+ """
307
+ return self._find_binary("ar")
308
+
309
+ def get_objcopy_path(self) -> Optional[Path]:
310
+ """Get path to objcopy utility.
311
+
312
+ Returns:
313
+ Path to objcopy binary or None if not found
314
+ """
315
+ return self._find_binary("objcopy")
316
+
317
+ def get_size_path(self) -> Optional[Path]:
318
+ """Get path to size utility.
319
+
320
+ Returns:
321
+ Path to size binary or None if not found
322
+ """
323
+ return self._find_binary("size")
324
+
325
+ def get_objdump_path(self) -> Optional[Path]:
326
+ """Get path to objdump utility.
327
+
328
+ Returns:
329
+ Path to objdump binary or None if not found
330
+ """
331
+ return self._find_binary("objdump")
332
+
333
+ def get_elf2uf2_path(self) -> Optional[Path]:
334
+ """Get path to elf2uf2 utility (if available).
335
+
336
+ Note: elf2uf2 is typically provided by the Pico SDK, not the toolchain.
337
+ This method is here for API compatibility but will likely return None.
338
+
339
+ Returns:
340
+ Path to elf2uf2 binary or None if not found
341
+ """
342
+ # Check if elf2uf2 exists in the toolchain bin directory
343
+ bin_dir = self.get_bin_dir()
344
+ if not bin_dir:
345
+ return None
346
+
347
+ # Try with and without .exe extension
348
+ elf2uf2_path = bin_dir / "elf2uf2"
349
+ if elf2uf2_path.exists():
350
+ return elf2uf2_path
351
+
352
+ elf2uf2_path_exe = bin_dir / "elf2uf2.exe"
353
+ if elf2uf2_path_exe.exists():
354
+ return elf2uf2_path_exe
355
+
356
+ return None
357
+
358
+ def get_all_tool_paths(self) -> Dict[str, Optional[Path]]:
359
+ """Get paths to all common toolchain binaries.
360
+
361
+ Returns:
362
+ Dictionary mapping tool names to their paths
363
+ """
364
+ return {
365
+ "gcc": self.get_gcc_path(),
366
+ "g++": self.get_gxx_path(),
367
+ "ar": self.get_ar_path(),
368
+ "objcopy": self.get_objcopy_path(),
369
+ "size": self.get_size_path(),
370
+ "objdump": self.get_objdump_path(),
371
+ "elf2uf2": self.get_elf2uf2_path(),
372
+ }
373
+
374
+ def get_all_tools(self) -> Dict[str, Path]:
375
+ """Get paths to all required tools (IToolchain interface).
376
+
377
+ Returns:
378
+ Dictionary mapping tool names to their paths (non-None only)
379
+
380
+ Raises:
381
+ ToolchainErrorRP2040: If any required tool is not found
382
+ """
383
+ all_tools = self.get_all_tool_paths()
384
+
385
+ # Filter out None values and verify all required tools exist
386
+ # Note: elf2uf2 is optional, as it may be provided by the framework
387
+ required_tools = ["gcc", "g++", "ar", "objcopy", "size"]
388
+ result = {}
389
+ for name, path in all_tools.items():
390
+ if name in required_tools and path is None:
391
+ raise ToolchainErrorRP2040(f"Required tool '{name}' not found in toolchain")
392
+ if path is not None:
393
+ result[name] = path
394
+
395
+ return result
396
+
397
+ def get_toolchain_info(self) -> Dict[str, Any]:
398
+ """Get information about the installed toolchain.
399
+
400
+ Returns:
401
+ Dictionary with toolchain information
402
+ """
403
+ info = {
404
+ "type": "arm-none-eabi",
405
+ "version": self.version,
406
+ "path": str(self.toolchain_path),
407
+ "url": self.toolchain_url,
408
+ "installed": self.is_installed(),
409
+ "binary_prefix": self.BINARY_PREFIX,
410
+ }
411
+
412
+ if self.is_installed():
413
+ info["bin_dir"] = str(self.get_bin_dir())
414
+ info["tools"] = {name: str(path) if path else None for name, path in self.get_all_tool_paths().items()}
415
+
416
+ return info
417
+
418
+ # Implement IPackage interface
419
+ def ensure_package(self) -> Path:
420
+ """Ensure package is downloaded and extracted.
421
+
422
+ Returns:
423
+ Path to the extracted package directory
424
+
425
+ Raises:
426
+ PackageError: If download or extraction fails
427
+ """
428
+ return self.ensure_toolchain()
429
+
430
+ def get_package_info(self) -> Dict[str, Any]:
431
+ """Get information about the package.
432
+
433
+ Returns:
434
+ Dictionary with package metadata (version, path, etc.)
435
+ """
436
+ return self.get_toolchain_info()