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
fbuild/__init__.py
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""fbuild - Modern embedded development tool."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
__version__ = "1.2.8"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def is_available() -> bool:
|
|
11
|
+
"""Check if fbuild is properly installed and functional.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
True if fbuild daemon client is available, False otherwise.
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
__import__("fbuild.daemon.client")
|
|
18
|
+
return True
|
|
19
|
+
except ImportError:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class BuildContext:
|
|
25
|
+
"""Configuration context for fbuild operations.
|
|
26
|
+
|
|
27
|
+
Groups common parameters used across build, deploy, and install operations.
|
|
28
|
+
Can be passed to Daemon methods instead of individual parameters.
|
|
29
|
+
|
|
30
|
+
Example usage:
|
|
31
|
+
import fbuild
|
|
32
|
+
|
|
33
|
+
# Create a context for repeated operations
|
|
34
|
+
ctx = fbuild.BuildContext(
|
|
35
|
+
project_dir=Path("my_project"),
|
|
36
|
+
environment="esp32dev",
|
|
37
|
+
port="COM3",
|
|
38
|
+
verbose=True
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Pre-install dependencies (toolchain, framework, libraries)
|
|
42
|
+
fbuild.Daemon.install_dependencies(ctx)
|
|
43
|
+
|
|
44
|
+
# Build using the context
|
|
45
|
+
fbuild.Daemon.build(ctx)
|
|
46
|
+
|
|
47
|
+
# Deploy using the context
|
|
48
|
+
fbuild.Daemon.deploy(ctx)
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
project_dir: Path to project directory containing platformio.ini
|
|
52
|
+
environment: Build environment name (e.g., 'esp32dev', 'esp32c6')
|
|
53
|
+
port: Serial port for upload/monitor (auto-detect if None)
|
|
54
|
+
clean_build: Whether to perform a clean build
|
|
55
|
+
verbose: Enable verbose output
|
|
56
|
+
timeout: Maximum wait time in seconds (default: 30 minutes)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
project_dir: Path
|
|
60
|
+
environment: str
|
|
61
|
+
port: str | None = None
|
|
62
|
+
clean_build: bool = False
|
|
63
|
+
verbose: bool = False
|
|
64
|
+
timeout: float = 1800
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Daemon:
|
|
68
|
+
"""Daemon management API for fbuild.
|
|
69
|
+
|
|
70
|
+
Provides static methods to control the fbuild daemon which handles
|
|
71
|
+
build, deploy, and monitor operations.
|
|
72
|
+
|
|
73
|
+
Example usage:
|
|
74
|
+
import fbuild
|
|
75
|
+
|
|
76
|
+
# Option 1: Use BuildContext (recommended for repeated operations)
|
|
77
|
+
ctx = fbuild.BuildContext(
|
|
78
|
+
project_dir=Path("my_project"),
|
|
79
|
+
environment="esp32dev"
|
|
80
|
+
)
|
|
81
|
+
fbuild.Daemon.install_dependencies(ctx)
|
|
82
|
+
fbuild.Daemon.build(ctx)
|
|
83
|
+
fbuild.Daemon.deploy(ctx)
|
|
84
|
+
|
|
85
|
+
# Option 2: Use individual parameters
|
|
86
|
+
fbuild.Daemon.build(
|
|
87
|
+
project_dir=Path("my_project"),
|
|
88
|
+
environment="esp32dev"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Daemon lifecycle
|
|
92
|
+
fbuild.Daemon.ensure_running()
|
|
93
|
+
fbuild.Daemon.status()
|
|
94
|
+
fbuild.Daemon.stop()
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def ensure_running() -> bool:
|
|
99
|
+
"""Ensure the fbuild daemon is running.
|
|
100
|
+
|
|
101
|
+
Starts the daemon if not already running.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
True if daemon is running or was started successfully, False otherwise.
|
|
105
|
+
"""
|
|
106
|
+
from fbuild.daemon import ensure_daemon_running
|
|
107
|
+
|
|
108
|
+
return ensure_daemon_running()
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def stop() -> bool:
|
|
112
|
+
"""Stop the fbuild daemon.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
True if daemon was stopped, False otherwise.
|
|
116
|
+
"""
|
|
117
|
+
from fbuild.daemon import stop_daemon
|
|
118
|
+
|
|
119
|
+
return stop_daemon()
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def status() -> dict[str, Any]:
|
|
123
|
+
"""Get current fbuild daemon status.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Dictionary with daemon status information including:
|
|
127
|
+
- state: Current daemon state
|
|
128
|
+
- message: Status message
|
|
129
|
+
- running: Whether daemon is running
|
|
130
|
+
"""
|
|
131
|
+
from fbuild.daemon import get_daemon_status
|
|
132
|
+
|
|
133
|
+
return get_daemon_status()
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def install_dependencies(
|
|
137
|
+
ctx_or_project_dir: BuildContext | Path,
|
|
138
|
+
environment: str | None = None,
|
|
139
|
+
verbose: bool = False,
|
|
140
|
+
timeout: float = 1800,
|
|
141
|
+
) -> bool:
|
|
142
|
+
"""Pre-install toolchain, platform, framework, and libraries.
|
|
143
|
+
|
|
144
|
+
This downloads and caches all dependencies required for a build
|
|
145
|
+
without actually compiling. Useful for:
|
|
146
|
+
- Pre-warming the cache before builds
|
|
147
|
+
- Ensuring dependencies are available offline
|
|
148
|
+
- Separating dependency installation from compilation
|
|
149
|
+
|
|
150
|
+
Can be called with a BuildContext or individual parameters:
|
|
151
|
+
|
|
152
|
+
# Using BuildContext
|
|
153
|
+
ctx = fbuild.BuildContext(project_dir=Path("."), environment="esp32dev")
|
|
154
|
+
fbuild.Daemon.install_dependencies(ctx)
|
|
155
|
+
|
|
156
|
+
# Using individual parameters
|
|
157
|
+
fbuild.Daemon.install_dependencies(
|
|
158
|
+
project_dir=Path("."),
|
|
159
|
+
environment="esp32dev"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
ctx_or_project_dir: BuildContext or Path to project directory
|
|
164
|
+
environment: Build environment name (ignored if BuildContext passed)
|
|
165
|
+
verbose: Enable verbose output (ignored if BuildContext passed)
|
|
166
|
+
timeout: Maximum wait time in seconds (ignored if BuildContext passed)
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
True if dependencies installed successfully, False otherwise.
|
|
170
|
+
"""
|
|
171
|
+
from fbuild.daemon import request_install_dependencies
|
|
172
|
+
|
|
173
|
+
# Handle BuildContext or individual parameters
|
|
174
|
+
if isinstance(ctx_or_project_dir, BuildContext):
|
|
175
|
+
ctx = ctx_or_project_dir
|
|
176
|
+
return request_install_dependencies(
|
|
177
|
+
project_dir=ctx.project_dir,
|
|
178
|
+
environment=ctx.environment,
|
|
179
|
+
verbose=ctx.verbose,
|
|
180
|
+
timeout=ctx.timeout,
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
if environment is None:
|
|
184
|
+
raise ValueError("environment is required when not using BuildContext")
|
|
185
|
+
return request_install_dependencies(
|
|
186
|
+
project_dir=ctx_or_project_dir,
|
|
187
|
+
environment=environment,
|
|
188
|
+
verbose=verbose,
|
|
189
|
+
timeout=timeout,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def build(
|
|
194
|
+
ctx_or_project_dir: BuildContext | Path,
|
|
195
|
+
environment: str | None = None,
|
|
196
|
+
clean_build: bool = False,
|
|
197
|
+
verbose: bool = False,
|
|
198
|
+
timeout: float = 1800,
|
|
199
|
+
) -> bool:
|
|
200
|
+
"""Request a build operation from the daemon.
|
|
201
|
+
|
|
202
|
+
Can be called with a BuildContext or individual parameters:
|
|
203
|
+
|
|
204
|
+
# Using BuildContext
|
|
205
|
+
ctx = fbuild.BuildContext(project_dir=Path("."), environment="esp32dev")
|
|
206
|
+
fbuild.Daemon.build(ctx)
|
|
207
|
+
|
|
208
|
+
# Using individual parameters
|
|
209
|
+
fbuild.Daemon.build(
|
|
210
|
+
project_dir=Path("."),
|
|
211
|
+
environment="esp32dev"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
ctx_or_project_dir: BuildContext or Path to project directory
|
|
216
|
+
environment: Build environment name (ignored if BuildContext passed)
|
|
217
|
+
clean_build: Whether to perform a clean build (ignored if BuildContext passed)
|
|
218
|
+
verbose: Enable verbose build output (ignored if BuildContext passed)
|
|
219
|
+
timeout: Maximum wait time in seconds (ignored if BuildContext passed)
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
True if build successful, False otherwise.
|
|
223
|
+
"""
|
|
224
|
+
from fbuild.daemon import request_build
|
|
225
|
+
|
|
226
|
+
# Handle BuildContext or individual parameters
|
|
227
|
+
if isinstance(ctx_or_project_dir, BuildContext):
|
|
228
|
+
ctx = ctx_or_project_dir
|
|
229
|
+
return request_build(
|
|
230
|
+
project_dir=ctx.project_dir,
|
|
231
|
+
environment=ctx.environment,
|
|
232
|
+
clean_build=ctx.clean_build,
|
|
233
|
+
verbose=ctx.verbose,
|
|
234
|
+
timeout=ctx.timeout,
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
if environment is None:
|
|
238
|
+
raise ValueError("environment is required when not using BuildContext")
|
|
239
|
+
return request_build(
|
|
240
|
+
project_dir=ctx_or_project_dir,
|
|
241
|
+
environment=environment,
|
|
242
|
+
clean_build=clean_build,
|
|
243
|
+
verbose=verbose,
|
|
244
|
+
timeout=timeout,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
@staticmethod
|
|
248
|
+
def deploy(
|
|
249
|
+
ctx_or_project_dir: BuildContext | Path,
|
|
250
|
+
environment: str | None = None,
|
|
251
|
+
port: str | None = None,
|
|
252
|
+
clean_build: bool = False,
|
|
253
|
+
monitor_after: bool = False,
|
|
254
|
+
monitor_timeout: float | None = None,
|
|
255
|
+
monitor_halt_on_error: str | None = None,
|
|
256
|
+
monitor_halt_on_success: str | None = None,
|
|
257
|
+
monitor_expect: str | None = None,
|
|
258
|
+
timeout: float = 1800,
|
|
259
|
+
) -> bool:
|
|
260
|
+
"""Request a deploy (build + upload) operation from the daemon.
|
|
261
|
+
|
|
262
|
+
Can be called with a BuildContext or individual parameters:
|
|
263
|
+
|
|
264
|
+
# Using BuildContext
|
|
265
|
+
ctx = fbuild.BuildContext(
|
|
266
|
+
project_dir=Path("."),
|
|
267
|
+
environment="esp32dev",
|
|
268
|
+
port="COM3"
|
|
269
|
+
)
|
|
270
|
+
fbuild.Daemon.deploy(ctx)
|
|
271
|
+
|
|
272
|
+
# Using individual parameters
|
|
273
|
+
fbuild.Daemon.deploy(
|
|
274
|
+
project_dir=Path("."),
|
|
275
|
+
environment="esp32dev",
|
|
276
|
+
port="COM3"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
ctx_or_project_dir: BuildContext or Path to project directory
|
|
281
|
+
environment: Build environment name (ignored if BuildContext passed)
|
|
282
|
+
port: Serial port for upload (ignored if BuildContext passed)
|
|
283
|
+
clean_build: Whether to perform a clean build (ignored if BuildContext passed)
|
|
284
|
+
monitor_after: Whether to start monitor after deploy
|
|
285
|
+
monitor_timeout: Timeout for monitor (if monitor_after=True)
|
|
286
|
+
monitor_halt_on_error: Pattern to halt on error (if monitor_after=True)
|
|
287
|
+
monitor_halt_on_success: Pattern to halt on success (if monitor_after=True)
|
|
288
|
+
monitor_expect: Expected pattern to check (if monitor_after=True)
|
|
289
|
+
timeout: Maximum wait time in seconds (ignored if BuildContext passed)
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
True if deploy successful, False otherwise.
|
|
293
|
+
"""
|
|
294
|
+
from fbuild.daemon import request_deploy
|
|
295
|
+
|
|
296
|
+
# Handle BuildContext or individual parameters
|
|
297
|
+
if isinstance(ctx_or_project_dir, BuildContext):
|
|
298
|
+
ctx = ctx_or_project_dir
|
|
299
|
+
return request_deploy(
|
|
300
|
+
project_dir=ctx.project_dir,
|
|
301
|
+
environment=ctx.environment,
|
|
302
|
+
port=ctx.port,
|
|
303
|
+
clean_build=ctx.clean_build,
|
|
304
|
+
monitor_after=monitor_after,
|
|
305
|
+
monitor_timeout=monitor_timeout,
|
|
306
|
+
monitor_halt_on_error=monitor_halt_on_error,
|
|
307
|
+
monitor_halt_on_success=monitor_halt_on_success,
|
|
308
|
+
monitor_expect=monitor_expect,
|
|
309
|
+
timeout=ctx.timeout,
|
|
310
|
+
)
|
|
311
|
+
else:
|
|
312
|
+
if environment is None:
|
|
313
|
+
raise ValueError("environment is required when not using BuildContext")
|
|
314
|
+
return request_deploy(
|
|
315
|
+
project_dir=ctx_or_project_dir,
|
|
316
|
+
environment=environment,
|
|
317
|
+
port=port,
|
|
318
|
+
clean_build=clean_build,
|
|
319
|
+
monitor_after=monitor_after,
|
|
320
|
+
monitor_timeout=monitor_timeout,
|
|
321
|
+
monitor_halt_on_error=monitor_halt_on_error,
|
|
322
|
+
monitor_halt_on_success=monitor_halt_on_success,
|
|
323
|
+
monitor_expect=monitor_expect,
|
|
324
|
+
timeout=timeout,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
@staticmethod
|
|
328
|
+
def monitor(
|
|
329
|
+
ctx_or_project_dir: BuildContext | Path,
|
|
330
|
+
environment: str | None = None,
|
|
331
|
+
port: str | None = None,
|
|
332
|
+
baud_rate: int | None = None,
|
|
333
|
+
halt_on_error: str | None = None,
|
|
334
|
+
halt_on_success: str | None = None,
|
|
335
|
+
expect: str | None = None,
|
|
336
|
+
timeout: float | None = None,
|
|
337
|
+
) -> bool:
|
|
338
|
+
"""Request a monitor operation from the daemon.
|
|
339
|
+
|
|
340
|
+
Can be called with a BuildContext or individual parameters.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
ctx_or_project_dir: BuildContext or Path to project directory
|
|
344
|
+
environment: Build environment name (ignored if BuildContext passed)
|
|
345
|
+
port: Serial port (ignored if BuildContext passed)
|
|
346
|
+
baud_rate: Serial baud rate (optional)
|
|
347
|
+
halt_on_error: Pattern to halt on (error detection)
|
|
348
|
+
halt_on_success: Pattern to halt on (success detection)
|
|
349
|
+
expect: Expected pattern to check at timeout/success
|
|
350
|
+
timeout: Maximum monitoring time in seconds
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
True if monitoring successful, False otherwise.
|
|
354
|
+
"""
|
|
355
|
+
from fbuild.daemon import request_monitor
|
|
356
|
+
|
|
357
|
+
# Handle BuildContext or individual parameters
|
|
358
|
+
if isinstance(ctx_or_project_dir, BuildContext):
|
|
359
|
+
ctx = ctx_or_project_dir
|
|
360
|
+
return request_monitor(
|
|
361
|
+
project_dir=ctx.project_dir,
|
|
362
|
+
environment=ctx.environment,
|
|
363
|
+
port=ctx.port,
|
|
364
|
+
baud_rate=baud_rate,
|
|
365
|
+
halt_on_error=halt_on_error,
|
|
366
|
+
halt_on_success=halt_on_success,
|
|
367
|
+
expect=expect,
|
|
368
|
+
timeout=timeout if timeout is not None else ctx.timeout,
|
|
369
|
+
)
|
|
370
|
+
else:
|
|
371
|
+
if environment is None:
|
|
372
|
+
raise ValueError("environment is required when not using BuildContext")
|
|
373
|
+
return request_monitor(
|
|
374
|
+
project_dir=ctx_or_project_dir,
|
|
375
|
+
environment=environment,
|
|
376
|
+
port=port,
|
|
377
|
+
baud_rate=baud_rate,
|
|
378
|
+
halt_on_error=halt_on_error,
|
|
379
|
+
halt_on_success=halt_on_success,
|
|
380
|
+
expect=expect,
|
|
381
|
+
timeout=timeout,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
__all__ = [
|
|
386
|
+
"__version__",
|
|
387
|
+
"is_available",
|
|
388
|
+
"BuildContext",
|
|
389
|
+
"Daemon",
|
|
390
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Example assets that will be deployed with python code.
|
fbuild/build/__init__.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Build system components for Fbuild.
|
|
3
|
+
|
|
4
|
+
This module provides the build system implementation including:
|
|
5
|
+
- Source file discovery and preprocessing
|
|
6
|
+
- Compilation (avr-gcc/avr-g++)
|
|
7
|
+
- Linking (avr-gcc linker, avr-objcopy)
|
|
8
|
+
- Build orchestration
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .source_scanner import SourceScanner, SourceCollection
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'SourceScanner',
|
|
15
|
+
'SourceCollection',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
# Import base classes
|
|
19
|
+
try:
|
|
20
|
+
from .orchestrator import ( # noqa: F401
|
|
21
|
+
IBuildOrchestrator,
|
|
22
|
+
BuildResult,
|
|
23
|
+
BuildOrchestratorError
|
|
24
|
+
)
|
|
25
|
+
__all__.extend(['IBuildOrchestrator', 'BuildResult', 'BuildOrchestratorError'])
|
|
26
|
+
except ImportError:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from .compiler import ICompiler, CompilerError, ILinker, LinkerError # noqa: F401
|
|
31
|
+
__all__.extend(['ICompiler', 'CompilerError', 'ILinker', 'LinkerError'])
|
|
32
|
+
except ImportError:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
# Import platform-specific implementations
|
|
36
|
+
try:
|
|
37
|
+
from .compiler_avr import CompilerAVR # noqa: F401
|
|
38
|
+
__all__.append('CompilerAVR')
|
|
39
|
+
except ImportError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
from .linker import LinkerAVR # noqa: F401
|
|
44
|
+
__all__.append('LinkerAVR')
|
|
45
|
+
except ImportError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
from .orchestrator_avr import BuildOrchestratorAVR # noqa: F401
|
|
50
|
+
__all__.append('BuildOrchestratorAVR')
|
|
51
|
+
except ImportError:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
from .orchestrator_esp32 import OrchestratorESP32 # noqa: F401
|
|
56
|
+
__all__.append('OrchestratorESP32')
|
|
57
|
+
except ImportError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
from .binary_generator import BinaryGenerator # noqa: F401
|
|
62
|
+
__all__.append('BinaryGenerator')
|
|
63
|
+
except ImportError:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
from .build_utils import SizeInfoPrinter # noqa: F401
|
|
68
|
+
__all__.append('SizeInfoPrinter')
|
|
69
|
+
except ImportError:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
from .flag_builder import FlagBuilder # noqa: F401
|
|
74
|
+
__all__.append('FlagBuilder')
|
|
75
|
+
except ImportError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
from .compilation_executor import CompilationExecutor # noqa: F401
|
|
80
|
+
__all__.append('CompilationExecutor')
|
|
81
|
+
except ImportError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
from .archive_creator import ArchiveCreator # noqa: F401
|
|
86
|
+
__all__.append('ArchiveCreator')
|
|
87
|
+
except ImportError:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
from .library_dependency_processor import ( # noqa: F401
|
|
92
|
+
LibraryDependencyProcessor,
|
|
93
|
+
LibraryProcessingResult
|
|
94
|
+
)
|
|
95
|
+
__all__.extend(['LibraryDependencyProcessor', 'LibraryProcessingResult'])
|
|
96
|
+
except ImportError:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
from .source_compilation_orchestrator import ( # noqa: F401
|
|
101
|
+
SourceCompilationOrchestrator,
|
|
102
|
+
SourceCompilationOrchestratorError,
|
|
103
|
+
MultiGroupCompilationResult
|
|
104
|
+
)
|
|
105
|
+
__all__.extend([
|
|
106
|
+
'SourceCompilationOrchestrator',
|
|
107
|
+
'SourceCompilationOrchestratorError',
|
|
108
|
+
'MultiGroupCompilationResult'
|
|
109
|
+
])
|
|
110
|
+
except ImportError:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
from .build_component_factory import BuildComponentFactory # noqa: F401
|
|
115
|
+
__all__.append('BuildComponentFactory')
|
|
116
|
+
except ImportError:
|
|
117
|
+
pass
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Archive Creator.
|
|
2
|
+
|
|
3
|
+
This module handles creating static library archives (.a files) from compiled
|
|
4
|
+
object files using the archiver tool (ar).
|
|
5
|
+
|
|
6
|
+
Design:
|
|
7
|
+
- Wraps ar command execution
|
|
8
|
+
- Creates .a archives from object files
|
|
9
|
+
- Provides clear error messages
|
|
10
|
+
- Shows archive size information
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import gc
|
|
14
|
+
import platform
|
|
15
|
+
import subprocess
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import List
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ArchiveError(Exception):
|
|
22
|
+
"""Raised when archive creation operations fail."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ArchiveCreator:
|
|
27
|
+
"""Creates static library archives from object files.
|
|
28
|
+
|
|
29
|
+
This class handles:
|
|
30
|
+
- Running archiver (ar) commands
|
|
31
|
+
- Creating .a archives from object files
|
|
32
|
+
- Validating archive creation
|
|
33
|
+
- Showing size information
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, show_progress: bool = True):
|
|
37
|
+
"""Initialize archive creator.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
show_progress: Whether to show archive creation progress
|
|
41
|
+
"""
|
|
42
|
+
self.show_progress = show_progress
|
|
43
|
+
|
|
44
|
+
def create_archive(
|
|
45
|
+
self,
|
|
46
|
+
ar_path: Path,
|
|
47
|
+
archive_path: Path,
|
|
48
|
+
object_files: List[Path]
|
|
49
|
+
) -> Path:
|
|
50
|
+
"""Create static library archive from object files.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
ar_path: Path to archiver tool (ar)
|
|
54
|
+
archive_path: Path for output .a file
|
|
55
|
+
object_files: List of object file paths to archive
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Path to generated archive file
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ArchiveError: If archive creation fails
|
|
62
|
+
"""
|
|
63
|
+
if not object_files:
|
|
64
|
+
raise ArchiveError("No object files provided for archive")
|
|
65
|
+
|
|
66
|
+
if not ar_path.exists():
|
|
67
|
+
raise ArchiveError(
|
|
68
|
+
f"Archiver not found: {ar_path}. Ensure toolchain is installed."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Ensure archive directory exists
|
|
72
|
+
archive_path.parent.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
|
|
74
|
+
# Build archiver command
|
|
75
|
+
# 'rcs' flags: r=insert/replace, c=create, s=index (ranlib)
|
|
76
|
+
cmd = [str(ar_path), "rcs", str(archive_path)]
|
|
77
|
+
cmd.extend([str(obj) for obj in object_files])
|
|
78
|
+
|
|
79
|
+
# Execute archiver
|
|
80
|
+
if self.show_progress:
|
|
81
|
+
print(f"Creating {archive_path.name} archive from {len(object_files)} object files...")
|
|
82
|
+
|
|
83
|
+
# On Windows, add retry logic to handle file locking issues
|
|
84
|
+
# Object files may still have open handles from compiler/antivirus
|
|
85
|
+
is_windows = platform.system() == "Windows"
|
|
86
|
+
max_retries = 5 if is_windows else 1
|
|
87
|
+
delay = 0.1
|
|
88
|
+
last_error = None
|
|
89
|
+
|
|
90
|
+
for attempt in range(max_retries):
|
|
91
|
+
try:
|
|
92
|
+
# On Windows, force garbage collection and add delay before retry
|
|
93
|
+
if is_windows and attempt > 0:
|
|
94
|
+
gc.collect()
|
|
95
|
+
time.sleep(delay)
|
|
96
|
+
if self.show_progress:
|
|
97
|
+
print(f" Retrying archive creation (attempt {attempt + 1}/{max_retries})...")
|
|
98
|
+
|
|
99
|
+
result = subprocess.run(
|
|
100
|
+
cmd,
|
|
101
|
+
capture_output=True,
|
|
102
|
+
text=True,
|
|
103
|
+
timeout=60
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if result.returncode != 0:
|
|
107
|
+
# Check if error is due to file truncation/locking (Windows-specific)
|
|
108
|
+
# Windows file locking manifests as: "file truncated", "error reading", or "No such file"
|
|
109
|
+
stderr_lower = result.stderr.lower()
|
|
110
|
+
is_file_locking_error = (
|
|
111
|
+
"file truncated" in stderr_lower or
|
|
112
|
+
"error reading" in stderr_lower or
|
|
113
|
+
"no such file" in stderr_lower
|
|
114
|
+
)
|
|
115
|
+
if is_windows and is_file_locking_error:
|
|
116
|
+
last_error = result.stderr
|
|
117
|
+
if attempt < max_retries - 1:
|
|
118
|
+
if self.show_progress:
|
|
119
|
+
print(" [Windows] Detected file locking error, retrying...")
|
|
120
|
+
delay = min(delay * 2, 1.0) # Exponential backoff, max 1s
|
|
121
|
+
continue
|
|
122
|
+
else:
|
|
123
|
+
# Last attempt failed
|
|
124
|
+
error_msg = f"Archive creation failed after {max_retries} attempts (file locking)\n"
|
|
125
|
+
error_msg += f"stderr: {result.stderr}\n"
|
|
126
|
+
error_msg += f"stdout: {result.stdout}"
|
|
127
|
+
raise ArchiveError(error_msg)
|
|
128
|
+
|
|
129
|
+
error_msg = f"Archive creation failed for {archive_path.name}\n"
|
|
130
|
+
error_msg += f"stderr: {result.stderr}\n"
|
|
131
|
+
error_msg += f"stdout: {result.stdout}"
|
|
132
|
+
raise ArchiveError(error_msg)
|
|
133
|
+
|
|
134
|
+
if not archive_path.exists():
|
|
135
|
+
raise ArchiveError(f"Archive was not created: {archive_path}")
|
|
136
|
+
|
|
137
|
+
if self.show_progress:
|
|
138
|
+
size = archive_path.stat().st_size
|
|
139
|
+
print(f"✓ Created {archive_path.name}: {size:,} bytes ({size / 1024 / 1024:.2f} MB)")
|
|
140
|
+
|
|
141
|
+
return archive_path
|
|
142
|
+
|
|
143
|
+
except subprocess.TimeoutExpired as e:
|
|
144
|
+
raise ArchiveError(f"Archive creation timeout for {archive_path.name}") from e
|
|
145
|
+
except KeyboardInterrupt as ke:
|
|
146
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
147
|
+
handle_keyboard_interrupt_properly(ke)
|
|
148
|
+
raise # Never reached, but satisfies type checker
|
|
149
|
+
except Exception as e:
|
|
150
|
+
if isinstance(e, ArchiveError):
|
|
151
|
+
raise
|
|
152
|
+
# If Windows file locking error, retry
|
|
153
|
+
if is_windows and attempt < max_retries - 1:
|
|
154
|
+
last_error = str(e)
|
|
155
|
+
delay = min(delay * 2, 1.0)
|
|
156
|
+
continue
|
|
157
|
+
raise ArchiveError(f"Failed to create archive {archive_path.name}: {e}") from e
|
|
158
|
+
|
|
159
|
+
# If we exhausted retries, raise the last error
|
|
160
|
+
if last_error:
|
|
161
|
+
raise ArchiveError(f"Archive creation failed after {max_retries} attempts: {last_error}")
|
|
162
|
+
raise ArchiveError(f"Archive creation failed after {max_retries} attempts")
|
|
163
|
+
|
|
164
|
+
def create_core_archive(
|
|
165
|
+
self,
|
|
166
|
+
ar_path: Path,
|
|
167
|
+
build_dir: Path,
|
|
168
|
+
object_files: List[Path]
|
|
169
|
+
) -> Path:
|
|
170
|
+
"""Create core.a archive from core object files.
|
|
171
|
+
|
|
172
|
+
Convenience method for creating the standard core.a archive.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
ar_path: Path to archiver tool (ar)
|
|
176
|
+
build_dir: Build directory
|
|
177
|
+
object_files: List of core object file paths
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Path to generated core.a file
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
ArchiveError: If archive creation fails
|
|
184
|
+
"""
|
|
185
|
+
archive_path = build_dir / "core.a"
|
|
186
|
+
return self.create_archive(ar_path, archive_path, object_files)
|