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,656 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Build orchestration for Fbuild projects.
|
|
3
|
+
|
|
4
|
+
This module coordinates the entire build process, from parsing platformio.ini
|
|
5
|
+
to generating firmware binaries. It integrates all build system components:
|
|
6
|
+
- Configuration parsing (platformio.ini, boards.txt)
|
|
7
|
+
- Package management (toolchain, Arduino core)
|
|
8
|
+
- Source scanning and preprocessing
|
|
9
|
+
- Compilation (avr-gcc/avr-g++)
|
|
10
|
+
- Linking (avr-gcc linker, avr-objcopy)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import time
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional, List, Any
|
|
17
|
+
|
|
18
|
+
from ..interrupt_utils import handle_keyboard_interrupt_properly
|
|
19
|
+
from ..config import PlatformIOConfig, BoardConfig, BoardConfigLoader
|
|
20
|
+
from ..config.board_config import BoardConfigError
|
|
21
|
+
from ..packages import Cache, Toolchain, ArduinoCore
|
|
22
|
+
from ..packages.toolchain import ToolchainError
|
|
23
|
+
from ..packages.arduino_core import ArduinoCoreError
|
|
24
|
+
from ..packages.library_manager import LibraryError
|
|
25
|
+
from .source_scanner import SourceScanner, SourceCollection
|
|
26
|
+
from .compiler import CompilerError as CompilerImportError
|
|
27
|
+
from .linker import LinkerError as LinkerImportError
|
|
28
|
+
from .orchestrator_esp32 import OrchestratorESP32
|
|
29
|
+
from .build_utils import SizeInfoPrinter
|
|
30
|
+
from .library_dependency_processor import LibraryDependencyProcessor
|
|
31
|
+
from .source_compilation_orchestrator import (
|
|
32
|
+
SourceCompilationOrchestrator,
|
|
33
|
+
SourceCompilationOrchestratorError
|
|
34
|
+
)
|
|
35
|
+
from .build_component_factory import BuildComponentFactory
|
|
36
|
+
from .orchestrator import IBuildOrchestrator, BuildResult, BuildOrchestratorError
|
|
37
|
+
from .build_state import BuildStateTracker
|
|
38
|
+
|
|
39
|
+
# Import daemon accessor functions for async compilation
|
|
40
|
+
# TODO: Re-enable when get_compilation_queue() is implemented in daemon module
|
|
41
|
+
# try:
|
|
42
|
+
# from ..daemon import daemon
|
|
43
|
+
# DAEMON_AVAILABLE = True
|
|
44
|
+
# except ImportError:
|
|
45
|
+
# DAEMON_AVAILABLE = False
|
|
46
|
+
DAEMON_AVAILABLE = False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BuildOrchestratorAVR(IBuildOrchestrator):
|
|
50
|
+
"""
|
|
51
|
+
Orchestrates the complete build process for embedded projects.
|
|
52
|
+
|
|
53
|
+
This class coordinates all phases of the build:
|
|
54
|
+
1. Parse platformio.ini configuration
|
|
55
|
+
2. Load board configuration
|
|
56
|
+
3. Ensure toolchain is downloaded and validated
|
|
57
|
+
4. Ensure Arduino core is downloaded and validated
|
|
58
|
+
5. Setup build directories
|
|
59
|
+
6. Download and compile library dependencies
|
|
60
|
+
7. Scan source files (sketch + core + variant)
|
|
61
|
+
8. Compile all sources to object files
|
|
62
|
+
9. Link objects (including libraries) into firmware.elf
|
|
63
|
+
10. Convert to firmware.hex (Intel HEX format)
|
|
64
|
+
11. Display size information
|
|
65
|
+
|
|
66
|
+
Example usage:
|
|
67
|
+
orchestrator = BuildOrchestrator()
|
|
68
|
+
result = orchestrator.build(
|
|
69
|
+
project_dir=Path("."),
|
|
70
|
+
env_name="uno",
|
|
71
|
+
clean=False,
|
|
72
|
+
verbose=False
|
|
73
|
+
)
|
|
74
|
+
if result.success:
|
|
75
|
+
print(f"Firmware: {result.hex_path}")
|
|
76
|
+
print(f"Flash: {result.size_info.total_flash} bytes")
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
cache: Optional[Cache] = None,
|
|
82
|
+
verbose: bool = False
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
Initialize build orchestrator.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
cache: Cache instance for package management (optional)
|
|
89
|
+
verbose: Enable verbose output
|
|
90
|
+
"""
|
|
91
|
+
self.cache = cache
|
|
92
|
+
self.verbose = verbose
|
|
93
|
+
|
|
94
|
+
def _log(self, message: str, verbose_only: bool = True) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Log a message and optionally print it.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
message: Message to log
|
|
100
|
+
verbose_only: If True, only log if verbose mode is enabled
|
|
101
|
+
"""
|
|
102
|
+
if not verbose_only or self.verbose:
|
|
103
|
+
logging.info(message)
|
|
104
|
+
|
|
105
|
+
def build(
|
|
106
|
+
self,
|
|
107
|
+
project_dir: Path,
|
|
108
|
+
env_name: Optional[str] = None,
|
|
109
|
+
clean: bool = False,
|
|
110
|
+
verbose: Optional[bool] = None
|
|
111
|
+
) -> BuildResult:
|
|
112
|
+
"""
|
|
113
|
+
Execute complete build process.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
project_dir: Project root directory containing platformio.ini
|
|
117
|
+
env_name: Environment name to build (defaults to first/default env)
|
|
118
|
+
clean: Clean build (remove all artifacts before building)
|
|
119
|
+
verbose: Override verbose setting
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
BuildResult with build status and output paths
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
BuildOrchestratorError: If build fails at any phase
|
|
126
|
+
"""
|
|
127
|
+
start_time = time.time()
|
|
128
|
+
verbose_mode = verbose if verbose is not None else self.verbose
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
project_dir = Path(project_dir).resolve()
|
|
132
|
+
|
|
133
|
+
# Initialize cache if not provided
|
|
134
|
+
if self.cache is None:
|
|
135
|
+
self.cache = Cache(project_dir)
|
|
136
|
+
|
|
137
|
+
# Phase 1: Parse configuration
|
|
138
|
+
if verbose_mode:
|
|
139
|
+
print("[1/9] Parsing platformio.ini...")
|
|
140
|
+
self._log("[1/9] Parsing platformio.ini...")
|
|
141
|
+
|
|
142
|
+
config = self._parse_config(project_dir)
|
|
143
|
+
|
|
144
|
+
# Determine environment to build
|
|
145
|
+
if env_name is None:
|
|
146
|
+
env_name = config.get_default_environment()
|
|
147
|
+
if env_name is None:
|
|
148
|
+
raise BuildOrchestratorError(
|
|
149
|
+
"No environment specified and no default found in platformio.ini"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if verbose_mode:
|
|
153
|
+
print(f" Building environment: {env_name}")
|
|
154
|
+
self._log(f" Building environment: {env_name}")
|
|
155
|
+
|
|
156
|
+
env_config = config.get_env_config(env_name)
|
|
157
|
+
|
|
158
|
+
# Phase 2: Load board configuration
|
|
159
|
+
if verbose_mode:
|
|
160
|
+
print("[2/9] Loading board configuration...")
|
|
161
|
+
self._log("[2/9] Loading board configuration...")
|
|
162
|
+
|
|
163
|
+
board_id = env_config['board']
|
|
164
|
+
board_config = BoardConfigLoader.load_board_config(board_id, env_config)
|
|
165
|
+
|
|
166
|
+
if verbose_mode:
|
|
167
|
+
print(f" Board: {board_config.name}")
|
|
168
|
+
print(f" MCU: {board_config.mcu}")
|
|
169
|
+
print(f" F_CPU: {board_config.f_cpu}")
|
|
170
|
+
self._log(f" Board: {board_config.name}, MCU: {board_config.mcu}, F_CPU: {board_config.f_cpu}")
|
|
171
|
+
|
|
172
|
+
# Detect platform and handle accordingly
|
|
173
|
+
if board_config.platform == "esp32":
|
|
174
|
+
if verbose_mode:
|
|
175
|
+
print(f" Platform: {board_config.platform} (using native ESP32 build)")
|
|
176
|
+
# Get build flags from platformio.ini
|
|
177
|
+
build_flags = config.get_build_flags(env_name)
|
|
178
|
+
return self._build_esp32(
|
|
179
|
+
project_dir, env_name, board_id, env_config, clean, verbose_mode, start_time, build_flags
|
|
180
|
+
)
|
|
181
|
+
elif board_config.platform == "teensy":
|
|
182
|
+
if verbose_mode:
|
|
183
|
+
print(f" Platform: {board_config.platform} (using native Teensy build)")
|
|
184
|
+
# Get build flags from platformio.ini
|
|
185
|
+
build_flags = config.get_build_flags(env_name)
|
|
186
|
+
return self._build_teensy(
|
|
187
|
+
project_dir, env_name, board_id, board_config, clean, verbose_mode, start_time, build_flags
|
|
188
|
+
)
|
|
189
|
+
elif board_config.platform != "avr":
|
|
190
|
+
# Only AVR, ESP32, and Teensy are supported natively
|
|
191
|
+
return BuildResult(
|
|
192
|
+
success=False,
|
|
193
|
+
hex_path=None,
|
|
194
|
+
elf_path=None,
|
|
195
|
+
size_info=None,
|
|
196
|
+
build_time=time.time() - start_time,
|
|
197
|
+
message=f"Platform '{board_config.platform}' is not supported. " +
|
|
198
|
+
"Fbuild currently supports 'avr', 'esp32', and 'teensy' platforms natively."
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Phase 3: Ensure toolchain
|
|
202
|
+
if verbose_mode:
|
|
203
|
+
print("[3/9] Ensuring AVR toolchain...")
|
|
204
|
+
self._log("[3/9] Ensuring AVR toolchain...")
|
|
205
|
+
|
|
206
|
+
toolchain = self._ensure_toolchain()
|
|
207
|
+
|
|
208
|
+
if verbose_mode:
|
|
209
|
+
print(" Toolchain ready")
|
|
210
|
+
self._log(" Toolchain ready")
|
|
211
|
+
|
|
212
|
+
# Phase 4: Ensure Arduino core
|
|
213
|
+
if verbose_mode:
|
|
214
|
+
print("[4/9] Ensuring Arduino core...")
|
|
215
|
+
self._log("[4/9] Ensuring Arduino core...")
|
|
216
|
+
|
|
217
|
+
arduino_core = self._ensure_arduino_core()
|
|
218
|
+
core_path = arduino_core.ensure_avr_core()
|
|
219
|
+
|
|
220
|
+
if verbose_mode:
|
|
221
|
+
print(f" Core ready: version {arduino_core.AVR_VERSION}")
|
|
222
|
+
self._log(f" Core ready: version {arduino_core.AVR_VERSION}")
|
|
223
|
+
|
|
224
|
+
# Phase 5: Setup build directories
|
|
225
|
+
if verbose_mode:
|
|
226
|
+
print("[5/11] Preparing build directories...")
|
|
227
|
+
self._log("[5/11] Preparing build directories...")
|
|
228
|
+
|
|
229
|
+
if clean:
|
|
230
|
+
self.cache.clean_build(env_name)
|
|
231
|
+
|
|
232
|
+
self.cache.ensure_build_directories(env_name)
|
|
233
|
+
build_dir = self.cache.get_build_dir(env_name)
|
|
234
|
+
core_build_dir = self.cache.get_core_build_dir(env_name)
|
|
235
|
+
src_build_dir = self.cache.get_src_build_dir(env_name)
|
|
236
|
+
|
|
237
|
+
# Phase 5.5: Check build state and invalidate cache if needed
|
|
238
|
+
if verbose_mode:
|
|
239
|
+
print("[5.5/11] Checking build configuration state...")
|
|
240
|
+
self._log("[5.5/11] Checking build configuration state...")
|
|
241
|
+
|
|
242
|
+
state_tracker = BuildStateTracker(build_dir)
|
|
243
|
+
build_flags = config.get_build_flags(env_name)
|
|
244
|
+
lib_deps = config.get_lib_deps(env_name)
|
|
245
|
+
|
|
246
|
+
needs_rebuild, reasons, current_state = state_tracker.check_invalidation(
|
|
247
|
+
platformio_ini_path=project_dir / "platformio.ini",
|
|
248
|
+
platform=board_config.platform,
|
|
249
|
+
board=board_id,
|
|
250
|
+
framework=env_config.get('framework', 'arduino'),
|
|
251
|
+
toolchain_version=toolchain.VERSION,
|
|
252
|
+
framework_version=arduino_core.AVR_VERSION,
|
|
253
|
+
platform_version=arduino_core.AVR_VERSION, # Using core version as platform version
|
|
254
|
+
build_flags=build_flags,
|
|
255
|
+
lib_deps=lib_deps,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if needs_rebuild:
|
|
259
|
+
if verbose_mode:
|
|
260
|
+
print(" Build cache invalidated:")
|
|
261
|
+
for reason in reasons:
|
|
262
|
+
print(f" - {reason}")
|
|
263
|
+
print(" Cleaning build artifacts...")
|
|
264
|
+
self._log(f" Build cache invalidated: {', '.join(reasons)}")
|
|
265
|
+
self._log(" Cleaning build artifacts...")
|
|
266
|
+
# Clean build artifacts to force rebuild
|
|
267
|
+
self.cache.clean_build(env_name)
|
|
268
|
+
# Recreate directories
|
|
269
|
+
self.cache.ensure_build_directories(env_name)
|
|
270
|
+
else:
|
|
271
|
+
if verbose_mode:
|
|
272
|
+
print(" Build configuration unchanged, using cached artifacts")
|
|
273
|
+
self._log(" Build configuration unchanged, using cached artifacts")
|
|
274
|
+
|
|
275
|
+
# Phase 6: Download and compile library dependencies
|
|
276
|
+
if verbose_mode:
|
|
277
|
+
print("[6/11] Processing library dependencies...")
|
|
278
|
+
self._log("[6/11] Processing library dependencies...")
|
|
279
|
+
|
|
280
|
+
lib_deps = config.get_lib_deps(env_name)
|
|
281
|
+
|
|
282
|
+
lib_processor = LibraryDependencyProcessor(
|
|
283
|
+
build_dir=build_dir,
|
|
284
|
+
mode="release",
|
|
285
|
+
verbose=verbose_mode
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
lib_result = lib_processor.process_dependencies(
|
|
289
|
+
lib_deps=lib_deps,
|
|
290
|
+
toolchain=toolchain,
|
|
291
|
+
board_config=board_config,
|
|
292
|
+
core_path=core_path
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
lib_include_paths = lib_result.include_paths
|
|
296
|
+
lib_objects = lib_result.object_files
|
|
297
|
+
|
|
298
|
+
# Phase 7: Scan source files
|
|
299
|
+
if verbose_mode:
|
|
300
|
+
print("[7/11] Scanning source files...")
|
|
301
|
+
self._log("[7/11] Scanning source files...")
|
|
302
|
+
|
|
303
|
+
sources = self._scan_sources(
|
|
304
|
+
project_dir,
|
|
305
|
+
build_dir,
|
|
306
|
+
board_config,
|
|
307
|
+
core_path
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
total_sources = (
|
|
311
|
+
len(sources.sketch_sources)
|
|
312
|
+
+ len(sources.core_sources)
|
|
313
|
+
+ len(sources.variant_sources)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if verbose_mode:
|
|
317
|
+
print(f" Sketch: {len(sources.sketch_sources)} files")
|
|
318
|
+
print(f" Core: {len(sources.core_sources)} files")
|
|
319
|
+
print(f" Variant: {len(sources.variant_sources)} files")
|
|
320
|
+
print(f" Total: {total_sources} files")
|
|
321
|
+
self._log(f" Sketch: {len(sources.sketch_sources)} files, Core: {len(sources.core_sources)} files, Variant: {len(sources.variant_sources)} files, Total: {total_sources} files")
|
|
322
|
+
|
|
323
|
+
# Phase 8: Compile sources
|
|
324
|
+
if verbose_mode:
|
|
325
|
+
print("[8/11] Compiling sources...")
|
|
326
|
+
self._log("[8/11] Compiling sources...")
|
|
327
|
+
|
|
328
|
+
# Get compilation queue from daemon if available
|
|
329
|
+
# TODO: Implement get_compilation_queue() in daemon module
|
|
330
|
+
compilation_queue = None
|
|
331
|
+
# if DAEMON_AVAILABLE:
|
|
332
|
+
# try:
|
|
333
|
+
# compilation_queue = daemon.get_compilation_queue()
|
|
334
|
+
# if compilation_queue and verbose_mode:
|
|
335
|
+
# print(f" [async] Using parallel compilation with {compilation_queue.num_workers} workers")
|
|
336
|
+
# self._log(f" [async] Using parallel compilation with {compilation_queue.num_workers} workers")
|
|
337
|
+
# except KeyboardInterrupt as ke:
|
|
338
|
+
# handle_keyboard_interrupt_properly(ke)
|
|
339
|
+
# except Exception:
|
|
340
|
+
# # Daemon not running or queue not initialized - use sync mode
|
|
341
|
+
# pass
|
|
342
|
+
|
|
343
|
+
compiler = BuildComponentFactory.create_compiler(
|
|
344
|
+
toolchain, board_config, core_path, lib_include_paths, compilation_queue
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
compilation_orchestrator = SourceCompilationOrchestrator(verbose=verbose_mode)
|
|
348
|
+
compilation_result = compilation_orchestrator.compile_multiple_groups(
|
|
349
|
+
compiler=compiler,
|
|
350
|
+
sketch_sources=sources.sketch_sources,
|
|
351
|
+
core_sources=sources.core_sources,
|
|
352
|
+
variant_sources=sources.variant_sources,
|
|
353
|
+
src_build_dir=src_build_dir,
|
|
354
|
+
core_build_dir=core_build_dir
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
sketch_objects = compilation_result.sketch_objects
|
|
358
|
+
all_core_objects = compilation_result.all_core_objects
|
|
359
|
+
|
|
360
|
+
# Phase 9: Link firmware
|
|
361
|
+
if verbose_mode:
|
|
362
|
+
print("[9/11] Linking firmware...")
|
|
363
|
+
self._log("[9/11] Linking firmware...")
|
|
364
|
+
|
|
365
|
+
elf_path = build_dir / 'firmware.elf'
|
|
366
|
+
hex_path = build_dir / 'firmware.hex'
|
|
367
|
+
|
|
368
|
+
linker = BuildComponentFactory.create_linker(toolchain, board_config)
|
|
369
|
+
# For LTO with -fno-fat-lto-objects, we pass library objects separately
|
|
370
|
+
# so they don't get archived (LTO bytecode doesn't work well in archives)
|
|
371
|
+
link_result = linker.link_legacy(
|
|
372
|
+
sketch_objects,
|
|
373
|
+
all_core_objects,
|
|
374
|
+
elf_path,
|
|
375
|
+
hex_path,
|
|
376
|
+
[], # No library archives
|
|
377
|
+
None, # No extra flags
|
|
378
|
+
lib_objects # Library objects passed separately for LTO
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if not link_result.success:
|
|
382
|
+
raise BuildOrchestratorError(
|
|
383
|
+
f"Linking failed:\n{link_result.stderr}"
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
if verbose_mode:
|
|
387
|
+
print(f" Firmware: {hex_path}")
|
|
388
|
+
self._log(f" Firmware: {hex_path}")
|
|
389
|
+
|
|
390
|
+
# Phase 10: Save build state for future cache validation
|
|
391
|
+
if verbose_mode:
|
|
392
|
+
print("[10/11] Saving build state...")
|
|
393
|
+
self._log("[10/11] Saving build state...")
|
|
394
|
+
state_tracker.save_state(current_state)
|
|
395
|
+
|
|
396
|
+
# Phase 11: Display results
|
|
397
|
+
build_time = time.time() - start_time
|
|
398
|
+
|
|
399
|
+
if verbose_mode:
|
|
400
|
+
print("[11/11] Build complete!")
|
|
401
|
+
print()
|
|
402
|
+
SizeInfoPrinter.print_size_info(link_result.size_info)
|
|
403
|
+
print()
|
|
404
|
+
print(f"Build time: {build_time:.2f}s")
|
|
405
|
+
self._log(f"[11/11] Build complete! Build time: {build_time:.2f}s")
|
|
406
|
+
|
|
407
|
+
return BuildResult(
|
|
408
|
+
success=True,
|
|
409
|
+
hex_path=hex_path,
|
|
410
|
+
elf_path=elf_path,
|
|
411
|
+
size_info=link_result.size_info,
|
|
412
|
+
build_time=build_time,
|
|
413
|
+
message="Build successful"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
except (
|
|
417
|
+
BuildOrchestratorError,
|
|
418
|
+
ToolchainError,
|
|
419
|
+
ArduinoCoreError,
|
|
420
|
+
CompilerImportError,
|
|
421
|
+
LinkerImportError,
|
|
422
|
+
BoardConfigError,
|
|
423
|
+
LibraryError,
|
|
424
|
+
SourceCompilationOrchestratorError
|
|
425
|
+
) as e:
|
|
426
|
+
build_time = time.time() - start_time
|
|
427
|
+
return BuildResult(
|
|
428
|
+
success=False,
|
|
429
|
+
hex_path=None,
|
|
430
|
+
elf_path=None,
|
|
431
|
+
size_info=None,
|
|
432
|
+
build_time=build_time,
|
|
433
|
+
message=str(e)
|
|
434
|
+
)
|
|
435
|
+
except KeyboardInterrupt as ke:
|
|
436
|
+
handle_keyboard_interrupt_properly(ke)
|
|
437
|
+
except Exception as e:
|
|
438
|
+
build_time = time.time() - start_time
|
|
439
|
+
return BuildResult(
|
|
440
|
+
success=False,
|
|
441
|
+
hex_path=None,
|
|
442
|
+
elf_path=None,
|
|
443
|
+
size_info=None,
|
|
444
|
+
build_time=build_time,
|
|
445
|
+
message=f"Unexpected error: {e}"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
def _build_esp32(
|
|
449
|
+
self,
|
|
450
|
+
project_dir: Path,
|
|
451
|
+
env_name: str,
|
|
452
|
+
board_id: str,
|
|
453
|
+
env_config: dict[str, Any],
|
|
454
|
+
clean: bool,
|
|
455
|
+
verbose: bool,
|
|
456
|
+
start_time: float,
|
|
457
|
+
build_flags: List[str]
|
|
458
|
+
) -> BuildResult:
|
|
459
|
+
"""
|
|
460
|
+
Build ESP32 project using native build system.
|
|
461
|
+
|
|
462
|
+
Delegates to ESP32Orchestrator for ESP32-specific build logic.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
project_dir: Project directory
|
|
466
|
+
env_name: Environment name
|
|
467
|
+
board_id: Board ID (e.g., esp32-c6-devkitm-1)
|
|
468
|
+
env_config: Environment configuration dict
|
|
469
|
+
clean: Whether to clean before build
|
|
470
|
+
verbose: Verbose output
|
|
471
|
+
start_time: Build start time
|
|
472
|
+
build_flags: User build flags from platformio.ini
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
BuildResult
|
|
476
|
+
"""
|
|
477
|
+
if self.cache is None:
|
|
478
|
+
return BuildResult(
|
|
479
|
+
success=False,
|
|
480
|
+
hex_path=None,
|
|
481
|
+
elf_path=None,
|
|
482
|
+
size_info=None,
|
|
483
|
+
build_time=time.time() - start_time,
|
|
484
|
+
message="Cache is required for ESP32 builds"
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
esp32_orchestrator = OrchestratorESP32(self.cache, verbose)
|
|
488
|
+
# Use the new BaseBuildOrchestrator-compliant interface
|
|
489
|
+
result = esp32_orchestrator.build(
|
|
490
|
+
project_dir=project_dir,
|
|
491
|
+
env_name=env_name,
|
|
492
|
+
clean=clean,
|
|
493
|
+
verbose=verbose
|
|
494
|
+
)
|
|
495
|
+
return result
|
|
496
|
+
|
|
497
|
+
def _build_teensy(
|
|
498
|
+
self,
|
|
499
|
+
project_dir: Path,
|
|
500
|
+
env_name: str,
|
|
501
|
+
board_id: str,
|
|
502
|
+
board_config: BoardConfig,
|
|
503
|
+
clean: bool,
|
|
504
|
+
verbose: bool,
|
|
505
|
+
start_time: float,
|
|
506
|
+
build_flags: List[str]
|
|
507
|
+
) -> BuildResult:
|
|
508
|
+
"""
|
|
509
|
+
Build Teensy project using native build system.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
project_dir: Project directory
|
|
513
|
+
env_name: Environment name
|
|
514
|
+
board_id: Board ID (e.g., teensy41)
|
|
515
|
+
board_config: Board configuration
|
|
516
|
+
clean: Whether to clean before build
|
|
517
|
+
verbose: Verbose output
|
|
518
|
+
start_time: Build start time
|
|
519
|
+
build_flags: User build flags from platformio.ini
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
BuildResult
|
|
523
|
+
"""
|
|
524
|
+
if self.cache is None:
|
|
525
|
+
return BuildResult(
|
|
526
|
+
success=False,
|
|
527
|
+
hex_path=None,
|
|
528
|
+
elf_path=None,
|
|
529
|
+
size_info=None,
|
|
530
|
+
build_time=time.time() - start_time,
|
|
531
|
+
message="Cache not initialized"
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# Delegate to OrchestratorTeensy for native Teensy build
|
|
535
|
+
from .orchestrator_teensy import OrchestratorTeensy
|
|
536
|
+
|
|
537
|
+
teensy_orchestrator = OrchestratorTeensy(self.cache, verbose)
|
|
538
|
+
result = teensy_orchestrator.build(
|
|
539
|
+
project_dir=project_dir,
|
|
540
|
+
env_name=env_name,
|
|
541
|
+
clean=clean,
|
|
542
|
+
verbose=verbose
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
return result
|
|
546
|
+
|
|
547
|
+
def _parse_config(self, project_dir: Path) -> PlatformIOConfig:
|
|
548
|
+
"""
|
|
549
|
+
Parse platformio.ini configuration file.
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
project_dir: Project directory
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
PlatformIOConfig instance
|
|
556
|
+
|
|
557
|
+
Raises:
|
|
558
|
+
BuildOrchestratorError: If platformio.ini not found or invalid
|
|
559
|
+
"""
|
|
560
|
+
ini_path = project_dir / 'platformio.ini'
|
|
561
|
+
|
|
562
|
+
if not ini_path.exists():
|
|
563
|
+
raise BuildOrchestratorError(
|
|
564
|
+
f"platformio.ini not found in {project_dir}\n" +
|
|
565
|
+
"Make sure you're in a valid project directory."
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
try:
|
|
569
|
+
return PlatformIOConfig(ini_path)
|
|
570
|
+
except KeyboardInterrupt as ke:
|
|
571
|
+
handle_keyboard_interrupt_properly(ke)
|
|
572
|
+
except Exception as e:
|
|
573
|
+
raise BuildOrchestratorError(
|
|
574
|
+
f"Failed to parse platformio.ini: {e}"
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
def _ensure_toolchain(self) -> Toolchain:
|
|
578
|
+
"""
|
|
579
|
+
Ensure AVR toolchain is available.
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Toolchain instance with toolchain ready
|
|
583
|
+
|
|
584
|
+
Raises:
|
|
585
|
+
BuildOrchestratorError: If toolchain cannot be obtained
|
|
586
|
+
"""
|
|
587
|
+
try:
|
|
588
|
+
cache = self.cache if self.cache else Cache()
|
|
589
|
+
toolchain = Toolchain(cache)
|
|
590
|
+
toolchain.ensure_toolchain()
|
|
591
|
+
return toolchain
|
|
592
|
+
except KeyboardInterrupt as ke:
|
|
593
|
+
handle_keyboard_interrupt_properly(ke)
|
|
594
|
+
except Exception as e:
|
|
595
|
+
raise BuildOrchestratorError(
|
|
596
|
+
f"Failed to setup toolchain: {e}"
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
def _ensure_arduino_core(self) -> ArduinoCore:
|
|
600
|
+
"""
|
|
601
|
+
Ensure Arduino core is available.
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
ArduinoCore instance with core ready
|
|
605
|
+
|
|
606
|
+
Raises:
|
|
607
|
+
BuildOrchestratorError: If core cannot be obtained
|
|
608
|
+
"""
|
|
609
|
+
try:
|
|
610
|
+
cache = self.cache if self.cache else Cache()
|
|
611
|
+
arduino_core = ArduinoCore(cache)
|
|
612
|
+
arduino_core.ensure_avr_core()
|
|
613
|
+
return arduino_core
|
|
614
|
+
except KeyboardInterrupt as ke:
|
|
615
|
+
handle_keyboard_interrupt_properly(ke)
|
|
616
|
+
except Exception as e:
|
|
617
|
+
raise BuildOrchestratorError(
|
|
618
|
+
f"Failed to setup Arduino core: {e}"
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
def _scan_sources(
|
|
622
|
+
self,
|
|
623
|
+
project_dir: Path,
|
|
624
|
+
build_dir: Path,
|
|
625
|
+
board_config: BoardConfig,
|
|
626
|
+
core_path: Path
|
|
627
|
+
) -> "SourceCollection":
|
|
628
|
+
"""
|
|
629
|
+
Scan for all source files.
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
project_dir: Project directory
|
|
633
|
+
build_dir: Build output directory
|
|
634
|
+
board_config: Board configuration
|
|
635
|
+
core_path: Arduino core installation path
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
SourceCollection with all sources
|
|
639
|
+
"""
|
|
640
|
+
scanner = SourceScanner(project_dir, build_dir)
|
|
641
|
+
|
|
642
|
+
# Determine source directories
|
|
643
|
+
# Check if 'src' directory exists, otherwise use project root
|
|
644
|
+
src_dir = project_dir / 'src'
|
|
645
|
+
if not src_dir.exists():
|
|
646
|
+
src_dir = project_dir
|
|
647
|
+
|
|
648
|
+
core_dir = board_config.get_core_sources_dir(core_path)
|
|
649
|
+
variant_dir = board_config.get_variant_dir(core_path)
|
|
650
|
+
|
|
651
|
+
return scanner.scan(
|
|
652
|
+
src_dir=src_dir,
|
|
653
|
+
core_dir=core_dir,
|
|
654
|
+
variant_dir=variant_dir
|
|
655
|
+
)
|
|
656
|
+
|