From bbc82a164061c0166438e4aae1bd7eaec5f2a798 Mon Sep 17 00:00:00 2001 From: Antoine Cellerier Date: Tue, 24 Mar 2026 15:52:12 +0100 Subject: [PATCH] Include file contents in custom_sdkconfig hash computation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When custom_sdkconfig uses a file:// reference (e.g. custom_sdkconfig = file://sdkconfig.custom), the hash used to detect configuration changes is computed from the option text ("file://sdkconfig.custom") rather than the file's actual contents. Editing the referenced file doesn't change the hash, so matching_custom_sdkconfig() returns True and stale recompiled libs are silently reused — even if the file now disables a major feature like Bluetooth. Fix: resolve file:// references before hash computation in both arduino.py (where the hash is checked) and espidf.py (where it is written). Both sides now hash the file contents + inline options + MCU name, so editing the file triggers lib recompilation via the existing Reinstall path (which also runs safe_remove_sdkconfig_files to clean stale per-env sdkconfigs). Inline custom_sdkconfig values (without file://) are unaffected. Tested on dfrobot_firebeetle2_esp32e with file://sdkconfig.custom: Before fix (stock pioarduino): Build 1: custom_sdkconfig = file://sdkconfig.custom (CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=4) → lib recompile, hash=3d58653b, RAM=45104, BTDM_RESERVE_DRAM=0xdb5c (55KB BT reservation) Edit: add "# CONFIG_BT_ENABLED is not set" to file Build 2: NO recompile (7s cached), hash unchanged, BT still reserved — file change silently ignored After fix: Build 1: same → lib recompile, hash=5c9e7d85, RAM=45064 Edit: add "# CONFIG_BT_ENABLED is not set" to file Build 2: hash mismatch detected → Reinstall + recompile, "Replace: CONFIG_BT_ENABLED=y with: # not set", BTDM_RESERVE_DRAM=0, RAM=44160 (-944 bytes) Also guards PathCache.sdk_dir against framework_lib_dir being None (crashes with TypeError on Python 3.13 when packages are missing). Co-Authored-By: Claude Opus 4.6 (1M context) --- builder/frameworks/arduino.py | 19 +++++++++++++++++++ builder/frameworks/espidf.py | 13 ++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/builder/frameworks/arduino.py b/builder/frameworks/arduino.py index 740403666..d98dd2c16 100644 --- a/builder/frameworks/arduino.py +++ b/builder/frameworks/arduino.py @@ -75,6 +75,8 @@ def framework_lib_dir(self): @property def sdk_dir(self): if self._sdk_dir is None: + if self.framework_lib_dir is None: + return None self._sdk_dir = fs.to_unix_path( str(Path(self.framework_lib_dir) / self.chip_variant / "include") ) @@ -277,6 +279,23 @@ def safe_remove_sdkconfig_files(): # Custom SDKConfig check if config.has_option(current_env_section, "custom_sdkconfig"): entry_custom_sdkconfig = env.GetProjectOption("custom_sdkconfig") + # When custom_sdkconfig references a file, include its contents in the + # value used for hash computation. Otherwise, editing the file doesn't + # change the hash and stale libs are silently reused. + # Combine file content with inline options (both are applied by the build). + for line in entry_custom_sdkconfig.splitlines(): + line = line.strip() + if line.startswith("file://"): + file_ref = line[7:] + file_path = file_ref if isabs(file_ref) else join(project_dir, file_ref) + if exists(file_path): + try: + with open(file_path, 'r') as f: + file_content = f.read() + entry_custom_sdkconfig = file_content + "\n" + entry_custom_sdkconfig + except IOError: + pass + break flag_custom_sdkconfig = True if board_sdkconfig: diff --git a/builder/frameworks/espidf.py b/builder/frameworks/espidf.py index c9049b406..a7d9f7257 100644 --- a/builder/frameworks/espidf.py +++ b/builder/frameworks/espidf.py @@ -779,11 +779,18 @@ def write_sdkconfig_file(idf_config_flags, checksum_source): # Convert to list for processing idf_config_list = [line for line in idf_config_flags.splitlines() if line.strip()] - # Write final configuration file with checksum + # Write final configuration file with checksum. + # Include resolved file content (not just the raw "file://..." reference) + # so that editing the referenced file changes the hash and triggers + # recompilation. Combine file content + inline options to match what + # build_idf_config_flags() produces. custom_sdk_config_flags = "" if config.has_option("env:" + env["PIOENV"], "custom_sdkconfig"): - custom_sdk_config_flags = env.GetProjectOption("custom_sdkconfig").rstrip("\n") + "\n" - + raw = env.GetProjectOption("custom_sdkconfig") + resolved = load_custom_sdkconfig_file() + # Combine resolved file content with raw inline options (both are applied) + custom_sdk_config_flags = ((resolved + "\n") if resolved else "") + raw.rstrip("\n") + "\n" + write_sdkconfig_file(idf_config_list, custom_sdk_config_flags)