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,696 @@
|
|
|
1
|
+
"""
|
|
2
|
+
STM32-specific build orchestration for Fbuild projects.
|
|
3
|
+
|
|
4
|
+
This module handles STM32 platform builds separately from other platforms,
|
|
5
|
+
providing cleaner separation of concerns and better maintainability.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import _thread
|
|
9
|
+
import logging
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
from ..packages import Cache
|
|
16
|
+
from ..packages.platform_stm32 import PlatformSTM32
|
|
17
|
+
from ..packages.toolchain_stm32 import ToolchainSTM32
|
|
18
|
+
from ..packages.library_manager import LibraryManager, LibraryError
|
|
19
|
+
from ..config.board_config import BoardConfig
|
|
20
|
+
from .configurable_compiler import ConfigurableCompiler
|
|
21
|
+
from .configurable_linker import ConfigurableLinker
|
|
22
|
+
from .linker import SizeInfo
|
|
23
|
+
from .orchestrator import IBuildOrchestrator, BuildResult
|
|
24
|
+
from .build_utils import safe_rmtree
|
|
25
|
+
from .build_state import BuildStateTracker
|
|
26
|
+
from .build_info_generator import BuildInfoGenerator
|
|
27
|
+
|
|
28
|
+
# Module-level logger
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class BuildResultSTM32:
|
|
34
|
+
"""Result of an STM32 build operation (internal use)."""
|
|
35
|
+
|
|
36
|
+
success: bool
|
|
37
|
+
firmware_hex: Optional[Path]
|
|
38
|
+
firmware_bin: Optional[Path]
|
|
39
|
+
firmware_elf: Optional[Path]
|
|
40
|
+
size_info: Optional[SizeInfo]
|
|
41
|
+
build_time: float
|
|
42
|
+
message: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class OrchestratorSTM32(IBuildOrchestrator):
|
|
46
|
+
"""
|
|
47
|
+
Orchestrates STM32-specific build process.
|
|
48
|
+
|
|
49
|
+
Handles platform initialization, toolchain setup, framework preparation,
|
|
50
|
+
and firmware generation for STM32 targets.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, cache: Cache, verbose: bool = False):
|
|
54
|
+
"""
|
|
55
|
+
Initialize STM32 orchestrator.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
cache: Cache instance for package management
|
|
59
|
+
verbose: Enable verbose output
|
|
60
|
+
"""
|
|
61
|
+
self.cache = cache
|
|
62
|
+
self.verbose = verbose
|
|
63
|
+
|
|
64
|
+
def build(
|
|
65
|
+
self,
|
|
66
|
+
project_dir: Path,
|
|
67
|
+
env_name: Optional[str] = None,
|
|
68
|
+
clean: bool = False,
|
|
69
|
+
verbose: Optional[bool] = None
|
|
70
|
+
) -> BuildResult:
|
|
71
|
+
"""Execute complete build process (IBuildOrchestrator interface).
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
project_dir: Project root directory containing platformio.ini
|
|
75
|
+
env_name: Environment name to build (defaults to first/default env)
|
|
76
|
+
clean: Clean build (remove all artifacts before building)
|
|
77
|
+
verbose: Override verbose setting
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
BuildResult with build status and output paths
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
BuildOrchestratorError: If build fails at any phase
|
|
84
|
+
"""
|
|
85
|
+
from ..config import PlatformIOConfig
|
|
86
|
+
|
|
87
|
+
verbose_mode = verbose if verbose is not None else self.verbose
|
|
88
|
+
|
|
89
|
+
# Parse platformio.ini to get environment configuration
|
|
90
|
+
ini_path = project_dir / "platformio.ini"
|
|
91
|
+
if not ini_path.exists():
|
|
92
|
+
return BuildResult(
|
|
93
|
+
success=False,
|
|
94
|
+
hex_path=None,
|
|
95
|
+
elf_path=None,
|
|
96
|
+
size_info=None,
|
|
97
|
+
build_time=0.0,
|
|
98
|
+
message=f"platformio.ini not found in {project_dir}"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
config = PlatformIOConfig(ini_path)
|
|
103
|
+
|
|
104
|
+
# Determine environment to build
|
|
105
|
+
if env_name is None:
|
|
106
|
+
env_name = config.get_default_environment()
|
|
107
|
+
if env_name is None:
|
|
108
|
+
return BuildResult(
|
|
109
|
+
success=False,
|
|
110
|
+
hex_path=None,
|
|
111
|
+
elf_path=None,
|
|
112
|
+
size_info=None,
|
|
113
|
+
build_time=0.0,
|
|
114
|
+
message="No environment specified and no default found in platformio.ini"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
env_config = config.get_env_config(env_name)
|
|
118
|
+
board_id = env_config.get("board", "nucleo_f446re")
|
|
119
|
+
build_flags = config.get_build_flags(env_name)
|
|
120
|
+
lib_deps = config.get_lib_deps(env_name)
|
|
121
|
+
|
|
122
|
+
# Call internal build method
|
|
123
|
+
stm32_result = self._build_stm32(
|
|
124
|
+
project_dir, env_name, board_id, env_config, build_flags, lib_deps, clean, verbose_mode
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Convert BuildResultSTM32 to BuildResult
|
|
128
|
+
return BuildResult(
|
|
129
|
+
success=stm32_result.success,
|
|
130
|
+
hex_path=stm32_result.firmware_hex,
|
|
131
|
+
elf_path=stm32_result.firmware_elf,
|
|
132
|
+
size_info=stm32_result.size_info,
|
|
133
|
+
build_time=stm32_result.build_time,
|
|
134
|
+
message=stm32_result.message
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
except KeyboardInterrupt:
|
|
138
|
+
_thread.interrupt_main()
|
|
139
|
+
raise
|
|
140
|
+
except Exception as e:
|
|
141
|
+
return BuildResult(
|
|
142
|
+
success=False,
|
|
143
|
+
hex_path=None,
|
|
144
|
+
elf_path=None,
|
|
145
|
+
size_info=None,
|
|
146
|
+
build_time=0.0,
|
|
147
|
+
message=f"Failed to parse configuration: {e}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def _build_stm32(
|
|
151
|
+
self,
|
|
152
|
+
project_dir: Path,
|
|
153
|
+
env_name: str,
|
|
154
|
+
board_id: str,
|
|
155
|
+
env_config: dict,
|
|
156
|
+
build_flags: List[str],
|
|
157
|
+
lib_deps: List[str],
|
|
158
|
+
clean: bool = False,
|
|
159
|
+
verbose: bool = False
|
|
160
|
+
) -> BuildResultSTM32:
|
|
161
|
+
"""
|
|
162
|
+
Execute complete STM32 build process (internal implementation).
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
project_dir: Project directory
|
|
166
|
+
env_name: Environment name
|
|
167
|
+
board_id: Board ID (e.g., nucleo_f446re, bluepill_f103c8)
|
|
168
|
+
env_config: Environment configuration dict
|
|
169
|
+
build_flags: User build flags from platformio.ini
|
|
170
|
+
lib_deps: Library dependencies from platformio.ini
|
|
171
|
+
clean: Whether to clean before build
|
|
172
|
+
verbose: Verbose output mode
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
BuildResultSTM32 with build status and output paths
|
|
176
|
+
"""
|
|
177
|
+
start_time = time.time()
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
# Get board configuration
|
|
181
|
+
from ..config.board_config import BoardConfig
|
|
182
|
+
|
|
183
|
+
if verbose:
|
|
184
|
+
logger.info("[2/7] Loading board configuration...")
|
|
185
|
+
else:
|
|
186
|
+
logger.info("Loading board configuration...")
|
|
187
|
+
|
|
188
|
+
board_config = BoardConfig.from_board_id(board_id)
|
|
189
|
+
|
|
190
|
+
# Initialize platform
|
|
191
|
+
if verbose:
|
|
192
|
+
logger.info("[3/7] Initializing STM32 platform...")
|
|
193
|
+
else:
|
|
194
|
+
logger.info("Initializing STM32 platform...")
|
|
195
|
+
|
|
196
|
+
platform = PlatformSTM32(
|
|
197
|
+
self.cache,
|
|
198
|
+
board_config.mcu,
|
|
199
|
+
show_progress=True
|
|
200
|
+
)
|
|
201
|
+
platform.ensure_package()
|
|
202
|
+
|
|
203
|
+
if verbose:
|
|
204
|
+
logger.info(f" Board: {board_id}")
|
|
205
|
+
logger.info(f" MCU: {board_config.mcu}")
|
|
206
|
+
logger.info(f" CPU Frequency: {board_config.f_cpu}")
|
|
207
|
+
|
|
208
|
+
# Setup build directory
|
|
209
|
+
build_dir = self._setup_build_directory(env_name, clean, verbose)
|
|
210
|
+
|
|
211
|
+
# Check build state and invalidate cache if needed
|
|
212
|
+
if verbose:
|
|
213
|
+
logger.info("[3.5/7] Checking build configuration state...")
|
|
214
|
+
|
|
215
|
+
state_tracker = BuildStateTracker(build_dir)
|
|
216
|
+
needs_rebuild, reasons, current_state = state_tracker.check_invalidation(
|
|
217
|
+
platformio_ini_path=project_dir / "platformio.ini",
|
|
218
|
+
platform="ststm32",
|
|
219
|
+
board=board_id,
|
|
220
|
+
framework=env_config.get('framework', 'arduino'),
|
|
221
|
+
toolchain_version=platform.toolchain.version,
|
|
222
|
+
framework_version=platform.framework.version,
|
|
223
|
+
platform_version=f"stm32-{platform.framework.version}",
|
|
224
|
+
build_flags=build_flags,
|
|
225
|
+
lib_deps=lib_deps,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if needs_rebuild:
|
|
229
|
+
if verbose:
|
|
230
|
+
logger.info(" Build cache invalidated:")
|
|
231
|
+
for reason in reasons:
|
|
232
|
+
logger.info(f" - {reason}")
|
|
233
|
+
logger.info(" Cleaning build artifacts...")
|
|
234
|
+
# Clean build artifacts to force rebuild
|
|
235
|
+
if build_dir.exists():
|
|
236
|
+
safe_rmtree(build_dir)
|
|
237
|
+
# Recreate build directory
|
|
238
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
239
|
+
else:
|
|
240
|
+
if verbose:
|
|
241
|
+
logger.info(" Build configuration unchanged, using cached artifacts")
|
|
242
|
+
|
|
243
|
+
# Load platform configuration JSON for MCU-specific settings
|
|
244
|
+
import json
|
|
245
|
+
mcu_family = platform._get_mcu_family(board_config.mcu).lower().replace("xx", "")
|
|
246
|
+
platform_config_path = Path(__file__).parent.parent / "platform_configs" / f"{mcu_family}.json"
|
|
247
|
+
platform_config = None
|
|
248
|
+
if platform_config_path.exists():
|
|
249
|
+
with open(platform_config_path, 'r') as f:
|
|
250
|
+
platform_config = json.load(f)
|
|
251
|
+
|
|
252
|
+
# Initialize compiler
|
|
253
|
+
if verbose:
|
|
254
|
+
logger.info("[4/7] Compiling Arduino core...")
|
|
255
|
+
else:
|
|
256
|
+
logger.info("Compiling Arduino core...")
|
|
257
|
+
|
|
258
|
+
compiler = ConfigurableCompiler(
|
|
259
|
+
platform,
|
|
260
|
+
platform.toolchain,
|
|
261
|
+
platform.framework,
|
|
262
|
+
board_id,
|
|
263
|
+
build_dir,
|
|
264
|
+
platform_config=platform_config,
|
|
265
|
+
show_progress=verbose,
|
|
266
|
+
user_build_flags=build_flags
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Compile Arduino core with progress bar
|
|
270
|
+
if verbose:
|
|
271
|
+
core_obj_files = compiler.compile_core()
|
|
272
|
+
else:
|
|
273
|
+
# Use tqdm progress bar for non-verbose mode
|
|
274
|
+
from tqdm import tqdm
|
|
275
|
+
|
|
276
|
+
# Get number of core source files for progress tracking
|
|
277
|
+
core_sources = platform.framework.get_core_sources("arduino")
|
|
278
|
+
total_files = len(core_sources)
|
|
279
|
+
|
|
280
|
+
# Create progress bar
|
|
281
|
+
with tqdm(
|
|
282
|
+
total=total_files,
|
|
283
|
+
desc='Compiling Arduino core',
|
|
284
|
+
unit='file',
|
|
285
|
+
ncols=80,
|
|
286
|
+
leave=False
|
|
287
|
+
) as pbar:
|
|
288
|
+
core_obj_files = compiler.compile_core(progress_bar=pbar)
|
|
289
|
+
|
|
290
|
+
# Print completion message
|
|
291
|
+
logger.info(f"Compiled {len(core_obj_files)} core files")
|
|
292
|
+
|
|
293
|
+
core_archive = compiler.create_core_archive(core_obj_files)
|
|
294
|
+
|
|
295
|
+
if verbose:
|
|
296
|
+
logger.info(f" Compiled {len(core_obj_files)} core source files")
|
|
297
|
+
|
|
298
|
+
# Handle library dependencies (if any)
|
|
299
|
+
library_archives, library_include_paths = self._process_libraries(
|
|
300
|
+
env_config, build_dir, compiler, platform.toolchain, board_config, verbose, project_dir=project_dir
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Add library include paths to compiler
|
|
304
|
+
if library_include_paths:
|
|
305
|
+
compiler.add_library_includes(library_include_paths)
|
|
306
|
+
|
|
307
|
+
# Get src_dir override from platformio.ini
|
|
308
|
+
from ..config import PlatformIOConfig
|
|
309
|
+
config_for_src_dir = PlatformIOConfig(project_dir / "platformio.ini")
|
|
310
|
+
src_dir_override = config_for_src_dir.get_src_dir()
|
|
311
|
+
|
|
312
|
+
# Find and compile sketch
|
|
313
|
+
sketch_obj_files = self._compile_sketch(project_dir, compiler, start_time, verbose, src_dir_override)
|
|
314
|
+
if sketch_obj_files is None:
|
|
315
|
+
search_dir = project_dir / src_dir_override if src_dir_override else project_dir
|
|
316
|
+
return self._error_result(
|
|
317
|
+
start_time,
|
|
318
|
+
f"No .ino sketch file found in {search_dir}"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Initialize linker
|
|
322
|
+
if verbose:
|
|
323
|
+
logger.info("[6/7] Linking firmware...")
|
|
324
|
+
else:
|
|
325
|
+
logger.info("Linking firmware...")
|
|
326
|
+
|
|
327
|
+
linker = ConfigurableLinker(
|
|
328
|
+
platform,
|
|
329
|
+
platform.toolchain,
|
|
330
|
+
platform.framework,
|
|
331
|
+
board_id,
|
|
332
|
+
build_dir,
|
|
333
|
+
platform_config=platform_config,
|
|
334
|
+
show_progress=verbose
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Link firmware
|
|
338
|
+
firmware_elf = linker.link(sketch_obj_files, core_archive, library_archives=library_archives)
|
|
339
|
+
|
|
340
|
+
# Generate bin and hex files
|
|
341
|
+
if verbose:
|
|
342
|
+
logger.info("[7/7] Generating firmware...")
|
|
343
|
+
else:
|
|
344
|
+
logger.info("Generating firmware...")
|
|
345
|
+
|
|
346
|
+
firmware_bin = linker.generate_bin(firmware_elf)
|
|
347
|
+
firmware_hex = self._generate_hex(firmware_elf, platform.toolchain, verbose)
|
|
348
|
+
|
|
349
|
+
# Get size info
|
|
350
|
+
size_info = linker.get_size_info(firmware_elf)
|
|
351
|
+
|
|
352
|
+
build_time = time.time() - start_time
|
|
353
|
+
|
|
354
|
+
if verbose:
|
|
355
|
+
self._print_success(
|
|
356
|
+
build_time, firmware_elf, firmware_hex, size_info
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Save build state for future cache validation
|
|
360
|
+
if verbose:
|
|
361
|
+
logger.info("[7.5/7] Saving build state...")
|
|
362
|
+
state_tracker.save_state(current_state)
|
|
363
|
+
|
|
364
|
+
# Generate build_info.json
|
|
365
|
+
build_info_generator = BuildInfoGenerator(build_dir)
|
|
366
|
+
# Parse f_cpu from string (e.g., "180000000L") to int
|
|
367
|
+
f_cpu_int = int(board_config.f_cpu.rstrip("L"))
|
|
368
|
+
# Build toolchain_paths dict, filtering out None values
|
|
369
|
+
toolchain_paths_raw = {
|
|
370
|
+
"gcc": platform.toolchain.get_gcc_path(),
|
|
371
|
+
"gxx": platform.toolchain.get_gxx_path(),
|
|
372
|
+
"ar": platform.toolchain.get_ar_path(),
|
|
373
|
+
"objcopy": platform.toolchain.get_objcopy_path(),
|
|
374
|
+
"size": platform.toolchain.get_size_path(),
|
|
375
|
+
}
|
|
376
|
+
toolchain_paths = {k: v for k, v in toolchain_paths_raw.items() if v is not None}
|
|
377
|
+
build_info = build_info_generator.generate_generic(
|
|
378
|
+
env_name=env_name,
|
|
379
|
+
board_id=board_id,
|
|
380
|
+
board_name=board_config.name,
|
|
381
|
+
mcu=board_config.mcu,
|
|
382
|
+
platform="ststm32",
|
|
383
|
+
f_cpu=f_cpu_int,
|
|
384
|
+
build_time=build_time,
|
|
385
|
+
elf_path=firmware_elf,
|
|
386
|
+
hex_path=firmware_hex,
|
|
387
|
+
bin_path=firmware_bin,
|
|
388
|
+
size_info=size_info,
|
|
389
|
+
build_flags=build_flags,
|
|
390
|
+
lib_deps=lib_deps,
|
|
391
|
+
toolchain_version=platform.toolchain.version,
|
|
392
|
+
toolchain_paths=toolchain_paths,
|
|
393
|
+
framework_name="arduino",
|
|
394
|
+
framework_version=platform.framework.version,
|
|
395
|
+
core_path=platform.framework.get_cores_dir(),
|
|
396
|
+
)
|
|
397
|
+
build_info_generator.save(build_info)
|
|
398
|
+
if verbose:
|
|
399
|
+
logger.info(f" Build info saved to {build_info_generator.build_info_path}")
|
|
400
|
+
|
|
401
|
+
return BuildResultSTM32(
|
|
402
|
+
success=True,
|
|
403
|
+
firmware_hex=firmware_hex,
|
|
404
|
+
firmware_bin=firmware_bin,
|
|
405
|
+
firmware_elf=firmware_elf,
|
|
406
|
+
size_info=size_info,
|
|
407
|
+
build_time=build_time,
|
|
408
|
+
message="Build successful (native STM32 build)"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
except KeyboardInterrupt as ke:
|
|
412
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
413
|
+
handle_keyboard_interrupt_properly(ke)
|
|
414
|
+
raise # Never reached, but satisfies type checker
|
|
415
|
+
except Exception as e:
|
|
416
|
+
build_time = time.time() - start_time
|
|
417
|
+
import traceback
|
|
418
|
+
error_trace = traceback.format_exc()
|
|
419
|
+
return BuildResultSTM32(
|
|
420
|
+
success=False,
|
|
421
|
+
firmware_hex=None,
|
|
422
|
+
firmware_bin=None,
|
|
423
|
+
firmware_elf=None,
|
|
424
|
+
size_info=None,
|
|
425
|
+
build_time=build_time,
|
|
426
|
+
message=f"STM32 native build failed: {e}\n\n{error_trace}"
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
def _generate_hex(self, elf_path: Path, toolchain: ToolchainSTM32, verbose: bool = False) -> Path:
|
|
430
|
+
"""Generate HEX file from ELF file.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
elf_path: Path to input ELF file
|
|
434
|
+
toolchain: STM32 toolchain instance
|
|
435
|
+
verbose: Verbose output mode
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
Path to generated HEX file
|
|
439
|
+
|
|
440
|
+
Raises:
|
|
441
|
+
Exception: If HEX generation fails
|
|
442
|
+
"""
|
|
443
|
+
import subprocess
|
|
444
|
+
|
|
445
|
+
hex_path = elf_path.parent / f"{elf_path.stem}.hex"
|
|
446
|
+
|
|
447
|
+
if verbose:
|
|
448
|
+
logger.info(f" Generating HEX file: {hex_path.name}")
|
|
449
|
+
|
|
450
|
+
objcopy = toolchain.get_objcopy_path()
|
|
451
|
+
if objcopy is None:
|
|
452
|
+
raise Exception("objcopy not found in toolchain")
|
|
453
|
+
|
|
454
|
+
cmd = [
|
|
455
|
+
str(objcopy),
|
|
456
|
+
"-O", "ihex",
|
|
457
|
+
str(elf_path),
|
|
458
|
+
str(hex_path)
|
|
459
|
+
]
|
|
460
|
+
|
|
461
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
462
|
+
if result.returncode != 0:
|
|
463
|
+
raise Exception(f"objcopy failed: {result.stderr}")
|
|
464
|
+
|
|
465
|
+
if verbose:
|
|
466
|
+
logger.info(f" HEX file generated: {hex_path}")
|
|
467
|
+
|
|
468
|
+
return hex_path
|
|
469
|
+
|
|
470
|
+
def _setup_build_directory(self, env_name: str, clean: bool, verbose: bool) -> Path:
|
|
471
|
+
"""
|
|
472
|
+
Setup build directory with optional cleaning.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
env_name: Environment name
|
|
476
|
+
clean: Whether to clean before build
|
|
477
|
+
verbose: Verbose output mode
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
Build directory path
|
|
481
|
+
"""
|
|
482
|
+
build_dir = self.cache.get_build_dir(env_name)
|
|
483
|
+
|
|
484
|
+
if clean and build_dir.exists():
|
|
485
|
+
if verbose:
|
|
486
|
+
logger.info("[1/7] Cleaning build directory...")
|
|
487
|
+
else:
|
|
488
|
+
logger.info("Cleaning build directory...")
|
|
489
|
+
safe_rmtree(build_dir)
|
|
490
|
+
|
|
491
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
492
|
+
return build_dir
|
|
493
|
+
|
|
494
|
+
def _process_libraries(
|
|
495
|
+
self,
|
|
496
|
+
env_config: dict,
|
|
497
|
+
build_dir: Path,
|
|
498
|
+
compiler: ConfigurableCompiler,
|
|
499
|
+
toolchain: ToolchainSTM32,
|
|
500
|
+
board_config: BoardConfig,
|
|
501
|
+
verbose: bool,
|
|
502
|
+
project_dir: Optional[Path] = None
|
|
503
|
+
) -> tuple[List[Path], List[Path]]:
|
|
504
|
+
"""
|
|
505
|
+
Process and compile library dependencies.
|
|
506
|
+
|
|
507
|
+
Args:
|
|
508
|
+
env_config: Environment configuration
|
|
509
|
+
build_dir: Build directory
|
|
510
|
+
compiler: Configured compiler instance
|
|
511
|
+
toolchain: STM32 toolchain instance
|
|
512
|
+
board_config: Board configuration instance
|
|
513
|
+
verbose: Verbose output mode
|
|
514
|
+
project_dir: Optional project directory for resolving relative library paths
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Tuple of (library_archives, library_include_paths)
|
|
518
|
+
"""
|
|
519
|
+
lib_deps = env_config.get('lib_deps', '')
|
|
520
|
+
library_archives = []
|
|
521
|
+
library_include_paths = []
|
|
522
|
+
|
|
523
|
+
if not lib_deps:
|
|
524
|
+
return library_archives, library_include_paths
|
|
525
|
+
|
|
526
|
+
if verbose:
|
|
527
|
+
logger.info("[4.5/7] Processing library dependencies...")
|
|
528
|
+
|
|
529
|
+
# Parse lib_deps (can be string or list)
|
|
530
|
+
if isinstance(lib_deps, str):
|
|
531
|
+
lib_specs = [dep.strip() for dep in lib_deps.split('\n') if dep.strip()]
|
|
532
|
+
else:
|
|
533
|
+
lib_specs = lib_deps
|
|
534
|
+
|
|
535
|
+
if not lib_specs:
|
|
536
|
+
return library_archives, library_include_paths
|
|
537
|
+
|
|
538
|
+
try:
|
|
539
|
+
# Initialize library manager
|
|
540
|
+
library_manager = LibraryManager(build_dir, mode="release")
|
|
541
|
+
|
|
542
|
+
# Prepare compilation parameters
|
|
543
|
+
lib_defines = []
|
|
544
|
+
defines_dict = board_config.get_defines()
|
|
545
|
+
for key, value in defines_dict.items():
|
|
546
|
+
if value:
|
|
547
|
+
lib_defines.append(f"{key}={value}")
|
|
548
|
+
else:
|
|
549
|
+
lib_defines.append(key)
|
|
550
|
+
|
|
551
|
+
# Get include paths from compiler configuration
|
|
552
|
+
lib_includes = compiler.get_include_paths()
|
|
553
|
+
|
|
554
|
+
# Get compiler path from toolchain (use C++ compiler for libraries)
|
|
555
|
+
compiler_path = toolchain.get_gxx_path()
|
|
556
|
+
if compiler_path is None:
|
|
557
|
+
raise LibraryError("C++ compiler not found in toolchain")
|
|
558
|
+
|
|
559
|
+
if verbose:
|
|
560
|
+
logger.info(f" Found {len(lib_specs)} library dependencies")
|
|
561
|
+
|
|
562
|
+
# Ensure all libraries are downloaded and compiled
|
|
563
|
+
libraries = library_manager.ensure_libraries(
|
|
564
|
+
lib_deps=lib_specs,
|
|
565
|
+
compiler_path=compiler_path,
|
|
566
|
+
mcu=board_config.mcu,
|
|
567
|
+
f_cpu=board_config.f_cpu,
|
|
568
|
+
defines=lib_defines,
|
|
569
|
+
include_paths=lib_includes,
|
|
570
|
+
extra_flags=[],
|
|
571
|
+
show_progress=verbose
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
# Get library artifacts
|
|
575
|
+
library_include_paths = library_manager.get_library_include_paths()
|
|
576
|
+
library_archives = library_manager.get_library_objects()
|
|
577
|
+
|
|
578
|
+
if verbose:
|
|
579
|
+
logger.info(f" Compiled {len(libraries)} libraries")
|
|
580
|
+
|
|
581
|
+
except LibraryError as e:
|
|
582
|
+
logger.warning(f" Error processing libraries: {e}")
|
|
583
|
+
# Continue build without libraries
|
|
584
|
+
library_archives = []
|
|
585
|
+
library_include_paths = []
|
|
586
|
+
|
|
587
|
+
return library_archives, library_include_paths
|
|
588
|
+
|
|
589
|
+
def _compile_sketch(
|
|
590
|
+
self,
|
|
591
|
+
project_dir: Path,
|
|
592
|
+
compiler: ConfigurableCompiler,
|
|
593
|
+
start_time: float,
|
|
594
|
+
verbose: bool,
|
|
595
|
+
src_dir_override: Optional[str] = None
|
|
596
|
+
) -> Optional[List[Path]]:
|
|
597
|
+
"""
|
|
598
|
+
Find and compile sketch files.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
project_dir: Project directory
|
|
602
|
+
compiler: Configured compiler instance
|
|
603
|
+
start_time: Build start time
|
|
604
|
+
verbose: Verbose output mode
|
|
605
|
+
src_dir_override: Optional src_dir override from platformio.ini
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
List of compiled object files, or None if no sketch found
|
|
609
|
+
"""
|
|
610
|
+
if verbose:
|
|
611
|
+
logger.info("[5/7] Compiling sketch...")
|
|
612
|
+
else:
|
|
613
|
+
logger.info("Compiling sketch...")
|
|
614
|
+
|
|
615
|
+
# Determine source directory
|
|
616
|
+
if src_dir_override:
|
|
617
|
+
src_dir = project_dir / src_dir_override
|
|
618
|
+
else:
|
|
619
|
+
src_dir = project_dir / "src"
|
|
620
|
+
if not src_dir.exists():
|
|
621
|
+
src_dir = project_dir
|
|
622
|
+
|
|
623
|
+
# Find sketch file (.ino or .cpp)
|
|
624
|
+
sketch_files = list(src_dir.glob("*.ino"))
|
|
625
|
+
if not sketch_files:
|
|
626
|
+
sketch_files = list(src_dir.glob("*.cpp"))
|
|
627
|
+
|
|
628
|
+
if not sketch_files:
|
|
629
|
+
return None
|
|
630
|
+
|
|
631
|
+
# Also find additional source files
|
|
632
|
+
cpp_files = list(src_dir.glob("*.cpp"))
|
|
633
|
+
c_files = list(src_dir.glob("*.c"))
|
|
634
|
+
all_source_files = sketch_files + [f for f in cpp_files if f not in sketch_files] + c_files
|
|
635
|
+
|
|
636
|
+
if verbose:
|
|
637
|
+
logger.info(f" Found {len(all_source_files)} source files")
|
|
638
|
+
|
|
639
|
+
# Compile sketch files - compile each file individually
|
|
640
|
+
obj_files = []
|
|
641
|
+
for source_file in all_source_files:
|
|
642
|
+
if source_file.suffix == '.ino':
|
|
643
|
+
# .ino files need preprocessing
|
|
644
|
+
compiled = compiler.compile_sketch(source_file)
|
|
645
|
+
obj_files.extend(compiled)
|
|
646
|
+
else:
|
|
647
|
+
# .c and .cpp files can be compiled directly
|
|
648
|
+
obj_dir = compiler.build_dir / "obj"
|
|
649
|
+
obj_dir.mkdir(parents=True, exist_ok=True)
|
|
650
|
+
obj_path = obj_dir / f"{source_file.stem}.o"
|
|
651
|
+
|
|
652
|
+
# Skip if up-to-date
|
|
653
|
+
if not compiler.needs_rebuild(source_file, obj_path):
|
|
654
|
+
obj_files.append(obj_path)
|
|
655
|
+
continue
|
|
656
|
+
|
|
657
|
+
compiled_obj = compiler.compile_source(source_file, obj_path)
|
|
658
|
+
obj_files.append(compiled_obj)
|
|
659
|
+
|
|
660
|
+
if verbose:
|
|
661
|
+
logger.info(f" Compiled {len(obj_files)} sketch files")
|
|
662
|
+
|
|
663
|
+
return obj_files
|
|
664
|
+
|
|
665
|
+
def _error_result(self, start_time: float, message: str) -> BuildResultSTM32:
|
|
666
|
+
"""Create an error result."""
|
|
667
|
+
return BuildResultSTM32(
|
|
668
|
+
success=False,
|
|
669
|
+
firmware_hex=None,
|
|
670
|
+
firmware_bin=None,
|
|
671
|
+
firmware_elf=None,
|
|
672
|
+
size_info=None,
|
|
673
|
+
build_time=time.time() - start_time,
|
|
674
|
+
message=message
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
def _print_success(
|
|
678
|
+
self,
|
|
679
|
+
build_time: float,
|
|
680
|
+
firmware_elf: Path,
|
|
681
|
+
firmware_hex: Path,
|
|
682
|
+
size_info: Optional[SizeInfo]
|
|
683
|
+
) -> None:
|
|
684
|
+
"""Print success message with build details."""
|
|
685
|
+
logger.info("")
|
|
686
|
+
logger.info("=" * 60)
|
|
687
|
+
logger.info("STM32 BUILD SUCCESSFUL")
|
|
688
|
+
logger.info("=" * 60)
|
|
689
|
+
logger.info(f"Build time: {build_time:.2f}s")
|
|
690
|
+
logger.info(f"Output: {firmware_hex}")
|
|
691
|
+
|
|
692
|
+
if size_info:
|
|
693
|
+
logger.info(f"Flash: {size_info.total_flash:,} bytes")
|
|
694
|
+
logger.info(f"RAM: {size_info.total_ram:,} bytes")
|
|
695
|
+
|
|
696
|
+
logger.info("=" * 60)
|