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,549 @@
|
|
|
1
|
+
"""Library dependency management for fbuild.
|
|
2
|
+
|
|
3
|
+
This module handles downloading, extracting, and compiling external libraries
|
|
4
|
+
from lib_deps in platformio.ini. Libraries are cached locally in the build
|
|
5
|
+
directory for fast rebuilds and proper invalidation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Optional, Tuple
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
from fbuild.packages.downloader import PackageDownloader
|
|
14
|
+
from fbuild.packages.github_utils import GitHubURLOptimizer
|
|
15
|
+
from fbuild.packages.library_compiler import LibraryCompilationError, LibraryCompiler
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LibraryError(Exception):
|
|
19
|
+
"""Base exception for library management errors."""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LibraryInfo:
|
|
25
|
+
"""Metadata about a downloaded and compiled library."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
name: str,
|
|
30
|
+
url: str,
|
|
31
|
+
version: str,
|
|
32
|
+
commit_hash: Optional[str],
|
|
33
|
+
compiler: str,
|
|
34
|
+
compile_commands: List[str],
|
|
35
|
+
link_commands: List[str],
|
|
36
|
+
):
|
|
37
|
+
"""Initialize library info.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
name: Library name
|
|
41
|
+
url: Source URL
|
|
42
|
+
version: Version or commit
|
|
43
|
+
commit_hash: Git commit hash if available
|
|
44
|
+
compiler: Compiler used
|
|
45
|
+
compile_commands: Commands used for compilation
|
|
46
|
+
link_commands: Commands/flags for linking
|
|
47
|
+
"""
|
|
48
|
+
self.name = name
|
|
49
|
+
self.url = url
|
|
50
|
+
self.version = version
|
|
51
|
+
self.commit_hash = commit_hash
|
|
52
|
+
self.compiler = compiler
|
|
53
|
+
self.compile_commands = compile_commands
|
|
54
|
+
self.link_commands = link_commands
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> Dict:
|
|
57
|
+
"""Convert to dictionary for JSON serialization."""
|
|
58
|
+
return {
|
|
59
|
+
"name": self.name,
|
|
60
|
+
"url": self.url,
|
|
61
|
+
"version": self.version,
|
|
62
|
+
"commit_hash": self.commit_hash,
|
|
63
|
+
"compiler": self.compiler,
|
|
64
|
+
"compile_commands": self.compile_commands,
|
|
65
|
+
"link_commands": self.link_commands,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_dict(cls, data: Dict) -> "LibraryInfo":
|
|
70
|
+
"""Create from dictionary."""
|
|
71
|
+
return cls(
|
|
72
|
+
name=data["name"],
|
|
73
|
+
url=data["url"],
|
|
74
|
+
version=data["version"],
|
|
75
|
+
commit_hash=data.get("commit_hash"),
|
|
76
|
+
compiler=data["compiler"],
|
|
77
|
+
compile_commands=data["compile_commands"],
|
|
78
|
+
link_commands=data["link_commands"],
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def save(self, path: Path) -> None:
|
|
82
|
+
"""Save library info to JSON file.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
path: Path to info.json file
|
|
86
|
+
"""
|
|
87
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
88
|
+
json.dump(self.to_dict(), f, indent=2)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def load(cls, path: Path) -> "LibraryInfo":
|
|
92
|
+
"""Load library info from JSON file.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
path: Path to info.json file
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
LibraryInfo instance
|
|
99
|
+
"""
|
|
100
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
101
|
+
data = json.load(f)
|
|
102
|
+
return cls.from_dict(data)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Library:
|
|
106
|
+
"""Represents a downloaded and compiled library."""
|
|
107
|
+
|
|
108
|
+
def __init__(self, lib_dir: Path, name: str):
|
|
109
|
+
"""Initialize library.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
lib_dir: Root directory for the library (.fbuild/build/{mode}/libs/{name})
|
|
113
|
+
name: Library name
|
|
114
|
+
"""
|
|
115
|
+
self.lib_dir = lib_dir
|
|
116
|
+
self.name = name
|
|
117
|
+
self.src_dir = lib_dir / "src"
|
|
118
|
+
self.info_file = lib_dir / "info.json"
|
|
119
|
+
self.archive_file = lib_dir / f"lib{name}.a"
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def exists(self) -> bool:
|
|
123
|
+
"""Check if library is downloaded and compiled."""
|
|
124
|
+
return self.lib_dir.exists() and self.src_dir.exists() and self.archive_file.exists() and self.info_file.exists()
|
|
125
|
+
|
|
126
|
+
def get_info(self) -> Optional[LibraryInfo]:
|
|
127
|
+
"""Load library info if available."""
|
|
128
|
+
if self.info_file.exists():
|
|
129
|
+
return LibraryInfo.load(self.info_file)
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def get_source_files(self) -> List[Path]:
|
|
133
|
+
"""Find all source files (.c, .cpp) in the library.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of source file paths
|
|
137
|
+
"""
|
|
138
|
+
if not self.src_dir.exists():
|
|
139
|
+
return []
|
|
140
|
+
|
|
141
|
+
sources = []
|
|
142
|
+
|
|
143
|
+
# Check for src/src/ structure (like FastLED)
|
|
144
|
+
src_src = self.src_dir / "src"
|
|
145
|
+
search_dir = src_src if (src_src.exists() and src_src.is_dir()) else self.src_dir
|
|
146
|
+
|
|
147
|
+
for pattern in ["**/*.c", "**/*.cpp", "**/*.cc", "**/*.cxx"]:
|
|
148
|
+
sources.extend(search_dir.glob(pattern))
|
|
149
|
+
|
|
150
|
+
return sources
|
|
151
|
+
|
|
152
|
+
def get_include_dirs(self) -> List[Path]:
|
|
153
|
+
"""Get include directories for this library.
|
|
154
|
+
|
|
155
|
+
For Arduino libraries, the typical structure is:
|
|
156
|
+
- libname/src/ (contains headers and sources)
|
|
157
|
+
- or libname/ (root contains headers)
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of include directory paths
|
|
161
|
+
"""
|
|
162
|
+
include_dirs = []
|
|
163
|
+
|
|
164
|
+
if not self.src_dir.exists():
|
|
165
|
+
return include_dirs
|
|
166
|
+
|
|
167
|
+
# Check if there's a src subdirectory inside src_dir
|
|
168
|
+
# This happens with libraries like FastLED that have src/src/ structure
|
|
169
|
+
src_src = self.src_dir / "src"
|
|
170
|
+
if src_src.exists() and src_src.is_dir():
|
|
171
|
+
# Arduino library with src/src/ structure
|
|
172
|
+
# Add src/src/ as the main include path
|
|
173
|
+
include_dirs.append(src_src)
|
|
174
|
+
else:
|
|
175
|
+
# Standard Arduino library structure
|
|
176
|
+
# Add src/ as the include path
|
|
177
|
+
include_dirs.append(self.src_dir)
|
|
178
|
+
|
|
179
|
+
# Look for additional include directories
|
|
180
|
+
for name in ["include", "Include", "INCLUDE"]:
|
|
181
|
+
inc_dir = self.lib_dir / name
|
|
182
|
+
if inc_dir.exists():
|
|
183
|
+
include_dirs.append(inc_dir)
|
|
184
|
+
|
|
185
|
+
return include_dirs
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class LibraryManager:
|
|
189
|
+
"""Manages library dependencies for a build."""
|
|
190
|
+
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
build_dir: Path,
|
|
194
|
+
mode: str = "release",
|
|
195
|
+
downloader: Optional[PackageDownloader] = None,
|
|
196
|
+
):
|
|
197
|
+
"""Initialize library manager.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
build_dir: Build directory (.fbuild/build/{env_name})
|
|
201
|
+
mode: Build mode (release, quick, debug)
|
|
202
|
+
downloader: Optional custom downloader instance
|
|
203
|
+
"""
|
|
204
|
+
self.build_dir = Path(build_dir)
|
|
205
|
+
self.mode = mode
|
|
206
|
+
self.libs_dir = self.build_dir / "libs"
|
|
207
|
+
self.downloader = downloader or PackageDownloader()
|
|
208
|
+
|
|
209
|
+
# Ensure libs directory exists
|
|
210
|
+
self.libs_dir.mkdir(parents=True, exist_ok=True)
|
|
211
|
+
|
|
212
|
+
def _extract_library_name(self, url: str) -> str:
|
|
213
|
+
"""Extract library name from URL.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
url: Library URL
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Library name in lowercase
|
|
220
|
+
"""
|
|
221
|
+
# For GitHub URLs, use the repo name
|
|
222
|
+
if GitHubURLOptimizer.is_github_url(url):
|
|
223
|
+
parts = urlparse(url).path.strip("/").split("/")
|
|
224
|
+
if len(parts) >= 2:
|
|
225
|
+
name = parts[1]
|
|
226
|
+
# Remove .git suffix if present
|
|
227
|
+
if name.endswith(".git"):
|
|
228
|
+
name = name[:-4]
|
|
229
|
+
return name.lower()
|
|
230
|
+
|
|
231
|
+
# For other URLs, use the filename without extension
|
|
232
|
+
filename = Path(urlparse(url).path).name
|
|
233
|
+
# Remove common archive extensions
|
|
234
|
+
for ext in [".zip", ".tar.gz", ".tar.bz2", ".tar.xz"]:
|
|
235
|
+
if filename.endswith(ext):
|
|
236
|
+
filename = filename[: -len(ext)]
|
|
237
|
+
break
|
|
238
|
+
|
|
239
|
+
return filename.lower()
|
|
240
|
+
|
|
241
|
+
def get_library(self, name: str) -> Library:
|
|
242
|
+
"""Get a Library instance for a given name.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
name: Library name
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Library instance
|
|
249
|
+
"""
|
|
250
|
+
lib_dir = self.libs_dir / name
|
|
251
|
+
return Library(lib_dir, name)
|
|
252
|
+
|
|
253
|
+
def download_library(self, url: str, show_progress: bool = True) -> Library:
|
|
254
|
+
"""Download and extract a library from a URL.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
url: Library source URL
|
|
258
|
+
show_progress: Whether to show download progress
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Library instance for the downloaded library
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
LibraryError: If download or extraction fails
|
|
265
|
+
"""
|
|
266
|
+
try:
|
|
267
|
+
# Optimize GitHub URLs
|
|
268
|
+
original_url = url
|
|
269
|
+
if GitHubURLOptimizer.is_github_url(url):
|
|
270
|
+
url = GitHubURLOptimizer.optimize_url(url)
|
|
271
|
+
if show_progress:
|
|
272
|
+
print(f"Optimized GitHub URL: {url}")
|
|
273
|
+
|
|
274
|
+
# Extract library name
|
|
275
|
+
lib_name = self._extract_library_name(original_url)
|
|
276
|
+
library = self.get_library(lib_name)
|
|
277
|
+
|
|
278
|
+
# Skip if already downloaded
|
|
279
|
+
if library.exists:
|
|
280
|
+
if show_progress:
|
|
281
|
+
print(f"Library '{lib_name}' already downloaded")
|
|
282
|
+
return library
|
|
283
|
+
|
|
284
|
+
# Create library directory
|
|
285
|
+
library.lib_dir.mkdir(parents=True, exist_ok=True)
|
|
286
|
+
|
|
287
|
+
# Download to temporary location
|
|
288
|
+
filename = Path(urlparse(url).path).name
|
|
289
|
+
temp_archive = library.lib_dir / filename
|
|
290
|
+
|
|
291
|
+
if show_progress:
|
|
292
|
+
print(f"Downloading library: {lib_name}")
|
|
293
|
+
|
|
294
|
+
self.downloader.download(url, temp_archive, show_progress=show_progress)
|
|
295
|
+
|
|
296
|
+
# Extract to src directory
|
|
297
|
+
if show_progress:
|
|
298
|
+
print(f"Extracting library: {lib_name}")
|
|
299
|
+
|
|
300
|
+
temp_extract = library.lib_dir / "_extract"
|
|
301
|
+
temp_extract.mkdir(exist_ok=True)
|
|
302
|
+
|
|
303
|
+
self.downloader.extract_archive(temp_archive, temp_extract, show_progress=show_progress)
|
|
304
|
+
|
|
305
|
+
# Find the actual source directory
|
|
306
|
+
# Archives often have a top-level directory like "FastLED-main"
|
|
307
|
+
extracted_contents = list(temp_extract.iterdir())
|
|
308
|
+
|
|
309
|
+
if len(extracted_contents) == 1 and extracted_contents[0].is_dir():
|
|
310
|
+
# Single directory - move its contents
|
|
311
|
+
source_root = extracted_contents[0]
|
|
312
|
+
else:
|
|
313
|
+
# Multiple items - use extract dir as root
|
|
314
|
+
source_root = temp_extract
|
|
315
|
+
|
|
316
|
+
# Move to src directory
|
|
317
|
+
if library.src_dir.exists():
|
|
318
|
+
import shutil
|
|
319
|
+
|
|
320
|
+
shutil.rmtree(library.src_dir)
|
|
321
|
+
|
|
322
|
+
source_root.rename(library.src_dir)
|
|
323
|
+
|
|
324
|
+
# Clean up
|
|
325
|
+
if temp_extract.exists():
|
|
326
|
+
import shutil
|
|
327
|
+
|
|
328
|
+
shutil.rmtree(temp_extract)
|
|
329
|
+
temp_archive.unlink()
|
|
330
|
+
|
|
331
|
+
# Create initial info.json
|
|
332
|
+
info = LibraryInfo(
|
|
333
|
+
name=lib_name,
|
|
334
|
+
url=original_url,
|
|
335
|
+
version="unknown",
|
|
336
|
+
commit_hash=None,
|
|
337
|
+
compiler="",
|
|
338
|
+
compile_commands=[],
|
|
339
|
+
link_commands=[],
|
|
340
|
+
)
|
|
341
|
+
info.save(library.info_file)
|
|
342
|
+
|
|
343
|
+
return library
|
|
344
|
+
|
|
345
|
+
except KeyboardInterrupt as ke:
|
|
346
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
347
|
+
|
|
348
|
+
handle_keyboard_interrupt_properly(ke)
|
|
349
|
+
raise # Never reached, but satisfies type checker
|
|
350
|
+
except Exception as e:
|
|
351
|
+
raise LibraryError(f"Failed to download library from {url}: {e}") from e
|
|
352
|
+
|
|
353
|
+
def needs_rebuild(self, library: Library, compiler_flags: List[str]) -> Tuple[bool, str]:
|
|
354
|
+
"""Check if a library needs to be rebuilt.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
library: Library to check
|
|
358
|
+
compiler_flags: Current compiler flags
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Tuple of (needs_rebuild, reason)
|
|
362
|
+
"""
|
|
363
|
+
return LibraryCompiler.needs_rebuild(
|
|
364
|
+
library.archive_file,
|
|
365
|
+
library.info_file,
|
|
366
|
+
compiler_flags,
|
|
367
|
+
library.get_info,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def compile_library(
|
|
371
|
+
self,
|
|
372
|
+
library: Library,
|
|
373
|
+
compiler_path: Path,
|
|
374
|
+
mcu: str,
|
|
375
|
+
f_cpu: str,
|
|
376
|
+
defines: List[str],
|
|
377
|
+
include_paths: List[Path],
|
|
378
|
+
extra_flags: List[str],
|
|
379
|
+
show_progress: bool = True,
|
|
380
|
+
) -> Path:
|
|
381
|
+
"""Compile a library into a static archive (.a file).
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
library: Library to compile
|
|
385
|
+
compiler_path: Path to avr-gcc/avr-g++
|
|
386
|
+
mcu: MCU target (e.g., atmega328p)
|
|
387
|
+
f_cpu: CPU frequency (e.g., 16000000L)
|
|
388
|
+
defines: Preprocessor defines
|
|
389
|
+
include_paths: Include directories
|
|
390
|
+
extra_flags: Additional compiler flags
|
|
391
|
+
show_progress: Whether to show progress
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Path to compiled archive file
|
|
395
|
+
|
|
396
|
+
Raises:
|
|
397
|
+
LibraryError: If compilation fails
|
|
398
|
+
"""
|
|
399
|
+
try:
|
|
400
|
+
# Get source files
|
|
401
|
+
sources = library.get_source_files()
|
|
402
|
+
if not sources:
|
|
403
|
+
raise LibraryError(f"No source files found in library '{library.name}'")
|
|
404
|
+
|
|
405
|
+
# Get library's own include directories
|
|
406
|
+
lib_includes = library.get_include_dirs()
|
|
407
|
+
all_includes = list(include_paths) + lib_includes
|
|
408
|
+
|
|
409
|
+
# Compile library using LibraryCompiler
|
|
410
|
+
archive_file, _, compile_commands = LibraryCompiler.compile_library(
|
|
411
|
+
library_name=library.name,
|
|
412
|
+
lib_dir=library.lib_dir,
|
|
413
|
+
source_files=sources,
|
|
414
|
+
include_dirs=all_includes,
|
|
415
|
+
compiler_path=compiler_path,
|
|
416
|
+
mcu=mcu,
|
|
417
|
+
f_cpu=f_cpu,
|
|
418
|
+
defines=defines,
|
|
419
|
+
extra_flags=extra_flags,
|
|
420
|
+
show_progress=show_progress,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# Update info.json with compile information
|
|
424
|
+
info = library.get_info() or LibraryInfo(
|
|
425
|
+
name=library.name,
|
|
426
|
+
url="",
|
|
427
|
+
version="unknown",
|
|
428
|
+
commit_hash=None,
|
|
429
|
+
compiler="",
|
|
430
|
+
compile_commands=[],
|
|
431
|
+
link_commands=[],
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
info.compiler = str(compiler_path)
|
|
435
|
+
info.compile_commands = compile_commands
|
|
436
|
+
info.link_commands = [str(archive_file)]
|
|
437
|
+
info.save(library.info_file)
|
|
438
|
+
|
|
439
|
+
return archive_file
|
|
440
|
+
|
|
441
|
+
except LibraryCompilationError as e:
|
|
442
|
+
raise LibraryError(str(e)) from e
|
|
443
|
+
except KeyboardInterrupt as ke:
|
|
444
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
445
|
+
|
|
446
|
+
handle_keyboard_interrupt_properly(ke)
|
|
447
|
+
raise # Never reached, but satisfies type checker
|
|
448
|
+
except Exception as e:
|
|
449
|
+
raise LibraryError(f"Failed to compile library '{library.name}': {e}") from e
|
|
450
|
+
|
|
451
|
+
def ensure_libraries(
|
|
452
|
+
self,
|
|
453
|
+
lib_deps: List[str],
|
|
454
|
+
compiler_path: Path,
|
|
455
|
+
mcu: str,
|
|
456
|
+
f_cpu: str,
|
|
457
|
+
defines: List[str],
|
|
458
|
+
include_paths: List[Path],
|
|
459
|
+
extra_flags: List[str],
|
|
460
|
+
show_progress: bool = True,
|
|
461
|
+
) -> List[Library]:
|
|
462
|
+
"""Ensure all library dependencies are downloaded and compiled.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
lib_deps: List of library dependency URLs
|
|
466
|
+
compiler_path: Path to compiler
|
|
467
|
+
mcu: MCU target
|
|
468
|
+
f_cpu: CPU frequency
|
|
469
|
+
defines: Preprocessor defines
|
|
470
|
+
include_paths: Include directories
|
|
471
|
+
extra_flags: Additional compiler flags
|
|
472
|
+
show_progress: Whether to show progress
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
List of compiled Library instances
|
|
476
|
+
"""
|
|
477
|
+
libraries = []
|
|
478
|
+
|
|
479
|
+
for url in lib_deps:
|
|
480
|
+
# Download library
|
|
481
|
+
library = self.download_library(url, show_progress)
|
|
482
|
+
|
|
483
|
+
# Check if rebuild needed
|
|
484
|
+
needs_rebuild, reason = self.needs_rebuild(library, extra_flags)
|
|
485
|
+
|
|
486
|
+
if needs_rebuild:
|
|
487
|
+
if show_progress and reason:
|
|
488
|
+
print(f"Rebuilding library '{library.name}': {reason}")
|
|
489
|
+
|
|
490
|
+
self.compile_library(
|
|
491
|
+
library,
|
|
492
|
+
compiler_path,
|
|
493
|
+
mcu,
|
|
494
|
+
f_cpu,
|
|
495
|
+
defines,
|
|
496
|
+
include_paths,
|
|
497
|
+
extra_flags,
|
|
498
|
+
show_progress,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
libraries.append(library)
|
|
502
|
+
|
|
503
|
+
return libraries
|
|
504
|
+
|
|
505
|
+
def get_library_archives(self) -> List[Path]:
|
|
506
|
+
"""Get paths to all compiled library archives.
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
List of .a archive file paths
|
|
510
|
+
"""
|
|
511
|
+
archives = []
|
|
512
|
+
if self.libs_dir.exists():
|
|
513
|
+
for lib_dir in self.libs_dir.iterdir():
|
|
514
|
+
if lib_dir.is_dir():
|
|
515
|
+
archive = lib_dir / f"lib{lib_dir.name}.a"
|
|
516
|
+
if archive.exists():
|
|
517
|
+
archives.append(archive)
|
|
518
|
+
return archives
|
|
519
|
+
|
|
520
|
+
def get_library_objects(self) -> List[Path]:
|
|
521
|
+
"""Get paths to all compiled library object files.
|
|
522
|
+
|
|
523
|
+
This is useful for LTO linking where object files work better than archives.
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
List of .o object file paths from all libraries
|
|
527
|
+
"""
|
|
528
|
+
objects = []
|
|
529
|
+
if self.libs_dir.exists():
|
|
530
|
+
for lib_dir in self.libs_dir.iterdir():
|
|
531
|
+
if lib_dir.is_dir():
|
|
532
|
+
# Find all .o files in the library directory
|
|
533
|
+
for obj_file in lib_dir.glob("*.o"):
|
|
534
|
+
objects.append(obj_file)
|
|
535
|
+
return objects
|
|
536
|
+
|
|
537
|
+
def get_library_include_paths(self) -> List[Path]:
|
|
538
|
+
"""Get all include paths from downloaded libraries.
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
List of include directory paths
|
|
542
|
+
"""
|
|
543
|
+
include_paths = []
|
|
544
|
+
if self.libs_dir.exists():
|
|
545
|
+
for lib_dir in self.libs_dir.iterdir():
|
|
546
|
+
if lib_dir.is_dir():
|
|
547
|
+
library = Library(lib_dir, lib_dir.name)
|
|
548
|
+
include_paths.extend(library.get_include_dirs())
|
|
549
|
+
return include_paths
|