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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Monitor Request Processor - Handles serial monitoring operations.
|
|
3
|
+
|
|
4
|
+
This module implements the MonitorRequestProcessor which executes serial
|
|
5
|
+
monitoring operations for Arduino/ESP32 devices. It captures serial output,
|
|
6
|
+
performs pattern matching, and handles halt conditions.
|
|
7
|
+
|
|
8
|
+
Enhanced in Iteration 2 with:
|
|
9
|
+
- SharedSerialManager integration for centralized serial port management
|
|
10
|
+
- Support for multiple reader clients (broadcast output)
|
|
11
|
+
- ConfigurationLockManager for centralized locking
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import sys
|
|
16
|
+
import uuid
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from fbuild.daemon.messages import DaemonState, OperationType
|
|
21
|
+
from fbuild.daemon.port_state_manager import PortState
|
|
22
|
+
from fbuild.daemon.request_processor import RequestProcessor
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from fbuild.daemon.daemon_context import DaemonContext
|
|
26
|
+
from fbuild.daemon.messages import MonitorRequest
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MonitorRequestProcessor(RequestProcessor):
|
|
30
|
+
"""Processor for monitor requests.
|
|
31
|
+
|
|
32
|
+
This processor handles serial monitoring of Arduino/ESP32 devices. It:
|
|
33
|
+
1. Connects to the specified serial port
|
|
34
|
+
2. Captures and streams output to a file
|
|
35
|
+
3. Performs pattern matching on the output
|
|
36
|
+
4. Handles halt conditions (error/success patterns)
|
|
37
|
+
5. Times out if specified
|
|
38
|
+
|
|
39
|
+
The monitor runs until:
|
|
40
|
+
- A halt pattern is matched (halt_on_error or halt_on_success)
|
|
41
|
+
- The timeout is reached
|
|
42
|
+
- The user interrupts it (Ctrl+C)
|
|
43
|
+
- An error occurs
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
>>> processor = MonitorRequestProcessor()
|
|
47
|
+
>>> success = processor.process_request(monitor_request, daemon_context)
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def get_operation_type(self) -> OperationType:
|
|
51
|
+
"""Return MONITOR operation type."""
|
|
52
|
+
return OperationType.MONITOR
|
|
53
|
+
|
|
54
|
+
def get_required_locks(self, request: "MonitorRequest", context: "DaemonContext") -> dict[str, str]:
|
|
55
|
+
"""Monitor operations require only a port lock.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
request: The monitor request
|
|
59
|
+
context: The daemon context
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Dictionary with port lock requirement
|
|
63
|
+
"""
|
|
64
|
+
return {"port": request.port} if request.port else {}
|
|
65
|
+
|
|
66
|
+
def validate_request(self, request: "MonitorRequest", context: "DaemonContext") -> bool:
|
|
67
|
+
"""Validate that the monitor request has a port specified.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
request: The monitor request
|
|
71
|
+
context: The daemon context
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
True if request is valid (has port), False otherwise
|
|
75
|
+
"""
|
|
76
|
+
if not request.port:
|
|
77
|
+
logging.error("Monitor requires port to be specified")
|
|
78
|
+
return False
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
def get_starting_state(self) -> DaemonState:
|
|
82
|
+
"""Monitor starts in MONITORING state."""
|
|
83
|
+
return DaemonState.MONITORING
|
|
84
|
+
|
|
85
|
+
def get_starting_message(self, request: "MonitorRequest") -> str:
|
|
86
|
+
"""Get the starting status message."""
|
|
87
|
+
return f"Monitoring {request.environment} on {request.port}"
|
|
88
|
+
|
|
89
|
+
def get_success_message(self, request: "MonitorRequest") -> str:
|
|
90
|
+
"""Get the success status message."""
|
|
91
|
+
return "Monitor completed"
|
|
92
|
+
|
|
93
|
+
def get_failure_message(self, request: "MonitorRequest") -> str:
|
|
94
|
+
"""Get the failure status message."""
|
|
95
|
+
return "Monitor failed"
|
|
96
|
+
|
|
97
|
+
def execute_operation(self, request: "MonitorRequest", context: "DaemonContext") -> bool:
|
|
98
|
+
"""Execute the serial monitoring operation.
|
|
99
|
+
|
|
100
|
+
This is the core monitor logic extracted from the original
|
|
101
|
+
process_monitor_request function. All boilerplate (locks, status
|
|
102
|
+
updates, error handling) is handled by the base RequestProcessor.
|
|
103
|
+
|
|
104
|
+
Enhanced in Iteration 2:
|
|
105
|
+
- If SharedSerialManager has the port open, we can attach as a reader
|
|
106
|
+
to get shared access (multiple clients can monitor the same port)
|
|
107
|
+
- Falls back to direct serial access for exclusive monitoring
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
request: The monitor request containing port, baud_rate, etc.
|
|
111
|
+
context: The daemon context with all subsystems
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
True if monitoring completed successfully, False otherwise
|
|
115
|
+
"""
|
|
116
|
+
logging.info(f"Starting monitor on {request.port}")
|
|
117
|
+
|
|
118
|
+
# Track port state as MONITORING
|
|
119
|
+
port = request.port
|
|
120
|
+
if port:
|
|
121
|
+
context.port_state_manager.acquire_port(
|
|
122
|
+
port=port,
|
|
123
|
+
state=PortState.MONITORING,
|
|
124
|
+
client_pid=request.caller_pid,
|
|
125
|
+
project_dir=request.project_dir,
|
|
126
|
+
environment=request.environment,
|
|
127
|
+
operation_id=request.request_id,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Generate a client ID for shared serial manager
|
|
131
|
+
client_id = f"monitor_{request.request_id}_{uuid.uuid4().hex[:8]}"
|
|
132
|
+
|
|
133
|
+
# Check if port is already managed by SharedSerialManager
|
|
134
|
+
shared_session = context.shared_serial_manager.get_session_info(port) if port else None
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
if shared_session and shared_session.get("is_open"):
|
|
138
|
+
# Port is already open - attach as a reader for shared access
|
|
139
|
+
assert port is not None # Guaranteed by shared_session check above
|
|
140
|
+
return self._monitor_shared(request, port, client_id, context)
|
|
141
|
+
else:
|
|
142
|
+
# Port not managed - use direct serial access
|
|
143
|
+
return self._monitor_direct(request, port, context)
|
|
144
|
+
|
|
145
|
+
finally:
|
|
146
|
+
# Always clean up
|
|
147
|
+
if port:
|
|
148
|
+
# Detach from shared session if we were attached
|
|
149
|
+
if shared_session and shared_session.get("is_open"):
|
|
150
|
+
context.shared_serial_manager.detach_reader(port, client_id)
|
|
151
|
+
# Release port state
|
|
152
|
+
context.port_state_manager.release_port(port)
|
|
153
|
+
|
|
154
|
+
def _monitor_shared(
|
|
155
|
+
self,
|
|
156
|
+
request: "MonitorRequest",
|
|
157
|
+
port: str,
|
|
158
|
+
client_id: str,
|
|
159
|
+
context: "DaemonContext",
|
|
160
|
+
) -> bool:
|
|
161
|
+
"""Monitor using SharedSerialManager for shared access.
|
|
162
|
+
|
|
163
|
+
This allows multiple clients to monitor the same port simultaneously,
|
|
164
|
+
receiving broadcast output from the daemon-managed serial session.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
request: The monitor request
|
|
168
|
+
port: Serial port to monitor
|
|
169
|
+
client_id: Client ID for the shared session
|
|
170
|
+
context: The daemon context
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
True if monitoring completed successfully
|
|
174
|
+
"""
|
|
175
|
+
import time
|
|
176
|
+
|
|
177
|
+
logging.info(f"Using shared serial access for {port}")
|
|
178
|
+
|
|
179
|
+
# Attach as a reader
|
|
180
|
+
if not context.shared_serial_manager.attach_reader(port, client_id):
|
|
181
|
+
logging.error(f"Failed to attach as reader to {port}")
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
# Create output file path for streaming
|
|
185
|
+
output_file = Path(request.project_dir) / ".fbuild" / "monitor_output.txt"
|
|
186
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
187
|
+
output_file.write_text("", encoding="utf-8")
|
|
188
|
+
|
|
189
|
+
# Monitor loop - poll the buffer and check for patterns
|
|
190
|
+
start_time = time.time()
|
|
191
|
+
timeout = request.timeout if request.timeout else float("inf")
|
|
192
|
+
last_buffer_size = 0
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
with output_file.open("a", encoding="utf-8") as f:
|
|
196
|
+
while True:
|
|
197
|
+
# Check timeout
|
|
198
|
+
elapsed = time.time() - start_time
|
|
199
|
+
if elapsed >= timeout:
|
|
200
|
+
logging.info(f"Monitor timeout after {elapsed:.1f}s")
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
# Read buffered output
|
|
204
|
+
lines = context.shared_serial_manager.read_buffer(port, client_id, max_lines=1000)
|
|
205
|
+
|
|
206
|
+
# Process new lines
|
|
207
|
+
if len(lines) > last_buffer_size:
|
|
208
|
+
new_lines = lines[last_buffer_size:]
|
|
209
|
+
for line in new_lines:
|
|
210
|
+
# Write to output file
|
|
211
|
+
f.write(line + "\n")
|
|
212
|
+
f.flush()
|
|
213
|
+
|
|
214
|
+
# Check halt patterns
|
|
215
|
+
if request.halt_on_error and request.halt_on_error in line:
|
|
216
|
+
logging.error(f"Halt on error pattern matched: {line}")
|
|
217
|
+
return False
|
|
218
|
+
if request.halt_on_success and request.halt_on_success in line:
|
|
219
|
+
logging.info(f"Halt on success pattern matched: {line}")
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
last_buffer_size = len(lines)
|
|
223
|
+
|
|
224
|
+
# Brief sleep to avoid busy-waiting
|
|
225
|
+
time.sleep(0.1)
|
|
226
|
+
|
|
227
|
+
except KeyboardInterrupt: # noqa: KBI002
|
|
228
|
+
logging.info("Monitor interrupted by user")
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def _monitor_direct(self, request: "MonitorRequest", port: str | None, context: "DaemonContext") -> bool:
|
|
232
|
+
"""Monitor using direct serial access (exclusive).
|
|
233
|
+
|
|
234
|
+
This is the original monitoring approach using SerialMonitor.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
request: The monitor request
|
|
238
|
+
port: Serial port to monitor
|
|
239
|
+
context: The daemon context (unused but passed for consistency)
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
True if monitoring completed successfully
|
|
243
|
+
"""
|
|
244
|
+
# Create output file path for streaming
|
|
245
|
+
output_file = Path(request.project_dir) / ".fbuild" / "monitor_output.txt"
|
|
246
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
247
|
+
# Clear/truncate output file before starting
|
|
248
|
+
output_file.write_text("", encoding="utf-8")
|
|
249
|
+
|
|
250
|
+
# Create summary file path
|
|
251
|
+
summary_file = Path(request.project_dir) / ".fbuild" / "monitor_summary.json"
|
|
252
|
+
# Clear old summary file
|
|
253
|
+
if summary_file.exists():
|
|
254
|
+
summary_file.unlink()
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
# Get fresh monitor class after module reload
|
|
258
|
+
# Using direct import would use cached version
|
|
259
|
+
monitor_class = getattr(sys.modules["fbuild.deploy.monitor"], "SerialMonitor")
|
|
260
|
+
except (KeyError, AttributeError) as e:
|
|
261
|
+
logging.error(f"Failed to get SerialMonitor class: {e}")
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
# Create monitor and execute
|
|
265
|
+
monitor = monitor_class(verbose=False)
|
|
266
|
+
exit_code = monitor.monitor(
|
|
267
|
+
project_dir=Path(request.project_dir),
|
|
268
|
+
env_name=request.environment,
|
|
269
|
+
port=request.port,
|
|
270
|
+
baud=request.baud_rate if request.baud_rate else 115200,
|
|
271
|
+
timeout=int(request.timeout) if request.timeout is not None else None,
|
|
272
|
+
halt_on_error=request.halt_on_error,
|
|
273
|
+
halt_on_success=request.halt_on_success,
|
|
274
|
+
expect=request.expect,
|
|
275
|
+
output_file=output_file,
|
|
276
|
+
summary_file=summary_file,
|
|
277
|
+
timestamp=request.show_timestamp,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if exit_code == 0:
|
|
281
|
+
logging.info("Monitor completed successfully")
|
|
282
|
+
return True
|
|
283
|
+
else:
|
|
284
|
+
logging.error(f"Monitor failed with exit code {exit_code}")
|
|
285
|
+
return False
|