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