Files
platform-espressif32/builder/frameworks/arduino.py
T

974 lines
33 KiB
Python
Raw Normal View History

2016-10-22 02:20:57 +03:00
# Copyright 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
2016-10-22 02:20:57 +03:00
"""
Arduino
Arduino Wiring-based Framework allows writing cross-platform software to
control devices attached to a wide range of Arduino boards to create all
kinds of creative coding, interactive objects, spaces or physical experiences.
http://arduino.cc/en/Reference/HomePage
"""
2024-12-14 12:42:42 +01:00
import subprocess
import json
2024-12-10 18:12:53 +01:00
import os
2025-01-18 15:47:53 +01:00
import sys
2024-12-14 12:42:42 +01:00
import shutil
2025-07-03 17:12:25 +02:00
import hashlib
import threading
from contextlib import suppress
from os.path import join, exists, isabs, splitdrive, commonpath, relpath
from pathlib import Path
from typing import Union, List
2016-10-24 20:23:25 +03:00
2025-07-03 17:12:25 +02:00
import semantic_version
from SCons.Script import DefaultEnvironment, SConscript
2025-01-16 22:49:42 +01:00
from platformio import fs
2024-12-14 12:42:42 +01:00
from platformio.package.version import pepver_to_semver
from platformio.package.manager.tool import ToolPackageManager
2016-10-24 20:23:25 +03:00
2025-07-03 17:12:25 +02:00
IS_WINDOWS = sys.platform.startswith("win")
python_deps = {
"wheel": ">=0.35.1",
"rich-click": ">=1.8.6",
"PyYAML": ">=6.0.2",
"intelhex": ">=2.3.0",
"rich": ">=14.0.0",
"esp-idf-size": ">=1.6.1"
}
# Constants for better performance
UNICORE_FLAGS = {
"CORE32SOLO1",
"CONFIG_FREERTOS_UNICORE=y"
}
# Thread-safe global flags to prevent message spam
_PATH_SHORTENING_LOCK = threading.Lock()
_PATH_SHORTENING_MESSAGES = {
'shortening_applied': False,
'no_framework_paths_warning': False,
'long_path_warning_shown': False
}
def get_platform_default_threshold(mcu):
"""
Platform-specific max performance default values for
INCLUDE_PATH_LENGTH_THRESHOLD
These values push the limits for maximum performance and minimal path
shortening
Args:
mcu: MCU type (esp32, esp32s2, esp32s3, etc.)
Returns:
int: Platform-specific default threshold
"""
# Max. performance values - pushing Windows command line limits
# Windows CMD has ~32768 character limit, we use aggressive values close
# to this
platform_defaults = {
"esp32": 32000, # Standard ESP32
"esp32s2": 32000, # ESP32-S2
"esp32s3": 32766, # ESP32-S3
"esp32c3": 30000, # ESP32-C3
"esp32c2": 32000, # ESP32-C2
"esp32c6": 31600, # ESP32-C6
"esp32h2": 32000, # ESP32-H2
"esp32p4": 32000, # ESP32-P4
}
default_value = platform_defaults.get(mcu, 31600)
return default_value
def validate_threshold(threshold, mcu):
"""
Validates threshold value with max. performance limits
Uses aggressive boundaries for maximum performance
Args:
threshold: Threshold value to validate
mcu: MCU type for context-specific validation
Returns:
int: Validated threshold value
"""
# Absolute limits - pushing boundaries
min_threshold = 20000 # Minimum reasonable value for complex projects
# Maximum aggressive value (beyond Windows CMD limit for testing)
max_threshold = 32767
# MCU-specific adjustments - all values are aggressive
mcu_adjustments = {
"esp32c2": {"min": 30000, "max": 32767},
"esp32c3": {"min": 30000, "max": 32767},
"esp32": {"min": 30000, "max": 32767},
"esp32s2": {"min": 30000, "max": 32767},
"esp32s3": {"min": 30000, "max": 32767},
"esp32p4": {"min": 30000, "max": 32767},
"esp32c6": {"min": 30000, "max": 32767},
"esp32h2": {"min": 30000, "max": 32767},
}
# Apply MCU-specific max. limits
if mcu in mcu_adjustments:
min_threshold = max(min_threshold, mcu_adjustments[mcu]["min"])
max_threshold = min(max_threshold, mcu_adjustments[mcu]["max"])
original_threshold = threshold
if threshold < min_threshold:
print(f"*** Warning: Include path threshold {threshold} too "
f"conservative for {mcu}, using safe minimum "
f"{min_threshold} ***")
threshold = min_threshold
elif threshold > max_threshold:
print(f"*** Warning: Include path threshold {threshold} exceeds "
f"possible maximum for {mcu}, using {max_threshold} ***")
threshold = max_threshold
# Warning for conservative values (opposite of original - warn if too low)
platform_default = get_platform_default_threshold(mcu)
if threshold < platform_default * 0.7: # More than 30% below max. default
print(f"*** Info: Include path threshold {threshold} is conservative "
f"compared to maximum default {platform_default} for "
f"{mcu} ***")
print("*** Consider using higher values for maximum performance ***")
if original_threshold != threshold:
print(f"Threshold adjusted from {original_threshold} to "
f"max. possible value {threshold} for {mcu}")
return threshold
def get_include_path_threshold(env, config, current_env_section):
"""
Determines Windows INCLUDE_PATH_LENGTH_THRESHOLD from various sources
with priority order and max. possible validation
Priority order:
1. Environment variable PLATFORMIO_INCLUDE_PATH_THRESHOLD
2. Environment-specific setting in platformio.ini
3. Global setting in [env] section
4. Setting in [platformio] section
5. MCU-specific max. possible default value
Args:
env: PlatformIO Environment
config: Project Configuration
current_env_section: Current environment section
Returns:
int: Validated max. threshold value
"""
mcu = env.BoardConfig().get("build.mcu", "esp32")
default_threshold = get_platform_default_threshold(mcu)
setting_name = "custom_include_path_length_threshold"
try:
# 1. Check environment variable (highest priority)
env_var = os.environ.get("PLATFORMIO_INCLUDE_PATH_THRESHOLD")
if env_var:
try:
threshold = int(env_var)
threshold = validate_threshold(threshold, mcu)
print(f"*** Using environment variable max. possible include "
f"path threshold: {threshold} (MCU: {mcu}) ***")
return threshold
except ValueError:
print(f"*** Warning: Invalid environment variable "
f"PLATFORMIO_INCLUDE_PATH_THRESHOLD='{env_var}', "
f"ignoring ***")
# 2. Check environment-specific setting
if config.has_option(current_env_section, setting_name):
threshold = config.getint(current_env_section, setting_name)
threshold = validate_threshold(threshold, mcu)
print(f"*** Using environment-specific max. possible include "
f"path threshold: {threshold} (MCU: {mcu}) ***")
return threshold
# 3. Check global setting in [env] section
if config.has_option("env", setting_name):
threshold = config.getint("env", setting_name)
threshold = validate_threshold(threshold, mcu)
print(f"*** Using global [env] max. possible include path "
f"threshold: {threshold} (MCU: {mcu}) ***")
return threshold
# 4. Check setting in [platformio] section
if config.has_option("platformio", setting_name):
threshold = config.getint("platformio", setting_name)
threshold = validate_threshold(threshold, mcu)
print(f"*** Using [platformio] section max. possible include "
f"path threshold: {threshold} (MCU: {mcu}) ***")
return threshold
# 5. Use MCU-specific max. possible default value
threshold = validate_threshold(default_threshold, mcu)
if env.get("VERBOSE"):
print(f"*** Using platform-specific max. possible default "
f"include path threshold: {threshold} (MCU: {mcu}) ***")
return threshold
except (ValueError, TypeError) as e:
print(f"*** Warning: Invalid include path threshold value, using "
f"max. possible platform default {default_threshold} for "
f"{mcu}: {e} ***")
return validate_threshold(default_threshold, mcu)
def get_threshold_info(env, config, current_env_section):
"""
Helper function for debug information about max. possible threshold
configuration
Args:
env: PlatformIO Environment
config: Project Configuration
current_env_section: Current environment section
Returns:
dict: Information about threshold configuration
"""
mcu = env.BoardConfig().get("build.mcu", "esp32")
setting_name = "custom_include_path_length_threshold"
info = {
"mcu": mcu,
"platform_default": get_platform_default_threshold(mcu),
"env_variable": os.environ.get("PLATFORMIO_INCLUDE_PATH_THRESHOLD"),
"env_specific": None,
"global_env": None,
"platformio_section": None,
"final_threshold": None,
"source": "bleeding_edge_platform_default",
"is_bleeding_edge": True
}
# Collect all possible sources
if config.has_option(current_env_section, setting_name):
with suppress(ValueError):
info["env_specific"] = config.getint(current_env_section,
setting_name)
if config.has_option("env", setting_name):
with suppress(ValueError):
info["global_env"] = config.getint("env", setting_name)
if config.has_option("platformio", setting_name):
with suppress(ValueError):
info["platformio_section"] = config.getint("platformio",
setting_name)
# Determine final threshold and source
info["final_threshold"] = get_include_path_threshold(env, config,
current_env_section)
# Determine source
if info["env_variable"]:
info["source"] = "environment_variable"
elif info["env_specific"] is not None:
info["source"] = "env_specific"
elif info["global_env"] is not None:
info["source"] = "global_env"
elif info["platformio_section"] is not None:
info["source"] = "platformio_section"
return info
# Cache class for frequently used paths
class PathCache:
def __init__(self, platform, mcu):
self.platform = platform
self.mcu = mcu
self._framework_dir = None
self._framework_lib_dir = None
self._sdk_dir = None
@property
def framework_dir(self):
if self._framework_dir is None:
self._framework_dir = self.platform.get_package_dir(
"framework-arduinoespressif32")
return self._framework_dir
@property
def framework_lib_dir(self):
if self._framework_lib_dir is None:
self._framework_lib_dir = self.platform.get_package_dir(
"framework-arduinoespressif32-libs")
return self._framework_lib_dir
@property
def sdk_dir(self):
if self._sdk_dir is None:
self._sdk_dir = fs.to_unix_path(
join(self.framework_lib_dir, self.mcu, "include")
)
return self._sdk_dir
def check_and_warn_long_path_support():
"""Checks Windows long path support and issues warning if disabled"""
with _PATH_SHORTENING_LOCK: # Thread-safe access
if not IS_WINDOWS or _PATH_SHORTENING_MESSAGES[
'long_path_warning_shown']:
return
try:
import winreg
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SYSTEM\CurrentControlSet\Control\FileSystem"
)
value, _ = winreg.QueryValueEx(key, "LongPathsEnabled")
winreg.CloseKey(key)
if value != 1:
print("*** WARNING: Windows Long Path Support is disabled ***")
print("*** Enable it for better performance: ***")
print("*** 1. Run as Administrator: gpedit.msc ***")
print("*** 2. Navigate to: Computer Configuration > "
"Administrative Templates > System > Filesystem ***")
print("*** 3. Enable 'Enable Win32 long paths' ***")
print("*** OR run PowerShell as Admin: ***")
print("*** New-ItemProperty -Path "
"'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\FileSystem' "
"-Name 'LongPathsEnabled' -Value 1 -PropertyType DWORD "
"-Force ***")
print("*** Restart required after enabling ***")
except Exception:
print("*** WARNING: Could not check Long Path Support status ***")
print("*** Consider enabling Windows Long Path Support for "
"better performance ***")
_PATH_SHORTENING_MESSAGES['long_path_warning_shown'] = True
# Secure deletion functions
def safe_delete_file(file_path: Union[str, Path],
force: bool = False) -> bool:
"""
Secure file deletion
Args:
file_path: Path to file to be deleted
force: Forces deletion even for write-protected files
Returns:
bool: True if successfully deleted
"""
file_path = Path(file_path)
try:
# Check existence
if not file_path.exists():
return False
# Remove write protection if necessary
if force and not os.access(file_path, os.W_OK):
file_path.chmod(0o666)
# Delete file
file_path.unlink()
return True
except PermissionError:
return False
except Exception as e:
return False
def safe_delete_directory(dir_path: Union[str, Path]) -> bool:
"""
Secure directory deletion
"""
dir_path = Path(dir_path)
try:
if not dir_path.exists():
return False
shutil.rmtree(dir_path)
return True
except Exception as e:
return False
def validate_platformio_path(path: Union[str, Path]) -> bool:
"""
Enhanced validation for PlatformIO package paths
"""
try:
path = Path(path).resolve()
path_str = str(path)
# Must be within .platformio directory structure
if ".platformio" not in path_str:
return False
# Must be a packages directory
if "packages" not in path_str:
return False
# Must be framework-related
framework_indicators = [
"framework-arduinoespressif32",
"framework-arduinoespressif32-libs"
]
if not any(indicator in path_str for indicator in framework_indicators):
return False
# Must not be a critical system path
critical_paths = ["/usr", "/bin", "/sbin", "/etc", "/boot",
"C:\\Windows", "C:\\Program Files"]
return not any(critical in path_str for critical in critical_paths)
except Exception as e:
return False
def validate_deletion_path(path: Union[str, Path],
allowed_patterns: List[str]) -> bool:
"""
Validates if a path can be safely deleted
Args:
path: Path to be checked
allowed_patterns: Allowed path patterns
Returns:
bool: True if deletion is safe
"""
path = Path(path).resolve()
# Check against critical system paths
critical_paths = [
Path.home(),
Path("/"),
Path("C:\\") if IS_WINDOWS else None,
Path("/usr"),
Path("/etc"),
Path("/bin"),
Path("/sbin")
]
for critical in filter(None, critical_paths):
try:
normalized_path = path.resolve()
normalized_critical = critical.resolve()
if (normalized_path == normalized_critical or
normalized_critical in normalized_path.parents):
return False
except (OSError, ValueError):
# Path comparison failed, reject for safety
return False
# Check against allowed patterns
path_str = str(path)
is_allowed = any(pattern in path_str for pattern in allowed_patterns)
return is_allowed
def safe_framework_cleanup():
"""Secure cleanup of Arduino Framework with enhanced error handling"""
success = True
# Framework directory cleanup
if exists(FRAMEWORK_DIR):
if validate_platformio_path(FRAMEWORK_DIR):
if not safe_delete_directory(FRAMEWORK_DIR):
print("Error removing framework")
success = False
# Framework libs directory cleanup
if exists(FRAMEWORK_LIB_DIR):
if validate_platformio_path(FRAMEWORK_LIB_DIR):
if not safe_delete_directory(FRAMEWORK_LIB_DIR):
print("Error removing framework libs")
success = False
return success
def safe_remove_sdkconfig_files():
"""Secure removal of SDKConfig files"""
envs = [section.replace("env:", "") for section in config.sections()
if section.startswith("env:")]
for env_name in envs:
file_path = join(project_dir, f"sdkconfig.{env_name}")
if exists(file_path):
safe_delete_file(file_path)
# Initialization
env = DefaultEnvironment()
2024-12-14 12:42:42 +01:00
pm = ToolPackageManager()
platform = env.PioPlatform()
config = env.GetProjectConfig()
2020-03-17 10:51:38 +00:00
board = env.BoardConfig()
2025-07-03 17:12:25 +02:00
# Cached values
2024-12-14 12:42:42 +01:00
mcu = board.get("build.mcu", "esp32")
2025-07-03 17:12:25 +02:00
pioenv = env["PIOENV"]
project_dir = env.subst("$PROJECT_DIR")
path_cache = PathCache(platform, mcu)
current_env_section = f"env:{pioenv}"
# Board configuration
2024-12-14 12:42:42 +01:00
board_sdkconfig = board.get("espidf.custom_sdkconfig", "")
entry_custom_sdkconfig = "\n"
flag_custom_sdkconfig = False
2025-07-03 17:12:25 +02:00
flag_custom_component_remove = False
flag_custom_component_add = False
flag_lib_ignore = False
if mcu == "esp32c2":
flag_custom_sdkconfig = True
# pio lib_ignore check
if config.has_option(current_env_section, "lib_ignore"):
flag_lib_ignore = True
2024-12-14 12:42:42 +01:00
2025-07-03 17:12:25 +02:00
# Custom Component remove check
if config.has_option(current_env_section, "custom_component_remove"):
flag_custom_component_remove = True
# Custom SDKConfig check
if config.has_option(current_env_section, "custom_sdkconfig"):
2024-12-14 12:42:42 +01:00
entry_custom_sdkconfig = env.GetProjectOption("custom_sdkconfig")
flag_custom_sdkconfig = True
2025-07-03 17:12:25 +02:00
if board_sdkconfig:
2024-12-14 12:42:42 +01:00
flag_custom_sdkconfig = True
2025-07-03 17:12:25 +02:00
extra_flags_raw = board.get("build.extra_flags", [])
if isinstance(extra_flags_raw, list):
extra_flags = " ".join(extra_flags_raw).replace("-D", " ")
else:
extra_flags = str(extra_flags_raw).replace("-D", " ")
2024-12-14 12:42:42 +01:00
framework_reinstall = False
2025-07-03 17:12:25 +02:00
FRAMEWORK_DIR = path_cache.framework_dir
FRAMEWORK_LIB_DIR = path_cache.framework_lib_dir
2025-01-16 22:49:42 +01:00
SConscript("_embed_files.py", exports="env")
2025-07-03 17:12:25 +02:00
flag_any_custom_sdkconfig = exists(join(
platform.get_package_dir("framework-arduinoespressif32-libs"),
"sdkconfig"))
def has_unicore_flags():
"""Check if any UNICORE flags are present in configuration"""
return any(flag in extra_flags or flag in entry_custom_sdkconfig
or flag in board_sdkconfig for flag in UNICORE_FLAGS)
2024-12-14 12:42:42 +01:00
2025-07-03 17:12:25 +02:00
# Esp32-solo1 libs settings
if flag_custom_sdkconfig and has_unicore_flags():
if not env.get('BUILD_UNFLAGS'): # Initialize if not set
env['BUILD_UNFLAGS'] = []
build_unflags = (" ".join(env['BUILD_UNFLAGS']) +
" -mdisable-hardware-atomics -ustart_app_other_cores")
2024-12-14 12:42:42 +01:00
new_build_unflags = build_unflags.split()
2025-07-03 17:12:25 +02:00
env.Replace(BUILD_UNFLAGS=new_build_unflags)
def get_packages_to_install(deps, installed_packages):
"""Generator for packages to install"""
for package, spec in deps.items():
if package not in installed_packages:
yield package
else:
version_spec = semantic_version.Spec(spec)
if not version_spec.match(installed_packages[package]):
yield package
2024-12-14 12:42:42 +01:00
def install_python_deps():
def _get_installed_pip_packages():
result = {}
try:
2025-07-03 17:12:25 +02:00
pip_output = subprocess.check_output([
env.subst("$PYTHONEXE"),
"-m", "pip", "list", "--format=json",
"--disable-pip-version-check"
])
2024-12-14 12:42:42 +01:00
packages = json.loads(pip_output)
2025-07-03 17:12:25 +02:00
for p in packages:
result[p["name"]] = pepver_to_semver(p["version"])
except Exception:
print("Warning! Couldn't extract the list of installed Python "
"packages.")
2024-12-14 12:42:42 +01:00
return result
installed_packages = _get_installed_pip_packages()
2025-07-03 17:12:25 +02:00
packages_to_install = list(get_packages_to_install(python_deps,
installed_packages))
2024-12-14 12:42:42 +01:00
if packages_to_install:
2025-07-03 17:12:25 +02:00
packages_str = " ".join(f'"{p}{python_deps[p]}"'
for p in packages_to_install)
2024-12-14 12:42:42 +01:00
env.Execute(
env.VerboseAction(
2025-07-03 17:12:25 +02:00
f'"$PYTHONEXE" -m pip install -U -q -q -q {packages_str}',
2024-12-14 12:42:42 +01:00
"Installing Arduino Python dependencies",
)
)
2025-07-03 17:12:25 +02:00
2024-12-14 12:42:42 +01:00
install_python_deps()
2025-07-03 17:12:25 +02:00
2024-12-14 12:42:42 +01:00
def get_MD5_hash(phrase):
2025-07-03 17:12:25 +02:00
return hashlib.md5(phrase.encode('utf-8')).hexdigest()[:16]
2024-12-14 12:42:42 +01:00
def matching_custom_sdkconfig():
2025-07-03 17:12:25 +02:00
"""Checks if current environment matches existing sdkconfig"""
2024-12-14 12:42:42 +01:00
cust_sdk_is_present = False
2025-07-03 17:12:25 +02:00
if not flag_any_custom_sdkconfig:
return True, cust_sdk_is_present
last_sdkconfig_path = join(project_dir, "sdkconfig.defaults")
if not exists(last_sdkconfig_path):
return False, cust_sdk_is_present
if not flag_custom_sdkconfig:
return False, cust_sdk_is_present
try:
with open(last_sdkconfig_path) as src:
line = src.readline()
if line.startswith("# TASMOTA__"):
cust_sdk_is_present = True
custom_options = entry_custom_sdkconfig
expected_hash = get_MD5_hash(custom_options.strip() + mcu)
if line.split("__")[1].strip() == expected_hash:
return True, cust_sdk_is_present
except (IOError, IndexError):
pass
return False, cust_sdk_is_present
2024-12-14 12:42:42 +01:00
def check_reinstall_frwrk():
2025-07-03 17:12:25 +02:00
if not flag_custom_sdkconfig and flag_any_custom_sdkconfig:
# case custom sdkconfig exists and an env without "custom_sdkconfig"
return True
if flag_custom_sdkconfig:
matching_sdkconfig, _ = matching_custom_sdkconfig()
if not matching_sdkconfig:
# check if current custom sdkconfig is different from existing
return True
return False
2025-01-16 22:49:42 +01:00
2025-07-03 17:12:25 +02:00
def call_compile_libs():
print(f"*** Compile Arduino IDF libs for {pioenv} ***")
SConscript("espidf.py")
FRAMEWORK_SDK_DIR = path_cache.sdk_dir
2025-01-16 22:49:42 +01:00
IS_INTEGRATION_DUMP = env.IsIntegrationDump()
def is_framework_subfolder(potential_subfolder):
2025-07-03 17:12:25 +02:00
"""Check if a path is a subfolder of the framework SDK directory"""
# carefully check before change this function
if not isabs(potential_subfolder):
2025-01-16 22:49:42 +01:00
return False
2025-07-03 17:12:25 +02:00
if (splitdrive(FRAMEWORK_SDK_DIR)[0] !=
splitdrive(potential_subfolder)[0]):
2025-01-16 22:49:42 +01:00
return False
2025-07-03 17:12:25 +02:00
return (commonpath([FRAMEWORK_SDK_DIR]) ==
commonpath([FRAMEWORK_SDK_DIR, potential_subfolder]))
# Performance optimization with caching
def calculate_include_path_length(includes):
"""Calculate total character count of all include paths with caching"""
if not hasattr(calculate_include_path_length, '_cache'):
calculate_include_path_length._cache = {}
cache_key = tuple(includes)
if cache_key not in calculate_include_path_length._cache:
calculate_include_path_length._cache[cache_key] = sum(
len(str(inc)) for inc in includes)
return calculate_include_path_length._cache[cache_key]
def analyze_path_distribution(includes):
"""Analyze the distribution of include path lengths for optimization
insights"""
if not includes:
return {}
lengths = [len(str(inc)) for inc in includes]
framework_lengths = [len(str(inc)) for inc in includes
if is_framework_subfolder(inc)]
return {
'total_paths': len(includes),
'total_length': sum(lengths),
'average_length': sum(lengths) / len(lengths),
'max_length': max(lengths),
'min_length': min(lengths),
'framework_paths': len(framework_lengths),
'framework_total_length': sum(framework_lengths),
'framework_avg_length': (sum(framework_lengths) /
len(framework_lengths)
if framework_lengths else 0)
}
2025-01-16 22:49:42 +01:00
2025-07-03 17:12:25 +02:00
def debug_framework_paths(env, include_count, total_length):
"""Debug framework paths to understand the issue (verbose mode only)"""
if not env.get("VERBOSE"):
return
print("*** Debug Framework Paths ***")
print(f"*** MCU: {mcu} ***")
print(f"*** FRAMEWORK_DIR: {FRAMEWORK_DIR} ***")
print(f"*** FRAMEWORK_SDK_DIR: {FRAMEWORK_SDK_DIR} ***")
print(f"*** SDK exists: {exists(FRAMEWORK_SDK_DIR)} ***")
print(f"*** Include count: {include_count} ***")
print(f"*** Total path length: {total_length} ***")
includes = env.get("CPPPATH", [])
framework_count = 0
longest_paths = sorted(includes, key=len, reverse=True)[:5]
print("*** Longest include paths: ***")
for i, inc in enumerate(longest_paths):
is_fw = is_framework_subfolder(inc)
if is_fw:
framework_count += 1
print(f"*** {i+1}: {inc} (length: {len(str(inc))}) -> "
f"Framework: {is_fw} ***")
print(f"*** Framework includes found: {framework_count}/"
f"{len(includes)} ***")
# Show path distribution analysis
analysis = analyze_path_distribution(includes)
print(f"*** Path Analysis: Avg={analysis.get('average_length', 0):.1f}, "
f"Max={analysis.get('max_length', 0)}, "
f"Framework Avg={analysis.get('framework_avg_length', 0):.1f} ***")
def apply_include_shortening(env, node, includes, total_length):
"""Applies include path shortening technique"""
env_get = env.get
to_unix_path = fs.to_unix_path
ccflags = env["CCFLAGS"]
asflags = env["ASFLAGS"]
includes = [to_unix_path(inc) for inc in env_get("CPPPATH", [])]
2025-01-16 22:49:42 +01:00
shortened_includes = []
generic_includes = []
2025-07-03 17:12:25 +02:00
original_length = total_length
saved_chars = 0
2025-01-16 22:49:42 +01:00
for inc in includes:
if is_framework_subfolder(inc):
2025-07-03 17:12:25 +02:00
relative_path = to_unix_path(relpath(inc, FRAMEWORK_SDK_DIR))
shortened_path = "-iwithprefix/" + relative_path
shortened_includes.append(shortened_path)
# Calculate character savings
# Original: full path in -I flag
# New: -iprefix + shortened relative path
original_chars = len(f"-I{inc}")
new_chars = len(shortened_path)
saved_chars += max(0, original_chars - new_chars)
2025-01-16 22:49:42 +01:00
else:
generic_includes.append(inc)
2025-07-03 17:12:25 +02:00
# Show result message only once with thread safety
with _PATH_SHORTENING_LOCK:
if not _PATH_SHORTENING_MESSAGES['shortening_applied']:
if shortened_includes:
# Each -I is 2 chars
removed_i_flags = len(shortened_includes) * 2
new_total_length = (original_length - saved_chars +
len(f"-iprefix{FRAMEWORK_SDK_DIR}") -
removed_i_flags)
print(f"*** Applied include path shortening for "
f"{len(shortened_includes)} framework paths ***")
print(f"*** Path length reduced from {original_length} to "
f"~{new_total_length} characters ***")
print(f"*** Estimated savings: {saved_chars} characters ***")
else:
if not _PATH_SHORTENING_MESSAGES[
'no_framework_paths_warning']:
print("*** Warning: Path length high but no framework "
"paths found for shortening ***")
print("*** This may indicate an architecture-specific "
"issue ***")
print("*** Run with -v (verbose) for detailed path "
"analysis ***")
_PATH_SHORTENING_MESSAGES[
'no_framework_paths_warning'] = True
_PATH_SHORTENING_MESSAGES['shortening_applied'] = True
common_flags = ["-iprefix", FRAMEWORK_SDK_DIR] + shortened_includes
2025-01-16 22:49:42 +01:00
return env.Object(
node,
CPPPATH=generic_includes,
2025-07-03 17:12:25 +02:00
CCFLAGS=ccflags + common_flags,
ASFLAGS=asflags + common_flags,
2025-01-16 22:49:42 +01:00
)
2024-12-14 12:42:42 +01:00
2025-07-03 17:12:25 +02:00
def smart_include_length_shorten(env, node):
"""
Include path shortening based on max. performance configurable threshold
with enhanced MCU support
Uses aggressive thresholds for maximum performance
"""
if IS_INTEGRATION_DUMP:
return node
if not IS_WINDOWS:
return env.Object(node)
# Get dynamically configurable bleeding edge threshold
include_path_threshold = get_include_path_threshold(env, config,
current_env_section)
check_and_warn_long_path_support()
includes = env.get("CPPPATH", [])
include_count = len(includes)
total_path_length = calculate_include_path_length(includes)
# Debug information in verbose mode
if env.get("VERBOSE"):
debug_framework_paths(env, include_count, total_path_length)
# Extended debug information about maximum edge threshold
# configuration
threshold_info = get_threshold_info(env, config, current_env_section)
print("*** Maximum Threshold Configuration Debug ***")
print(f"*** MCU: {threshold_info['mcu']} ***")
print(f"*** Maximum Platform Default: "
f"{threshold_info['platform_default']} ***")
print(f"*** Final maximum Threshold: "
f"{threshold_info['final_threshold']} ***")
print(f"*** Source: {threshold_info['source']} ***")
print("*** Performance Mode: Maximum Aggressive ***")
if threshold_info['env_variable']:
print(f"*** Env Variable: {threshold_info['env_variable']} ***")
if threshold_info['env_specific']:
print(f"*** Env Specific: {threshold_info['env_specific']} ***")
if threshold_info['global_env']:
print(f"*** Global Env: {threshold_info['global_env']} ***")
if threshold_info['platformio_section']:
print(f"*** PlatformIO Section: "
f"{threshold_info['platformio_section']} ***")
# Use the configurable and validated bleeding edge threshold
if total_path_length <= include_path_threshold:
return env.Object(node)
return apply_include_shortening(env, node, includes, total_path_length)
def get_frameworks_in_current_env():
"""Determines the frameworks of the current environment"""
if "framework" in config.options(current_env_section):
return config.get(current_env_section, "framework", "")
return []
# Framework check
current_env_frameworks = get_frameworks_in_current_env()
if "arduino" in current_env_frameworks and "espidf" in current_env_frameworks:
# Arduino as component is set, switch off Hybrid compile
flag_custom_sdkconfig = False
# Framework reinstallation if required - Enhanced with secure deletion and
# error handling
if check_reinstall_frwrk():
# Secure removal of SDKConfig files
safe_remove_sdkconfig_files()
2025-02-28 18:38:21 +01:00
print("*** Reinstall Arduino framework ***")
2025-07-03 17:12:25 +02:00
# Secure framework cleanup with enhanced error handling
if safe_framework_cleanup():
arduino_frmwrk_url = str(platform.get_package_spec(
"framework-arduinoespressif32")).split("uri=", 1)[1][:-1]
arduino_frmwrk_lib_url = str(platform.get_package_spec(
"framework-arduinoespressif32-libs")).split("uri=", 1)[1][:-1]
pm.install(arduino_frmwrk_url)
pm.install(arduino_frmwrk_lib_url)
if flag_custom_sdkconfig:
call_compile_libs()
flag_custom_sdkconfig = False
else:
print("Framework cleanup failed - installation aborted")
sys.exit(1)
if flag_custom_sdkconfig and not flag_any_custom_sdkconfig:
2024-12-14 12:42:42 +01:00
call_compile_libs()
2025-07-03 17:12:25 +02:00
# Main logic for Arduino Framework
pioframework = env.subst("$PIOFRAMEWORK")
arduino_lib_compile_flag = env.subst("$ARDUINO_LIB_COMPILE_FLAG")
if ("arduino" in pioframework and "espidf" not in pioframework and
arduino_lib_compile_flag in ("Inactive", "True")):
# try to remove not needed include path if an lib_ignore entry exists
from component_manager import ComponentManager
component_manager = ComponentManager(env)
component_manager.handle_component_settings()
silent_action = env.Action(component_manager.restore_pioarduino_build_py)
# hack to silence scons command output
silent_action.strfunction = lambda target, source, env: ''
env.AddPostAction("checkprogsize", silent_action)
2025-01-18 15:47:53 +01:00
if IS_WINDOWS:
2025-07-03 17:12:25 +02:00
# Smart include path optimization based on bleeding edge configurable
# threshold
env.AddBuildMiddleware(smart_include_length_shorten)
build_script_path = join(FRAMEWORK_DIR, "tools", "pioarduino-build.py")
SConscript(build_script_path)