fbuild 1.2.8__py3-none-any.whl → 1.2.15__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 +5 -1
- fbuild/build/configurable_compiler.py +49 -6
- fbuild/build/configurable_linker.py +14 -9
- fbuild/build/orchestrator_esp32.py +6 -3
- fbuild/build/orchestrator_rp2040.py +6 -2
- fbuild/cli.py +300 -5
- fbuild/config/ini_parser.py +13 -1
- fbuild/daemon/__init__.py +11 -0
- fbuild/daemon/async_client.py +5 -4
- fbuild/daemon/async_client_lib.py +1543 -0
- fbuild/daemon/async_protocol.py +825 -0
- fbuild/daemon/async_server.py +2100 -0
- fbuild/daemon/client.py +425 -13
- fbuild/daemon/configuration_lock.py +13 -13
- fbuild/daemon/connection.py +508 -0
- fbuild/daemon/connection_registry.py +579 -0
- fbuild/daemon/daemon.py +517 -164
- fbuild/daemon/daemon_context.py +72 -1
- fbuild/daemon/device_discovery.py +477 -0
- fbuild/daemon/device_manager.py +821 -0
- fbuild/daemon/error_collector.py +263 -263
- fbuild/daemon/file_cache.py +332 -332
- fbuild/daemon/firmware_ledger.py +46 -123
- fbuild/daemon/lock_manager.py +508 -508
- fbuild/daemon/messages.py +431 -0
- fbuild/daemon/operation_registry.py +288 -288
- fbuild/daemon/processors/build_processor.py +34 -1
- fbuild/daemon/processors/deploy_processor.py +1 -3
- fbuild/daemon/processors/locking_processor.py +7 -7
- fbuild/daemon/request_processor.py +457 -457
- fbuild/daemon/shared_serial.py +7 -7
- fbuild/daemon/status_manager.py +238 -238
- fbuild/daemon/subprocess_manager.py +316 -316
- fbuild/deploy/docker_utils.py +182 -2
- fbuild/deploy/monitor.py +1 -1
- fbuild/deploy/qemu_runner.py +71 -13
- fbuild/ledger/board_ledger.py +46 -122
- fbuild/output.py +238 -2
- fbuild/packages/library_compiler.py +15 -5
- fbuild/packages/library_manager.py +12 -6
- fbuild-1.2.15.dist-info/METADATA +569 -0
- {fbuild-1.2.8.dist-info → fbuild-1.2.15.dist-info}/RECORD +46 -39
- fbuild-1.2.8.dist-info/METADATA +0 -468
- {fbuild-1.2.8.dist-info → fbuild-1.2.15.dist-info}/WHEEL +0 -0
- {fbuild-1.2.8.dist-info → fbuild-1.2.15.dist-info}/entry_points.txt +0 -0
- {fbuild-1.2.8.dist-info → fbuild-1.2.15.dist-info}/licenses/LICENSE +0 -0
- {fbuild-1.2.8.dist-info → fbuild-1.2.15.dist-info}/top_level.txt +0 -0
fbuild/daemon/firmware_ledger.py
CHANGED
|
@@ -3,17 +3,18 @@ Firmware Ledger - Track deployed firmware on devices.
|
|
|
3
3
|
|
|
4
4
|
This module provides a ledger to track what firmware is currently deployed on each
|
|
5
5
|
device/port, allowing clients to skip re-upload if the same firmware is already running.
|
|
6
|
-
The cache is stored in ~/.fbuild/firmware_ledger.json (or dev path if FBUILD_DEV_MODE)
|
|
7
|
-
and uses file locking for thread-safe access.
|
|
6
|
+
The cache is stored in ~/.fbuild/firmware_ledger.json (or dev path if FBUILD_DEV_MODE).
|
|
8
7
|
|
|
9
8
|
Features:
|
|
10
9
|
- Port to firmware hash mapping with timestamps
|
|
11
10
|
- Source file hash tracking for change detection
|
|
12
11
|
- Build flags hash for build configuration tracking
|
|
13
12
|
- Automatic stale entry expiration (configurable, default 24 hours)
|
|
14
|
-
- Thread-safe
|
|
13
|
+
- Thread-safe in-process access via threading.Lock
|
|
15
14
|
- Skip re-upload when firmware matches what's deployed
|
|
16
15
|
|
|
16
|
+
Note: Cross-process synchronization is handled by the daemon which holds locks in memory.
|
|
17
|
+
|
|
17
18
|
Example:
|
|
18
19
|
>>> from fbuild.daemon.firmware_ledger import FirmwareLedger, compute_firmware_hash
|
|
19
20
|
>>>
|
|
@@ -30,7 +31,6 @@ Example:
|
|
|
30
31
|
import hashlib
|
|
31
32
|
import json
|
|
32
33
|
import os
|
|
33
|
-
import sys
|
|
34
34
|
import threading
|
|
35
35
|
import time
|
|
36
36
|
from dataclasses import dataclass
|
|
@@ -131,7 +131,8 @@ class FirmwareLedger:
|
|
|
131
131
|
"""Manages port to firmware mapping with persistent storage.
|
|
132
132
|
|
|
133
133
|
The ledger stores mappings in ~/.fbuild/firmware_ledger.json (or dev path)
|
|
134
|
-
and provides thread-safe access through
|
|
134
|
+
and provides thread-safe in-process access through threading.Lock.
|
|
135
|
+
Cross-process synchronization is handled by the daemon which holds locks in memory.
|
|
135
136
|
|
|
136
137
|
Example:
|
|
137
138
|
>>> ledger = FirmwareLedger()
|
|
@@ -196,60 +197,6 @@ class FirmwareLedger:
|
|
|
196
197
|
except OSError as e:
|
|
197
198
|
raise FirmwareLedgerError(f"Failed to write ledger: {e}") from e
|
|
198
199
|
|
|
199
|
-
def _acquire_file_lock(self) -> Any:
|
|
200
|
-
"""Acquire a file lock for cross-process synchronization.
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
Lock file handle (or None on platforms without locking support)
|
|
204
|
-
"""
|
|
205
|
-
self._ensure_directory()
|
|
206
|
-
lock_path = self._ledger_path.with_suffix(".lock")
|
|
207
|
-
|
|
208
|
-
try:
|
|
209
|
-
# Open lock file
|
|
210
|
-
lock_file = open(lock_path, "w", encoding="utf-8")
|
|
211
|
-
|
|
212
|
-
# Platform-specific locking
|
|
213
|
-
if sys.platform == "win32":
|
|
214
|
-
import msvcrt
|
|
215
|
-
|
|
216
|
-
msvcrt.locking(lock_file.fileno(), msvcrt.LK_NBLCK, 1)
|
|
217
|
-
else: # pragma: no cover - Unix only
|
|
218
|
-
import fcntl # type: ignore[import-not-found]
|
|
219
|
-
|
|
220
|
-
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
|
|
221
|
-
|
|
222
|
-
return lock_file
|
|
223
|
-
except (ImportError, OSError):
|
|
224
|
-
# Locking not available or failed - continue without lock
|
|
225
|
-
return None
|
|
226
|
-
|
|
227
|
-
def _release_file_lock(self, lock_file: Any) -> None:
|
|
228
|
-
"""Release a file lock.
|
|
229
|
-
|
|
230
|
-
Args:
|
|
231
|
-
lock_file: Lock file handle from _acquire_file_lock
|
|
232
|
-
"""
|
|
233
|
-
if lock_file is None:
|
|
234
|
-
return
|
|
235
|
-
|
|
236
|
-
try:
|
|
237
|
-
if sys.platform == "win32":
|
|
238
|
-
import msvcrt
|
|
239
|
-
|
|
240
|
-
try:
|
|
241
|
-
msvcrt.locking(lock_file.fileno(), msvcrt.LK_UNLCK, 1)
|
|
242
|
-
except OSError:
|
|
243
|
-
pass
|
|
244
|
-
else: # pragma: no cover - Unix only
|
|
245
|
-
import fcntl # type: ignore[import-not-found]
|
|
246
|
-
|
|
247
|
-
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
|
248
|
-
|
|
249
|
-
lock_file.close()
|
|
250
|
-
except (ImportError, OSError):
|
|
251
|
-
pass
|
|
252
|
-
|
|
253
200
|
def record_deployment(
|
|
254
201
|
self,
|
|
255
202
|
port: str,
|
|
@@ -280,13 +227,9 @@ class FirmwareLedger:
|
|
|
280
227
|
)
|
|
281
228
|
|
|
282
229
|
with self._lock:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
data[port] = entry.to_dict()
|
|
287
|
-
self._write_ledger(data)
|
|
288
|
-
finally:
|
|
289
|
-
self._release_file_lock(lock_file)
|
|
230
|
+
data = self._read_ledger()
|
|
231
|
+
data[port] = entry.to_dict()
|
|
232
|
+
self._write_ledger(data)
|
|
290
233
|
|
|
291
234
|
def get_deployment(self, port: str) -> FirmwareEntry | None:
|
|
292
235
|
"""Get the deployment entry for a port.
|
|
@@ -298,20 +241,16 @@ class FirmwareLedger:
|
|
|
298
241
|
FirmwareEntry or None if not found or stale
|
|
299
242
|
"""
|
|
300
243
|
with self._lock:
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if entry_data is None:
|
|
306
|
-
return None
|
|
244
|
+
data = self._read_ledger()
|
|
245
|
+
entry_data = data.get(port)
|
|
246
|
+
if entry_data is None:
|
|
247
|
+
return None
|
|
307
248
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
249
|
+
entry = FirmwareEntry.from_dict(entry_data)
|
|
250
|
+
if entry.is_stale():
|
|
251
|
+
return None
|
|
311
252
|
|
|
312
|
-
|
|
313
|
-
finally:
|
|
314
|
-
self._release_file_lock(lock_file)
|
|
253
|
+
return entry
|
|
315
254
|
|
|
316
255
|
def is_current(
|
|
317
256
|
self,
|
|
@@ -384,16 +323,12 @@ class FirmwareLedger:
|
|
|
384
323
|
True if entry was cleared, False if not found
|
|
385
324
|
"""
|
|
386
325
|
with self._lock:
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
data
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return True
|
|
394
|
-
return False
|
|
395
|
-
finally:
|
|
396
|
-
self._release_file_lock(lock_file)
|
|
326
|
+
data = self._read_ledger()
|
|
327
|
+
if port in data:
|
|
328
|
+
del data[port]
|
|
329
|
+
self._write_ledger(data)
|
|
330
|
+
return True
|
|
331
|
+
return False
|
|
397
332
|
|
|
398
333
|
def clear_all(self) -> int:
|
|
399
334
|
"""Clear all entries from the ledger.
|
|
@@ -402,14 +337,10 @@ class FirmwareLedger:
|
|
|
402
337
|
Number of entries cleared
|
|
403
338
|
"""
|
|
404
339
|
with self._lock:
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
self._write_ledger({})
|
|
410
|
-
return count
|
|
411
|
-
finally:
|
|
412
|
-
self._release_file_lock(lock_file)
|
|
340
|
+
data = self._read_ledger()
|
|
341
|
+
count = len(data)
|
|
342
|
+
self._write_ledger({})
|
|
343
|
+
return count
|
|
413
344
|
|
|
414
345
|
def clear_stale(
|
|
415
346
|
self,
|
|
@@ -424,22 +355,18 @@ class FirmwareLedger:
|
|
|
424
355
|
Number of entries removed
|
|
425
356
|
"""
|
|
426
357
|
with self._lock:
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
self._write_ledger(fresh_data)
|
|
440
|
-
return original_count - len(fresh_data)
|
|
441
|
-
finally:
|
|
442
|
-
self._release_file_lock(lock_file)
|
|
358
|
+
data = self._read_ledger()
|
|
359
|
+
original_count = len(data)
|
|
360
|
+
|
|
361
|
+
# Filter out stale entries
|
|
362
|
+
fresh_data = {}
|
|
363
|
+
for port, entry_data in data.items():
|
|
364
|
+
entry = FirmwareEntry.from_dict(entry_data)
|
|
365
|
+
if not entry.is_stale(threshold_seconds):
|
|
366
|
+
fresh_data[port] = entry_data
|
|
367
|
+
|
|
368
|
+
self._write_ledger(fresh_data)
|
|
369
|
+
return original_count - len(fresh_data)
|
|
443
370
|
|
|
444
371
|
def get_all(self) -> dict[str, FirmwareEntry]:
|
|
445
372
|
"""Get all non-stale entries in the ledger.
|
|
@@ -448,17 +375,13 @@ class FirmwareLedger:
|
|
|
448
375
|
Dictionary mapping port names to FirmwareEntry objects
|
|
449
376
|
"""
|
|
450
377
|
with self._lock:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
result[port] = entry
|
|
459
|
-
return result
|
|
460
|
-
finally:
|
|
461
|
-
self._release_file_lock(lock_file)
|
|
378
|
+
data = self._read_ledger()
|
|
379
|
+
result = {}
|
|
380
|
+
for port, entry_data in data.items():
|
|
381
|
+
entry = FirmwareEntry.from_dict(entry_data)
|
|
382
|
+
if not entry.is_stale():
|
|
383
|
+
result[port] = entry
|
|
384
|
+
return result
|
|
462
385
|
|
|
463
386
|
|
|
464
387
|
def compute_firmware_hash(firmware_path: Path) -> str:
|