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