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