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