2025-07-03 17:12:25 +02:00
|
|
|
"""
|
|
|
|
|
Component manager for ESP32 Arduino framework builds in PlatformIO.
|
|
|
|
|
|
|
|
|
|
This module provides the ComponentManager class for handling IDF component
|
|
|
|
|
addition/removal, library ignore processing, and build script modifications.
|
|
|
|
|
It supports managing ESP-IDF components within Arduino framework projects,
|
|
|
|
|
allowing developers to add or remove specific components and handle library
|
|
|
|
|
dependencies efficiently.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import shutil
|
|
|
|
|
import re
|
|
|
|
|
import yaml
|
|
|
|
|
from yaml import SafeLoader
|
2025-09-17 00:09:47 +02:00
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Set, Optional, Dict, Any, List, Tuple, Pattern
|
2025-07-03 17:12:25 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ComponentManagerConfig:
|
|
|
|
|
"""
|
|
|
|
|
Handles configuration and environment setup for component management.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
This class centralizes all configuration-related operations and provides
|
|
|
|
|
a unified interface for accessing PlatformIO environment settings,
|
2025-09-17 00:09:47 +02:00
|
|
|
board configurations, and framework paths with optimized caching.
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def __init__(self, env):
|
|
|
|
|
"""
|
|
|
|
|
Initialize the configuration manager with PlatformIO environment.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Extracts and stores essential configuration parameters from the PlatformIO
|
|
|
|
|
environment including platform details, board configuration, MCU type,
|
|
|
|
|
and framework paths. This initialization ensures all dependent classes
|
|
|
|
|
have consistent access to configuration data.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
env: PlatformIO environment object containing project configuration,
|
|
|
|
|
board settings, and platform information
|
|
|
|
|
"""
|
|
|
|
|
self.env = env
|
|
|
|
|
self.platform = env.PioPlatform()
|
|
|
|
|
self.config = env.GetProjectConfig()
|
|
|
|
|
self.board = env.BoardConfig()
|
|
|
|
|
# Extract MCU type from board configuration, defaulting to esp32
|
|
|
|
|
self.mcu = self.board.get("build.mcu", "esp32").lower()
|
|
|
|
|
# Get project source directory path
|
|
|
|
|
self.project_src_dir = env.subst("$PROJECT_SRC_DIR")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
# Cache expensive operations using lazy loading
|
|
|
|
|
self._arduino_framework_dir = None
|
|
|
|
|
self._arduino_libs_mcu = None
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def arduino_framework_dir(self):
|
|
|
|
|
"""
|
|
|
|
|
Lazy-loaded property for Arduino framework directory.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Path to Arduino ESP32 framework installation directory
|
|
|
|
|
"""
|
|
|
|
|
if self._arduino_framework_dir is None:
|
|
|
|
|
self._arduino_framework_dir = self.platform.get_package_dir("framework-arduinoespressif32")
|
|
|
|
|
return self._arduino_framework_dir
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def arduino_libs_mcu(self):
|
|
|
|
|
"""
|
|
|
|
|
Lazy-loaded property for MCU-specific Arduino libraries directory.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Path to MCU-specific Arduino libraries directory
|
|
|
|
|
"""
|
|
|
|
|
if self._arduino_libs_mcu is None:
|
|
|
|
|
ald = self.platform.get_package_dir("framework-arduinoespressif32-libs")
|
|
|
|
|
self._arduino_libs_mcu = str(Path(ald) / self.mcu) if ald else ""
|
|
|
|
|
return self._arduino_libs_mcu
|
2025-07-03 17:12:25 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ComponentLogger:
|
|
|
|
|
"""
|
|
|
|
|
Simple logging functionality for component operations.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Provides centralized logging for all component management operations,
|
|
|
|
|
tracking changes made during the build process and offering summary
|
|
|
|
|
reporting capabilities.
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def __init__(self):
|
|
|
|
|
"""
|
|
|
|
|
Initialize the logger with empty change tracking.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Sets up internal data structures for tracking component changes
|
|
|
|
|
and modifications made during the build process.
|
|
|
|
|
"""
|
|
|
|
|
# List to store all change messages for summary reporting
|
|
|
|
|
self.component_changes: List[str] = []
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def log_change(self, message: str) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Log a change message with immediate console output.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Records the change message internally for summary reporting and
|
|
|
|
|
immediately prints it to the console with a component manager prefix
|
|
|
|
|
for real-time feedback during build operations.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
message: Descriptive message about the change or operation performed
|
|
|
|
|
"""
|
|
|
|
|
self.component_changes.append(message)
|
|
|
|
|
print(f"[ComponentManager] {message}")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def get_changes_summary(self) -> List[str]:
|
|
|
|
|
"""
|
|
|
|
|
Get a copy of all changes made during the session.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns a defensive copy of the change log to prevent external
|
|
|
|
|
modification while allowing access to the complete change history.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
List of change messages in chronological order
|
|
|
|
|
"""
|
|
|
|
|
return self.component_changes.copy()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def print_changes_summary(self) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Print a formatted summary of all changes made.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Outputs a nicely formatted summary of all component changes if any
|
|
|
|
|
were made, or a simple message indicating no changes occurred.
|
|
|
|
|
Useful for end-of-build reporting and debugging.
|
|
|
|
|
"""
|
|
|
|
|
if self.component_changes:
|
|
|
|
|
print("\n=== Component Manager Changes ===")
|
|
|
|
|
for change in self.component_changes:
|
|
|
|
|
print(f" {change}")
|
|
|
|
|
print("=" * 35)
|
|
|
|
|
else:
|
|
|
|
|
print("[ComponentManager] No changes made")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ComponentHandler:
|
|
|
|
|
"""
|
|
|
|
|
Handles IDF component addition and removal operations.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Manages the core functionality for adding and removing ESP-IDF components
|
|
|
|
|
from Arduino framework projects, including YAML file manipulation,
|
|
|
|
|
component validation, and cleanup operations.
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def __init__(self, config: ComponentManagerConfig, logger: ComponentLogger):
|
|
|
|
|
"""
|
|
|
|
|
Initialize the component handler with configuration and logging.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Sets up the component handler with necessary dependencies for
|
|
|
|
|
configuration access and change logging. Initializes tracking
|
|
|
|
|
for removed components to enable proper cleanup operations.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
config: Configuration manager instance providing access to paths and settings
|
|
|
|
|
logger: Logger instance for recording component operations
|
|
|
|
|
"""
|
|
|
|
|
self.config = config
|
|
|
|
|
self.logger = logger
|
|
|
|
|
# Track removed components for cleanup operations
|
|
|
|
|
self.removed_components: Set[str] = set()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def handle_component_settings(self, add_components: bool = False, remove_components: bool = False) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Handle adding and removing IDF components based on project configuration.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Main entry point for component management operations. Processes both
|
|
|
|
|
component additions and removals based on project configuration options,
|
|
|
|
|
manages backup creation, and handles cleanup of removed components.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
add_components: Whether to process component additions from custom_component_add
|
|
|
|
|
remove_components: Whether to process component removals from custom_component_remove
|
|
|
|
|
"""
|
|
|
|
|
# Create backup before first component removal and on every add of a component
|
|
|
|
|
if remove_components and not self.removed_components or add_components:
|
|
|
|
|
self._backup_pioarduino_build_py()
|
|
|
|
|
self.logger.log_change("Created backup of build file")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Check if env and GetProjectOption are available
|
|
|
|
|
if hasattr(self.config, 'env') and hasattr(self.config.env, 'GetProjectOption'):
|
|
|
|
|
component_yml_path = self._get_or_create_component_yml()
|
|
|
|
|
component_data = self._load_component_yml(component_yml_path)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if remove_components:
|
|
|
|
|
self._process_component_removals(component_data)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if add_components:
|
|
|
|
|
self._process_component_additions(component_data)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
self._save_component_yml(component_yml_path, component_data)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Clean up removed components
|
|
|
|
|
if self.removed_components:
|
|
|
|
|
self._cleanup_removed_components()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _process_component_removals(self, component_data: Dict[str, Any]) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Process component removal requests from project configuration.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Reads the custom_component_remove option from platformio.ini and
|
|
|
|
|
processes each component for removal from the dependency list.
|
|
|
|
|
Handles errors gracefully and logs all operations.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
component_data: Component configuration data dictionary containing dependencies
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
remove_option = self.config.env.GetProjectOption("custom_component_remove", None)
|
|
|
|
|
if remove_option:
|
|
|
|
|
# Split multiline option into individual components
|
|
|
|
|
components_to_remove = remove_option.splitlines()
|
|
|
|
|
self._remove_components(component_data, components_to_remove)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.log_change(f"Error removing components: {str(e)}")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _process_component_additions(self, component_data: Dict[str, Any]) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Process component addition requests from project configuration.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Reads the custom_component_add option from platformio.ini and
|
|
|
|
|
processes each component for addition to the dependency list.
|
|
|
|
|
Handles errors gracefully and logs all operations.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
component_data: Component configuration data dictionary containing dependencies
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
add_option = self.config.env.GetProjectOption("custom_component_add", None)
|
|
|
|
|
if add_option:
|
|
|
|
|
# Split multiline option into individual components
|
|
|
|
|
components_to_add = add_option.splitlines()
|
|
|
|
|
self._add_components(component_data, components_to_add)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.log_change(f"Error adding components: {str(e)}")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _get_or_create_component_yml(self) -> str:
|
|
|
|
|
"""
|
|
|
|
|
Get path to idf_component.yml, creating it if necessary.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Searches for existing idf_component.yml files in the Arduino framework
|
|
|
|
|
directory first, then in the project source directory. If no file
|
|
|
|
|
exists, creates a new one in the project source directory with
|
|
|
|
|
default content.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
Absolute path to the component YAML file
|
|
|
|
|
"""
|
|
|
|
|
# Try Arduino framework first
|
2025-09-17 00:09:47 +02:00
|
|
|
afd = self.config.arduino_framework_dir
|
|
|
|
|
framework_yml = str(Path(afd) / "idf_component.yml") if afd else ""
|
|
|
|
|
if framework_yml and os.path.exists(framework_yml):
|
2025-07-03 17:12:25 +02:00
|
|
|
self._create_backup(framework_yml)
|
|
|
|
|
return framework_yml
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Try project source directory
|
2025-09-17 00:09:47 +02:00
|
|
|
project_yml = str(Path(self.config.project_src_dir) / "idf_component.yml")
|
2025-07-03 17:12:25 +02:00
|
|
|
if os.path.exists(project_yml):
|
|
|
|
|
self._create_backup(project_yml)
|
|
|
|
|
return project_yml
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Create new file in project source
|
|
|
|
|
self._create_default_component_yml(project_yml)
|
|
|
|
|
return project_yml
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _create_backup(self, file_path: str) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Create backup of a file with .orig extension.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Creates a backup copy of the specified file by appending .orig
|
|
|
|
|
to the filename. Only creates the backup if it doesn't already
|
|
|
|
|
exist to preserve the original state.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
file_path: Absolute path to the file to backup
|
|
|
|
|
"""
|
|
|
|
|
backup_path = f"{file_path}.orig"
|
|
|
|
|
if not os.path.exists(backup_path):
|
|
|
|
|
shutil.copy(file_path, backup_path)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _create_default_component_yml(self, file_path: str) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Create a default idf_component.yml file with basic ESP-IDF dependency.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Creates a new component YAML file with minimal default content
|
|
|
|
|
specifying ESP-IDF version 5.1 or higher as the base dependency.
|
|
|
|
|
This ensures compatibility with modern ESP-IDF features.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
file_path: Absolute path where to create the new YAML file
|
|
|
|
|
"""
|
|
|
|
|
default_content = {
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"idf": ">=5.1"
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
yaml.dump(default_content, f)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _load_component_yml(self, file_path: str) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
Load and parse idf_component.yml file safely.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Attempts to load and parse the YAML file using SafeLoader for
|
|
|
|
|
security. Returns a default structure with empty dependencies
|
|
|
|
|
if the file cannot be read or parsed.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
file_path: Absolute path to the YAML file to load
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
Parsed YAML data as dictionary, or default structure on failure
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
with open(file_path, "r", encoding='utf-8') as f:
|
|
|
|
|
return yaml.load(f, Loader=SafeLoader) or {"dependencies": {}}
|
|
|
|
|
except Exception:
|
|
|
|
|
return {"dependencies": {}}
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _save_component_yml(self, file_path: str, data: Dict[str, Any]) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Save component data to YAML file safely.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Attempts to write the component data dictionary to the specified
|
|
|
|
|
YAML file. Handles errors gracefully by silently failing to
|
|
|
|
|
prevent build interruption.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
file_path: Absolute path to the YAML file to write
|
|
|
|
|
data: Component data dictionary to serialize
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
with open(file_path, "w", encoding='utf-8') as f:
|
|
|
|
|
yaml.dump(data, f)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _remove_components(self, component_data: Dict[str, Any], components_to_remove: list) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Remove specified components from the configuration.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Iterates through the list of components to remove, checking if each
|
|
|
|
|
exists in the dependencies and removing it if found. Tracks removed
|
|
|
|
|
components for later cleanup operations and logs all actions.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
component_data: Component configuration data dictionary
|
|
|
|
|
components_to_remove: List of component names to remove
|
|
|
|
|
"""
|
|
|
|
|
dependencies = component_data.setdefault("dependencies", {})
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
for component in components_to_remove:
|
|
|
|
|
component = component.strip()
|
|
|
|
|
if not component:
|
|
|
|
|
continue
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if component in dependencies:
|
|
|
|
|
self.logger.log_change(f"Removed component: {component}")
|
|
|
|
|
del dependencies[component]
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Track for cleanup - convert to filesystem-safe name
|
|
|
|
|
filesystem_name = self._convert_component_name_to_filesystem(component)
|
|
|
|
|
self.removed_components.add(filesystem_name)
|
|
|
|
|
else:
|
|
|
|
|
self.logger.log_change(f"Component not found: {component}")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _add_components(self, component_data: Dict[str, Any], components_to_add: list) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Add specified components to the configuration.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Processes each component entry, parsing name and version information,
|
|
|
|
|
and adds new components to the dependencies. Skips components that
|
|
|
|
|
already exist and filters out entries that are too short to be valid.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
component_data: Component configuration data dictionary
|
|
|
|
|
components_to_add: List of component entries to add (format: name@version or name)
|
|
|
|
|
"""
|
|
|
|
|
dependencies = component_data.setdefault("dependencies", {})
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
for component in components_to_add:
|
|
|
|
|
component = component.strip()
|
2025-09-17 00:09:47 +02:00
|
|
|
if not component: # Skip empty entries
|
2025-07-03 17:12:25 +02:00
|
|
|
continue
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
component_name, version = self._parse_component_entry(component)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if component_name not in dependencies:
|
|
|
|
|
dependencies[component_name] = {"version": version}
|
|
|
|
|
self.logger.log_change(f"Added component: {component_name} ({version})")
|
|
|
|
|
else:
|
|
|
|
|
self.logger.log_change(f"Component already exists: {component_name}")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _parse_component_entry(self, entry: str) -> Tuple[str, str]:
|
|
|
|
|
"""
|
|
|
|
|
Parse component entry into name and version components.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Splits component entries that contain version information (format: name@version)
|
|
|
|
|
and returns both parts. If no version is specified, defaults to "*" for
|
|
|
|
|
latest version.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
entry: Component entry string (e.g., "espressif/esp_timer@1.0.0" or "espressif/esp_timer")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
Tuple containing (component_name, version)
|
|
|
|
|
"""
|
|
|
|
|
if "@" in entry:
|
|
|
|
|
name, version = entry.split("@", 1)
|
|
|
|
|
return (name.strip(), version.strip())
|
|
|
|
|
return (entry.strip(), "*")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _convert_component_name_to_filesystem(self, component_name: str) -> str:
|
|
|
|
|
"""
|
|
|
|
|
Convert component name from registry format to filesystem format.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Converts component names from ESP Component Registry format (using forward slashes)
|
|
|
|
|
to filesystem-safe format (using double underscores) for directory operations.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
component_name: Component name in registry format (e.g., "espressif/esp_timer")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
Filesystem-safe component name (e.g., "espressif__esp_timer")
|
|
|
|
|
"""
|
|
|
|
|
return component_name.replace("/", "__")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _backup_pioarduino_build_py(self) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Create backup of the original pioarduino-build.py file.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Creates a backup of the Arduino framework's build script before
|
|
|
|
|
making modifications. Only operates when Arduino framework is active
|
|
|
|
|
and creates MCU-specific backup names to avoid conflicts.
|
|
|
|
|
"""
|
|
|
|
|
if "arduino" not in self.config.env.subst("$PIOFRAMEWORK"):
|
|
|
|
|
return
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
if not self.config.arduino_libs_mcu:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
build_py_path = str(Path(self.config.arduino_libs_mcu) / "pioarduino-build.py")
|
|
|
|
|
backup_path = str(Path(self.config.arduino_libs_mcu) / f"pioarduino-build.py.{self.config.mcu}")
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if os.path.exists(build_py_path) and not os.path.exists(backup_path):
|
|
|
|
|
shutil.copy2(build_py_path, backup_path)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _cleanup_removed_components(self) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Clean up removed components and restore original build file.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
Performs optimized batch cleanup operations for all components that were removed,
|
2025-07-03 17:12:25 +02:00
|
|
|
including removing include directories and cleaning up CPPPATH
|
2025-09-17 00:09:47 +02:00
|
|
|
entries from the build script in a single pass.
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
if not self.removed_components:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Batch remove include directories
|
|
|
|
|
self._batch_remove_include_directories()
|
|
|
|
|
|
|
|
|
|
# Single pass through build file for all components
|
|
|
|
|
self._batch_remove_cpppath_entries()
|
|
|
|
|
|
|
|
|
|
def _batch_remove_include_directories(self) -> None:
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Remove multiple include directories in one optimized operation.
|
|
|
|
|
|
|
|
|
|
Removes all component include directories efficiently without
|
|
|
|
|
individual file system calls for each component.
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
include_base_path = Path(self.config.arduino_libs_mcu) / "include"
|
|
|
|
|
|
|
|
|
|
for component in self.removed_components:
|
|
|
|
|
include_path = include_base_path / component
|
|
|
|
|
if include_path.exists():
|
|
|
|
|
try:
|
|
|
|
|
shutil.rmtree(include_path)
|
|
|
|
|
except OSError:
|
|
|
|
|
pass # Continue with other components
|
|
|
|
|
|
|
|
|
|
def _batch_remove_cpppath_entries(self) -> None:
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Remove CPPPATH entries for all components in single optimized file pass.
|
|
|
|
|
|
|
|
|
|
Uses compiled regex patterns and processes all removed components
|
|
|
|
|
in a single pass through the build file for maximum efficiency.
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
build_py_path = str(Path(self.config.arduino_libs_mcu) / "pioarduino-build.py")
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if not os.path.exists(build_py_path):
|
|
|
|
|
return
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
try:
|
|
|
|
|
with open(build_py_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
content = f.read()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
original_content = content
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
# Create combined pattern for all components for maximum efficiency
|
|
|
|
|
escaped_components = [re.escape(comp) for comp in self.removed_components]
|
|
|
|
|
component_pattern = '|'.join(escaped_components)
|
|
|
|
|
|
|
|
|
|
# Compile patterns once for all components
|
|
|
|
|
combined_patterns = [
|
|
|
|
|
re.compile(rf'.*join\([^,]*,\s*"include",\s*"({component_pattern})"[^)]*\),?\n'),
|
|
|
|
|
re.compile(rf'.*"include/({component_pattern})"[^,\n]*,?\n'),
|
|
|
|
|
re.compile(rf'.*"[^"]*include[^"]*({component_pattern})[^"]*"[^,\n]*,?\n')
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Apply all patterns in single pass
|
|
|
|
|
for pattern in combined_patterns:
|
|
|
|
|
content = pattern.sub('', content)
|
|
|
|
|
|
|
|
|
|
# Write changes if any were made
|
2025-07-03 17:12:25 +02:00
|
|
|
if content != original_content:
|
|
|
|
|
with open(build_py_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
f.write(content)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[ComponentManager] Error updating build file during CPPPATH cleanup: {e!s}")
|
2025-07-03 17:12:25 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class LibraryIgnoreHandler:
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Handles lib_ignore processing and include removal with optimized performance.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Manages the processing of lib_ignore entries from platformio.ini,
|
|
|
|
|
converting library names to include paths and removing corresponding
|
|
|
|
|
entries from the build script while protecting critical components.
|
2025-09-17 00:09:47 +02:00
|
|
|
Uses compiled regex patterns and caching for maximum performance.
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def __init__(self, config: ComponentManagerConfig, logger: ComponentLogger):
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Initialize the library ignore handler with performance optimizations.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Sets up the handler with configuration and logging dependencies,
|
2025-09-17 00:09:47 +02:00
|
|
|
initializes tracking for ignored libraries, and prepares optimized
|
|
|
|
|
caching and compiled patterns for maximum performance.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
config: Configuration manager instance for accessing paths and settings
|
|
|
|
|
logger: Logger instance for recording library operations
|
|
|
|
|
"""
|
|
|
|
|
self.config = config
|
|
|
|
|
self.logger = logger
|
|
|
|
|
# Track ignored libraries for processing
|
|
|
|
|
self.ignored_libs: Set[str] = set()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
# Performance optimization: Pre-compute critical components as set for O(1) lookup
|
|
|
|
|
self._critical_components = {
|
|
|
|
|
'lwip', # Network stack
|
|
|
|
|
'freertos', # Real-time OS
|
|
|
|
|
'esp_system', # System functions
|
|
|
|
|
'esp_common', # Common ESP functions
|
|
|
|
|
'driver', # Hardware drivers
|
|
|
|
|
'nvs_flash', # Non-volatile storage
|
|
|
|
|
'spi_flash', # Flash memory access
|
|
|
|
|
'esp_timer', # Timer functions
|
|
|
|
|
'esp_event', # Event system
|
|
|
|
|
'log', # Logging system
|
|
|
|
|
'arduino_tinyusb', # Arduino TinyUSB library
|
|
|
|
|
'tinyusb' # TinyUSB library
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Pre-compute BT-related keywords as set for O(1) lookup
|
|
|
|
|
self._bt_keywords = {
|
|
|
|
|
'BLE', 'BT', 'NIMBLE', 'BLUETOOTH', 'ESP32_BLE', 'ESP32BLE',
|
|
|
|
|
'BLUETOOTHSERIAL', 'BLE_ARDUINO', 'ESP_BLE', 'ESP_BT'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Cache for expensive operations (lazy loaded)
|
2025-07-03 17:12:25 +02:00
|
|
|
self._arduino_libraries_cache = None
|
2025-09-17 00:09:47 +02:00
|
|
|
self._compiled_patterns_cache = {}
|
|
|
|
|
self._cleanup_patterns = None
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def handle_lib_ignore(self) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Handle lib_ignore entries from platformio.ini and remove corresponding includes.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Main entry point for library ignore processing. Creates backup if needed,
|
|
|
|
|
processes lib_ignore entries from the current environment, and removes
|
2025-09-17 00:09:47 +02:00
|
|
|
corresponding include paths from the build script using optimized algorithms.
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
|
|
|
|
# Create backup before processing lib_ignore
|
|
|
|
|
if not self.ignored_libs:
|
|
|
|
|
self._backup_pioarduino_build_py()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Get lib_ignore entries from current environment only
|
|
|
|
|
lib_ignore_entries = self._get_lib_ignore_entries()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if lib_ignore_entries:
|
|
|
|
|
self.ignored_libs.update(lib_ignore_entries)
|
|
|
|
|
self._remove_ignored_lib_includes()
|
|
|
|
|
self.logger.log_change(f"Processed {len(lib_ignore_entries)} ignored libraries")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _get_lib_ignore_entries(self) -> List[str]:
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Get lib_ignore entries from current environment configuration with optimized filtering.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Extracts and processes lib_ignore entries from the platformio.ini
|
|
|
|
|
configuration, converting library names to include directory names
|
2025-09-17 00:09:47 +02:00
|
|
|
and filtering out critical ESP32 components using O(1) set lookups.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
List of processed library names ready for include path removal
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Get lib_ignore from current environment only
|
|
|
|
|
lib_ignore = self.config.env.GetProjectOption("lib_ignore", [])
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if isinstance(lib_ignore, str):
|
|
|
|
|
lib_ignore = [lib_ignore]
|
|
|
|
|
elif lib_ignore is None:
|
|
|
|
|
lib_ignore = []
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Clean and normalize entries
|
|
|
|
|
cleaned_entries = []
|
|
|
|
|
for entry in lib_ignore:
|
|
|
|
|
entry = str(entry).strip()
|
|
|
|
|
if entry:
|
|
|
|
|
# Convert library names to potential include directory names
|
|
|
|
|
include_name = self._convert_lib_name_to_include(entry)
|
2025-09-17 00:09:47 +02:00
|
|
|
# Use optimized set lookup for critical components check (O(1) vs O(n))
|
|
|
|
|
if include_name not in self._critical_components:
|
|
|
|
|
cleaned_entries.append(include_name)
|
|
|
|
|
|
|
|
|
|
return sorted(set(cleaned_entries))
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
except Exception:
|
|
|
|
|
return []
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _has_bt_ble_dependencies(self) -> bool:
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Check if lib_deps contains any BT/BLE related dependencies using optimized search.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Scans the lib_deps configuration option for Bluetooth or BLE
|
|
|
|
|
related keywords to determine if BT components should be protected
|
|
|
|
|
from removal even if they appear in lib_ignore.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
True if BT/BLE dependencies are found in lib_deps
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Get lib_deps from current environment
|
|
|
|
|
lib_deps = self.config.env.GetProjectOption("lib_deps", [])
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if isinstance(lib_deps, str):
|
|
|
|
|
lib_deps = [lib_deps]
|
|
|
|
|
elif lib_deps is None:
|
|
|
|
|
lib_deps = []
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
# Convert to string and check for BT/BLE keywords using set intersection
|
2025-07-03 17:12:25 +02:00
|
|
|
lib_deps_str = ' '.join(str(dep) for dep in lib_deps).upper()
|
2025-09-17 00:09:47 +02:00
|
|
|
return any(keyword in lib_deps_str for keyword in self._bt_keywords)
|
2025-07-03 17:12:25 +02:00
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
return False
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _is_bt_related_library(self, lib_name: str) -> bool:
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Check if a library name is related to Bluetooth/BLE functionality using optimized lookup.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Examines library names for Bluetooth and BLE related keywords
|
|
|
|
|
to determine if the library should be protected when BT dependencies
|
2025-09-17 00:09:47 +02:00
|
|
|
are present in the project. Uses pre-computed set for fast lookup.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
lib_name: Library name to check for BT/BLE relation
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
True if library name contains BT/BLE related keywords
|
|
|
|
|
"""
|
|
|
|
|
lib_name_upper = lib_name.upper()
|
2025-09-17 00:09:47 +02:00
|
|
|
return any(bt_keyword in lib_name_upper for bt_keyword in self._bt_keywords)
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _get_arduino_core_libraries(self) -> Dict[str, str]:
|
|
|
|
|
"""
|
|
|
|
|
Get all Arduino core libraries and their corresponding include paths.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Scans the Arduino framework libraries directory to build a mapping
|
|
|
|
|
of library names to their corresponding include paths. Reads
|
|
|
|
|
library.properties files to get official library names.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
Dictionary mapping library names to include directory names
|
|
|
|
|
"""
|
|
|
|
|
libraries_mapping = {}
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Path to Arduino Core Libraries
|
2025-09-17 00:09:47 +02:00
|
|
|
afd = self.config.arduino_framework_dir
|
|
|
|
|
if not afd:
|
|
|
|
|
return libraries_mapping
|
|
|
|
|
arduino_libs_dir = str(Path(afd).resolve() / "libraries")
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if not os.path.exists(arduino_libs_dir):
|
|
|
|
|
return libraries_mapping
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
try:
|
|
|
|
|
for entry in os.listdir(arduino_libs_dir):
|
2025-09-17 00:09:47 +02:00
|
|
|
lib_path = str(Path(arduino_libs_dir) / entry)
|
2025-07-03 17:12:25 +02:00
|
|
|
if os.path.isdir(lib_path):
|
|
|
|
|
lib_name = self._get_library_name_from_properties(lib_path)
|
|
|
|
|
if lib_name:
|
|
|
|
|
include_path = self._map_library_to_include_path(lib_name, entry)
|
|
|
|
|
libraries_mapping[lib_name.lower()] = include_path
|
|
|
|
|
libraries_mapping[entry.lower()] = include_path # Also use directory name as key
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
return libraries_mapping
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _get_library_name_from_properties(self, lib_dir: str) -> Optional[str]:
|
|
|
|
|
"""
|
|
|
|
|
Extract library name from library.properties file.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Reads the library.properties file in the given directory and
|
|
|
|
|
extracts the official library name from the 'name=' field.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
lib_dir: Path to library directory containing library.properties
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
Official library name or None if not found or readable
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
prop_path = str(Path(lib_dir) / "library.properties")
|
2025-07-03 17:12:25 +02:00
|
|
|
if not os.path.isfile(prop_path):
|
|
|
|
|
return None
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
try:
|
|
|
|
|
with open(prop_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
for line in f:
|
|
|
|
|
line = line.strip()
|
|
|
|
|
if line.startswith('name='):
|
|
|
|
|
return line.split('=', 1)[1].strip()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
return None
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _map_library_to_include_path(self, lib_name: str, dir_name: str) -> str:
|
|
|
|
|
"""
|
|
|
|
|
Map library name to corresponding include path.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Converts Arduino library names to their corresponding ESP-IDF
|
|
|
|
|
component include paths using an extensive mapping table.
|
|
|
|
|
Handles common Arduino libraries and their ESP-IDF equivalents.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
lib_name: Official library name from library.properties
|
|
|
|
|
dir_name: Directory name of the library
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
Corresponding ESP-IDF component include path name
|
|
|
|
|
"""
|
|
|
|
|
lib_name_lower = lib_name.lower().replace(' ', '').replace('-', '_')
|
|
|
|
|
dir_name_lower = dir_name.lower()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Extended mapping list with Arduino Core Libraries
|
|
|
|
|
extended_mapping = {
|
|
|
|
|
# Core ESP32 mappings
|
|
|
|
|
'wifi': 'esp_wifi',
|
|
|
|
|
'bluetooth': 'bt',
|
|
|
|
|
'bluetoothserial': 'bt',
|
|
|
|
|
'ble': 'bt',
|
|
|
|
|
'bt': 'bt',
|
|
|
|
|
'ethernet': 'esp_eth',
|
|
|
|
|
'websocket': 'esp_websocket_client',
|
|
|
|
|
'http': 'esp_http_client',
|
|
|
|
|
'https': 'esp_https_ota',
|
|
|
|
|
'ota': 'esp_https_ota',
|
|
|
|
|
'spiffs': 'spiffs',
|
|
|
|
|
'fatfs': 'fatfs',
|
|
|
|
|
'mesh': 'esp_wifi_mesh',
|
|
|
|
|
'smartconfig': 'esp_smartconfig',
|
|
|
|
|
'mdns': 'mdns',
|
|
|
|
|
'coap': 'coap',
|
|
|
|
|
'mqtt': 'mqtt',
|
|
|
|
|
'json': 'cjson',
|
|
|
|
|
'mbedtls': 'mbedtls',
|
|
|
|
|
'openssl': 'openssl',
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Arduino Core specific mappings (safe mappings that don't conflict with critical components)
|
|
|
|
|
'esp32blearduino': 'bt',
|
|
|
|
|
'esp32_ble_arduino': 'bt',
|
2025-09-17 00:09:47 +02:00
|
|
|
'simpleble': 'bt',
|
|
|
|
|
'esp_nimble_cpp': 'bt',
|
|
|
|
|
'nimble_arduino': 'bt',
|
2025-07-03 17:12:25 +02:00
|
|
|
'esp32': 'esp32',
|
|
|
|
|
'wire': 'driver',
|
|
|
|
|
'spi': 'driver',
|
|
|
|
|
'i2c': 'driver',
|
|
|
|
|
'uart': 'driver',
|
|
|
|
|
'serial': 'driver',
|
|
|
|
|
'analogwrite': 'driver',
|
|
|
|
|
'ledc': 'driver',
|
|
|
|
|
'pwm': 'driver',
|
|
|
|
|
'dac': 'driver',
|
|
|
|
|
'adc': 'driver',
|
|
|
|
|
'touch': 'driver',
|
|
|
|
|
'hall': 'driver',
|
|
|
|
|
'rtc': 'driver',
|
|
|
|
|
'timer': 'esp_timer',
|
|
|
|
|
'preferences': 'arduino_preferences',
|
|
|
|
|
'eeprom': 'arduino_eeprom',
|
|
|
|
|
'update': 'esp_https_ota',
|
|
|
|
|
'httpupdate': 'esp_https_ota',
|
|
|
|
|
'httpclient': 'esp_http_client',
|
|
|
|
|
'httpsclient': 'esp_https_ota',
|
|
|
|
|
'wifimanager': 'esp_wifi',
|
|
|
|
|
'wificlientsecure': 'esp_wifi',
|
|
|
|
|
'wifiserver': 'esp_wifi',
|
|
|
|
|
'wifiudp': 'esp_wifi',
|
|
|
|
|
'wificlient': 'esp_wifi',
|
|
|
|
|
'wifiap': 'esp_wifi',
|
|
|
|
|
'wifimulti': 'esp_wifi',
|
|
|
|
|
'esp32webserver': 'esp_http_server',
|
|
|
|
|
'webserver': 'esp_http_server',
|
|
|
|
|
'asyncwebserver': 'esp_http_server',
|
|
|
|
|
'dnsserver': 'lwip',
|
|
|
|
|
'netbios': 'netbios',
|
|
|
|
|
'simpletime': 'lwip',
|
|
|
|
|
'fs': 'vfs',
|
|
|
|
|
'sd': 'fatfs',
|
|
|
|
|
'sd_mmc': 'fatfs',
|
|
|
|
|
'littlefs': 'esp_littlefs',
|
|
|
|
|
'ffat': 'fatfs',
|
|
|
|
|
'camera': 'esp32_camera',
|
|
|
|
|
'esp_camera': 'esp32_camera',
|
|
|
|
|
'arducam': 'esp32_camera',
|
|
|
|
|
'rainmaker': 'esp_rainmaker',
|
|
|
|
|
'esp_rainmaker': 'esp_rainmaker',
|
|
|
|
|
'provisioning': 'wifi_provisioning',
|
|
|
|
|
'wifiprovisioning': 'wifi_provisioning',
|
|
|
|
|
'espnow': 'esp_now',
|
|
|
|
|
'esp_now': 'esp_now',
|
|
|
|
|
'esptouch': 'esp_smartconfig',
|
|
|
|
|
'ping': 'lwip',
|
|
|
|
|
'netif': 'lwip',
|
2025-09-17 00:09:47 +02:00
|
|
|
'tcpip': 'lwip',
|
|
|
|
|
'usb': 'arduino_tinyusb',
|
|
|
|
|
'tinyusb': 'arduino_tinyusb',
|
|
|
|
|
'dsp': 'espressif__esp-dsp',
|
|
|
|
|
'esp_dsp': 'espressif__esp-dsp',
|
|
|
|
|
'dsps': 'espressif__esp-dsp',
|
|
|
|
|
'fft2r': 'espressif__esp-dsp',
|
|
|
|
|
'dsps_fft2r': 'espressif__esp-dsp',
|
|
|
|
|
'esp-dsp': 'espressif__esp-dsp',
|
|
|
|
|
'espressif/esp-dsp': 'espressif__esp-dsp',
|
|
|
|
|
'espressif__esp-dsp': 'espressif__esp-dsp'
|
2025-07-03 17:12:25 +02:00
|
|
|
}
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Check extended mapping first
|
|
|
|
|
if lib_name_lower in extended_mapping:
|
|
|
|
|
return extended_mapping[lib_name_lower]
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Check directory name
|
|
|
|
|
if dir_name_lower in extended_mapping:
|
|
|
|
|
return extended_mapping[dir_name_lower]
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Fallback: Use directory name as include path
|
|
|
|
|
return dir_name_lower
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _convert_lib_name_to_include(self, lib_name: str) -> str:
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Convert library name to potential include directory name with optimized fast paths.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Converts library names from platformio.ini lib_ignore entries
|
|
|
|
|
to their corresponding include directory names. Uses Arduino
|
2025-09-17 00:09:47 +02:00
|
|
|
core library mappings and common naming conventions with
|
|
|
|
|
performance optimizations for common cases like DSP.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
lib_name: Library name from lib_ignore configuration
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
Converted include directory name for path removal
|
|
|
|
|
"""
|
|
|
|
|
lib_name_lower = lib_name.lower()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
# Fast path optimization for DSP components (most performance-critical case)
|
|
|
|
|
dsp_patterns = {
|
|
|
|
|
'dsp', 'esp_dsp', 'dsps', 'fft2r', 'dsps_fft2r', 'esp-dsp',
|
|
|
|
|
'espressif/esp-dsp', 'espressif__esp-dsp'
|
|
|
|
|
}
|
|
|
|
|
if lib_name_lower in dsp_patterns:
|
|
|
|
|
return 'espressif__esp-dsp'
|
|
|
|
|
|
|
|
|
|
# Fast path for BT components
|
|
|
|
|
bt_patterns = {
|
|
|
|
|
'ble', 'bluetooth', 'bluetoothserial', 'simpleble', 'esp-nimble-cpp'
|
|
|
|
|
}
|
|
|
|
|
if lib_name_lower in bt_patterns:
|
|
|
|
|
return 'bt'
|
|
|
|
|
|
|
|
|
|
# Load Arduino Core Libraries on first call (lazy loading)
|
|
|
|
|
if self._arduino_libraries_cache is None:
|
|
|
|
|
self._arduino_libraries_cache = self._get_arduino_core_libraries()
|
|
|
|
|
|
|
|
|
|
# Check Arduino Core Libraries cache
|
2025-07-03 17:12:25 +02:00
|
|
|
if lib_name_lower in self._arduino_libraries_cache:
|
|
|
|
|
return self._arduino_libraries_cache[lib_name_lower]
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
# Continue with full conversion logic for less common cases
|
|
|
|
|
return self._full_conversion_logic(lib_name_lower)
|
|
|
|
|
|
|
|
|
|
def _full_conversion_logic(self, lib_name_lower: str) -> str:
|
|
|
|
|
"""
|
|
|
|
|
Full conversion logic for library names not handled by fast paths.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
lib_name_lower: Lowercase library name to convert
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Converted include directory name
|
|
|
|
|
"""
|
2025-07-03 17:12:25 +02:00
|
|
|
# Remove common prefixes and suffixes
|
|
|
|
|
cleaned_name = lib_name_lower
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Remove common prefixes
|
|
|
|
|
prefixes_to_remove = ['lib', 'arduino-', 'esp32-', 'esp-']
|
|
|
|
|
for prefix in prefixes_to_remove:
|
|
|
|
|
if cleaned_name.startswith(prefix):
|
|
|
|
|
cleaned_name = cleaned_name[len(prefix):]
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Remove common suffixes
|
|
|
|
|
suffixes_to_remove = ['-lib', '-library', '.h']
|
|
|
|
|
for suffix in suffixes_to_remove:
|
|
|
|
|
if cleaned_name.endswith(suffix):
|
|
|
|
|
cleaned_name = cleaned_name[:-len(suffix)]
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Check again with cleaned name
|
|
|
|
|
if cleaned_name in self._arduino_libraries_cache:
|
|
|
|
|
return self._arduino_libraries_cache[cleaned_name]
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
return cleaned_name
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
def _get_compiled_patterns(self, lib_name: str) -> List[Pattern]:
|
|
|
|
|
"""
|
|
|
|
|
Get pre-compiled regex patterns for a library name with caching.
|
|
|
|
|
|
|
|
|
|
Compiles and caches regex patterns for library name matching
|
|
|
|
|
to avoid repeated compilation overhead during processing.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
lib_name: Library name to create patterns for
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List of compiled regex patterns for the library
|
|
|
|
|
"""
|
|
|
|
|
if lib_name not in self._compiled_patterns_cache:
|
|
|
|
|
escaped_name = re.escape(lib_name)
|
|
|
|
|
patterns = [
|
|
|
|
|
re.compile(rf'.*join\([^,]*,\s*"include",\s*"{escaped_name}"[^)]*\),?\n'),
|
|
|
|
|
re.compile(rf'.*"include/{escaped_name}"[^,\n]*,?\n'),
|
|
|
|
|
re.compile(rf'.*"[^"]*include[^"]*{escaped_name}[^"]*"[^,\n]*,?\n'),
|
|
|
|
|
re.compile(rf'.*"[^"]*/{escaped_name}/include[^"]*"[^,\n]*,?\n'),
|
|
|
|
|
re.compile(rf'.*"[^"]*{escaped_name}[^"]*include[^"]*"[^,\n]*,?\n'),
|
|
|
|
|
re.compile(rf'.*join\([^)]*"include"[^)]*"{escaped_name}"[^)]*\),?\n'),
|
|
|
|
|
re.compile(rf'.*"{escaped_name}/include"[^,\n]*,?\n'),
|
|
|
|
|
re.compile(rf'\s*"[^"]*[\\/]{escaped_name}[\\/][^"]*",?\n'),
|
|
|
|
|
re.compile(rf'.*Path\([^)]*\)\s*/\s*"include"\s*/\s*"{escaped_name}"[^,\n]*,?\n'),
|
|
|
|
|
re.compile(rf'.*Path\([^)]*{escaped_name}[^)]*\)\s*/\s*"include"[^,\n]*,?\n')
|
|
|
|
|
]
|
|
|
|
|
self._compiled_patterns_cache[lib_name] = patterns
|
|
|
|
|
return self._compiled_patterns_cache[lib_name]
|
|
|
|
|
|
|
|
|
|
def _get_cleanup_patterns(self) -> List[Pattern]:
|
|
|
|
|
"""
|
|
|
|
|
Get compiled cleanup patterns with caching.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List of compiled regex patterns for content cleanup
|
|
|
|
|
"""
|
|
|
|
|
if self._cleanup_patterns is None:
|
|
|
|
|
self._cleanup_patterns = [
|
|
|
|
|
re.compile(r'\n\s*\n'),
|
|
|
|
|
re.compile(r',\s*\n\s*\]')
|
|
|
|
|
]
|
|
|
|
|
return self._cleanup_patterns
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _remove_ignored_lib_includes(self) -> None:
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Remove include entries for ignored libraries using optimized batch processing.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Processes the Arduino build script to remove CPPPATH entries for
|
2025-09-17 00:09:47 +02:00
|
|
|
all ignored libraries using compiled regex patterns and batch processing.
|
|
|
|
|
Implements protection for BT/BLE components when dependencies are detected.
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
build_py_path = str(Path(self.config.arduino_libs_mcu) / "pioarduino-build.py")
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if not os.path.exists(build_py_path):
|
|
|
|
|
self.logger.log_change("Build file not found")
|
|
|
|
|
return
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
# Check if BT/BLE dependencies exist in lib_deps (single check)
|
2025-07-03 17:12:25 +02:00
|
|
|
bt_ble_protected = self._has_bt_ble_dependencies()
|
|
|
|
|
if bt_ble_protected:
|
|
|
|
|
self.logger.log_change("BT/BLE protection enabled")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
try:
|
2025-09-17 00:09:47 +02:00
|
|
|
# Read file once
|
2025-07-03 17:12:25 +02:00
|
|
|
with open(build_py_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
content = f.read()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
original_content = content
|
|
|
|
|
total_removed = 0
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
# Pre-filter libraries to process (avoid processing protected libraries)
|
|
|
|
|
libs_to_process = []
|
2025-07-03 17:12:25 +02:00
|
|
|
for lib_name in self.ignored_libs:
|
|
|
|
|
if bt_ble_protected and self._is_bt_related_library(lib_name):
|
|
|
|
|
self.logger.log_change(f"Protected BT library: {lib_name}")
|
|
|
|
|
continue
|
2025-09-17 00:09:47 +02:00
|
|
|
libs_to_process.append(lib_name)
|
|
|
|
|
|
|
|
|
|
# Batch process all libraries using compiled patterns
|
|
|
|
|
if libs_to_process:
|
|
|
|
|
content, total_removed = self._batch_remove_patterns(content, libs_to_process)
|
|
|
|
|
|
|
|
|
|
# Clean up content once at the end
|
|
|
|
|
if total_removed > 0:
|
|
|
|
|
content = self._cleanup_content(content)
|
|
|
|
|
|
|
|
|
|
# Validate and write changes
|
|
|
|
|
if self._validate_changes(original_content, content):
|
|
|
|
|
with open(build_py_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
f.write(content)
|
|
|
|
|
self.logger.log_change(f"Updated build file ({total_removed} total removals)")
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
except Exception as e:
|
2025-09-17 00:09:47 +02:00
|
|
|
self.logger.log_change(f"Error processing libraries: {e!s} ({e.__class__.__name__})")
|
|
|
|
|
|
|
|
|
|
def _batch_remove_patterns(self, content: str, libs_to_process: List[str]) -> Tuple[str, int]:
|
|
|
|
|
"""
|
|
|
|
|
Process all libraries in batches using compiled patterns for optimal performance.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
content: File content to process
|
|
|
|
|
libs_to_process: List of library names to remove
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Tuple of (modified_content, total_removed_count)
|
|
|
|
|
"""
|
|
|
|
|
total_removed = 0
|
|
|
|
|
|
|
|
|
|
for lib_name in libs_to_process:
|
|
|
|
|
patterns = self._get_compiled_patterns(lib_name)
|
|
|
|
|
removed_count = 0
|
|
|
|
|
|
|
|
|
|
for pattern in patterns:
|
|
|
|
|
matches = pattern.findall(content)
|
|
|
|
|
if matches:
|
|
|
|
|
content = pattern.sub('', content)
|
|
|
|
|
removed_count += len(matches)
|
|
|
|
|
|
|
|
|
|
if removed_count > 0:
|
|
|
|
|
self.logger.log_change(f"Ignored library: {lib_name} ({removed_count} entries)")
|
|
|
|
|
total_removed += removed_count
|
|
|
|
|
|
|
|
|
|
return content, total_removed
|
|
|
|
|
|
|
|
|
|
def _cleanup_content(self, content: str) -> str:
|
|
|
|
|
"""
|
|
|
|
|
Optimized content cleanup using compiled patterns.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
content: Content to clean up
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Cleaned content
|
|
|
|
|
"""
|
|
|
|
|
cleanup_patterns = self._get_cleanup_patterns()
|
|
|
|
|
content = cleanup_patterns[0].sub('\n', content)
|
|
|
|
|
content = cleanup_patterns[1].sub('\n]', content)
|
|
|
|
|
return content
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _validate_changes(self, original_content: str, new_content: str) -> bool:
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
Validate that changes are safe and don't break the build file structure.
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
2025-09-17 00:09:47 +02:00
|
|
|
original_content: Original file content
|
|
|
|
|
new_content: Modified file content
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
2025-09-17 00:09:47 +02:00
|
|
|
True if changes are safe to apply
|
|
|
|
|
"""
|
|
|
|
|
# Basic validation - ensure we haven't broken basic Python syntax
|
|
|
|
|
if new_content != original_content:
|
|
|
|
|
# Check that we still have essential structure elements
|
|
|
|
|
essential_elements = ['CPPPATH', 'env.Append', '[', ']']
|
|
|
|
|
return all(element in new_content for element in essential_elements)
|
|
|
|
|
return True
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def _backup_pioarduino_build_py(self) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Create backup of the original pioarduino-build.py file.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
Creates a backup of the Arduino framework's build script before
|
|
|
|
|
making modifications. Only operates when Arduino framework is active
|
|
|
|
|
and creates MCU-specific backup names to avoid conflicts.
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
|
|
|
|
if "arduino" not in self.config.env.subst("$PIOFRAMEWORK"):
|
|
|
|
|
return
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
if not self.config.arduino_libs_mcu:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
build_py_path = str(Path(self.config.arduino_libs_mcu) / "pioarduino-build.py")
|
|
|
|
|
backup_path = str(Path(self.config.arduino_libs_mcu) / f"pioarduino-build.py.{self.config.mcu}")
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if os.path.exists(build_py_path) and not os.path.exists(backup_path):
|
|
|
|
|
shutil.copy2(build_py_path, backup_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BackupManager:
|
|
|
|
|
"""
|
|
|
|
|
Handles backup and restore operations for build files.
|
|
|
|
|
|
|
|
|
|
Manages the creation and restoration of backup files for the Arduino
|
|
|
|
|
framework build scripts, ensuring that original files can be restored
|
|
|
|
|
when needed or when builds are cleaned.
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def __init__(self, config: ComponentManagerConfig):
|
|
|
|
|
"""
|
|
|
|
|
Initialize the backup manager with configuration access.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Sets up the backup manager with access to configuration paths
|
|
|
|
|
and settings needed for backup and restore operations.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
config: Configuration manager instance providing access to paths
|
|
|
|
|
"""
|
|
|
|
|
self.config = config
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def backup_pioarduino_build_py(self) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Create backup of the original pioarduino-build.py file.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Creates a backup copy of the Arduino framework's build script
|
|
|
|
|
with MCU-specific naming to prevent conflicts between different
|
|
|
|
|
ESP32 variants. Only creates backup if it doesn't already exist.
|
|
|
|
|
"""
|
|
|
|
|
if "arduino" not in self.config.env.subst("$PIOFRAMEWORK"):
|
|
|
|
|
return
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
build_py_path = str(Path(self.config.arduino_libs_mcu) / "pioarduino-build.py")
|
|
|
|
|
backup_path = str(Path(self.config.arduino_libs_mcu) / f"pioarduino-build.py.{self.config.mcu}")
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if os.path.exists(build_py_path) and not os.path.exists(backup_path):
|
|
|
|
|
shutil.copy2(build_py_path, backup_path)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def restore_pioarduino_build_py(self, target=None, source=None, env=None) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Restore the original pioarduino-build.py from backup.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Restores the original Arduino build script from the backup copy
|
|
|
|
|
and removes the backup file. This is typically called during
|
|
|
|
|
clean operations or when resetting the build environment.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
target: Build target (unused, for PlatformIO compatibility)
|
|
|
|
|
source: Build source (unused, for PlatformIO compatibility)
|
|
|
|
|
env: Environment (unused, for PlatformIO compatibility)
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
build_py_path = str(Path(self.config.arduino_libs_mcu) / "pioarduino-build.py")
|
|
|
|
|
backup_path = str(Path(self.config.arduino_libs_mcu) / f"pioarduino-build.py.{self.config.mcu}")
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if os.path.exists(backup_path):
|
|
|
|
|
shutil.copy2(backup_path, build_py_path)
|
|
|
|
|
os.remove(backup_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ComponentManager:
|
|
|
|
|
"""
|
|
|
|
|
Main component manager that orchestrates all operations.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Primary interface for component management operations, coordinating
|
|
|
|
|
between specialized handlers for components, libraries, and backups.
|
|
|
|
|
Uses composition pattern to organize functionality into focused classes.
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def __init__(self, env):
|
|
|
|
|
"""
|
|
|
|
|
Initialize the ComponentManager with composition pattern.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Creates and configures all specialized handler instances using
|
|
|
|
|
the composition pattern for better separation of concerns and
|
|
|
|
|
maintainability. Each handler focuses on a specific aspect
|
|
|
|
|
of component management.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
env: PlatformIO environment object containing project configuration
|
|
|
|
|
"""
|
|
|
|
|
self.config = ComponentManagerConfig(env)
|
|
|
|
|
self.logger = ComponentLogger()
|
|
|
|
|
self.component_handler = ComponentHandler(self.config, self.logger)
|
|
|
|
|
self.library_handler = LibraryIgnoreHandler(self.config, self.logger)
|
|
|
|
|
self.backup_manager = BackupManager(self.config)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def handle_component_settings(self, add_components: bool = False, remove_components: bool = False) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Handle component operations by delegating to specialized handlers.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Main entry point for component management operations. Coordinates
|
|
|
|
|
component addition/removal and library ignore processing, then
|
|
|
|
|
provides a summary of all changes made during the session.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
add_components: Whether to process component additions from configuration
|
|
|
|
|
remove_components: Whether to process component removals from configuration
|
|
|
|
|
"""
|
|
|
|
|
self.component_handler.handle_component_settings(add_components, remove_components)
|
|
|
|
|
self.library_handler.handle_lib_ignore()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Print summary
|
|
|
|
|
changes = self.logger.get_changes_summary()
|
|
|
|
|
if changes:
|
|
|
|
|
self.logger.log_change(f"Session completed with {len(changes)} changes")
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def handle_lib_ignore(self) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Delegate lib_ignore handling to specialized handler.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Provides direct access to library ignore processing for cases
|
|
|
|
|
where only library handling is needed without component operations.
|
|
|
|
|
"""
|
|
|
|
|
self.library_handler.handle_lib_ignore()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def restore_pioarduino_build_py(self, target=None, source=None, env=None) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Delegate backup restoration to backup manager.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Provides access to backup restoration functionality, typically
|
|
|
|
|
used during clean operations or build environment resets.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Args:
|
|
|
|
|
target: Build target (unused, for PlatformIO compatibility)
|
|
|
|
|
source: Build source (unused, for PlatformIO compatibility)
|
|
|
|
|
env: Environment (unused, for PlatformIO compatibility)
|
|
|
|
|
"""
|
|
|
|
|
self.backup_manager.restore_pioarduino_build_py(target, source, env)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def get_changes_summary(self) -> List[str]:
|
|
|
|
|
"""
|
|
|
|
|
Get summary of changes from logger.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Provides access to the complete list of changes made during
|
|
|
|
|
the current session for reporting or debugging purposes.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Returns:
|
|
|
|
|
List of change messages in chronological order
|
|
|
|
|
"""
|
|
|
|
|
return self.logger.get_changes_summary()
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def print_changes_summary(self) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Print changes summary via logger.
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
Outputs a formatted summary of all changes made during the
|
|
|
|
|
session, useful for build reporting and debugging.
|
|
|
|
|
"""
|
|
|
|
|
self.logger.print_changes_summary()
|