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.
- fbuild/__init__.py +0 -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_state.py +325 -0
- fbuild/build/build_utils.py +98 -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 +612 -0
- fbuild/build/configurable_linker.py +637 -0
- fbuild/build/flag_builder.py +186 -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 +656 -0
- fbuild/build/orchestrator_esp32.py +797 -0
- fbuild/build/orchestrator_teensy.py +543 -0
- fbuild/build/source_compilation_orchestrator.py +220 -0
- fbuild/build/source_scanner.py +516 -0
- fbuild/cli.py +566 -0
- fbuild/cli_utils.py +312 -0
- fbuild/config/__init__.py +16 -0
- fbuild/config/board_config.py +457 -0
- fbuild/config/board_loader.py +92 -0
- fbuild/config/ini_parser.py +209 -0
- fbuild/config/mcu_specs.py +88 -0
- fbuild/daemon/__init__.py +34 -0
- fbuild/daemon/client.py +929 -0
- fbuild/daemon/compilation_queue.py +293 -0
- fbuild/daemon/daemon.py +474 -0
- fbuild/daemon/daemon_context.py +196 -0
- fbuild/daemon/error_collector.py +263 -0
- fbuild/daemon/file_cache.py +332 -0
- fbuild/daemon/lock_manager.py +270 -0
- fbuild/daemon/logging_utils.py +149 -0
- fbuild/daemon/messages.py +301 -0
- fbuild/daemon/operation_registry.py +288 -0
- fbuild/daemon/process_tracker.py +366 -0
- fbuild/daemon/processors/__init__.py +12 -0
- fbuild/daemon/processors/build_processor.py +157 -0
- fbuild/daemon/processors/deploy_processor.py +327 -0
- fbuild/daemon/processors/monitor_processor.py +146 -0
- fbuild/daemon/request_processor.py +401 -0
- fbuild/daemon/status_manager.py +216 -0
- fbuild/daemon/subprocess_manager.py +316 -0
- fbuild/deploy/__init__.py +17 -0
- fbuild/deploy/deployer.py +67 -0
- fbuild/deploy/deployer_esp32.py +314 -0
- fbuild/deploy/monitor.py +495 -0
- fbuild/interrupt_utils.py +34 -0
- fbuild/packages/__init__.py +53 -0
- fbuild/packages/archive_utils.py +1098 -0
- fbuild/packages/arduino_core.py +412 -0
- fbuild/packages/cache.py +249 -0
- fbuild/packages/downloader.py +366 -0
- fbuild/packages/framework_esp32.py +538 -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 +413 -0
- fbuild/packages/package.py +163 -0
- fbuild/packages/platform_esp32.py +383 -0
- fbuild/packages/platform_teensy.py +312 -0
- fbuild/packages/platform_utils.py +131 -0
- fbuild/packages/platformio_registry.py +325 -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 +484 -0
- fbuild/packages/toolchain_metadata.py +185 -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-1.1.0.dist-info/METADATA +447 -0
- fbuild-1.1.0.dist-info/RECORD +93 -0
- fbuild-1.1.0.dist-info/WHEEL +5 -0
- fbuild-1.1.0.dist-info/entry_points.txt +5 -0
- fbuild-1.1.0.dist-info/licenses/LICENSE +21 -0
- fbuild-1.1.0.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/deploy/monitor.py
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Serial monitor module for embedded devices.
|
|
3
|
+
|
|
4
|
+
This module provides serial monitoring capabilities with optional halt conditions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import _thread
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from fbuild.cli_utils import safe_print
|
|
16
|
+
from fbuild.config import PlatformIOConfig
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MonitorError(Exception):
|
|
20
|
+
"""Raised when monitor operations fail."""
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SerialMonitor:
|
|
26
|
+
"""Serial monitor for embedded devices."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, verbose: bool = False):
|
|
29
|
+
"""Initialize serial monitor.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
verbose: Whether to show verbose output
|
|
33
|
+
"""
|
|
34
|
+
self.verbose = verbose
|
|
35
|
+
|
|
36
|
+
def _write_summary(
|
|
37
|
+
self,
|
|
38
|
+
summary_file: Optional[Path],
|
|
39
|
+
expect: Optional[str],
|
|
40
|
+
expect_found: bool,
|
|
41
|
+
halt_on_error: Optional[str],
|
|
42
|
+
halt_on_error_found: bool,
|
|
43
|
+
halt_on_success: Optional[str],
|
|
44
|
+
halt_on_success_found: bool,
|
|
45
|
+
lines_processed: int,
|
|
46
|
+
elapsed_time: float,
|
|
47
|
+
exit_reason: str,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Write monitoring summary to JSON file.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
summary_file: Path to write summary JSON
|
|
53
|
+
expect: Expected pattern (or None)
|
|
54
|
+
expect_found: Whether expect pattern was found
|
|
55
|
+
halt_on_error: Error pattern (or None)
|
|
56
|
+
halt_on_error_found: Whether error pattern was found
|
|
57
|
+
halt_on_success: Success pattern (or None)
|
|
58
|
+
halt_on_success_found: Whether success pattern was found
|
|
59
|
+
lines_processed: Total lines read from serial
|
|
60
|
+
elapsed_time: Time elapsed in seconds
|
|
61
|
+
exit_reason: Reason for exit (timeout/expect_found/halt_error/halt_success/interrupted/error)
|
|
62
|
+
"""
|
|
63
|
+
if not summary_file:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
summary = {
|
|
67
|
+
"expect_pattern": expect,
|
|
68
|
+
"expect_found": expect_found,
|
|
69
|
+
"halt_on_error_pattern": halt_on_error,
|
|
70
|
+
"halt_on_error_found": halt_on_error_found,
|
|
71
|
+
"halt_on_success_pattern": halt_on_success,
|
|
72
|
+
"halt_on_success_found": halt_on_success_found,
|
|
73
|
+
"lines_processed": lines_processed,
|
|
74
|
+
"elapsed_time": round(elapsed_time, 2),
|
|
75
|
+
"exit_reason": exit_reason,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
summary_file.parent.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
with open(summary_file, "w", encoding="utf-8") as f:
|
|
81
|
+
json.dump(summary, f, indent=2)
|
|
82
|
+
except KeyboardInterrupt:
|
|
83
|
+
raise
|
|
84
|
+
except Exception as e:
|
|
85
|
+
# Silently fail - don't disrupt the monitor operation
|
|
86
|
+
if self.verbose:
|
|
87
|
+
print(f"Warning: Could not write summary file: {e}")
|
|
88
|
+
|
|
89
|
+
def monitor(
|
|
90
|
+
self,
|
|
91
|
+
project_dir: Path,
|
|
92
|
+
env_name: str,
|
|
93
|
+
port: Optional[str] = None,
|
|
94
|
+
baud: int = 115200,
|
|
95
|
+
timeout: Optional[int] = None,
|
|
96
|
+
halt_on_error: Optional[str] = None,
|
|
97
|
+
halt_on_success: Optional[str] = None,
|
|
98
|
+
expect: Optional[str] = None,
|
|
99
|
+
output_file: Optional[Path] = None,
|
|
100
|
+
summary_file: Optional[Path] = None,
|
|
101
|
+
) -> int:
|
|
102
|
+
"""Monitor serial output from device.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
project_dir: Path to project directory
|
|
106
|
+
env_name: Environment name
|
|
107
|
+
port: Serial port to use (auto-detect if None)
|
|
108
|
+
baud: Baud rate (default: 115200)
|
|
109
|
+
timeout: Timeout in seconds (None for infinite)
|
|
110
|
+
halt_on_error: String pattern that triggers error exit
|
|
111
|
+
halt_on_success: String pattern that triggers success exit
|
|
112
|
+
expect: Expected pattern - checked at timeout/success for exit code
|
|
113
|
+
output_file: Optional file to write serial output to (for client streaming)
|
|
114
|
+
summary_file: Optional file to write summary JSON to (for client display)
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Exit code (0 for success, 1 for error)
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
import serial
|
|
121
|
+
except ImportError:
|
|
122
|
+
print("Error: pyserial not installed. Install with: pip install pyserial")
|
|
123
|
+
return 1
|
|
124
|
+
|
|
125
|
+
# Load platformio.ini to get board config
|
|
126
|
+
ini_path = project_dir / "platformio.ini"
|
|
127
|
+
if not ini_path.exists():
|
|
128
|
+
print(f"Error: platformio.ini not found in {project_dir}")
|
|
129
|
+
return 1
|
|
130
|
+
|
|
131
|
+
config = PlatformIOConfig(ini_path)
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
env_config = config.get_env_config(env_name)
|
|
135
|
+
except KeyboardInterrupt as ke:
|
|
136
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
137
|
+
|
|
138
|
+
handle_keyboard_interrupt_properly(ke)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
print(f"Error: {e}")
|
|
141
|
+
return 1
|
|
142
|
+
|
|
143
|
+
# Get monitor baud rate from config if specified
|
|
144
|
+
monitor_speed = env_config.get("monitor_speed")
|
|
145
|
+
if monitor_speed:
|
|
146
|
+
try:
|
|
147
|
+
baud = int(monitor_speed)
|
|
148
|
+
except ValueError:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
# Auto-detect port if not specified
|
|
152
|
+
if not port:
|
|
153
|
+
port = self._detect_serial_port()
|
|
154
|
+
if not port:
|
|
155
|
+
print("Error: No serial port specified and auto-detection failed. " + "Use --port to specify a port.")
|
|
156
|
+
return 1
|
|
157
|
+
|
|
158
|
+
print(f"Opening serial port {port} at {baud} baud...")
|
|
159
|
+
|
|
160
|
+
ser = None
|
|
161
|
+
output_fp = None
|
|
162
|
+
try:
|
|
163
|
+
# Open serial port
|
|
164
|
+
ser = serial.Serial(
|
|
165
|
+
port,
|
|
166
|
+
baud,
|
|
167
|
+
timeout=0.1, # Short timeout for readline
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Reset the device to ensure we catch all output from the start
|
|
171
|
+
# This is necessary because the device may have already booted
|
|
172
|
+
# between esptool finishing and the monitor starting
|
|
173
|
+
ser.setDTR(False) # type: ignore[attr-defined]
|
|
174
|
+
ser.setRTS(True) # type: ignore[attr-defined]
|
|
175
|
+
time.sleep(0.1)
|
|
176
|
+
ser.setRTS(False) # type: ignore[attr-defined]
|
|
177
|
+
time.sleep(0.1)
|
|
178
|
+
ser.setDTR(True) # type: ignore[attr-defined]
|
|
179
|
+
|
|
180
|
+
print(f"Connected to {port}")
|
|
181
|
+
print("--- Serial Monitor (Ctrl+C to exit) ---")
|
|
182
|
+
print()
|
|
183
|
+
|
|
184
|
+
# Give device a moment to start booting after reset
|
|
185
|
+
time.sleep(0.2)
|
|
186
|
+
|
|
187
|
+
# Open output file for streaming (if specified)
|
|
188
|
+
if output_file:
|
|
189
|
+
try:
|
|
190
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
output_fp = open(output_file, "w", encoding="utf-8", errors="replace")
|
|
192
|
+
except KeyboardInterrupt:
|
|
193
|
+
raise
|
|
194
|
+
except Exception as e:
|
|
195
|
+
print(f"Warning: Could not open output file {output_file}: {e}")
|
|
196
|
+
|
|
197
|
+
start_time = time.time()
|
|
198
|
+
|
|
199
|
+
# Track statistics
|
|
200
|
+
expect_found = False
|
|
201
|
+
halt_on_error_found = False
|
|
202
|
+
halt_on_success_found = False
|
|
203
|
+
lines_processed = 0
|
|
204
|
+
|
|
205
|
+
while True:
|
|
206
|
+
# Check timeout
|
|
207
|
+
if timeout and (time.time() - start_time) > timeout:
|
|
208
|
+
elapsed_time = time.time() - start_time
|
|
209
|
+
print()
|
|
210
|
+
print(f"--- Monitor timeout after {timeout} seconds ---")
|
|
211
|
+
|
|
212
|
+
# Print statistics
|
|
213
|
+
if expect or halt_on_error or halt_on_success:
|
|
214
|
+
safe_print("\n--- Test Results ---")
|
|
215
|
+
if expect:
|
|
216
|
+
if expect_found:
|
|
217
|
+
safe_print(f"✓ Expected pattern found: '{expect}'")
|
|
218
|
+
else:
|
|
219
|
+
safe_print(f"✗ Expected pattern NOT found: '{expect}'")
|
|
220
|
+
if halt_on_error:
|
|
221
|
+
if halt_on_error_found:
|
|
222
|
+
safe_print(f"✗ Error pattern found: '{halt_on_error}'")
|
|
223
|
+
else:
|
|
224
|
+
safe_print(f"✓ Error pattern not found: '{halt_on_error}'")
|
|
225
|
+
if halt_on_success:
|
|
226
|
+
if halt_on_success_found:
|
|
227
|
+
safe_print(f"✓ Success pattern found: '{halt_on_success}'")
|
|
228
|
+
else:
|
|
229
|
+
safe_print(f"✗ Success pattern NOT found: '{halt_on_success}'")
|
|
230
|
+
|
|
231
|
+
ser.close()
|
|
232
|
+
if output_fp:
|
|
233
|
+
output_fp.close()
|
|
234
|
+
|
|
235
|
+
# Write summary
|
|
236
|
+
self._write_summary(
|
|
237
|
+
summary_file,
|
|
238
|
+
expect,
|
|
239
|
+
expect_found,
|
|
240
|
+
halt_on_error,
|
|
241
|
+
halt_on_error_found,
|
|
242
|
+
halt_on_success,
|
|
243
|
+
halt_on_success_found,
|
|
244
|
+
lines_processed,
|
|
245
|
+
elapsed_time,
|
|
246
|
+
"timeout",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Check expect keyword for exit code
|
|
250
|
+
if expect:
|
|
251
|
+
return 0 if expect_found else 1
|
|
252
|
+
else:
|
|
253
|
+
# Legacy behavior when no expect is specified
|
|
254
|
+
if halt_on_error or halt_on_success:
|
|
255
|
+
return 1 # Error: pattern was expected but not found
|
|
256
|
+
else:
|
|
257
|
+
return 0 # Success: just a timed monitoring session
|
|
258
|
+
|
|
259
|
+
# Read line from serial
|
|
260
|
+
try:
|
|
261
|
+
if ser.in_waiting:
|
|
262
|
+
line = ser.readline()
|
|
263
|
+
try:
|
|
264
|
+
text = line.decode("utf-8", errors="replace").rstrip()
|
|
265
|
+
except KeyboardInterrupt as ke:
|
|
266
|
+
from fbuild.interrupt_utils import (
|
|
267
|
+
handle_keyboard_interrupt_properly,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
handle_keyboard_interrupt_properly(ke)
|
|
271
|
+
except Exception:
|
|
272
|
+
text = str(line)
|
|
273
|
+
|
|
274
|
+
# Print the line
|
|
275
|
+
safe_print(text)
|
|
276
|
+
sys.stdout.flush()
|
|
277
|
+
|
|
278
|
+
# Write to output file if specified
|
|
279
|
+
if output_fp:
|
|
280
|
+
try:
|
|
281
|
+
output_fp.write(text + "\n")
|
|
282
|
+
output_fp.flush()
|
|
283
|
+
except KeyboardInterrupt:
|
|
284
|
+
raise
|
|
285
|
+
except Exception:
|
|
286
|
+
pass # Ignore write errors
|
|
287
|
+
|
|
288
|
+
# Increment line counter
|
|
289
|
+
lines_processed += 1
|
|
290
|
+
|
|
291
|
+
# Check for expect pattern (track but don't halt)
|
|
292
|
+
if expect and re.search(expect, text, re.IGNORECASE):
|
|
293
|
+
expect_found = True
|
|
294
|
+
|
|
295
|
+
# Check halt conditions
|
|
296
|
+
if halt_on_error and re.search(halt_on_error, text, re.IGNORECASE):
|
|
297
|
+
halt_on_error_found = True
|
|
298
|
+
elapsed_time = time.time() - start_time
|
|
299
|
+
print()
|
|
300
|
+
print(f"--- Found error pattern: '{halt_on_error}' ---")
|
|
301
|
+
|
|
302
|
+
# Print statistics
|
|
303
|
+
if expect or halt_on_success:
|
|
304
|
+
safe_print("\n--- Test Results ---")
|
|
305
|
+
if expect:
|
|
306
|
+
if expect_found:
|
|
307
|
+
safe_print(f"✓ Expected pattern found: '{expect}'")
|
|
308
|
+
else:
|
|
309
|
+
safe_print(f"✗ Expected pattern NOT found: '{expect}'")
|
|
310
|
+
if halt_on_success:
|
|
311
|
+
if halt_on_success_found:
|
|
312
|
+
safe_print(f"✓ Success pattern found: '{halt_on_success}'")
|
|
313
|
+
else:
|
|
314
|
+
safe_print(f"✗ Success pattern NOT found: '{halt_on_success}'")
|
|
315
|
+
safe_print(f"✗ Error pattern found: '{halt_on_error}'")
|
|
316
|
+
|
|
317
|
+
ser.close()
|
|
318
|
+
if output_fp:
|
|
319
|
+
output_fp.close()
|
|
320
|
+
|
|
321
|
+
# Write summary
|
|
322
|
+
self._write_summary(
|
|
323
|
+
summary_file,
|
|
324
|
+
expect,
|
|
325
|
+
expect_found,
|
|
326
|
+
halt_on_error,
|
|
327
|
+
halt_on_error_found,
|
|
328
|
+
halt_on_success,
|
|
329
|
+
halt_on_success_found,
|
|
330
|
+
lines_processed,
|
|
331
|
+
elapsed_time,
|
|
332
|
+
"halt_error",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return 1
|
|
336
|
+
|
|
337
|
+
if halt_on_success and re.search(halt_on_success, text, re.IGNORECASE):
|
|
338
|
+
halt_on_success_found = True
|
|
339
|
+
elapsed_time = time.time() - start_time
|
|
340
|
+
print()
|
|
341
|
+
print(f"--- Found success pattern: '{halt_on_success}' ---")
|
|
342
|
+
|
|
343
|
+
# Print statistics
|
|
344
|
+
if expect or halt_on_error:
|
|
345
|
+
safe_print("\n--- Test Results ---")
|
|
346
|
+
if expect:
|
|
347
|
+
if expect_found:
|
|
348
|
+
safe_print(f"✓ Expected pattern found: '{expect}'")
|
|
349
|
+
else:
|
|
350
|
+
safe_print(f"✗ Expected pattern NOT found: '{expect}'")
|
|
351
|
+
safe_print(f"✓ Success pattern found: '{halt_on_success}'")
|
|
352
|
+
if halt_on_error:
|
|
353
|
+
if halt_on_error_found:
|
|
354
|
+
safe_print(f"✗ Error pattern found: '{halt_on_error}'")
|
|
355
|
+
else:
|
|
356
|
+
safe_print(f"✓ Error pattern not found: '{halt_on_error}'")
|
|
357
|
+
|
|
358
|
+
ser.close()
|
|
359
|
+
if output_fp:
|
|
360
|
+
output_fp.close()
|
|
361
|
+
|
|
362
|
+
# Write summary
|
|
363
|
+
exit_reason = "expect_found" if (expect and expect_found) else "halt_success"
|
|
364
|
+
self._write_summary(
|
|
365
|
+
summary_file,
|
|
366
|
+
expect,
|
|
367
|
+
expect_found,
|
|
368
|
+
halt_on_error,
|
|
369
|
+
halt_on_error_found,
|
|
370
|
+
halt_on_success,
|
|
371
|
+
halt_on_success_found,
|
|
372
|
+
lines_processed,
|
|
373
|
+
elapsed_time,
|
|
374
|
+
exit_reason,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Check expect keyword for exit code
|
|
378
|
+
if expect:
|
|
379
|
+
return 0 if expect_found else 1
|
|
380
|
+
else:
|
|
381
|
+
return 0
|
|
382
|
+
else:
|
|
383
|
+
time.sleep(0.01)
|
|
384
|
+
|
|
385
|
+
except serial.SerialException as e:
|
|
386
|
+
elapsed_time = time.time() - start_time
|
|
387
|
+
print(f"\nError reading from serial port: {e}")
|
|
388
|
+
ser.close()
|
|
389
|
+
if output_fp:
|
|
390
|
+
output_fp.close()
|
|
391
|
+
|
|
392
|
+
# Write summary
|
|
393
|
+
self._write_summary(
|
|
394
|
+
summary_file,
|
|
395
|
+
expect,
|
|
396
|
+
expect_found,
|
|
397
|
+
halt_on_error,
|
|
398
|
+
halt_on_error_found,
|
|
399
|
+
halt_on_success,
|
|
400
|
+
halt_on_success_found,
|
|
401
|
+
lines_processed,
|
|
402
|
+
elapsed_time,
|
|
403
|
+
"error",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
return 1
|
|
407
|
+
|
|
408
|
+
except serial.SerialException as e:
|
|
409
|
+
print(f"Error opening serial port {port}: {e}")
|
|
410
|
+
if output_fp:
|
|
411
|
+
output_fp.close()
|
|
412
|
+
|
|
413
|
+
# Write summary (minimal - couldn't even start monitoring)
|
|
414
|
+
self._write_summary(
|
|
415
|
+
summary_file,
|
|
416
|
+
expect,
|
|
417
|
+
False,
|
|
418
|
+
halt_on_error,
|
|
419
|
+
False,
|
|
420
|
+
halt_on_success,
|
|
421
|
+
False,
|
|
422
|
+
0,
|
|
423
|
+
0.0,
|
|
424
|
+
"error",
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
return 1
|
|
428
|
+
except KeyboardInterrupt:
|
|
429
|
+
# Interrupt other threads
|
|
430
|
+
_thread.interrupt_main()
|
|
431
|
+
|
|
432
|
+
elapsed_time = time.time() - start_time if "start_time" in locals() else 0.0
|
|
433
|
+
lines = lines_processed if "lines_processed" in locals() else 0
|
|
434
|
+
exp_found = expect_found if "expect_found" in locals() else False
|
|
435
|
+
halt_err_found = halt_on_error_found if "halt_on_error_found" in locals() else False
|
|
436
|
+
halt_succ_found = halt_on_success_found if "halt_on_success_found" in locals() else False
|
|
437
|
+
|
|
438
|
+
print()
|
|
439
|
+
print("--- Monitor interrupted ---")
|
|
440
|
+
if ser is not None:
|
|
441
|
+
ser.close()
|
|
442
|
+
if output_fp:
|
|
443
|
+
output_fp.close()
|
|
444
|
+
|
|
445
|
+
# Write summary
|
|
446
|
+
self._write_summary(
|
|
447
|
+
summary_file,
|
|
448
|
+
expect,
|
|
449
|
+
exp_found,
|
|
450
|
+
halt_on_error,
|
|
451
|
+
halt_err_found,
|
|
452
|
+
halt_on_success,
|
|
453
|
+
halt_succ_found,
|
|
454
|
+
lines,
|
|
455
|
+
elapsed_time,
|
|
456
|
+
"interrupted",
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
return 0
|
|
460
|
+
|
|
461
|
+
def _detect_serial_port(self) -> Optional[str]:
|
|
462
|
+
"""Auto-detect serial port for device.
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Serial port name or None if not found
|
|
466
|
+
"""
|
|
467
|
+
try:
|
|
468
|
+
import serial.tools.list_ports
|
|
469
|
+
|
|
470
|
+
ports = list(serial.tools.list_ports.comports())
|
|
471
|
+
|
|
472
|
+
# Look for ESP32 or USB-SERIAL devices
|
|
473
|
+
for port in ports:
|
|
474
|
+
description = (port.description or "").lower()
|
|
475
|
+
manufacturer = (port.manufacturer or "").lower()
|
|
476
|
+
|
|
477
|
+
if any(x in description or x in manufacturer for x in ["cp210", "ch340", "usb-serial", "uart", "esp32"]):
|
|
478
|
+
return port.device
|
|
479
|
+
|
|
480
|
+
# If no specific match, return first port
|
|
481
|
+
if ports:
|
|
482
|
+
return ports[0].device
|
|
483
|
+
|
|
484
|
+
except ImportError:
|
|
485
|
+
if self.verbose:
|
|
486
|
+
print("pyserial not installed. Cannot auto-detect port.")
|
|
487
|
+
except KeyboardInterrupt as ke:
|
|
488
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
489
|
+
|
|
490
|
+
handle_keyboard_interrupt_properly(ke)
|
|
491
|
+
except Exception as e:
|
|
492
|
+
if self.verbose:
|
|
493
|
+
print(f"Port detection failed: {e}")
|
|
494
|
+
|
|
495
|
+
return None
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Utilities for handling KeyboardInterrupt in try-except blocks.
|
|
2
|
+
|
|
3
|
+
This module provides utilities to ensure KeyboardInterrupt is properly
|
|
4
|
+
propagated to the main thread when caught in exception handlers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import _thread
|
|
8
|
+
from typing import NoReturn
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def handle_keyboard_interrupt_properly(ke: KeyboardInterrupt) -> NoReturn:
|
|
12
|
+
"""Handle KeyboardInterrupt by propagating it to the main thread.
|
|
13
|
+
|
|
14
|
+
This utility ensures that KeyboardInterrupt is properly handled in try-except
|
|
15
|
+
blocks by calling _thread.interrupt_main() before re-raising the exception.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
try:
|
|
19
|
+
# Some code that might be interrupted
|
|
20
|
+
pass
|
|
21
|
+
except KeyboardInterrupt as ke:
|
|
22
|
+
handle_keyboard_interrupt_properly(ke)
|
|
23
|
+
except Exception:
|
|
24
|
+
# Handle other exceptions
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
ke: The KeyboardInterrupt exception to handle
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
KeyboardInterrupt: Always re-raises the exception after handling
|
|
32
|
+
"""
|
|
33
|
+
_thread.interrupt_main()
|
|
34
|
+
raise ke
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Package management for fbuild.
|
|
2
|
+
|
|
3
|
+
This module handles downloading, caching, and managing external packages
|
|
4
|
+
including toolchains, platforms, and libraries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .archive_utils import ArchiveExtractionError, ArchiveExtractor, URLVersionExtractor
|
|
8
|
+
from .arduino_core import ArduinoCore, ArduinoCoreError
|
|
9
|
+
from .cache import Cache
|
|
10
|
+
from .downloader import ChecksumError, DownloadError, ExtractionError, PackageDownloader
|
|
11
|
+
from .github_utils import GitHubURLOptimizer
|
|
12
|
+
from .library_compiler import LibraryCompilationError, LibraryCompiler
|
|
13
|
+
from .package import IFramework, IPackage
|
|
14
|
+
from .package import IToolchain as BaseToolchain
|
|
15
|
+
from .package import PackageError
|
|
16
|
+
from .platform_esp32 import PlatformErrorESP32, PlatformESP32
|
|
17
|
+
from .platform_utils import PlatformDetector, PlatformError
|
|
18
|
+
from .sdk_utils import SDKPathResolver
|
|
19
|
+
from .toolchain import ToolchainAVR as Toolchain
|
|
20
|
+
from .toolchain import ToolchainError
|
|
21
|
+
from .toolchain_binaries import BinaryNotFoundError, ToolchainBinaryFinder
|
|
22
|
+
from .toolchain_metadata import MetadataParseError, ToolchainMetadataParser
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"IPackage",
|
|
26
|
+
"BaseToolchain",
|
|
27
|
+
"IFramework",
|
|
28
|
+
"PackageError",
|
|
29
|
+
"Cache",
|
|
30
|
+
"PackageDownloader",
|
|
31
|
+
"DownloadError",
|
|
32
|
+
"ChecksumError",
|
|
33
|
+
"ExtractionError",
|
|
34
|
+
"Toolchain",
|
|
35
|
+
"ToolchainError",
|
|
36
|
+
"ArduinoCore",
|
|
37
|
+
"ArduinoCoreError",
|
|
38
|
+
"PlatformESP32",
|
|
39
|
+
"PlatformErrorESP32",
|
|
40
|
+
"GitHubURLOptimizer",
|
|
41
|
+
"LibraryCompiler",
|
|
42
|
+
"LibraryCompilationError",
|
|
43
|
+
"ArchiveExtractor",
|
|
44
|
+
"ArchiveExtractionError",
|
|
45
|
+
"URLVersionExtractor",
|
|
46
|
+
"SDKPathResolver",
|
|
47
|
+
"PlatformDetector",
|
|
48
|
+
"PlatformError",
|
|
49
|
+
"ToolchainBinaryFinder",
|
|
50
|
+
"BinaryNotFoundError",
|
|
51
|
+
"ToolchainMetadataParser",
|
|
52
|
+
"MetadataParseError",
|
|
53
|
+
]
|