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,163 @@
|
|
|
1
|
+
"""Abstract base classes for package management.
|
|
2
|
+
|
|
3
|
+
This module defines the interface for platform-specific package managers
|
|
4
|
+
(toolchains, frameworks, cores, etc.) to ensure consistent behavior across
|
|
5
|
+
different platforms.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PackageError(Exception):
|
|
14
|
+
"""Base exception for package management errors."""
|
|
15
|
+
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IPackage(ABC):
|
|
20
|
+
"""Interface for downloadable packages.
|
|
21
|
+
|
|
22
|
+
This interface defines the common contract for all package types:
|
|
23
|
+
- Toolchains (AVR, RISC-V, Xtensa)
|
|
24
|
+
- Frameworks (Arduino-ESP32)
|
|
25
|
+
- Cores (Arduino AVR Core)
|
|
26
|
+
- Platforms (ESP32 Platform)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def ensure_package(self) -> Path:
|
|
31
|
+
"""Ensure package is downloaded and extracted.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Path to the extracted package directory
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
PackageError: If download or extraction fails
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def is_installed(self) -> bool:
|
|
43
|
+
"""Check if package is already installed.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
True if package directory exists and is valid
|
|
47
|
+
"""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def get_package_info(self) -> Dict[str, Any]:
|
|
52
|
+
"""Get information about the package.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Dictionary with package metadata (version, path, etc.)
|
|
56
|
+
"""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class IToolchain(IPackage):
|
|
61
|
+
"""Interface for toolchain packages.
|
|
62
|
+
|
|
63
|
+
Toolchains provide compiler, linker, and binary utilities for
|
|
64
|
+
specific architectures (AVR, RISC-V, Xtensa).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def get_gcc_path(self) -> Optional[Path]:
|
|
69
|
+
"""Get path to GCC compiler.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Path to gcc binary or None if not found
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def get_gxx_path(self) -> Optional[Path]:
|
|
78
|
+
"""Get path to G++ compiler.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Path to g++ binary or None if not found
|
|
82
|
+
"""
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def get_ar_path(self) -> Optional[Path]:
|
|
87
|
+
"""Get path to archiver (ar).
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Path to ar binary or None if not found
|
|
91
|
+
"""
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def get_objcopy_path(self) -> Optional[Path]:
|
|
96
|
+
"""Get path to objcopy utility.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Path to objcopy binary or None if not found
|
|
100
|
+
"""
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def get_size_path(self) -> Optional[Path]:
|
|
105
|
+
"""Get path to size utility.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Path to size binary or None if not found
|
|
109
|
+
"""
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def get_bin_dir(self) -> Optional[Path]:
|
|
114
|
+
"""Get path to toolchain bin directory.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Path to bin directory containing compiler binaries
|
|
118
|
+
"""
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def get_all_tools(self) -> Dict[str, Path]:
|
|
123
|
+
"""Get paths to all required tools.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Dictionary mapping tool names to their paths
|
|
127
|
+
"""
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class IFramework(IPackage):
|
|
132
|
+
"""Interface for framework packages.
|
|
133
|
+
|
|
134
|
+
Frameworks provide core Arduino implementation, variants,
|
|
135
|
+
and built-in libraries for specific platforms.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def get_cores_dir(self) -> Path:
|
|
140
|
+
"""Get path to cores directory.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Path to cores directory containing Arduino core implementation
|
|
144
|
+
"""
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
@abstractmethod
|
|
148
|
+
def get_variants_dir(self) -> Path:
|
|
149
|
+
"""Get path to variants directory.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Path to variants directory containing board-specific configurations
|
|
153
|
+
"""
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def get_libraries_dir(self) -> Path:
|
|
158
|
+
"""Get path to built-in libraries directory.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Path to libraries directory
|
|
162
|
+
"""
|
|
163
|
+
pass
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""ESP32 Platform Package Management.
|
|
2
|
+
|
|
3
|
+
This module handles downloading, extracting, and managing ESP32 platform packages
|
|
4
|
+
from GitHub releases. It provides access to the Arduino-ESP32 core, toolchains,
|
|
5
|
+
and platform-specific tools needed for ESP32 builds.
|
|
6
|
+
|
|
7
|
+
Platform Structure (after extraction):
|
|
8
|
+
platform-espressif32/
|
|
9
|
+
├── platform.json # Package metadata with download URLs
|
|
10
|
+
├── boards/ # Board definitions (JSON files)
|
|
11
|
+
│ └── esp32-c6-devkitm-1.json
|
|
12
|
+
├── builder/ # PlatformIO build scripts
|
|
13
|
+
│ └── frameworks/
|
|
14
|
+
│ └── arduino.py
|
|
15
|
+
└── ... # Other platform files
|
|
16
|
+
|
|
17
|
+
Key Packages (from platform.json):
|
|
18
|
+
- framework-arduinoespressif32: Arduino core (cores/, variants/)
|
|
19
|
+
- framework-arduinoespressif32-libs: Pre-built ESP-IDF libraries
|
|
20
|
+
- toolchain-riscv32-esp: RISC-V GCC (for C3, C6, H2)
|
|
21
|
+
- toolchain-xtensa-esp-elf: Xtensa GCC (for ESP32, S2, S3)
|
|
22
|
+
- tool-esptoolpy: Upload tool
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Dict, Optional
|
|
28
|
+
|
|
29
|
+
from .cache import Cache
|
|
30
|
+
from .downloader import DownloadError, ExtractionError, PackageDownloader
|
|
31
|
+
from .package import IPackage, PackageError
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PlatformErrorESP32(PackageError):
|
|
35
|
+
"""Raised when ESP32 platform operations fail."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PlatformESP32(IPackage):
|
|
41
|
+
"""Manages ESP32 platform package download, extraction, and access.
|
|
42
|
+
|
|
43
|
+
This class handles the pioarduino/platform-espressif32 package which contains:
|
|
44
|
+
- Arduino core for ESP32 family (C3, C6, S2, S3, H2, etc.)
|
|
45
|
+
- Toolchains (riscv32-esp-elf-gcc, xtensa-esp-elf-gcc)
|
|
46
|
+
- Platform tools (esptool, mkspiffs, etc.)
|
|
47
|
+
- Board definitions and variants
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, cache: Cache, platform_url: str, show_progress: bool = True):
|
|
51
|
+
"""Initialize ESP32 platform manager.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
cache: Cache manager instance
|
|
55
|
+
platform_url: URL to platform package (e.g., GitHub release ZIP)
|
|
56
|
+
show_progress: Whether to show download/extraction progress
|
|
57
|
+
"""
|
|
58
|
+
self.cache = cache
|
|
59
|
+
self.platform_url = platform_url
|
|
60
|
+
self.show_progress = show_progress
|
|
61
|
+
self.downloader = PackageDownloader()
|
|
62
|
+
|
|
63
|
+
# Extract version from URL (e.g., "55.03.34" from release tag)
|
|
64
|
+
self.version = self._extract_version_from_url(platform_url)
|
|
65
|
+
|
|
66
|
+
# Get platform path from cache
|
|
67
|
+
self.platform_path = cache.get_platform_path(platform_url, self.version)
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _extract_version_from_url(url: str) -> str:
|
|
71
|
+
"""Extract version string from platform URL.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
url: Platform URL (e.g., https://github.com/.../55.03.34/platform.zip)
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Version string (e.g., "55.03.34")
|
|
78
|
+
"""
|
|
79
|
+
# URL format: .../releases/download/{version}/platform-espressif32.zip
|
|
80
|
+
parts = url.split("/")
|
|
81
|
+
for i, part in enumerate(parts):
|
|
82
|
+
if part == "download" and i + 1 < len(parts):
|
|
83
|
+
return parts[i + 1]
|
|
84
|
+
|
|
85
|
+
# Fallback: use URL hash if version extraction fails
|
|
86
|
+
from .cache import Cache
|
|
87
|
+
|
|
88
|
+
return Cache.hash_url(url)[:8]
|
|
89
|
+
|
|
90
|
+
def ensure_package(self) -> Path:
|
|
91
|
+
"""Ensure platform is downloaded and extracted.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Path to the extracted platform directory
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
PlatformErrorESP32: If download or extraction fails
|
|
98
|
+
"""
|
|
99
|
+
return self.ensure_platform()
|
|
100
|
+
|
|
101
|
+
def ensure_platform(self) -> Path:
|
|
102
|
+
"""Ensure platform is downloaded and extracted.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Path to the extracted platform directory
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
PlatformErrorESP32: If download or extraction fails
|
|
109
|
+
"""
|
|
110
|
+
if self.is_installed():
|
|
111
|
+
if self.show_progress:
|
|
112
|
+
print(f"Using cached ESP32 platform {self.version}")
|
|
113
|
+
return self.platform_path
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
if self.show_progress:
|
|
117
|
+
print(f"Downloading ESP32 platform {self.version}...")
|
|
118
|
+
|
|
119
|
+
# Download and extract platform package
|
|
120
|
+
self.cache.ensure_directories()
|
|
121
|
+
|
|
122
|
+
# Use downloader to handle download and extraction
|
|
123
|
+
archive_name = Path(self.platform_url).name
|
|
124
|
+
archive_path = self.platform_path.parent / archive_name
|
|
125
|
+
|
|
126
|
+
# Download if not cached
|
|
127
|
+
if not archive_path.exists():
|
|
128
|
+
archive_path.parent.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
self.downloader.download(self.platform_url, archive_path, show_progress=self.show_progress)
|
|
130
|
+
else:
|
|
131
|
+
if self.show_progress:
|
|
132
|
+
print(f"Using cached archive {archive_name}")
|
|
133
|
+
|
|
134
|
+
# Extract to platform directory
|
|
135
|
+
if self.show_progress:
|
|
136
|
+
print(f"Extracting platform to {self.platform_path}...")
|
|
137
|
+
|
|
138
|
+
# Create temp extraction directory
|
|
139
|
+
temp_extract = self.platform_path.parent / "temp_extract"
|
|
140
|
+
temp_extract.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
|
|
142
|
+
self.downloader.extract_archive(archive_path, temp_extract, show_progress=self.show_progress)
|
|
143
|
+
|
|
144
|
+
# Find the platform directory in the extracted content
|
|
145
|
+
# Usually it's a subdirectory like "platform-espressif32/"
|
|
146
|
+
extracted_dirs = list(temp_extract.glob("platform-*"))
|
|
147
|
+
if not extracted_dirs:
|
|
148
|
+
# Maybe it extracted directly
|
|
149
|
+
extracted_dirs = [temp_extract]
|
|
150
|
+
|
|
151
|
+
source_dir = extracted_dirs[0]
|
|
152
|
+
|
|
153
|
+
# Move to final location
|
|
154
|
+
if self.platform_path.exists():
|
|
155
|
+
import shutil
|
|
156
|
+
|
|
157
|
+
shutil.rmtree(self.platform_path)
|
|
158
|
+
|
|
159
|
+
source_dir.rename(self.platform_path)
|
|
160
|
+
|
|
161
|
+
# Clean up temp directory
|
|
162
|
+
if temp_extract.exists() and temp_extract != self.platform_path:
|
|
163
|
+
import shutil
|
|
164
|
+
|
|
165
|
+
shutil.rmtree(temp_extract, ignore_errors=True)
|
|
166
|
+
|
|
167
|
+
if self.show_progress:
|
|
168
|
+
print(f"ESP32 platform installed to {self.platform_path}")
|
|
169
|
+
|
|
170
|
+
return self.platform_path
|
|
171
|
+
|
|
172
|
+
except (DownloadError, ExtractionError) as e:
|
|
173
|
+
raise PlatformErrorESP32(f"Failed to install ESP32 platform: {e}")
|
|
174
|
+
except KeyboardInterrupt as ke:
|
|
175
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
176
|
+
|
|
177
|
+
handle_keyboard_interrupt_properly(ke)
|
|
178
|
+
raise # Never reached, but satisfies type checker
|
|
179
|
+
except Exception as e:
|
|
180
|
+
raise PlatformErrorESP32(f"Unexpected error installing platform: {e}")
|
|
181
|
+
|
|
182
|
+
def is_installed(self) -> bool:
|
|
183
|
+
"""Check if platform is already installed.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
True if platform directory exists with key files
|
|
187
|
+
"""
|
|
188
|
+
if not self.platform_path.exists():
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
# Verify essential platform files exist
|
|
192
|
+
required_files = [
|
|
193
|
+
self.platform_path / "platform.json",
|
|
194
|
+
self.platform_path / "boards",
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
return all(f.exists() for f in required_files)
|
|
198
|
+
|
|
199
|
+
def get_platform_json(self) -> Dict[str, Any]:
|
|
200
|
+
"""Load and parse platform.json metadata.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Dictionary containing platform metadata
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
PlatformErrorESP32: If platform.json doesn't exist or is invalid
|
|
207
|
+
"""
|
|
208
|
+
platform_json_path = self.platform_path / "platform.json"
|
|
209
|
+
|
|
210
|
+
if not platform_json_path.exists():
|
|
211
|
+
raise PlatformErrorESP32(f"platform.json not found at {platform_json_path}. " + "Ensure platform is downloaded first.")
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
with open(platform_json_path, "r") as f:
|
|
215
|
+
return json.load(f)
|
|
216
|
+
except json.JSONDecodeError as e:
|
|
217
|
+
raise PlatformErrorESP32(f"Failed to parse platform.json: {e}")
|
|
218
|
+
except KeyboardInterrupt as ke:
|
|
219
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
220
|
+
|
|
221
|
+
handle_keyboard_interrupt_properly(ke)
|
|
222
|
+
raise # Never reached, but satisfies type checker
|
|
223
|
+
except Exception as e:
|
|
224
|
+
raise PlatformErrorESP32(f"Failed to read platform.json: {e}")
|
|
225
|
+
|
|
226
|
+
def get_package_url(self, package_name: str) -> Optional[str]:
|
|
227
|
+
"""Get download URL for a specific package.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
package_name: Name of the package (e.g., "toolchain-riscv32-esp")
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
URL string or None if package not found
|
|
234
|
+
"""
|
|
235
|
+
platform_json = self.get_platform_json()
|
|
236
|
+
packages = platform_json.get("packages", {})
|
|
237
|
+
|
|
238
|
+
if package_name not in packages:
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
package_info = packages[package_name]
|
|
242
|
+
return package_info.get("version") # "version" field contains URL
|
|
243
|
+
|
|
244
|
+
def get_required_packages(self, board_mcu: str) -> Dict[str, str]:
|
|
245
|
+
"""Get required packages for a specific MCU.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
board_mcu: MCU type (e.g., "esp32c6", "esp32s3", "esp32")
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Dictionary of package_name -> url for required packages
|
|
252
|
+
"""
|
|
253
|
+
packages = {}
|
|
254
|
+
|
|
255
|
+
# All ESP32 boards need the Arduino framework
|
|
256
|
+
framework_url = self.get_package_url("framework-arduinoespressif32")
|
|
257
|
+
if framework_url:
|
|
258
|
+
packages["framework-arduinoespressif32"] = framework_url
|
|
259
|
+
|
|
260
|
+
libs_url = self.get_package_url("framework-arduinoespressif32-libs")
|
|
261
|
+
if libs_url:
|
|
262
|
+
packages["framework-arduinoespressif32-libs"] = libs_url
|
|
263
|
+
|
|
264
|
+
# Check for MCU-specific skeleton libraries
|
|
265
|
+
# These are used when the main libs package is empty or incomplete
|
|
266
|
+
# The naming pattern is: framework-arduino-{mcu_suffix}-skeleton-lib
|
|
267
|
+
# where mcu_suffix is extracted from the MCU name (e.g., "esp32c2" -> "c2")
|
|
268
|
+
mcu_suffix = board_mcu.replace("esp32", "")
|
|
269
|
+
skeleton_lib_name = f"framework-arduino-{mcu_suffix}-skeleton-lib"
|
|
270
|
+
skeleton_lib_url = self.get_package_url(skeleton_lib_name)
|
|
271
|
+
if skeleton_lib_url:
|
|
272
|
+
packages[skeleton_lib_name] = skeleton_lib_url
|
|
273
|
+
|
|
274
|
+
# Determine which toolchain is needed based on MCU architecture
|
|
275
|
+
if board_mcu in [
|
|
276
|
+
"esp32c3",
|
|
277
|
+
"esp32c6",
|
|
278
|
+
"esp32h2",
|
|
279
|
+
"esp32c2",
|
|
280
|
+
"esp32c5",
|
|
281
|
+
"esp32p4",
|
|
282
|
+
]:
|
|
283
|
+
# RISC-V based ESP32s
|
|
284
|
+
toolchain_url = self.get_package_url("toolchain-riscv32-esp")
|
|
285
|
+
if toolchain_url:
|
|
286
|
+
packages["toolchain-riscv32-esp"] = toolchain_url
|
|
287
|
+
else:
|
|
288
|
+
# Xtensa based ESP32s (original ESP32, S2, S3)
|
|
289
|
+
toolchain_url = self.get_package_url("toolchain-xtensa-esp-elf")
|
|
290
|
+
if toolchain_url:
|
|
291
|
+
packages["toolchain-xtensa-esp-elf"] = toolchain_url
|
|
292
|
+
|
|
293
|
+
# Add esptool (needed for all ESP32 boards)
|
|
294
|
+
esptool_url = self.get_package_url("tool-esptoolpy")
|
|
295
|
+
if esptool_url:
|
|
296
|
+
packages["tool-esptoolpy"] = esptool_url
|
|
297
|
+
|
|
298
|
+
return packages
|
|
299
|
+
|
|
300
|
+
def get_boards_dir(self) -> Path:
|
|
301
|
+
"""Get path to boards directory.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Path to boards directory containing JSON board definitions
|
|
305
|
+
"""
|
|
306
|
+
return self.platform_path / "boards"
|
|
307
|
+
|
|
308
|
+
def get_board_json(self, board_id: str) -> Dict[str, Any]:
|
|
309
|
+
"""Load board configuration from JSON.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
board_id: Board identifier (e.g., "esp32-c6-devkitm-1")
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Dictionary containing board configuration
|
|
316
|
+
|
|
317
|
+
Raises:
|
|
318
|
+
PlatformErrorESP32: If board JSON doesn't exist or is invalid
|
|
319
|
+
"""
|
|
320
|
+
board_json_path = self.get_boards_dir() / f"{board_id}.json"
|
|
321
|
+
|
|
322
|
+
if not board_json_path.exists():
|
|
323
|
+
raise PlatformErrorESP32(f"Board definition not found: {board_id} " + f"at {board_json_path}")
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
with open(board_json_path, "r") as f:
|
|
327
|
+
return json.load(f)
|
|
328
|
+
except json.JSONDecodeError as e:
|
|
329
|
+
raise PlatformErrorESP32(f"Failed to parse board JSON: {e}")
|
|
330
|
+
except KeyboardInterrupt as ke:
|
|
331
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
332
|
+
|
|
333
|
+
handle_keyboard_interrupt_properly(ke)
|
|
334
|
+
raise # Never reached, but satisfies type checker
|
|
335
|
+
except Exception as e:
|
|
336
|
+
raise PlatformErrorESP32(f"Failed to read board JSON: {e}")
|
|
337
|
+
|
|
338
|
+
def list_boards(self) -> list[str]:
|
|
339
|
+
"""List all available board IDs.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
List of board identifiers
|
|
343
|
+
"""
|
|
344
|
+
boards_dir = self.get_boards_dir()
|
|
345
|
+
if not boards_dir.exists():
|
|
346
|
+
return []
|
|
347
|
+
|
|
348
|
+
return [f.stem for f in boards_dir.glob("*.json") if f.is_file() and not f.name.endswith(".py")]
|
|
349
|
+
|
|
350
|
+
def get_package_info(self) -> Dict[str, Any]:
|
|
351
|
+
"""Get information about the installed platform.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Dictionary with platform information
|
|
355
|
+
"""
|
|
356
|
+
return self.get_platform_info()
|
|
357
|
+
|
|
358
|
+
def get_platform_info(self) -> Dict[str, Any]:
|
|
359
|
+
"""Get information about the installed platform.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Dictionary with platform information
|
|
363
|
+
"""
|
|
364
|
+
info = {
|
|
365
|
+
"version": self.version,
|
|
366
|
+
"path": str(self.platform_path),
|
|
367
|
+
"url": self.platform_url,
|
|
368
|
+
"installed": self.is_installed(),
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if self.is_installed():
|
|
372
|
+
info["boards_dir"] = str(self.get_boards_dir())
|
|
373
|
+
info["available_boards"] = len(self.list_boards())
|
|
374
|
+
|
|
375
|
+
# Get package information
|
|
376
|
+
try:
|
|
377
|
+
platform_json = self.get_platform_json()
|
|
378
|
+
info["platform_version"] = platform_json.get("version")
|
|
379
|
+
info["available_packages"] = list(platform_json.get("packages", {}).keys())
|
|
380
|
+
except PlatformErrorESP32:
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
return info
|