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,878 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ESP32-specific build orchestration for Fbuild projects.
|
|
3
|
+
|
|
4
|
+
This module handles ESP32 platform builds separately from AVR builds,
|
|
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_esp32 import PlatformESP32
|
|
17
|
+
from ..packages.toolchain_esp32 import ToolchainESP32
|
|
18
|
+
from ..packages.framework_esp32 import FrameworkESP32
|
|
19
|
+
from ..packages.library_manager_esp32 import LibraryManagerESP32
|
|
20
|
+
from ..cli_utils import BannerFormatter
|
|
21
|
+
from .configurable_compiler import ConfigurableCompiler
|
|
22
|
+
from .configurable_linker import ConfigurableLinker
|
|
23
|
+
from .linker import SizeInfo
|
|
24
|
+
from .orchestrator import IBuildOrchestrator, BuildResult
|
|
25
|
+
from .build_utils import safe_rmtree
|
|
26
|
+
from .build_state import BuildStateTracker
|
|
27
|
+
from .build_info_generator import BuildInfoGenerator
|
|
28
|
+
from ..output import log_phase, log_detail, log_warning
|
|
29
|
+
|
|
30
|
+
# Module-level logger
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class BuildResultESP32:
|
|
36
|
+
"""Result of an ESP32 build operation (internal use)."""
|
|
37
|
+
|
|
38
|
+
success: bool
|
|
39
|
+
firmware_bin: Optional[Path]
|
|
40
|
+
firmware_elf: Optional[Path]
|
|
41
|
+
bootloader_bin: Optional[Path]
|
|
42
|
+
partitions_bin: Optional[Path]
|
|
43
|
+
size_info: Optional[SizeInfo]
|
|
44
|
+
build_time: float
|
|
45
|
+
message: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OrchestratorESP32(IBuildOrchestrator):
|
|
49
|
+
"""
|
|
50
|
+
Orchestrates ESP32-specific build process.
|
|
51
|
+
|
|
52
|
+
Handles platform initialization, toolchain setup, framework preparation,
|
|
53
|
+
library compilation, and firmware generation for ESP32 targets.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, cache: Cache, verbose: bool = False):
|
|
57
|
+
"""
|
|
58
|
+
Initialize ESP32 orchestrator.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
cache: Cache instance for package management
|
|
62
|
+
verbose: Enable verbose output
|
|
63
|
+
"""
|
|
64
|
+
self.cache = cache
|
|
65
|
+
self.verbose = verbose
|
|
66
|
+
|
|
67
|
+
def build(
|
|
68
|
+
self,
|
|
69
|
+
project_dir: Path,
|
|
70
|
+
env_name: Optional[str] = None,
|
|
71
|
+
clean: bool = False,
|
|
72
|
+
verbose: Optional[bool] = None
|
|
73
|
+
) -> BuildResult:
|
|
74
|
+
"""Execute complete build process (BaseBuildOrchestrator interface).
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
project_dir: Project root directory containing platformio.ini
|
|
78
|
+
env_name: Environment name to build (defaults to first/default env)
|
|
79
|
+
clean: Clean build (remove all artifacts before building)
|
|
80
|
+
verbose: Override verbose setting
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
BuildResult with build status and output paths
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
BuildOrchestratorError: If build fails at any phase
|
|
87
|
+
"""
|
|
88
|
+
from ..config import PlatformIOConfig
|
|
89
|
+
|
|
90
|
+
verbose_mode = verbose if verbose is not None else self.verbose
|
|
91
|
+
|
|
92
|
+
# Parse platformio.ini to get environment configuration
|
|
93
|
+
ini_path = project_dir / "platformio.ini"
|
|
94
|
+
if not ini_path.exists():
|
|
95
|
+
return BuildResult(
|
|
96
|
+
success=False,
|
|
97
|
+
hex_path=None,
|
|
98
|
+
elf_path=None,
|
|
99
|
+
size_info=None,
|
|
100
|
+
build_time=0.0,
|
|
101
|
+
message=f"platformio.ini not found in {project_dir}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
config = PlatformIOConfig(ini_path)
|
|
106
|
+
|
|
107
|
+
# Determine environment to build
|
|
108
|
+
if env_name is None:
|
|
109
|
+
env_name = config.get_default_environment()
|
|
110
|
+
if env_name is None:
|
|
111
|
+
return BuildResult(
|
|
112
|
+
success=False,
|
|
113
|
+
hex_path=None,
|
|
114
|
+
elf_path=None,
|
|
115
|
+
size_info=None,
|
|
116
|
+
build_time=0.0,
|
|
117
|
+
message="No environment specified and no default found in platformio.ini"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
env_config = config.get_env_config(env_name)
|
|
121
|
+
board_id = env_config.get("board", "")
|
|
122
|
+
build_flags = config.get_build_flags(env_name)
|
|
123
|
+
|
|
124
|
+
# Add debug logging for lib_deps
|
|
125
|
+
logger.debug(f"[ORCHESTRATOR] About to call config.get_lib_deps('{env_name}')")
|
|
126
|
+
lib_deps = config.get_lib_deps(env_name)
|
|
127
|
+
logger.debug(f"[ORCHESTRATOR] get_lib_deps returned: {lib_deps}")
|
|
128
|
+
|
|
129
|
+
# Call internal build method
|
|
130
|
+
esp32_result = self._build_esp32(
|
|
131
|
+
project_dir, env_name, board_id, env_config, build_flags, lib_deps, clean, verbose_mode
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Convert BuildResultESP32 to BuildResult
|
|
135
|
+
return BuildResult(
|
|
136
|
+
success=esp32_result.success,
|
|
137
|
+
hex_path=esp32_result.firmware_bin,
|
|
138
|
+
elf_path=esp32_result.firmware_elf,
|
|
139
|
+
size_info=esp32_result.size_info,
|
|
140
|
+
build_time=esp32_result.build_time,
|
|
141
|
+
message=esp32_result.message
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
except KeyboardInterrupt:
|
|
145
|
+
_thread.interrupt_main()
|
|
146
|
+
raise
|
|
147
|
+
except Exception as e:
|
|
148
|
+
return BuildResult(
|
|
149
|
+
success=False,
|
|
150
|
+
hex_path=None,
|
|
151
|
+
elf_path=None,
|
|
152
|
+
size_info=None,
|
|
153
|
+
build_time=0.0,
|
|
154
|
+
message=f"Failed to parse configuration: {e}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def _build_esp32(
|
|
158
|
+
self,
|
|
159
|
+
project_dir: Path,
|
|
160
|
+
env_name: str,
|
|
161
|
+
board_id: str,
|
|
162
|
+
env_config: dict,
|
|
163
|
+
build_flags: List[str],
|
|
164
|
+
lib_deps: List[str],
|
|
165
|
+
clean: bool = False,
|
|
166
|
+
verbose: bool = False
|
|
167
|
+
) -> BuildResultESP32:
|
|
168
|
+
"""
|
|
169
|
+
Execute complete ESP32 build process (internal implementation).
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
project_dir: Project directory
|
|
173
|
+
env_name: Environment name
|
|
174
|
+
board_id: Board ID (e.g., esp32-c6-devkitm-1)
|
|
175
|
+
env_config: Environment configuration dict
|
|
176
|
+
build_flags: User build flags from platformio.ini
|
|
177
|
+
lib_deps: Library dependencies from platformio.ini
|
|
178
|
+
clean: Whether to clean before build
|
|
179
|
+
verbose: Verbose output mode
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
BuildResultESP32 with build status and output paths
|
|
183
|
+
"""
|
|
184
|
+
start_time = time.time()
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
# Get platform URL from env_config
|
|
188
|
+
platform_url = env_config.get('platform')
|
|
189
|
+
if not platform_url:
|
|
190
|
+
return self._error_result(
|
|
191
|
+
start_time,
|
|
192
|
+
"No platform URL specified in platformio.ini"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Resolve platform shorthand to actual download URL
|
|
196
|
+
# PlatformIO supports formats like "platformio/espressif32" which need
|
|
197
|
+
# to be converted to a real download URL
|
|
198
|
+
platform_url = self._resolve_platform_url(platform_url)
|
|
199
|
+
|
|
200
|
+
# Initialize platform
|
|
201
|
+
log_phase(3, 12, "Initializing ESP32 platform...")
|
|
202
|
+
|
|
203
|
+
platform = PlatformESP32(self.cache, platform_url, show_progress=True)
|
|
204
|
+
platform.ensure_platform()
|
|
205
|
+
|
|
206
|
+
# Get board configuration
|
|
207
|
+
board_json = platform.get_board_json(board_id)
|
|
208
|
+
mcu = board_json.get("build", {}).get("mcu", "esp32c6")
|
|
209
|
+
|
|
210
|
+
log_detail(f"Board: {board_id}", verbose_only=True)
|
|
211
|
+
log_detail(f"MCU: {mcu}", verbose_only=True)
|
|
212
|
+
|
|
213
|
+
# Get required packages
|
|
214
|
+
packages = platform.get_required_packages(mcu)
|
|
215
|
+
|
|
216
|
+
# Initialize toolchain
|
|
217
|
+
toolchain = self._setup_toolchain(packages, start_time, verbose)
|
|
218
|
+
if toolchain is None:
|
|
219
|
+
return self._error_result(
|
|
220
|
+
start_time,
|
|
221
|
+
"Failed to initialize toolchain"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Initialize framework
|
|
225
|
+
framework = self._setup_framework(packages, start_time, verbose)
|
|
226
|
+
if framework is None:
|
|
227
|
+
return self._error_result(
|
|
228
|
+
start_time,
|
|
229
|
+
"Failed to initialize framework"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Setup build directory
|
|
233
|
+
build_dir = self._setup_build_directory(env_name, clean, verbose)
|
|
234
|
+
|
|
235
|
+
# Check build state and invalidate cache if needed
|
|
236
|
+
log_detail("Checking build configuration state...", verbose_only=True)
|
|
237
|
+
|
|
238
|
+
state_tracker = BuildStateTracker(build_dir)
|
|
239
|
+
needs_rebuild, reasons, current_state = state_tracker.check_invalidation(
|
|
240
|
+
platformio_ini_path=project_dir / "platformio.ini",
|
|
241
|
+
platform="esp32",
|
|
242
|
+
board=board_id,
|
|
243
|
+
framework=env_config.get('framework', 'arduino'),
|
|
244
|
+
toolchain_version=toolchain.version,
|
|
245
|
+
framework_version=framework.version,
|
|
246
|
+
platform_version=platform.version,
|
|
247
|
+
build_flags=build_flags,
|
|
248
|
+
lib_deps=lib_deps,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if needs_rebuild:
|
|
252
|
+
log_detail("Build cache invalidated:", verbose_only=True)
|
|
253
|
+
for reason in reasons:
|
|
254
|
+
log_detail(f" - {reason}", indent=8, verbose_only=True)
|
|
255
|
+
log_detail("Cleaning build artifacts...", verbose_only=True)
|
|
256
|
+
# Clean build artifacts to force rebuild
|
|
257
|
+
from .build_utils import safe_rmtree
|
|
258
|
+
if build_dir.exists():
|
|
259
|
+
safe_rmtree(build_dir)
|
|
260
|
+
# Recreate build directory
|
|
261
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
262
|
+
else:
|
|
263
|
+
log_detail("Build configuration unchanged, using cached artifacts", verbose_only=True)
|
|
264
|
+
|
|
265
|
+
# Initialize compilation executor early to show sccache status
|
|
266
|
+
from .compilation_executor import CompilationExecutor
|
|
267
|
+
compilation_executor = CompilationExecutor(
|
|
268
|
+
build_dir=build_dir,
|
|
269
|
+
show_progress=verbose
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Try to get compilation queue from daemon for async compilation
|
|
273
|
+
# TODO: Implement get_compilation_queue() in daemon module
|
|
274
|
+
compilation_queue = None
|
|
275
|
+
# try:
|
|
276
|
+
# from ..daemon import daemon
|
|
277
|
+
# compilation_queue = daemon.get_compilation_queue()
|
|
278
|
+
# if compilation_queue and verbose:
|
|
279
|
+
# num_workers = getattr(compilation_queue, 'num_workers', 'unknown')
|
|
280
|
+
# print(f"[Async Mode] Using daemon compilation queue with {num_workers} workers")
|
|
281
|
+
# except (ImportError, AttributeError):
|
|
282
|
+
# # Daemon not available or not running - use synchronous compilation
|
|
283
|
+
# if verbose:
|
|
284
|
+
# print("[Sync Mode] Daemon not available, using synchronous compilation")
|
|
285
|
+
|
|
286
|
+
# Initialize compiler
|
|
287
|
+
log_phase(7, 12, "Compiling Arduino core...")
|
|
288
|
+
|
|
289
|
+
compiler = ConfigurableCompiler(
|
|
290
|
+
platform,
|
|
291
|
+
toolchain,
|
|
292
|
+
framework,
|
|
293
|
+
board_id,
|
|
294
|
+
build_dir,
|
|
295
|
+
platform_config=None,
|
|
296
|
+
show_progress=verbose,
|
|
297
|
+
user_build_flags=build_flags,
|
|
298
|
+
compilation_executor=compilation_executor,
|
|
299
|
+
compilation_queue=compilation_queue
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Compile Arduino core with progress bar
|
|
303
|
+
if verbose:
|
|
304
|
+
core_obj_files = compiler.compile_core()
|
|
305
|
+
else:
|
|
306
|
+
# Use tqdm progress bar for non-verbose mode
|
|
307
|
+
from tqdm import tqdm
|
|
308
|
+
|
|
309
|
+
# Get number of core source files for progress tracking
|
|
310
|
+
core_sources = framework.get_core_sources(compiler.core)
|
|
311
|
+
total_files = len(core_sources)
|
|
312
|
+
|
|
313
|
+
# Create progress bar
|
|
314
|
+
with tqdm(
|
|
315
|
+
total=total_files,
|
|
316
|
+
desc='Compiling Arduino core',
|
|
317
|
+
unit='file',
|
|
318
|
+
ncols=80,
|
|
319
|
+
leave=False
|
|
320
|
+
) as pbar:
|
|
321
|
+
core_obj_files = compiler.compile_core(progress_bar=pbar)
|
|
322
|
+
|
|
323
|
+
# Print completion message
|
|
324
|
+
log_detail(f"Compiled {len(core_obj_files)} core files")
|
|
325
|
+
|
|
326
|
+
# Add Bluetooth stub for non-ESP32 targets (ESP32-C6, ESP32-S3, etc.)
|
|
327
|
+
# where esp32-hal-bt.c fails to compile but btInUse() is still referenced
|
|
328
|
+
bt_stub_obj = self._create_bt_stub(build_dir, compiler, verbose)
|
|
329
|
+
if bt_stub_obj:
|
|
330
|
+
core_obj_files.append(bt_stub_obj)
|
|
331
|
+
|
|
332
|
+
core_archive = compiler.create_core_archive(core_obj_files)
|
|
333
|
+
|
|
334
|
+
log_detail(f"Compiled {len(core_obj_files)} core source files", verbose_only=True)
|
|
335
|
+
|
|
336
|
+
# Handle library dependencies
|
|
337
|
+
library_archives, library_include_paths = self._process_libraries(
|
|
338
|
+
env_config, build_dir, compiler, toolchain, verbose, project_dir=project_dir
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Add library include paths to compiler
|
|
342
|
+
if library_include_paths:
|
|
343
|
+
compiler.add_library_includes(library_include_paths)
|
|
344
|
+
|
|
345
|
+
# Get src_dir override from platformio.ini
|
|
346
|
+
from ..config import PlatformIOConfig
|
|
347
|
+
config_for_src_dir = PlatformIOConfig(project_dir / "platformio.ini")
|
|
348
|
+
src_dir_override = config_for_src_dir.get_src_dir()
|
|
349
|
+
|
|
350
|
+
# Find and compile sketch
|
|
351
|
+
sketch_obj_files = self._compile_sketch(project_dir, compiler, start_time, verbose, src_dir_override)
|
|
352
|
+
if sketch_obj_files is None:
|
|
353
|
+
search_dir = project_dir / src_dir_override if src_dir_override else project_dir
|
|
354
|
+
return self._error_result(
|
|
355
|
+
start_time,
|
|
356
|
+
f"No .ino sketch file found in {search_dir}"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Initialize linker
|
|
360
|
+
log_phase(9, 12, "Linking firmware...")
|
|
361
|
+
|
|
362
|
+
linker = ConfigurableLinker(
|
|
363
|
+
platform,
|
|
364
|
+
toolchain,
|
|
365
|
+
framework,
|
|
366
|
+
board_id,
|
|
367
|
+
build_dir,
|
|
368
|
+
platform_config=None,
|
|
369
|
+
show_progress=verbose
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Link firmware
|
|
373
|
+
firmware_elf = linker.link(sketch_obj_files, core_archive, library_archives=library_archives)
|
|
374
|
+
|
|
375
|
+
# Generate binary
|
|
376
|
+
log_phase(10, 12, "Generating firmware binary...")
|
|
377
|
+
|
|
378
|
+
firmware_bin = linker.generate_bin(firmware_elf)
|
|
379
|
+
|
|
380
|
+
# Generate bootloader and partition table
|
|
381
|
+
bootloader_bin, partitions_bin = self._generate_boot_components(
|
|
382
|
+
linker, mcu, verbose
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# Get size information from ELF file
|
|
386
|
+
size_info = linker.get_size_info(firmware_elf)
|
|
387
|
+
|
|
388
|
+
build_time = time.time() - start_time
|
|
389
|
+
|
|
390
|
+
if verbose:
|
|
391
|
+
self._print_success(
|
|
392
|
+
build_time, firmware_elf, firmware_bin,
|
|
393
|
+
bootloader_bin, partitions_bin, size_info
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# Save build state for future cache validation
|
|
397
|
+
log_detail("Saving build state...", verbose_only=True)
|
|
398
|
+
state_tracker.save_state(current_state)
|
|
399
|
+
|
|
400
|
+
# Generate build_info.json
|
|
401
|
+
build_info_generator = BuildInfoGenerator(build_dir)
|
|
402
|
+
board_name = board_json.get("name", board_id)
|
|
403
|
+
# Parse f_cpu from string (e.g., "160000000L" or "160000000") to int
|
|
404
|
+
f_cpu_raw = board_json.get("build", {}).get("f_cpu", "0")
|
|
405
|
+
f_cpu_int = int(str(f_cpu_raw).rstrip("L")) if f_cpu_raw else 0
|
|
406
|
+
# Build toolchain_paths dict, filtering out None values
|
|
407
|
+
toolchain_paths_raw = {
|
|
408
|
+
"gcc": toolchain.get_gcc_path(),
|
|
409
|
+
"gxx": toolchain.get_gxx_path(),
|
|
410
|
+
"ar": toolchain.get_ar_path(),
|
|
411
|
+
"objcopy": toolchain.get_objcopy_path(),
|
|
412
|
+
"size": toolchain.get_size_path(),
|
|
413
|
+
}
|
|
414
|
+
toolchain_paths = {k: v for k, v in toolchain_paths_raw.items() if v is not None}
|
|
415
|
+
build_info = build_info_generator.generate_esp32(
|
|
416
|
+
env_name=env_name,
|
|
417
|
+
board_id=board_id,
|
|
418
|
+
board_name=board_name,
|
|
419
|
+
mcu=mcu,
|
|
420
|
+
f_cpu=f_cpu_int,
|
|
421
|
+
build_time=build_time,
|
|
422
|
+
elf_path=firmware_elf,
|
|
423
|
+
bin_path=firmware_bin,
|
|
424
|
+
size_info=size_info,
|
|
425
|
+
build_flags=build_flags,
|
|
426
|
+
lib_deps=lib_deps,
|
|
427
|
+
toolchain_version=toolchain.version,
|
|
428
|
+
toolchain_paths=toolchain_paths,
|
|
429
|
+
framework_version=framework.version,
|
|
430
|
+
core_path=framework.get_cores_dir(),
|
|
431
|
+
bootloader_path=bootloader_bin,
|
|
432
|
+
partitions_path=partitions_bin,
|
|
433
|
+
application_offset=board_json.get("build", {}).get("app_offset", "0x10000"),
|
|
434
|
+
flash_mode=env_config.get("board_build.flash_mode"),
|
|
435
|
+
flash_size=env_config.get("board_build.flash_size"),
|
|
436
|
+
)
|
|
437
|
+
build_info_generator.save(build_info)
|
|
438
|
+
log_detail(f"Build info saved to {build_info_generator.build_info_path}", verbose_only=True)
|
|
439
|
+
|
|
440
|
+
return BuildResultESP32(
|
|
441
|
+
success=True,
|
|
442
|
+
firmware_bin=firmware_bin,
|
|
443
|
+
firmware_elf=firmware_elf,
|
|
444
|
+
bootloader_bin=bootloader_bin,
|
|
445
|
+
partitions_bin=partitions_bin,
|
|
446
|
+
size_info=size_info,
|
|
447
|
+
build_time=build_time,
|
|
448
|
+
message="Build successful (native ESP32 build)"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
except KeyboardInterrupt as ke:
|
|
452
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
453
|
+
handle_keyboard_interrupt_properly(ke)
|
|
454
|
+
raise # Never reached, but satisfies type checker
|
|
455
|
+
except Exception as e:
|
|
456
|
+
build_time = time.time() - start_time
|
|
457
|
+
import traceback
|
|
458
|
+
error_trace = traceback.format_exc()
|
|
459
|
+
return BuildResultESP32(
|
|
460
|
+
success=False,
|
|
461
|
+
firmware_bin=None,
|
|
462
|
+
firmware_elf=None,
|
|
463
|
+
bootloader_bin=None,
|
|
464
|
+
partitions_bin=None,
|
|
465
|
+
size_info=None,
|
|
466
|
+
build_time=build_time,
|
|
467
|
+
message=f"ESP32 native build failed: {e}\n\n{error_trace}"
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
def _setup_toolchain(
|
|
471
|
+
self,
|
|
472
|
+
packages: dict,
|
|
473
|
+
start_time: float,
|
|
474
|
+
verbose: bool
|
|
475
|
+
) -> Optional['ToolchainESP32']:
|
|
476
|
+
"""
|
|
477
|
+
Initialize ESP32 toolchain.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
packages: Package URLs dictionary
|
|
481
|
+
start_time: Build start time for error reporting
|
|
482
|
+
verbose: Verbose output mode
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
ToolchainESP32 instance or None on failure
|
|
486
|
+
"""
|
|
487
|
+
log_phase(4, 12, "Initializing ESP32 toolchain...")
|
|
488
|
+
|
|
489
|
+
toolchain_url = packages.get("toolchain-riscv32-esp") or packages.get("toolchain-xtensa-esp-elf")
|
|
490
|
+
if not toolchain_url:
|
|
491
|
+
return None
|
|
492
|
+
|
|
493
|
+
# Determine toolchain type
|
|
494
|
+
toolchain_type = "riscv32-esp" if "riscv32" in toolchain_url else "xtensa-esp-elf"
|
|
495
|
+
toolchain = ToolchainESP32(
|
|
496
|
+
self.cache,
|
|
497
|
+
toolchain_url,
|
|
498
|
+
toolchain_type,
|
|
499
|
+
show_progress=True
|
|
500
|
+
)
|
|
501
|
+
toolchain.ensure_toolchain()
|
|
502
|
+
return toolchain
|
|
503
|
+
|
|
504
|
+
def _setup_framework(
|
|
505
|
+
self,
|
|
506
|
+
packages: dict,
|
|
507
|
+
start_time: float,
|
|
508
|
+
verbose: bool
|
|
509
|
+
) -> Optional[FrameworkESP32]:
|
|
510
|
+
"""
|
|
511
|
+
Initialize ESP32 framework.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
packages: Package URLs dictionary
|
|
515
|
+
start_time: Build start time for error reporting
|
|
516
|
+
verbose: Verbose output mode
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
FrameworkESP32 instance or None on failure
|
|
520
|
+
"""
|
|
521
|
+
log_phase(5, 12, "Initializing ESP32 framework...")
|
|
522
|
+
|
|
523
|
+
framework_url = packages.get("framework-arduinoespressif32")
|
|
524
|
+
libs_url = packages.get("framework-arduinoespressif32-libs", "")
|
|
525
|
+
|
|
526
|
+
if not framework_url:
|
|
527
|
+
return None
|
|
528
|
+
|
|
529
|
+
# Find skeleton library if present (e.g., framework-arduino-esp32c2-skeleton-lib)
|
|
530
|
+
skeleton_lib_url = None
|
|
531
|
+
for package_name, package_url in packages.items():
|
|
532
|
+
if package_name.startswith("framework-arduino-") and package_name.endswith("-skeleton-lib"):
|
|
533
|
+
skeleton_lib_url = package_url
|
|
534
|
+
break
|
|
535
|
+
|
|
536
|
+
framework = FrameworkESP32(
|
|
537
|
+
self.cache,
|
|
538
|
+
framework_url,
|
|
539
|
+
libs_url,
|
|
540
|
+
skeleton_lib_url=skeleton_lib_url,
|
|
541
|
+
show_progress=True
|
|
542
|
+
)
|
|
543
|
+
framework.ensure_framework()
|
|
544
|
+
return framework
|
|
545
|
+
|
|
546
|
+
def _setup_build_directory(self, env_name: str, clean: bool, verbose: bool) -> Path:
|
|
547
|
+
"""
|
|
548
|
+
Setup build directory with optional cleaning.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
env_name: Environment name
|
|
552
|
+
clean: Whether to clean before build
|
|
553
|
+
verbose: Verbose output mode
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
Build directory path
|
|
557
|
+
"""
|
|
558
|
+
build_dir = self.cache.get_build_dir(env_name)
|
|
559
|
+
|
|
560
|
+
if clean and build_dir.exists():
|
|
561
|
+
log_phase(6, 12, "Cleaning build directory...")
|
|
562
|
+
safe_rmtree(build_dir)
|
|
563
|
+
|
|
564
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
565
|
+
return build_dir
|
|
566
|
+
|
|
567
|
+
def _process_libraries(
|
|
568
|
+
self,
|
|
569
|
+
env_config: dict,
|
|
570
|
+
build_dir: Path,
|
|
571
|
+
compiler: ConfigurableCompiler,
|
|
572
|
+
toolchain: ToolchainESP32,
|
|
573
|
+
verbose: bool,
|
|
574
|
+
project_dir: Optional[Path] = None
|
|
575
|
+
) -> tuple[List[Path], List[Path]]:
|
|
576
|
+
"""
|
|
577
|
+
Process and compile library dependencies.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
env_config: Environment configuration
|
|
581
|
+
build_dir: Build directory
|
|
582
|
+
compiler: Configured compiler instance
|
|
583
|
+
toolchain: ESP32 toolchain instance
|
|
584
|
+
verbose: Verbose output mode
|
|
585
|
+
project_dir: Optional project directory for resolving relative library paths
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
Tuple of (library_archives, library_include_paths)
|
|
589
|
+
"""
|
|
590
|
+
lib_deps = env_config.get('lib_deps', '')
|
|
591
|
+
library_archives = []
|
|
592
|
+
library_include_paths = []
|
|
593
|
+
|
|
594
|
+
if not lib_deps:
|
|
595
|
+
return library_archives, library_include_paths
|
|
596
|
+
|
|
597
|
+
log_phase(8, 12, "Processing library dependencies...", verbose_only=True)
|
|
598
|
+
|
|
599
|
+
# Parse lib_deps (can be string or list)
|
|
600
|
+
if isinstance(lib_deps, str):
|
|
601
|
+
lib_specs = [dep.strip() for dep in lib_deps.split('\n') if dep.strip()]
|
|
602
|
+
else:
|
|
603
|
+
lib_specs = lib_deps
|
|
604
|
+
|
|
605
|
+
if not lib_specs:
|
|
606
|
+
return library_archives, library_include_paths
|
|
607
|
+
|
|
608
|
+
# Initialize library manager with project directory for resolving local paths
|
|
609
|
+
lib_manager = LibraryManagerESP32(build_dir, project_dir=project_dir)
|
|
610
|
+
|
|
611
|
+
# Get compiler flags for library compilation
|
|
612
|
+
lib_compiler_flags = compiler.get_base_flags()
|
|
613
|
+
|
|
614
|
+
# Get include paths for library compilation
|
|
615
|
+
lib_include_paths = compiler.get_include_paths()
|
|
616
|
+
|
|
617
|
+
# Get toolchain bin path
|
|
618
|
+
toolchain_bin_path = toolchain.get_bin_path()
|
|
619
|
+
if toolchain_bin_path is None:
|
|
620
|
+
log_warning("Toolchain bin directory not found, skipping libraries")
|
|
621
|
+
return library_archives, library_include_paths
|
|
622
|
+
|
|
623
|
+
# Ensure libraries are downloaded and compiled
|
|
624
|
+
logger.debug(f"[ORCHESTRATOR] Calling lib_manager.ensure_libraries with {len(lib_specs)} specs: {lib_specs}")
|
|
625
|
+
libraries = lib_manager.ensure_libraries(
|
|
626
|
+
lib_specs,
|
|
627
|
+
toolchain_bin_path,
|
|
628
|
+
lib_compiler_flags,
|
|
629
|
+
lib_include_paths,
|
|
630
|
+
show_progress=verbose
|
|
631
|
+
)
|
|
632
|
+
logger.debug(f"[ORCHESTRATOR] ensure_libraries returned {len(libraries)} libraries")
|
|
633
|
+
|
|
634
|
+
# Get library archives and include paths
|
|
635
|
+
library_archives = [lib.archive_file for lib in libraries if lib.is_compiled]
|
|
636
|
+
library_include_paths = lib_manager.get_library_include_paths()
|
|
637
|
+
|
|
638
|
+
log_detail(f"Compiled {len(libraries)} library dependencies", verbose_only=True)
|
|
639
|
+
|
|
640
|
+
return library_archives, library_include_paths
|
|
641
|
+
|
|
642
|
+
def _compile_sketch(
|
|
643
|
+
self,
|
|
644
|
+
project_dir: Path,
|
|
645
|
+
compiler: ConfigurableCompiler,
|
|
646
|
+
start_time: float,
|
|
647
|
+
verbose: bool,
|
|
648
|
+
src_dir_override: Optional[str] = None
|
|
649
|
+
) -> Optional[List[Path]]:
|
|
650
|
+
"""
|
|
651
|
+
Find and compile sketch files.
|
|
652
|
+
|
|
653
|
+
Args:
|
|
654
|
+
project_dir: Project directory
|
|
655
|
+
compiler: Configured compiler instance
|
|
656
|
+
start_time: Build start time for error reporting
|
|
657
|
+
verbose: Verbose output mode
|
|
658
|
+
src_dir_override: Optional source directory override (relative to project_dir)
|
|
659
|
+
|
|
660
|
+
Returns:
|
|
661
|
+
List of compiled object files or None if no sketch found
|
|
662
|
+
"""
|
|
663
|
+
log_phase(8, 12, "Compiling sketch...", verbose_only=True)
|
|
664
|
+
|
|
665
|
+
# Determine source directory
|
|
666
|
+
if src_dir_override:
|
|
667
|
+
src_dir = project_dir / src_dir_override
|
|
668
|
+
log_detail(f"Using source directory override: {src_dir_override}", verbose_only=True)
|
|
669
|
+
else:
|
|
670
|
+
src_dir = project_dir
|
|
671
|
+
|
|
672
|
+
# Look for .ino files in the source directory
|
|
673
|
+
sketch_files = list(src_dir.glob("*.ino"))
|
|
674
|
+
if not sketch_files:
|
|
675
|
+
return None
|
|
676
|
+
|
|
677
|
+
sketch_path = sketch_files[0]
|
|
678
|
+
sketch_obj_files = compiler.compile_sketch(sketch_path)
|
|
679
|
+
|
|
680
|
+
log_detail(f"Compiled {len(sketch_obj_files)} sketch file(s)", verbose_only=True)
|
|
681
|
+
|
|
682
|
+
return sketch_obj_files
|
|
683
|
+
|
|
684
|
+
def _create_bt_stub(
|
|
685
|
+
self,
|
|
686
|
+
build_dir: Path,
|
|
687
|
+
compiler: ConfigurableCompiler,
|
|
688
|
+
verbose: bool
|
|
689
|
+
) -> Optional[Path]:
|
|
690
|
+
"""
|
|
691
|
+
Create a Bluetooth stub for ESP32 targets where esp32-hal-bt.c fails to compile.
|
|
692
|
+
|
|
693
|
+
On non-ESP32 targets (ESP32-C6, ESP32-S3, etc.), the esp32-hal-bt.c file may
|
|
694
|
+
fail to compile due to SDK incompatibilities, but initArduino() still references
|
|
695
|
+
btInUse(). This creates a stub implementation that returns false.
|
|
696
|
+
|
|
697
|
+
Args:
|
|
698
|
+
build_dir: Build directory
|
|
699
|
+
compiler: Configured compiler instance
|
|
700
|
+
verbose: Whether to print verbose output
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
Path to compiled stub object file, or None on error
|
|
704
|
+
"""
|
|
705
|
+
try:
|
|
706
|
+
# Create stub source file
|
|
707
|
+
stub_dir = build_dir / "stubs"
|
|
708
|
+
stub_dir.mkdir(parents=True, exist_ok=True)
|
|
709
|
+
stub_file = stub_dir / "bt_stub.c"
|
|
710
|
+
|
|
711
|
+
# Write minimal btInUse() implementation
|
|
712
|
+
stub_content = """// Bluetooth stub for ESP32 targets where esp32-hal-bt.c fails to compile
|
|
713
|
+
// This provides a fallback implementation of btInUse() that always returns false
|
|
714
|
+
|
|
715
|
+
#include <stdbool.h>
|
|
716
|
+
|
|
717
|
+
// Weak attribute allows this to be overridden if the real implementation links
|
|
718
|
+
__attribute__((weak)) bool btInUse(void) {
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
"""
|
|
722
|
+
stub_file.write_text(stub_content)
|
|
723
|
+
|
|
724
|
+
# Compile the stub
|
|
725
|
+
stub_obj = stub_dir / "bt_stub.o"
|
|
726
|
+
compiled_obj = compiler.compile_source(stub_file, stub_obj)
|
|
727
|
+
|
|
728
|
+
log_detail(f"Created Bluetooth stub: {compiled_obj.name}", verbose_only=True)
|
|
729
|
+
|
|
730
|
+
return compiled_obj
|
|
731
|
+
|
|
732
|
+
except KeyboardInterrupt as ke:
|
|
733
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
734
|
+
|
|
735
|
+
handle_keyboard_interrupt_properly(ke)
|
|
736
|
+
raise # Never reached, but satisfies type checker
|
|
737
|
+
except Exception as e:
|
|
738
|
+
log_warning(f"Failed to create Bluetooth stub: {e}")
|
|
739
|
+
return None
|
|
740
|
+
|
|
741
|
+
def _generate_boot_components(
|
|
742
|
+
self,
|
|
743
|
+
linker: ConfigurableLinker,
|
|
744
|
+
mcu: str,
|
|
745
|
+
verbose: bool
|
|
746
|
+
) -> tuple[Optional[Path], Optional[Path]]:
|
|
747
|
+
"""
|
|
748
|
+
Generate bootloader and partition table for ESP32.
|
|
749
|
+
|
|
750
|
+
Args:
|
|
751
|
+
linker: Configured linker instance
|
|
752
|
+
mcu: MCU identifier
|
|
753
|
+
verbose: Verbose output mode
|
|
754
|
+
|
|
755
|
+
Returns:
|
|
756
|
+
Tuple of (bootloader_bin, partitions_bin)
|
|
757
|
+
"""
|
|
758
|
+
bootloader_bin = None
|
|
759
|
+
partitions_bin = None
|
|
760
|
+
|
|
761
|
+
if not mcu.startswith("esp32"):
|
|
762
|
+
return bootloader_bin, partitions_bin
|
|
763
|
+
|
|
764
|
+
log_phase(11, 12, "Generating bootloader...", verbose_only=True)
|
|
765
|
+
try:
|
|
766
|
+
bootloader_bin = linker.generate_bootloader()
|
|
767
|
+
except KeyboardInterrupt as ke:
|
|
768
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
769
|
+
handle_keyboard_interrupt_properly(ke)
|
|
770
|
+
raise # Never reached, but satisfies type checker
|
|
771
|
+
except Exception as e:
|
|
772
|
+
log_warning(f"Could not generate bootloader: {e}")
|
|
773
|
+
|
|
774
|
+
log_phase(12, 12, "Generating partition table...", verbose_only=True)
|
|
775
|
+
try:
|
|
776
|
+
partitions_bin = linker.generate_partition_table()
|
|
777
|
+
except KeyboardInterrupt as ke:
|
|
778
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
779
|
+
handle_keyboard_interrupt_properly(ke)
|
|
780
|
+
raise # Never reached, but satisfies type checker
|
|
781
|
+
except Exception as e:
|
|
782
|
+
log_warning(f"Could not generate partition table: {e}")
|
|
783
|
+
|
|
784
|
+
return bootloader_bin, partitions_bin
|
|
785
|
+
|
|
786
|
+
def _print_success(
|
|
787
|
+
self,
|
|
788
|
+
build_time: float,
|
|
789
|
+
firmware_elf: Path,
|
|
790
|
+
firmware_bin: Path,
|
|
791
|
+
bootloader_bin: Optional[Path],
|
|
792
|
+
partitions_bin: Optional[Path],
|
|
793
|
+
size_info: Optional[SizeInfo] = None
|
|
794
|
+
) -> None:
|
|
795
|
+
"""
|
|
796
|
+
Print build success message.
|
|
797
|
+
|
|
798
|
+
Args:
|
|
799
|
+
build_time: Total build time
|
|
800
|
+
firmware_elf: Path to firmware ELF
|
|
801
|
+
firmware_bin: Path to firmware binary
|
|
802
|
+
bootloader_bin: Optional path to bootloader
|
|
803
|
+
partitions_bin: Optional path to partition table
|
|
804
|
+
size_info: Optional size information to display
|
|
805
|
+
"""
|
|
806
|
+
# Build success message
|
|
807
|
+
message_lines = ["BUILD SUCCESSFUL!"]
|
|
808
|
+
message_lines.append(f"Build time: {build_time:.2f}s")
|
|
809
|
+
message_lines.append(f"Firmware ELF: {firmware_elf}")
|
|
810
|
+
message_lines.append(f"Firmware BIN: {firmware_bin}")
|
|
811
|
+
if bootloader_bin:
|
|
812
|
+
message_lines.append(f"Bootloader: {bootloader_bin}")
|
|
813
|
+
if partitions_bin:
|
|
814
|
+
message_lines.append(f"Partitions: {partitions_bin}")
|
|
815
|
+
|
|
816
|
+
BannerFormatter.print_banner("\n".join(message_lines), width=60, center=False)
|
|
817
|
+
|
|
818
|
+
# Print size information if available
|
|
819
|
+
if size_info:
|
|
820
|
+
print()
|
|
821
|
+
from .build_utils import SizeInfoPrinter
|
|
822
|
+
SizeInfoPrinter.print_size_info(size_info)
|
|
823
|
+
print()
|
|
824
|
+
|
|
825
|
+
def _error_result(self, start_time: float, message: str) -> BuildResultESP32:
|
|
826
|
+
"""
|
|
827
|
+
Create an error result.
|
|
828
|
+
|
|
829
|
+
Args:
|
|
830
|
+
start_time: Build start time
|
|
831
|
+
message: Error message
|
|
832
|
+
|
|
833
|
+
Returns:
|
|
834
|
+
BuildResultESP32 indicating failure
|
|
835
|
+
"""
|
|
836
|
+
return BuildResultESP32(
|
|
837
|
+
success=False,
|
|
838
|
+
firmware_bin=None,
|
|
839
|
+
firmware_elf=None,
|
|
840
|
+
bootloader_bin=None,
|
|
841
|
+
partitions_bin=None,
|
|
842
|
+
size_info=None,
|
|
843
|
+
build_time=time.time() - start_time,
|
|
844
|
+
message=message
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
@staticmethod
|
|
848
|
+
def _resolve_platform_url(platform_spec: str) -> str:
|
|
849
|
+
"""
|
|
850
|
+
Resolve platform specification to actual download URL.
|
|
851
|
+
|
|
852
|
+
PlatformIO supports several formats for specifying platforms:
|
|
853
|
+
- Full URL: "https://github.com/.../platform-espressif32.zip" -> used as-is
|
|
854
|
+
- Shorthand: "platformio/espressif32" -> resolved to pioarduino stable release
|
|
855
|
+
- Name only: "espressif32" -> resolved to pioarduino stable release
|
|
856
|
+
|
|
857
|
+
Args:
|
|
858
|
+
platform_spec: Platform specification from platformio.ini
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
Actual download URL for the platform
|
|
862
|
+
"""
|
|
863
|
+
# Default stable release URL for espressif32 (pioarduino fork)
|
|
864
|
+
# This is the recommended platform for ESP32 Arduino development
|
|
865
|
+
DEFAULT_ESP32_URL = "https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip"
|
|
866
|
+
|
|
867
|
+
# If it's already a proper URL, use it as-is
|
|
868
|
+
if platform_spec.startswith("http://") or platform_spec.startswith("https://"):
|
|
869
|
+
return platform_spec
|
|
870
|
+
|
|
871
|
+
# Handle PlatformIO shorthand formats
|
|
872
|
+
if platform_spec in ("platformio/espressif32", "espressif32"):
|
|
873
|
+
log_detail(f"Resolving platform shorthand '{platform_spec}' to pioarduino stable release")
|
|
874
|
+
return DEFAULT_ESP32_URL
|
|
875
|
+
|
|
876
|
+
# For unknown formats, return as-is and let the download fail with a clear error
|
|
877
|
+
log_warning(f"Unknown platform format: {platform_spec}, attempting to use as URL")
|
|
878
|
+
return platform_spec
|