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
fbuild/cli_utils.py ADDED
@@ -0,0 +1,314 @@
1
+ """CLI utility functions for fbuild.
2
+
3
+ This module provides common utilities used across CLI commands including:
4
+ - Environment detection from platformio.ini
5
+ - Error handling and formatting
6
+ - Monitor argument parsing
7
+ """
8
+
9
+ import _thread
10
+ import shlex
11
+ import sys
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ from fbuild.config import PlatformIOConfig
17
+
18
+
19
+ def safe_print(text: str, end: str = "\n") -> None:
20
+ """Print text safely handling encoding errors on Windows.
21
+
22
+ Args:
23
+ text: Text to print
24
+ end: End character (default newline)
25
+ """
26
+ try:
27
+ print(text, end=end)
28
+ sys.stdout.flush()
29
+ except KeyboardInterrupt:
30
+ _thread.interrupt_main()
31
+ except (UnicodeEncodeError, AttributeError):
32
+ # Fallback for Windows console that doesn't support UTF-8
33
+ # Replace Unicode checkmarks and X marks with ASCII equivalents
34
+ safe_text = text.replace("✓", "[OK]").replace("✗", "[X]")
35
+ try:
36
+ print(safe_text, end=end)
37
+ sys.stdout.flush()
38
+ except KeyboardInterrupt:
39
+ _thread.interrupt_main()
40
+ except Exception:
41
+ # Last resort: write bytes directly
42
+ try:
43
+ output = (safe_text + end).encode("ascii", errors="replace")
44
+ sys.stdout.buffer.write(output)
45
+ sys.stdout.buffer.flush()
46
+ except KeyboardInterrupt:
47
+ _thread.interrupt_main()
48
+ except Exception:
49
+ # If all else fails, just skip the output
50
+ pass
51
+
52
+
53
+ @dataclass
54
+ class MonitorFlags:
55
+ """Parsed monitor flags from command-line string."""
56
+
57
+ timeout: Optional[int] = None
58
+ halt_on_error: Optional[str] = None
59
+ halt_on_success: Optional[str] = None
60
+ expect: Optional[str] = None
61
+ baud: int = 115200
62
+ timestamp: bool = True
63
+
64
+
65
+ class EnvironmentDetector:
66
+ """Handles environment detection from platformio.ini."""
67
+
68
+ @staticmethod
69
+ def detect_environment(project_dir: Path, env_name: Optional[str] = None) -> str:
70
+ """Detect or validate environment name from platformio.ini.
71
+
72
+ Args:
73
+ project_dir: Project directory containing platformio.ini
74
+ env_name: Optional explicit environment name
75
+
76
+ Returns:
77
+ Environment name to use
78
+
79
+ Raises:
80
+ FileNotFoundError: If platformio.ini doesn't exist
81
+ ValueError: If no environments found in platformio.ini
82
+ """
83
+ if env_name:
84
+ return env_name
85
+
86
+ # Auto-detect environment from platformio.ini
87
+ ini_path = project_dir / "platformio.ini"
88
+ if not ini_path.exists():
89
+ raise FileNotFoundError(f"platformio.ini not found in {project_dir}")
90
+
91
+ config = PlatformIOConfig(ini_path)
92
+ detected_env = config.get_default_environment()
93
+
94
+ if not detected_env:
95
+ raise ValueError("No environments found in platformio.ini")
96
+
97
+ return detected_env
98
+
99
+
100
+ class MonitorFlagParser:
101
+ """Parses monitor flags from command-line strings."""
102
+
103
+ @staticmethod
104
+ def parse_monitor_flags(flags_string: str) -> MonitorFlags:
105
+ """Parse monitor flags from a command-line string.
106
+
107
+ Args:
108
+ flags_string: String containing monitor flags
109
+ (e.g., "--timeout 60 --halt-on-success 'TEST PASSED' --no-timestamp")
110
+
111
+ Returns:
112
+ MonitorFlags object with parsed values
113
+ """
114
+ flags = MonitorFlags()
115
+ monitor_args = shlex.split(flags_string)
116
+
117
+ i = 0
118
+ while i < len(monitor_args):
119
+ arg = monitor_args[i]
120
+ if arg == "--timeout" and i + 1 < len(monitor_args):
121
+ flags.timeout = int(monitor_args[i + 1])
122
+ i += 2
123
+ elif arg == "--halt-on-error" and i + 1 < len(monitor_args):
124
+ flags.halt_on_error = monitor_args[i + 1]
125
+ i += 2
126
+ elif arg == "--halt-on-success" and i + 1 < len(monitor_args):
127
+ flags.halt_on_success = monitor_args[i + 1]
128
+ i += 2
129
+ elif arg == "--expect" and i + 1 < len(monitor_args):
130
+ flags.expect = monitor_args[i + 1]
131
+ i += 2
132
+ elif arg == "--baud" and i + 1 < len(monitor_args):
133
+ flags.baud = int(monitor_args[i + 1])
134
+ i += 2
135
+ elif arg == "--no-timestamp":
136
+ flags.timestamp = False
137
+ i += 1
138
+ else:
139
+ i += 1
140
+
141
+ return flags
142
+
143
+
144
+ class ErrorFormatter:
145
+ """Formats and displays error messages with ANSI color codes."""
146
+
147
+ # ANSI color codes
148
+ RED = "\033[1;31m"
149
+ GREEN = "\033[1;32m"
150
+ YELLOW = "\033[1;33m"
151
+ RESET = "\033[0m"
152
+
153
+ @staticmethod
154
+ def print_error(title: str, message: str, verbose: bool = False) -> None:
155
+ """Print formatted error message.
156
+
157
+ Args:
158
+ title: Error title (e.g., "File not found", "Build failed")
159
+ message: Error message details
160
+ verbose: Whether to print verbose output (e.g., traceback)
161
+ """
162
+ print()
163
+ print(f"{ErrorFormatter.RED}✗ {title}{ErrorFormatter.RESET}")
164
+ print()
165
+ print(message)
166
+ print()
167
+
168
+ @staticmethod
169
+ def print_success(message: str) -> None:
170
+ """Print formatted success message.
171
+
172
+ Args:
173
+ message: Success message
174
+ """
175
+ print()
176
+ print(f"{ErrorFormatter.GREEN}✓ {message}{ErrorFormatter.RESET}")
177
+
178
+ @staticmethod
179
+ def print_warning(message: str) -> None:
180
+ """Print formatted warning message.
181
+
182
+ Args:
183
+ message: Warning message
184
+ """
185
+ print()
186
+ print(f"{ErrorFormatter.YELLOW}✗ {message}{ErrorFormatter.RESET}")
187
+
188
+ @staticmethod
189
+ def handle_file_not_found(error: FileNotFoundError) -> None:
190
+ """Handle FileNotFoundError with standard formatting.
191
+
192
+ Args:
193
+ error: The FileNotFoundError to handle
194
+ """
195
+ ErrorFormatter.print_error("Error: File not found", str(error))
196
+ print("Make sure you're in a fbuild project directory with a platformio.ini file.")
197
+ sys.exit(1)
198
+
199
+ @staticmethod
200
+ def handle_permission_error(error: PermissionError) -> None:
201
+ """Handle PermissionError with standard formatting.
202
+
203
+ Args:
204
+ error: The PermissionError to handle
205
+ """
206
+ ErrorFormatter.print_error("Error: Permission denied", str(error))
207
+ sys.exit(1)
208
+
209
+ @staticmethod
210
+ def handle_keyboard_interrupt() -> None:
211
+ """Handle KeyboardInterrupt with standard formatting."""
212
+ ErrorFormatter.print_warning("Build interrupted")
213
+ sys.exit(130) # Standard exit code for SIGINT
214
+
215
+ @staticmethod
216
+ def handle_unexpected_error(error: Exception, verbose: bool = False) -> None:
217
+ """Handle unexpected errors with standard formatting.
218
+
219
+ Args:
220
+ error: The exception to handle
221
+ verbose: Whether to print traceback
222
+ """
223
+ message = f"{type(error).__name__}: {error}"
224
+ ErrorFormatter.print_error("Unexpected error", message)
225
+
226
+ if verbose:
227
+ import traceback
228
+
229
+ print("Traceback:")
230
+ print(traceback.format_exc())
231
+
232
+ sys.exit(1)
233
+
234
+
235
+ class BannerFormatter:
236
+ """Formats and displays banner messages with borders."""
237
+
238
+ DEFAULT_WIDTH = 80
239
+ DEFAULT_BORDER_CHAR = "="
240
+
241
+ @staticmethod
242
+ def format_banner(
243
+ message: str,
244
+ width: int = DEFAULT_WIDTH,
245
+ border_char: str = DEFAULT_BORDER_CHAR,
246
+ center: bool = True,
247
+ ) -> str:
248
+ """Format a banner message with top and bottom borders.
249
+
250
+ Args:
251
+ message: The message to display (can be multi-line)
252
+ width: Width of the banner in characters (default: 80)
253
+ border_char: Character to use for borders (default: "=")
254
+ center: Whether to center text (default: True)
255
+
256
+ Returns:
257
+ Formatted banner string with borders
258
+ """
259
+ lines = message.split("\n")
260
+ border = border_char * width
261
+ formatted_lines = [border]
262
+
263
+ for line in lines:
264
+ if center:
265
+ # Center the text
266
+ padding = (width - len(line)) // 2
267
+ formatted_line = " " * padding + line
268
+ else:
269
+ # Left-align with 2-space indent
270
+ formatted_line = " " + line
271
+
272
+ formatted_lines.append(formatted_line)
273
+
274
+ formatted_lines.append(border)
275
+ return "\n".join(formatted_lines)
276
+
277
+ @staticmethod
278
+ def print_banner(
279
+ message: str,
280
+ width: int = DEFAULT_WIDTH,
281
+ border_char: str = DEFAULT_BORDER_CHAR,
282
+ center: bool = True,
283
+ ) -> None:
284
+ """Print a banner message with top and bottom borders.
285
+
286
+ Args:
287
+ message: The message to display (can be multi-line)
288
+ width: Width of the banner in characters (default: 80)
289
+ border_char: Character to use for borders (default: "=")
290
+ center: Whether to center text (default: True)
291
+ """
292
+ print()
293
+ print(BannerFormatter.format_banner(message, width=width, border_char=border_char, center=center))
294
+
295
+
296
+ class PathValidator:
297
+ """Validates project paths and directories."""
298
+
299
+ @staticmethod
300
+ def validate_project_dir(project_dir: Path) -> None:
301
+ """Validate that project directory exists and is a directory.
302
+
303
+ Args:
304
+ project_dir: Path to validate
305
+
306
+ Raises:
307
+ SystemExit: If path doesn't exist or isn't a directory
308
+ """
309
+ if not project_dir.exists():
310
+ print(f"{ErrorFormatter.RED}✗ Error: Path does not exist: {project_dir}{ErrorFormatter.RESET}")
311
+ sys.exit(2)
312
+ if not project_dir.is_dir():
313
+ print(f"{ErrorFormatter.RED}✗ Error: Path is not a directory: {project_dir}{ErrorFormatter.RESET}")
314
+ sys.exit(2)
@@ -0,0 +1,16 @@
1
+ """Configuration parsing modules for fbuild."""
2
+
3
+ from .board_config import BoardConfig
4
+ from .board_loader import BoardConfigLoader
5
+ from .ini_parser import PlatformIOConfig
6
+ from .mcu_specs import MCUSpec, get_max_flash, get_max_ram, get_mcu_spec
7
+
8
+ __all__ = [
9
+ "PlatformIOConfig",
10
+ "BoardConfig",
11
+ "BoardConfigLoader",
12
+ "MCUSpec",
13
+ "get_mcu_spec",
14
+ "get_max_flash",
15
+ "get_max_ram",
16
+ ]