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
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""Arduino core platform management.
|
|
2
|
+
|
|
3
|
+
This module handles downloading and managing Arduino core platforms
|
|
4
|
+
(e.g., ArduinoCore-avr) required for building Arduino sketches.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
from .cache import Cache
|
|
11
|
+
from .downloader import PackageDownloader
|
|
12
|
+
from .package import IFramework, PackageError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArduinoCoreError(PackageError):
|
|
16
|
+
"""Raised when Arduino core operations fail."""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ArduinoCore(IFramework):
|
|
22
|
+
"""Manages Arduino core platform packages."""
|
|
23
|
+
|
|
24
|
+
# Arduino AVR core version
|
|
25
|
+
AVR_VERSION = "1.8.6"
|
|
26
|
+
|
|
27
|
+
# Package URL and checksum
|
|
28
|
+
# The Arduino AVR core is hosted on GitHub
|
|
29
|
+
AVR_URL = f"https://github.com/arduino/ArduinoCore-avr/archive/refs/tags/{AVR_VERSION}.tar.gz"
|
|
30
|
+
AVR_CHECKSUM = "49241fd5e504482b94954b5843c7d69ce38ebc1ab47ad3b677e8bb77e0cb8fe6"
|
|
31
|
+
|
|
32
|
+
def __init__(self, cache: Cache):
|
|
33
|
+
"""Initialize Arduino core manager.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
cache: Cache instance for storing cores
|
|
37
|
+
"""
|
|
38
|
+
self.cache = cache
|
|
39
|
+
self.downloader = PackageDownloader()
|
|
40
|
+
self._core_path: Optional[Path] = None
|
|
41
|
+
|
|
42
|
+
def ensure_package(self) -> Path:
|
|
43
|
+
"""Ensure Arduino AVR core is available.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Path to Arduino AVR core directory
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ArduinoCoreError: If core cannot be obtained
|
|
50
|
+
"""
|
|
51
|
+
return self.ensure_avr_core()
|
|
52
|
+
|
|
53
|
+
def ensure_avr_core(self, force_download: bool = False) -> Path:
|
|
54
|
+
"""Ensure Arduino AVR core is available.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
force_download: Force re-download even if cached
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Path to Arduino AVR core directory
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ArduinoCoreError: If core cannot be obtained
|
|
64
|
+
"""
|
|
65
|
+
# Check if already loaded
|
|
66
|
+
if self._core_path and not force_download:
|
|
67
|
+
return self._core_path
|
|
68
|
+
|
|
69
|
+
# Use URL-based caching
|
|
70
|
+
core_path = self.cache.get_platform_path(self.AVR_URL, self.AVR_VERSION)
|
|
71
|
+
package_name = f"avr-{self.AVR_VERSION}.tar.gz"
|
|
72
|
+
package_path = self.cache.get_package_path(self.AVR_URL, self.AVR_VERSION, package_name)
|
|
73
|
+
|
|
74
|
+
# Check if already extracted and valid
|
|
75
|
+
if not force_download and self.cache.is_platform_cached(self.AVR_URL, self.AVR_VERSION):
|
|
76
|
+
if self._verify_core(core_path):
|
|
77
|
+
self._core_path = core_path
|
|
78
|
+
return core_path
|
|
79
|
+
else:
|
|
80
|
+
print("Cached Arduino core failed validation, re-downloading...")
|
|
81
|
+
|
|
82
|
+
# Need to download and extract
|
|
83
|
+
self.cache.ensure_directories()
|
|
84
|
+
|
|
85
|
+
print(f"Downloading Arduino AVR core ({self.AVR_VERSION})...")
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# Ensure package directory exists
|
|
89
|
+
package_path.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
|
|
91
|
+
# Download if not cached
|
|
92
|
+
if force_download or not package_path.exists():
|
|
93
|
+
self.downloader.download(self.AVR_URL, package_path, self.AVR_CHECKSUM)
|
|
94
|
+
else:
|
|
95
|
+
print(f"Using cached {package_name}")
|
|
96
|
+
|
|
97
|
+
# Extract
|
|
98
|
+
print("Extracting Arduino core...")
|
|
99
|
+
core_path.parent.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
|
|
101
|
+
# Extract to temporary location first
|
|
102
|
+
import shutil
|
|
103
|
+
import tempfile
|
|
104
|
+
|
|
105
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
106
|
+
temp_path = Path(temp_dir)
|
|
107
|
+
self.downloader.extract_archive(package_path, temp_path, show_progress=False)
|
|
108
|
+
|
|
109
|
+
# The archive extracts to avr/ subdirectory
|
|
110
|
+
extracted_dir = temp_path / "avr"
|
|
111
|
+
if not extracted_dir.exists():
|
|
112
|
+
# If not in avr/ subdirectory, use first directory found
|
|
113
|
+
extracted_dirs = [d for d in temp_path.iterdir() if d.is_dir()]
|
|
114
|
+
if extracted_dirs:
|
|
115
|
+
extracted_dir = extracted_dirs[0]
|
|
116
|
+
else:
|
|
117
|
+
raise ArduinoCoreError("No directory found in extracted archive")
|
|
118
|
+
|
|
119
|
+
# Move to final location
|
|
120
|
+
if core_path.exists():
|
|
121
|
+
shutil.rmtree(core_path)
|
|
122
|
+
shutil.move(str(extracted_dir), str(core_path))
|
|
123
|
+
|
|
124
|
+
# Verify installation
|
|
125
|
+
if not self._verify_core(core_path):
|
|
126
|
+
raise ArduinoCoreError("Core verification failed after extraction")
|
|
127
|
+
|
|
128
|
+
self._core_path = core_path
|
|
129
|
+
print(f"Arduino core ready at {core_path}")
|
|
130
|
+
return core_path
|
|
131
|
+
|
|
132
|
+
except KeyboardInterrupt as ke:
|
|
133
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
134
|
+
|
|
135
|
+
handle_keyboard_interrupt_properly(ke)
|
|
136
|
+
raise # Never reached, but satisfies type checker
|
|
137
|
+
except Exception as e:
|
|
138
|
+
raise ArduinoCoreError(f"Failed to setup Arduino core: {e}")
|
|
139
|
+
|
|
140
|
+
def is_installed(self) -> bool:
|
|
141
|
+
"""Check if Arduino core is already installed.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
True if core is installed and valid
|
|
145
|
+
"""
|
|
146
|
+
if not self._core_path:
|
|
147
|
+
# Check cache
|
|
148
|
+
core_path = self.cache.get_platform_path(self.AVR_URL, self.AVR_VERSION)
|
|
149
|
+
if core_path.exists():
|
|
150
|
+
return self._verify_core(core_path)
|
|
151
|
+
return False
|
|
152
|
+
return self._verify_core(self._core_path)
|
|
153
|
+
|
|
154
|
+
def get_package_info(self) -> Dict[str, Any]:
|
|
155
|
+
"""Get information about the installed core.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Dictionary with core information
|
|
159
|
+
"""
|
|
160
|
+
info = {
|
|
161
|
+
"version": self.AVR_VERSION,
|
|
162
|
+
"url": self.AVR_URL,
|
|
163
|
+
"installed": self.is_installed(),
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if self._core_path:
|
|
167
|
+
info["path"] = str(self._core_path)
|
|
168
|
+
info["cores_dir"] = str(self.get_cores_dir())
|
|
169
|
+
info["variants_dir"] = str(self.get_variants_dir())
|
|
170
|
+
|
|
171
|
+
return info
|
|
172
|
+
|
|
173
|
+
def _verify_core(self, core_path: Path) -> bool:
|
|
174
|
+
"""Comprehensively verify Arduino core is complete.
|
|
175
|
+
|
|
176
|
+
Checks for:
|
|
177
|
+
- Required directories (cores/arduino, variants)
|
|
178
|
+
- Configuration files (boards.txt, platform.txt)
|
|
179
|
+
- Key core source files
|
|
180
|
+
- Key header files
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
core_path: Path to core directory
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
True if core appears valid
|
|
187
|
+
"""
|
|
188
|
+
# Check for essential directories
|
|
189
|
+
required_dirs = [
|
|
190
|
+
"cores/arduino",
|
|
191
|
+
"variants",
|
|
192
|
+
"variants/standard", # Uno variant
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
for dir_path in required_dirs:
|
|
196
|
+
if not (core_path / dir_path).exists():
|
|
197
|
+
print(f"Missing directory: {dir_path}")
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
# Check for essential configuration files
|
|
201
|
+
required_files = [
|
|
202
|
+
"boards.txt",
|
|
203
|
+
"platform.txt",
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
for file_path in required_files:
|
|
207
|
+
if not (core_path / file_path).exists():
|
|
208
|
+
print(f"Missing file: {file_path}")
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
# Check for key core header files
|
|
212
|
+
required_headers = [
|
|
213
|
+
"cores/arduino/Arduino.h",
|
|
214
|
+
"cores/arduino/HardwareSerial.h",
|
|
215
|
+
"variants/standard/pins_arduino.h",
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
for header in required_headers:
|
|
219
|
+
if not (core_path / header).exists():
|
|
220
|
+
print(f"Missing header: {header}")
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
# Check for key core source files
|
|
224
|
+
required_sources = [
|
|
225
|
+
"cores/arduino/main.cpp",
|
|
226
|
+
"cores/arduino/wiring.c",
|
|
227
|
+
"cores/arduino/wiring_digital.c",
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
for source in required_sources:
|
|
231
|
+
if not (core_path / source).exists():
|
|
232
|
+
print(f"Missing source: {source}")
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
# Verify boards.txt contains uno configuration
|
|
236
|
+
boards_txt = core_path / "boards.txt"
|
|
237
|
+
try:
|
|
238
|
+
content = boards_txt.read_text(encoding="utf-8", errors="ignore")
|
|
239
|
+
if "uno.name" not in content:
|
|
240
|
+
print("boards.txt missing uno configuration")
|
|
241
|
+
return False
|
|
242
|
+
except KeyboardInterrupt as ke:
|
|
243
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
244
|
+
|
|
245
|
+
handle_keyboard_interrupt_properly(ke)
|
|
246
|
+
raise # Never reached, but satisfies type checker
|
|
247
|
+
except Exception as e:
|
|
248
|
+
print(f"Failed to read boards.txt: {e}")
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
def get_boards_txt(self) -> Path:
|
|
254
|
+
"""Get path to boards.txt file.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Path to boards.txt
|
|
258
|
+
|
|
259
|
+
Raises:
|
|
260
|
+
ArduinoCoreError: If core not initialized
|
|
261
|
+
"""
|
|
262
|
+
if not self._core_path:
|
|
263
|
+
raise ArduinoCoreError("Core not initialized. Call ensure_avr_core() first.")
|
|
264
|
+
|
|
265
|
+
boards_txt = self._core_path / "boards.txt"
|
|
266
|
+
if not boards_txt.exists():
|
|
267
|
+
raise ArduinoCoreError("boards.txt not found in core")
|
|
268
|
+
|
|
269
|
+
return boards_txt
|
|
270
|
+
|
|
271
|
+
def get_platform_txt(self) -> Path:
|
|
272
|
+
"""Get path to platform.txt file.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Path to platform.txt
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
ArduinoCoreError: If core not initialized
|
|
279
|
+
"""
|
|
280
|
+
if not self._core_path:
|
|
281
|
+
raise ArduinoCoreError("Core not initialized. Call ensure_avr_core() first.")
|
|
282
|
+
|
|
283
|
+
platform_txt = self._core_path / "platform.txt"
|
|
284
|
+
if not platform_txt.exists():
|
|
285
|
+
raise ArduinoCoreError("platform.txt not found in core")
|
|
286
|
+
|
|
287
|
+
return platform_txt
|
|
288
|
+
|
|
289
|
+
def get_cores_dir(self) -> Path:
|
|
290
|
+
"""Get path to cores directory.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Path to cores directory
|
|
294
|
+
|
|
295
|
+
Raises:
|
|
296
|
+
ArduinoCoreError: If core not initialized
|
|
297
|
+
"""
|
|
298
|
+
if not self._core_path:
|
|
299
|
+
raise ArduinoCoreError("Core not initialized. Call ensure_avr_core() first.")
|
|
300
|
+
|
|
301
|
+
cores_dir = self._core_path / "cores"
|
|
302
|
+
if not cores_dir.exists():
|
|
303
|
+
raise ArduinoCoreError("cores directory not found")
|
|
304
|
+
|
|
305
|
+
return cores_dir
|
|
306
|
+
|
|
307
|
+
def get_variants_dir(self) -> Path:
|
|
308
|
+
"""Get path to variants directory.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Path to variants directory
|
|
312
|
+
|
|
313
|
+
Raises:
|
|
314
|
+
ArduinoCoreError: If core not initialized
|
|
315
|
+
"""
|
|
316
|
+
if not self._core_path:
|
|
317
|
+
raise ArduinoCoreError("Core not initialized. Call ensure_avr_core() first.")
|
|
318
|
+
|
|
319
|
+
variants_dir = self._core_path / "variants"
|
|
320
|
+
if not variants_dir.exists():
|
|
321
|
+
raise ArduinoCoreError("variants directory not found")
|
|
322
|
+
|
|
323
|
+
return variants_dir
|
|
324
|
+
|
|
325
|
+
def get_libraries_dir(self) -> Path:
|
|
326
|
+
"""Get path to built-in libraries directory.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Path to libraries directory
|
|
330
|
+
|
|
331
|
+
Raises:
|
|
332
|
+
ArduinoCoreError: If core not initialized
|
|
333
|
+
"""
|
|
334
|
+
if not self._core_path:
|
|
335
|
+
raise ArduinoCoreError("Core not initialized. Call ensure_avr_core() first.")
|
|
336
|
+
|
|
337
|
+
# Arduino AVR core doesn't have a libraries directory, return a non-existent path
|
|
338
|
+
libraries_dir = self._core_path / "libraries"
|
|
339
|
+
return libraries_dir
|
|
340
|
+
|
|
341
|
+
def get_core_dir(self) -> Path:
|
|
342
|
+
"""Get path to cores/arduino directory.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
Path to core library sources
|
|
346
|
+
|
|
347
|
+
Raises:
|
|
348
|
+
ArduinoCoreError: If core not initialized
|
|
349
|
+
"""
|
|
350
|
+
if not self._core_path:
|
|
351
|
+
raise ArduinoCoreError("Core not initialized. Call ensure_avr_core() first.")
|
|
352
|
+
|
|
353
|
+
core_dir = self._core_path / "cores" / "arduino"
|
|
354
|
+
if not core_dir.exists():
|
|
355
|
+
raise ArduinoCoreError("cores/arduino directory not found")
|
|
356
|
+
|
|
357
|
+
return core_dir
|
|
358
|
+
|
|
359
|
+
def get_variant_dir(self, variant_name: str = "standard") -> Path:
|
|
360
|
+
"""Get path to a board variant directory.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
variant_name: Name of the variant (e.g., 'standard' for Uno)
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Path to variant directory
|
|
367
|
+
|
|
368
|
+
Raises:
|
|
369
|
+
ArduinoCoreError: If core not initialized or variant not found
|
|
370
|
+
"""
|
|
371
|
+
if not self._core_path:
|
|
372
|
+
raise ArduinoCoreError("Core not initialized. Call ensure_avr_core() first.")
|
|
373
|
+
|
|
374
|
+
variant_dir = self._core_path / "variants" / variant_name
|
|
375
|
+
if not variant_dir.exists():
|
|
376
|
+
raise ArduinoCoreError(f"Variant '{variant_name}' not found")
|
|
377
|
+
|
|
378
|
+
return variant_dir
|
|
379
|
+
|
|
380
|
+
def get_core_sources(self) -> list[Path]:
|
|
381
|
+
"""Get list of all core source files (.c and .cpp).
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
List of paths to core source files
|
|
385
|
+
|
|
386
|
+
Raises:
|
|
387
|
+
ArduinoCoreError: If core not initialized
|
|
388
|
+
"""
|
|
389
|
+
core_dir = self.get_core_dir()
|
|
390
|
+
|
|
391
|
+
sources: list[Path] = []
|
|
392
|
+
for pattern in ("*.c", "*.cpp"):
|
|
393
|
+
sources.extend(core_dir.glob(pattern))
|
|
394
|
+
|
|
395
|
+
return sorted(sources)
|
|
396
|
+
|
|
397
|
+
def get_variant_sources(self, variant_name: str = "standard") -> list[Path]:
|
|
398
|
+
"""Get list of variant source files.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
variant_name: Name of the variant
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
List of paths to variant source files
|
|
405
|
+
"""
|
|
406
|
+
variant_dir = self.get_variant_dir(variant_name)
|
|
407
|
+
|
|
408
|
+
sources: list[Path] = []
|
|
409
|
+
for pattern in ("*.c", "*.cpp"):
|
|
410
|
+
sources.extend(variant_dir.glob(pattern))
|
|
411
|
+
|
|
412
|
+
return sorted(sources)
|
fbuild/packages/cache.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Cache management for fbuild packages.
|
|
2
|
+
|
|
3
|
+
This module provides a unified cache structure for storing downloaded
|
|
4
|
+
packages, toolchains, platforms, and build artifacts.
|
|
5
|
+
|
|
6
|
+
Cache Structure:
|
|
7
|
+
.fbuild/
|
|
8
|
+
├── cache/
|
|
9
|
+
│ ├── packages/
|
|
10
|
+
│ │ └── {url_hash}/ # SHA256 hash of base URL
|
|
11
|
+
│ │ └── {version}/ # Version string
|
|
12
|
+
│ │ └── archive # Downloaded archive
|
|
13
|
+
│ ├── toolchains/
|
|
14
|
+
│ │ └── {url_hash}/ # SHA256 hash of base URL
|
|
15
|
+
│ │ └── {version}/ # Version string
|
|
16
|
+
│ │ └── bin/ # Extracted toolchain binaries
|
|
17
|
+
│ ├── platforms/
|
|
18
|
+
│ │ └── {url_hash}/ # SHA256 hash of base URL
|
|
19
|
+
│ │ └── {version}/ # Version string
|
|
20
|
+
│ │ ├── cores/
|
|
21
|
+
│ │ ├── variants/
|
|
22
|
+
│ │ ├── boards.txt
|
|
23
|
+
│ │ └── platform.txt
|
|
24
|
+
│ └── libraries/
|
|
25
|
+
│ └── {url_hash}/ # SHA256 hash of base URL
|
|
26
|
+
│ └── {version}/ # Version string
|
|
27
|
+
└── build/
|
|
28
|
+
└── {env_name}/ # Build output per environment
|
|
29
|
+
├── core/ # Compiled core objects
|
|
30
|
+
├── src/ # Compiled sketch objects
|
|
31
|
+
└── firmware.* # Final firmware files
|
|
32
|
+
|
|
33
|
+
This structure ensures that different versions from the same URL don't
|
|
34
|
+
stomp on each other, and allows multiple sources to coexist.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import hashlib
|
|
38
|
+
import os
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from typing import Optional
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Cache:
|
|
44
|
+
"""Manages the fbuild cache directory structure.
|
|
45
|
+
|
|
46
|
+
The cache can be located in the project directory (.fbuild/) or in a
|
|
47
|
+
global location specified by the FBUILD_CACHE_DIR environment variable.
|
|
48
|
+
|
|
49
|
+
Uses URL hashing to organize cached items, preventing version conflicts.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, project_dir: Optional[Path] = None):
|
|
53
|
+
"""Initialize cache manager.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
project_dir: Project directory. If None, uses current directory.
|
|
57
|
+
"""
|
|
58
|
+
if project_dir is None:
|
|
59
|
+
project_dir = Path.cwd()
|
|
60
|
+
|
|
61
|
+
self.project_dir = Path(project_dir).resolve()
|
|
62
|
+
|
|
63
|
+
# Check for environment variable override
|
|
64
|
+
cache_env = os.environ.get("FBUILD_CACHE_DIR")
|
|
65
|
+
if cache_env:
|
|
66
|
+
self.cache_root = Path(cache_env).resolve()
|
|
67
|
+
else:
|
|
68
|
+
self.cache_root = self.project_dir / ".fbuild" / "cache"
|
|
69
|
+
|
|
70
|
+
self.build_root = self.project_dir / ".fbuild" / "build"
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def hash_url(url: str) -> str:
|
|
74
|
+
"""Generate a SHA256 hash of a URL for cache directory naming.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
url: The base URL to hash
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
First 16 characters of SHA256 hash (sufficient for uniqueness)
|
|
81
|
+
"""
|
|
82
|
+
return hashlib.sha256(url.encode("utf-8")).hexdigest()[:16]
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def packages_dir(self) -> Path:
|
|
86
|
+
"""Directory for downloaded package archives."""
|
|
87
|
+
return self.cache_root / "packages"
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def toolchains_dir(self) -> Path:
|
|
91
|
+
"""Directory for extracted toolchain binaries."""
|
|
92
|
+
return self.cache_root / "toolchains"
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def platforms_dir(self) -> Path:
|
|
96
|
+
"""Directory for extracted platform cores."""
|
|
97
|
+
return self.cache_root / "platforms"
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def libraries_dir(self) -> Path:
|
|
101
|
+
"""Directory for downloaded libraries."""
|
|
102
|
+
return self.cache_root / "libraries"
|
|
103
|
+
|
|
104
|
+
def get_build_dir(self, env_name: str) -> Path:
|
|
105
|
+
"""Get build directory for a specific environment.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
env_name: Environment name (e.g., 'uno', 'mega')
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Path to the environment's build directory
|
|
112
|
+
"""
|
|
113
|
+
return self.build_root / env_name
|
|
114
|
+
|
|
115
|
+
def get_core_build_dir(self, env_name: str) -> Path:
|
|
116
|
+
"""Get directory for compiled core objects.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
env_name: Environment name
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Path to core build directory
|
|
123
|
+
"""
|
|
124
|
+
return self.get_build_dir(env_name) / "core"
|
|
125
|
+
|
|
126
|
+
def get_src_build_dir(self, env_name: str) -> Path:
|
|
127
|
+
"""Get directory for compiled sketch objects.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
env_name: Environment name
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Path to sketch build directory
|
|
134
|
+
"""
|
|
135
|
+
return self.get_build_dir(env_name) / "src"
|
|
136
|
+
|
|
137
|
+
def ensure_directories(self) -> None:
|
|
138
|
+
"""Create all cache directories if they don't exist."""
|
|
139
|
+
for directory in [
|
|
140
|
+
self.packages_dir,
|
|
141
|
+
self.toolchains_dir,
|
|
142
|
+
self.platforms_dir,
|
|
143
|
+
self.libraries_dir,
|
|
144
|
+
]:
|
|
145
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
146
|
+
|
|
147
|
+
def ensure_build_directories(self, env_name: str) -> None:
|
|
148
|
+
"""Create build directories for a specific environment.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
env_name: Environment name
|
|
152
|
+
"""
|
|
153
|
+
for directory in [
|
|
154
|
+
self.get_build_dir(env_name),
|
|
155
|
+
self.get_core_build_dir(env_name),
|
|
156
|
+
self.get_src_build_dir(env_name),
|
|
157
|
+
]:
|
|
158
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
|
|
160
|
+
def clean_build(self, env_name: str) -> None:
|
|
161
|
+
"""Remove all build artifacts for an environment.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
env_name: Environment name
|
|
165
|
+
"""
|
|
166
|
+
import shutil
|
|
167
|
+
|
|
168
|
+
build_dir = self.get_build_dir(env_name)
|
|
169
|
+
if build_dir.exists():
|
|
170
|
+
shutil.rmtree(build_dir)
|
|
171
|
+
|
|
172
|
+
def get_package_path(self, url: str, version: str, filename: str) -> Path:
|
|
173
|
+
"""Get path where a package archive would be stored.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
url: Base URL for the package source
|
|
177
|
+
version: Version string (e.g., '7.3.0-atmel3.6.1-arduino7')
|
|
178
|
+
filename: Archive filename (e.g., 'avr-gcc-7.3.0.tar.bz2')
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Path to the package archive
|
|
182
|
+
"""
|
|
183
|
+
url_hash = self.hash_url(url)
|
|
184
|
+
return self.packages_dir / url_hash / version / filename
|
|
185
|
+
|
|
186
|
+
def get_toolchain_path(self, url: str, version: str) -> Path:
|
|
187
|
+
"""Get path where a toolchain would be extracted.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
url: Base URL for the toolchain source
|
|
191
|
+
version: Version string (e.g., '7.3.0-atmel3.6.1-arduino7')
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Path to the extracted toolchain directory
|
|
195
|
+
"""
|
|
196
|
+
url_hash = self.hash_url(url)
|
|
197
|
+
return self.toolchains_dir / url_hash / version
|
|
198
|
+
|
|
199
|
+
def get_platform_path(self, url: str, version: str) -> Path:
|
|
200
|
+
"""Get path where a platform would be extracted.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
url: Base URL for the platform source
|
|
204
|
+
version: Version string (e.g., '1.8.6')
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Path to the extracted platform directory
|
|
208
|
+
"""
|
|
209
|
+
url_hash = self.hash_url(url)
|
|
210
|
+
return self.platforms_dir / url_hash / version
|
|
211
|
+
|
|
212
|
+
def is_package_cached(self, url: str, version: str, filename: str) -> bool:
|
|
213
|
+
"""Check if a package is already downloaded.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
url: Base URL for the package source
|
|
217
|
+
version: Version string
|
|
218
|
+
filename: Archive filename
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
True if package exists in cache
|
|
222
|
+
"""
|
|
223
|
+
return self.get_package_path(url, version, filename).exists()
|
|
224
|
+
|
|
225
|
+
def is_toolchain_cached(self, url: str, version: str) -> bool:
|
|
226
|
+
"""Check if a toolchain is already extracted.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
url: Base URL for the toolchain source
|
|
230
|
+
version: Version string
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
True if toolchain exists in cache
|
|
234
|
+
"""
|
|
235
|
+
toolchain_path = self.get_toolchain_path(url, version)
|
|
236
|
+
return toolchain_path.exists() and toolchain_path.is_dir()
|
|
237
|
+
|
|
238
|
+
def is_platform_cached(self, url: str, version: str) -> bool:
|
|
239
|
+
"""Check if a platform is already extracted.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
url: Base URL for the platform source
|
|
243
|
+
version: Version string
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
True if platform exists in cache
|
|
247
|
+
"""
|
|
248
|
+
platform_path = self.get_platform_path(url, version)
|
|
249
|
+
return platform_path.exists() and platform_path.is_dir()
|