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.
- fbuild/__init__.py +390 -0
- fbuild/assets/example.txt +1 -0
- fbuild/build/__init__.py +117 -0
- fbuild/build/archive_creator.py +186 -0
- fbuild/build/binary_generator.py +444 -0
- fbuild/build/build_component_factory.py +131 -0
- fbuild/build/build_info_generator.py +624 -0
- fbuild/build/build_state.py +325 -0
- fbuild/build/build_utils.py +93 -0
- fbuild/build/compilation_executor.py +422 -0
- fbuild/build/compiler.py +165 -0
- fbuild/build/compiler_avr.py +574 -0
- fbuild/build/configurable_compiler.py +664 -0
- fbuild/build/configurable_linker.py +637 -0
- fbuild/build/flag_builder.py +214 -0
- fbuild/build/library_dependency_processor.py +185 -0
- fbuild/build/linker.py +708 -0
- fbuild/build/orchestrator.py +67 -0
- fbuild/build/orchestrator_avr.py +651 -0
- fbuild/build/orchestrator_esp32.py +878 -0
- fbuild/build/orchestrator_rp2040.py +719 -0
- fbuild/build/orchestrator_stm32.py +696 -0
- fbuild/build/orchestrator_teensy.py +580 -0
- fbuild/build/source_compilation_orchestrator.py +218 -0
- fbuild/build/source_scanner.py +516 -0
- fbuild/cli.py +717 -0
- fbuild/cli_utils.py +314 -0
- fbuild/config/__init__.py +16 -0
- fbuild/config/board_config.py +542 -0
- fbuild/config/board_loader.py +92 -0
- fbuild/config/ini_parser.py +369 -0
- fbuild/config/mcu_specs.py +88 -0
- fbuild/daemon/__init__.py +42 -0
- fbuild/daemon/async_client.py +531 -0
- fbuild/daemon/client.py +1505 -0
- fbuild/daemon/compilation_queue.py +293 -0
- fbuild/daemon/configuration_lock.py +865 -0
- fbuild/daemon/daemon.py +585 -0
- fbuild/daemon/daemon_context.py +293 -0
- fbuild/daemon/error_collector.py +263 -0
- fbuild/daemon/file_cache.py +332 -0
- fbuild/daemon/firmware_ledger.py +546 -0
- fbuild/daemon/lock_manager.py +508 -0
- fbuild/daemon/logging_utils.py +149 -0
- fbuild/daemon/messages.py +957 -0
- fbuild/daemon/operation_registry.py +288 -0
- fbuild/daemon/port_state_manager.py +249 -0
- fbuild/daemon/process_tracker.py +366 -0
- fbuild/daemon/processors/__init__.py +18 -0
- fbuild/daemon/processors/build_processor.py +248 -0
- fbuild/daemon/processors/deploy_processor.py +664 -0
- fbuild/daemon/processors/install_deps_processor.py +431 -0
- fbuild/daemon/processors/locking_processor.py +777 -0
- fbuild/daemon/processors/monitor_processor.py +285 -0
- fbuild/daemon/request_processor.py +457 -0
- fbuild/daemon/shared_serial.py +819 -0
- fbuild/daemon/status_manager.py +238 -0
- fbuild/daemon/subprocess_manager.py +316 -0
- fbuild/deploy/__init__.py +21 -0
- fbuild/deploy/deployer.py +67 -0
- fbuild/deploy/deployer_esp32.py +310 -0
- fbuild/deploy/docker_utils.py +315 -0
- fbuild/deploy/monitor.py +519 -0
- fbuild/deploy/qemu_runner.py +603 -0
- fbuild/interrupt_utils.py +34 -0
- fbuild/ledger/__init__.py +52 -0
- fbuild/ledger/board_ledger.py +560 -0
- fbuild/output.py +352 -0
- fbuild/packages/__init__.py +66 -0
- fbuild/packages/archive_utils.py +1098 -0
- fbuild/packages/arduino_core.py +412 -0
- fbuild/packages/cache.py +256 -0
- fbuild/packages/concurrent_manager.py +510 -0
- fbuild/packages/downloader.py +518 -0
- fbuild/packages/fingerprint.py +423 -0
- fbuild/packages/framework_esp32.py +538 -0
- fbuild/packages/framework_rp2040.py +349 -0
- fbuild/packages/framework_stm32.py +459 -0
- fbuild/packages/framework_teensy.py +346 -0
- fbuild/packages/github_utils.py +96 -0
- fbuild/packages/header_trampoline_cache.py +394 -0
- fbuild/packages/library_compiler.py +203 -0
- fbuild/packages/library_manager.py +549 -0
- fbuild/packages/library_manager_esp32.py +725 -0
- fbuild/packages/package.py +163 -0
- fbuild/packages/platform_esp32.py +383 -0
- fbuild/packages/platform_rp2040.py +400 -0
- fbuild/packages/platform_stm32.py +581 -0
- fbuild/packages/platform_teensy.py +312 -0
- fbuild/packages/platform_utils.py +131 -0
- fbuild/packages/platformio_registry.py +369 -0
- fbuild/packages/sdk_utils.py +231 -0
- fbuild/packages/toolchain.py +436 -0
- fbuild/packages/toolchain_binaries.py +196 -0
- fbuild/packages/toolchain_esp32.py +489 -0
- fbuild/packages/toolchain_metadata.py +185 -0
- fbuild/packages/toolchain_rp2040.py +436 -0
- fbuild/packages/toolchain_stm32.py +417 -0
- fbuild/packages/toolchain_teensy.py +404 -0
- fbuild/platform_configs/esp32.json +150 -0
- fbuild/platform_configs/esp32c2.json +144 -0
- fbuild/platform_configs/esp32c3.json +143 -0
- fbuild/platform_configs/esp32c5.json +151 -0
- fbuild/platform_configs/esp32c6.json +151 -0
- fbuild/platform_configs/esp32p4.json +149 -0
- fbuild/platform_configs/esp32s3.json +151 -0
- fbuild/platform_configs/imxrt1062.json +56 -0
- fbuild/platform_configs/rp2040.json +70 -0
- fbuild/platform_configs/rp2350.json +76 -0
- fbuild/platform_configs/stm32f1.json +59 -0
- fbuild/platform_configs/stm32f4.json +63 -0
- fbuild/py.typed +0 -0
- fbuild-1.2.8.dist-info/METADATA +468 -0
- fbuild-1.2.8.dist-info/RECORD +121 -0
- fbuild-1.2.8.dist-info/WHEEL +5 -0
- fbuild-1.2.8.dist-info/entry_points.txt +5 -0
- fbuild-1.2.8.dist-info/licenses/LICENSE +21 -0
- fbuild-1.2.8.dist-info/top_level.txt +2 -0
- fbuild_lint/__init__.py +0 -0
- fbuild_lint/ruff_plugins/__init__.py +0 -0
- 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
|
+
]
|