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,325 @@
|
|
|
1
|
+
"""Build state tracking for cache invalidation.
|
|
2
|
+
|
|
3
|
+
This module tracks build configuration state to detect when builds need to be
|
|
4
|
+
invalidated due to changes in:
|
|
5
|
+
- platformio.ini configuration
|
|
6
|
+
- Framework versions
|
|
7
|
+
- Platform versions
|
|
8
|
+
- Toolchain versions
|
|
9
|
+
- Library dependencies
|
|
10
|
+
|
|
11
|
+
Design:
|
|
12
|
+
- Stores build state metadata in .fbuild/build/{env_name}/build_state.json
|
|
13
|
+
- Compares current state with saved state to detect changes
|
|
14
|
+
- Provides clear reasons when invalidation is needed
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Dict, List, Optional, Tuple
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BuildStateError(Exception):
|
|
24
|
+
"""Raised when build state operations fail."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BuildState:
|
|
29
|
+
"""Represents the current state of a build configuration."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
platformio_ini_hash: str,
|
|
34
|
+
platform: str,
|
|
35
|
+
board: str,
|
|
36
|
+
framework: str,
|
|
37
|
+
toolchain_version: Optional[str] = None,
|
|
38
|
+
framework_version: Optional[str] = None,
|
|
39
|
+
platform_version: Optional[str] = None,
|
|
40
|
+
build_flags: Optional[List[str]] = None,
|
|
41
|
+
lib_deps: Optional[List[str]] = None,
|
|
42
|
+
):
|
|
43
|
+
"""Initialize build state.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
platformio_ini_hash: SHA256 hash of platformio.ini content
|
|
47
|
+
platform: Platform name (e.g., 'atmelavr', 'espressif32')
|
|
48
|
+
board: Board ID (e.g., 'uno', 'esp32dev')
|
|
49
|
+
framework: Framework name (e.g., 'arduino')
|
|
50
|
+
toolchain_version: Version of toolchain (e.g., '7.3.0-atmel3.6.1-arduino7')
|
|
51
|
+
framework_version: Version of framework (e.g., '1.8.6')
|
|
52
|
+
platform_version: Version of platform package
|
|
53
|
+
build_flags: List of build flags from platformio.ini
|
|
54
|
+
lib_deps: List of library dependencies from platformio.ini
|
|
55
|
+
"""
|
|
56
|
+
self.platformio_ini_hash = platformio_ini_hash
|
|
57
|
+
self.platform = platform
|
|
58
|
+
self.board = board
|
|
59
|
+
self.framework = framework
|
|
60
|
+
self.toolchain_version = toolchain_version
|
|
61
|
+
self.framework_version = framework_version
|
|
62
|
+
self.platform_version = platform_version
|
|
63
|
+
self.build_flags = build_flags or []
|
|
64
|
+
self.lib_deps = lib_deps or []
|
|
65
|
+
|
|
66
|
+
def to_dict(self) -> Dict:
|
|
67
|
+
"""Convert to dictionary for JSON serialization."""
|
|
68
|
+
return {
|
|
69
|
+
"platformio_ini_hash": self.platformio_ini_hash,
|
|
70
|
+
"platform": self.platform,
|
|
71
|
+
"board": self.board,
|
|
72
|
+
"framework": self.framework,
|
|
73
|
+
"toolchain_version": self.toolchain_version,
|
|
74
|
+
"framework_version": self.framework_version,
|
|
75
|
+
"platform_version": self.platform_version,
|
|
76
|
+
"build_flags": self.build_flags,
|
|
77
|
+
"lib_deps": self.lib_deps,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_dict(cls, data: Dict) -> "BuildState":
|
|
82
|
+
"""Create from dictionary."""
|
|
83
|
+
return cls(
|
|
84
|
+
platformio_ini_hash=data["platformio_ini_hash"],
|
|
85
|
+
platform=data["platform"],
|
|
86
|
+
board=data["board"],
|
|
87
|
+
framework=data["framework"],
|
|
88
|
+
toolchain_version=data.get("toolchain_version"),
|
|
89
|
+
framework_version=data.get("framework_version"),
|
|
90
|
+
platform_version=data.get("platform_version"),
|
|
91
|
+
build_flags=data.get("build_flags", []),
|
|
92
|
+
lib_deps=data.get("lib_deps", []),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def save(self, path: Path) -> None:
|
|
96
|
+
"""Save build state to JSON file.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
path: Path to build_state.json file
|
|
100
|
+
"""
|
|
101
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
103
|
+
json.dump(self.to_dict(), f, indent=2)
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def load(cls, path: Path) -> Optional["BuildState"]:
|
|
107
|
+
"""Load build state from JSON file.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
path: Path to build_state.json file
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
BuildState instance or None if file doesn't exist
|
|
114
|
+
"""
|
|
115
|
+
if not path.exists():
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
120
|
+
data = json.load(f)
|
|
121
|
+
return cls.from_dict(data)
|
|
122
|
+
except (json.JSONDecodeError, KeyError):
|
|
123
|
+
# Corrupted state file - return None to trigger rebuild
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
def compare(self, other: Optional["BuildState"]) -> Tuple[bool, List[str]]:
|
|
127
|
+
"""Compare this state with another state.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
other: Previous build state (or None if no previous build)
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Tuple of (needs_rebuild, reasons)
|
|
134
|
+
- needs_rebuild: True if build needs to be invalidated
|
|
135
|
+
- reasons: List of human-readable reasons for invalidation
|
|
136
|
+
"""
|
|
137
|
+
if other is None:
|
|
138
|
+
return True, ["No previous build state found"]
|
|
139
|
+
|
|
140
|
+
reasons = []
|
|
141
|
+
|
|
142
|
+
# Check platformio.ini changes
|
|
143
|
+
if self.platformio_ini_hash != other.platformio_ini_hash:
|
|
144
|
+
reasons.append("platformio.ini has changed")
|
|
145
|
+
|
|
146
|
+
# Check platform changes
|
|
147
|
+
if self.platform != other.platform:
|
|
148
|
+
reasons.append(f"Platform changed: {other.platform} -> {self.platform}")
|
|
149
|
+
|
|
150
|
+
# Check board changes
|
|
151
|
+
if self.board != other.board:
|
|
152
|
+
reasons.append(f"Board changed: {other.board} -> {self.board}")
|
|
153
|
+
|
|
154
|
+
# Check framework changes
|
|
155
|
+
if self.framework != other.framework:
|
|
156
|
+
reasons.append(f"Framework changed: {other.framework} -> {self.framework}")
|
|
157
|
+
|
|
158
|
+
# Check toolchain version changes
|
|
159
|
+
if self.toolchain_version != other.toolchain_version:
|
|
160
|
+
reasons.append(
|
|
161
|
+
f"Toolchain version changed: {other.toolchain_version} -> {self.toolchain_version}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Check framework version changes
|
|
165
|
+
if self.framework_version != other.framework_version:
|
|
166
|
+
reasons.append(
|
|
167
|
+
f"Framework version changed: {other.framework_version} -> {self.framework_version}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Check platform version changes
|
|
171
|
+
if self.platform_version != other.platform_version:
|
|
172
|
+
reasons.append(
|
|
173
|
+
f"Platform version changed: {other.platform_version} -> {self.platform_version}"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Check build flags changes
|
|
177
|
+
if set(self.build_flags) != set(other.build_flags):
|
|
178
|
+
reasons.append("Build flags have changed")
|
|
179
|
+
|
|
180
|
+
# Check library dependencies changes
|
|
181
|
+
if set(self.lib_deps) != set(other.lib_deps):
|
|
182
|
+
reasons.append("Library dependencies have changed")
|
|
183
|
+
|
|
184
|
+
needs_rebuild = len(reasons) > 0
|
|
185
|
+
return needs_rebuild, reasons
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class BuildStateTracker:
|
|
189
|
+
"""Tracks build state for cache invalidation."""
|
|
190
|
+
|
|
191
|
+
def __init__(self, build_dir: Path):
|
|
192
|
+
"""Initialize build state tracker.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
build_dir: Build directory (.fbuild/build/{env_name})
|
|
196
|
+
"""
|
|
197
|
+
self.build_dir = Path(build_dir)
|
|
198
|
+
self.state_file = self.build_dir / "build_state.json"
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def hash_file(path: Path) -> str:
|
|
202
|
+
"""Calculate SHA256 hash of a file.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
path: Path to file
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
SHA256 hash as hex string
|
|
209
|
+
"""
|
|
210
|
+
sha256 = hashlib.sha256()
|
|
211
|
+
with open(path, "rb") as f:
|
|
212
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
213
|
+
sha256.update(chunk)
|
|
214
|
+
return sha256.hexdigest()
|
|
215
|
+
|
|
216
|
+
def create_state(
|
|
217
|
+
self,
|
|
218
|
+
platformio_ini_path: Path,
|
|
219
|
+
platform: str,
|
|
220
|
+
board: str,
|
|
221
|
+
framework: str,
|
|
222
|
+
toolchain_version: Optional[str] = None,
|
|
223
|
+
framework_version: Optional[str] = None,
|
|
224
|
+
platform_version: Optional[str] = None,
|
|
225
|
+
build_flags: Optional[List[str]] = None,
|
|
226
|
+
lib_deps: Optional[List[str]] = None,
|
|
227
|
+
) -> BuildState:
|
|
228
|
+
"""Create a BuildState from current configuration.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
platformio_ini_path: Path to platformio.ini file
|
|
232
|
+
platform: Platform name
|
|
233
|
+
board: Board ID
|
|
234
|
+
framework: Framework name
|
|
235
|
+
toolchain_version: Toolchain version
|
|
236
|
+
framework_version: Framework version
|
|
237
|
+
platform_version: Platform version
|
|
238
|
+
build_flags: Build flags from platformio.ini
|
|
239
|
+
lib_deps: Library dependencies from platformio.ini
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
BuildState instance representing current configuration
|
|
243
|
+
"""
|
|
244
|
+
# Hash platformio.ini
|
|
245
|
+
ini_hash = self.hash_file(platformio_ini_path)
|
|
246
|
+
|
|
247
|
+
return BuildState(
|
|
248
|
+
platformio_ini_hash=ini_hash,
|
|
249
|
+
platform=platform,
|
|
250
|
+
board=board,
|
|
251
|
+
framework=framework,
|
|
252
|
+
toolchain_version=toolchain_version,
|
|
253
|
+
framework_version=framework_version,
|
|
254
|
+
platform_version=platform_version,
|
|
255
|
+
build_flags=build_flags,
|
|
256
|
+
lib_deps=lib_deps,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def load_previous_state(self) -> Optional[BuildState]:
|
|
260
|
+
"""Load the previous build state.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Previous BuildState or None if no previous build
|
|
264
|
+
"""
|
|
265
|
+
return BuildState.load(self.state_file)
|
|
266
|
+
|
|
267
|
+
def save_state(self, state: BuildState) -> None:
|
|
268
|
+
"""Save the current build state.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
state: BuildState to save
|
|
272
|
+
"""
|
|
273
|
+
state.save(self.state_file)
|
|
274
|
+
|
|
275
|
+
def check_invalidation(
|
|
276
|
+
self,
|
|
277
|
+
platformio_ini_path: Path,
|
|
278
|
+
platform: str,
|
|
279
|
+
board: str,
|
|
280
|
+
framework: str,
|
|
281
|
+
toolchain_version: Optional[str] = None,
|
|
282
|
+
framework_version: Optional[str] = None,
|
|
283
|
+
platform_version: Optional[str] = None,
|
|
284
|
+
build_flags: Optional[List[str]] = None,
|
|
285
|
+
lib_deps: Optional[List[str]] = None,
|
|
286
|
+
) -> Tuple[bool, List[str], BuildState]:
|
|
287
|
+
"""Check if build cache should be invalidated.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
platformio_ini_path: Path to platformio.ini file
|
|
291
|
+
platform: Current platform name
|
|
292
|
+
board: Current board ID
|
|
293
|
+
framework: Current framework name
|
|
294
|
+
toolchain_version: Current toolchain version
|
|
295
|
+
framework_version: Current framework version
|
|
296
|
+
platform_version: Current platform version
|
|
297
|
+
build_flags: Current build flags
|
|
298
|
+
lib_deps: Current library dependencies
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Tuple of (needs_rebuild, reasons, current_state)
|
|
302
|
+
- needs_rebuild: True if build should be invalidated
|
|
303
|
+
- reasons: List of reasons for invalidation
|
|
304
|
+
- current_state: Current BuildState (for saving after build)
|
|
305
|
+
"""
|
|
306
|
+
# Create current state
|
|
307
|
+
current_state = self.create_state(
|
|
308
|
+
platformio_ini_path=platformio_ini_path,
|
|
309
|
+
platform=platform,
|
|
310
|
+
board=board,
|
|
311
|
+
framework=framework,
|
|
312
|
+
toolchain_version=toolchain_version,
|
|
313
|
+
framework_version=framework_version,
|
|
314
|
+
platform_version=platform_version,
|
|
315
|
+
build_flags=build_flags,
|
|
316
|
+
lib_deps=lib_deps,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Load previous state
|
|
320
|
+
previous_state = self.load_previous_state()
|
|
321
|
+
|
|
322
|
+
# Compare states
|
|
323
|
+
needs_rebuild, reasons = current_state.compare(previous_state)
|
|
324
|
+
|
|
325
|
+
return needs_rebuild, reasons, current_state
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Build utilities for Fbuild.
|
|
2
|
+
|
|
3
|
+
This module provides utility functions for build operations like
|
|
4
|
+
printing size information and formatting build output.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import stat
|
|
9
|
+
import shutil
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Callable, Optional
|
|
12
|
+
|
|
13
|
+
from ..build.linker import SizeInfo
|
|
14
|
+
from ..output import log_size_info
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SizeInfoPrinter:
|
|
18
|
+
"""Utility class for printing firmware size information."""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def print_size_info(size_info: Optional[SizeInfo]) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Print firmware size information in a formatted display.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
size_info: Size information from linker (None to skip printing)
|
|
27
|
+
"""
|
|
28
|
+
if not size_info:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
log_size_info(
|
|
32
|
+
program_bytes=size_info.total_flash,
|
|
33
|
+
program_percent=size_info.flash_percent,
|
|
34
|
+
max_flash=size_info.max_flash,
|
|
35
|
+
data_bytes=size_info.data,
|
|
36
|
+
bss_bytes=size_info.bss,
|
|
37
|
+
ram_bytes=size_info.total_ram,
|
|
38
|
+
ram_percent=size_info.ram_percent,
|
|
39
|
+
max_ram=size_info.max_ram
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def remove_readonly(func: Callable[[str], None], path: str, excinfo: Any) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Error handler for shutil.rmtree on Windows.
|
|
46
|
+
|
|
47
|
+
On Windows, read-only files cannot be deleted and will cause
|
|
48
|
+
shutil.rmtree to fail. This handler removes the read-only attribute
|
|
49
|
+
and retries the operation.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
func: The function that raised the exception
|
|
53
|
+
path: The path to the file/directory
|
|
54
|
+
excinfo: Exception information (unused)
|
|
55
|
+
"""
|
|
56
|
+
os.chmod(path, stat.S_IWRITE)
|
|
57
|
+
func(path)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def safe_rmtree(path: Path, max_retries: int = 3) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Safely remove a directory tree, handling Windows-specific issues.
|
|
63
|
+
|
|
64
|
+
This function handles common Windows issues with directory deletion:
|
|
65
|
+
- Read-only files
|
|
66
|
+
- Locked files (with retries)
|
|
67
|
+
- Hidden system files
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
path: Path to directory to remove
|
|
71
|
+
max_retries: Maximum number of retry attempts for locked files
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
OSError: If directory cannot be removed after all retries
|
|
75
|
+
"""
|
|
76
|
+
import time
|
|
77
|
+
|
|
78
|
+
if not path.exists():
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
for attempt in range(max_retries):
|
|
82
|
+
try:
|
|
83
|
+
shutil.rmtree(path, onerror=remove_readonly)
|
|
84
|
+
return
|
|
85
|
+
except OSError as e:
|
|
86
|
+
if attempt < max_retries - 1:
|
|
87
|
+
# Wait a bit and retry (files might be temporarily locked)
|
|
88
|
+
time.sleep(0.5)
|
|
89
|
+
else:
|
|
90
|
+
# Last attempt failed, raise the error
|
|
91
|
+
raise OSError(
|
|
92
|
+
f"Failed to remove directory {path} after {max_retries} attempts: {e}"
|
|
93
|
+
) from e
|