"""This script updates the sdkconfigs based on the menuconfig results in a given build.""" import pathlib import click import copy import kconfiglib import os OPT_SETTINGS = [ "CONFIG_ESP_ERR_TO_NAME_LOOKUP", "CONFIG_ESP_CONSOLE_", "CONFIG_CONSOLE_UART_", "CONFIG_ESP_SYSTEM_PANIC_", "CONFIG_ESP32S2_PANIC_", "COMPILER_OPTIMIZATION_", "CONFIG_ESP32S3_DEBUG_OCDAWARE", "CONFIG_FREERTOS_ASSERT_", "CONFIG_FREERTOS_DEBUG_OCDAWARE", "CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER", "CONFIG_HAL_ASSERTION_", "CONFIG_LWIP_ESP_LWIP_ASSERT", "CONFIG_OPTIMIZATION_ASSERTION_LEVEL", "CONFIG_OPTIMIZATION_ASSERTIONS_", "CONFIG_HAL_DEFAULT_ASSERTION_LEVEL", "CONFIG_BOOTLOADER_LOG_LEVEL", "LOG_DEFAULT_LEVEL", ] TARGET_SETTINGS = [ "CONFIG_IDF_TARGET", "CONFIG_IDF_FIRMWARE_CHIP_ID", "CONFIG_BOOTLOADER_OFFSET_IN_FLASH", "CONFIG_ESP_SLEEP_POWER_DOWN_FLASH", "CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE", "CONFIG_ESP_SYSTEM_MEMPROT_", "CONFIG_ESP_MAIN_TASK_AFFINITY", "CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE", "CONFIG_FREERTOS_UNICORE", "CONFIG_FREERTOS_TICK_SUPPORT_CORETIMER", "CONFIG_FREERTOS_CORETIMER_0", "CONFIG_FREERTOS_CORETIMER_1", "CONFIG_FREERTOS_SYSTICK_USES_CCOUNT", "CONFIG_FREERTOS_OPTIMIZED_SCHEDULER", "CONFIG_MBEDTLS_HARDWARE_GCM", "CONFIG_ESP_SYSTEM_PD_FLASH", "CONFIG_EXTERNAL_COEX_ENABLE", "CONFIG_SDK_TOOLPREFIX", "CONFIG_TOOLPREFIX", "ESP_SLEEP_GPIO_RESET_WORKAROUND", "CONFIG_ESP_PHY_ENABLE_USB", "CONFIG_BT_SOC_SUPPORT_5_0", "CONFIG_NIMBLE_PINNED_TO_CORE", "CONFIG_BT_NIMBLE_PINNED_TO_CORE", "CONFIG_BT_CTRL_PINNED_TO_CORE", "CONFIG_SPIRAM_SPEED_2", "CONFIG_SPIRAM_BANKSWITCH_ENABLE", # For ESP32 ] BOARD_SETTINGS = [ "CONFIG_LWIP_LOCAL_HOSTNAME", ] FLASH_SIZE_SETTINGS = [ "CONFIG_ESPTOOLPY_FLASHSIZE", "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "CONFIG_PARTITION_TABLE_FILENAME", ] FLASH_MODE_SETTINGS = [ "CONFIG_ESPTOOLPY_FLASHMODE_", "CONFIG_ESPTOOLPY_OCT_FLASH", "CONFIG_ESPTOOLPY_FLASH_SAMBLE_MODE_", ] FLASH_FREQ_SETTINGS = [ "CONFIG_ESPTOOLPY_FLASHFREQ_", ] PSRAM_SETTINGS = ["CONFIG_SPIRAM"] PSRAM_SIZE_SETTINGS = ["CONFIG_SPIRAM_TYPE_"] PSRAM_MODE_SETTINGS = ["CONFIG_SPIRAM_MODE_"] PSRAM_FREQ_SETTINGS = ["CONFIG_SPIRAM_SPEED_"] # Some settings are target dependent but we want to always include them anyway # because the files they are in will be used across targets. ALWAYS_INCLUDE = FLASH_MODE_SETTINGS + FLASH_FREQ_SETTINGS + PSRAM_FREQ_SETTINGS BLE_SETTINGS = ["CONFIG_BT_", "CONFIG_BLUEDROID_", "CONFIG_NIMBLE_", "CONFIG_SW_COEXIST_ENABLE"] # boards/lilygo_ttgo_t8_s2_st7789/sdkconfig # CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y # boards/morpheans_morphesp-240/sdkconfig # CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y def matches_group(line, group): for setting in group: if setting in line: return True return False def add_group(lines, last_group, current_group): if not current_group or last_group != current_group: while last_group and last_group[-1] not in current_group: lines.append("# end of " + last_group[-1]) lines.append("") last_group.pop() for category in current_group: if last_group and category in last_group: continue lines.append("#") lines.append("# " + category) lines.append("#") return copy.copy(current_group) return last_group def sym_default(sym): # Skip symbols that cannot be changed. Only check # non-choice symbols, as selects don't affect choice # symbols. if not sym.choice and sym.visibility <= kconfiglib.expr_value(sym.rev_dep): return True # Skip symbols whose value matches their default if sym.str_value == sym._str_default(): return True # Skip symbols that would be selected by default in a # choice, unless the choice is optional or the symbol type # isn't bool (it might be possible to set the choice mode # to n or the symbol to m in those cases). if ( sym.choice and not sym.choice.is_optional and sym.choice._selection_from_defaults() is sym and sym.orig_type is kconfiglib.BOOL and sym.tri_value == 2 ): return True return False @click.command() @click.option("--debug") @click.option("--board") @click.option( "--update_all", is_flag=True, default=False, help="Updates the sdkconfigs outside of the board directory.", ) def update(debug, board, update_all): """Updates related sdkconfig files based on the build directory version that was likely modified by menuconfig.""" board_make = pathlib.Path(f"boards/{board}/mpconfigboard.mk") psram_size = "0" uf2_bootloader = None for line in board_make.read_text().split("\n"): if "=" not in line or line.startswith("#"): continue key, value = line.split("=", maxsplit=1) key = key.strip() value = value.strip() if key == "IDF_TARGET": target = value if uf2_bootloader is None: uf2_bootloader = target not in ("esp32", "esp32c3") elif key == "CIRCUITPY_ESP_FLASH_SIZE": flash_size = value elif key == "CIRCUITPY_ESP_FLASH_MODE": flash_mode = value elif key == "CIRCUITPY_ESP_FLASH_FREQ": flash_freq = value elif key == "CIRCUITPY_ESP_PSRAM_SIZE": psram_size = value elif key == "CIRCUITPY_ESP_PSRAM_MODE": psram_mode = value elif key == "CIRCUITPY_ESP_PSRAM_FREQ": psram_freq = value elif key == "UF2_BOOTLOADER": uf2_bootloader = not (value == "0") os.environ["IDF_TARGET"] = target os.environ[ "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE" ] = f"build-{board}/esp-idf/kconfigs_projbuild.in" os.environ["COMPONENT_KCONFIGS_SOURCE_FILE"] = f"build-{board}/esp-idf/kconfigs.in" kconfig_path = pathlib.Path(f"build-{board}/esp-idf/kconfigs.in") kconfig_path = pathlib.Path(f"esp-idf/Kconfig") kconfig = kconfiglib.Kconfig(kconfig_path) input_config = pathlib.Path(f"build-{board}/esp-idf/sdkconfig") kconfig.load_config(input_config) sdkconfigs = [] default_config = pathlib.Path("esp-idf-config/sdkconfig.defaults") sdkconfigs.append(default_config) if debug: opt_config = pathlib.Path("esp-idf-config/sdkconfig-debug.defaults") else: opt_config = pathlib.Path("esp-idf-config/sdkconfig-opt.defaults") sdkconfigs.append(opt_config) size_options = "" if flash_size == "2MB": size_options = "-no-ota-no-uf2" elif not uf2_bootloader: # These boards don't have native USB. size_options = "-no-uf2" flash_size_config = pathlib.Path( f"esp-idf-config/sdkconfig-flash-{flash_size}{size_options}.defaults" ) flash_mode_config = pathlib.Path(f"esp-idf-config/sdkconfig-flash-{flash_mode}.defaults") flash_freq_config = pathlib.Path(f"esp-idf-config/sdkconfig-flash-{flash_freq}.defaults") sdkconfigs.extend((flash_size_config, flash_mode_config, flash_freq_config)) if psram_size != "0": psram_config = pathlib.Path(f"esp-idf-config/sdkconfig-psram.defaults") psram_size_config = pathlib.Path(f"esp-idf-config/sdkconfig-psram-{psram_size}.defaults") psram_mode_config = pathlib.Path(f"esp-idf-config/sdkconfig-psram-{psram_mode}.defaults") psram_freq_config = pathlib.Path(f"esp-idf-config/sdkconfig-psram-{psram_freq}.defaults") sdkconfigs.extend((psram_config, psram_size_config, psram_mode_config, psram_freq_config)) target_config = pathlib.Path(f"esp-idf-config/sdkconfig-{target}.defaults") sdkconfigs.append(target_config) ble_config = pathlib.Path(f"esp-idf-config/sdkconfig-ble.defaults") sdkconfigs.append(ble_config) board_config = pathlib.Path(f"boards/{board}/sdkconfig") # Don't include the board file in cp defaults. The board may have custom # overrides. cp_kconfig_defaults = kconfiglib.Kconfig(kconfig_path) for default_file in sdkconfigs: cp_kconfig_defaults.load_config(default_file, replace=False) board_settings = [] last_board_group = None flash_size_settings = [] last_flash_size_group = None flash_mode_settings = [] flash_freq_settings = [] psram_settings = [] last_psram_group = None psram_size_settings = [] psram_mode_settings = [] psram_freq_settings = [] opt_settings = [] last_opt_group = None target_settings = [] last_target_group = None ble_settings = [] last_ble_group = None default_settings = [] last_default_group = None target_kconfig_snippets = set() target_symbols = set() current_group = [] for sym in kconfig.unique_defined_syms: sym._visited = False # This merges the normal `write_config`, `write_min_config` and CP settings to split into # different files. pending_nodes = [kconfig.top_node] i = 0 while pending_nodes: node = pending_nodes.pop() if node is None: current_group.pop() continue if node.item is kconfiglib.MENU: if node.prompt: print(" " * len(current_group), i, node.prompt[0]) i += 1 if node.next: pending_nodes.append(node.next) # if i > 300: # break # We have a configuration item. item = node.item if isinstance(item, kconfiglib.Symbol): if item._visited: continue item._visited = True config_string = item.config_string.strip() if not config_string: cp_sym = cp_kconfig_defaults.syms[item.name] if cp_sym.str_value == "n": config_string = f"# CONFIG_{item.name} is not set" else: continue if node.list: pending_nodes.append(node.list) matches_cp_default = cp_kconfig_defaults.syms[item.name].str_value == item.str_value matches_esp_default = sym_default(item) print_debug = not matches_esp_default or (not update_all and not matches_cp_default) if print_debug: print(" " * len(current_group), i, config_string.strip()) # Some files are `rsource`d into another kconfig with $IDF_TARGET as # part of the path. kconfiglib doesn't show this as a reference so # we have to look ourselves. target_reference = target in item.name_and_loc if target_reference: loc = item.name_and_loc.split("defined at ")[1].split(":")[0].replace(target, "*") if loc not in target_kconfig_snippets: differing_keys = set() shared_keys = {} first = True for path in pathlib.Path(".").glob(loc): kc = kconfiglib.Kconfig(path) all_file_syms = set() for sym in kc.unique_defined_syms: all_file_syms.add(sym) if sym.name in differing_keys: continue if first: shared_keys[sym.name] = sym.str_value elif ( sym.name not in shared_keys or shared_keys[sym.name] != sym.str_value ): differing_keys.add(sym.name) if sym.name in shared_keys: del shared_keys[sym.name] # Any settings missing from a file are *not* shared. shared_syms = set(shared_keys.keys()) for missing in shared_syms - all_file_syms: differing_keys.add(missing) del shared_keys[missing] first = False target_kconfig_snippets.add(loc) target_symbols = target_symbols.union(differing_keys) psram_reference = False for referenced in item.referenced: if referenced.name.startswith("IDF_TARGET"): target_reference = True break if referenced.name in target_symbols: # Implicit target symbols target_reference = True break if referenced.name == "SPIRAM": psram_reference = True if (not update_all and not matches_cp_default) or ( update_all and matches_group(config_string, BOARD_SETTINGS) and not matches_esp_default ): print(" " * (len(current_group) + 1), "board") last_board_group = add_group(board_settings, last_board_group, current_group) board_settings.append(config_string) elif update_all: target_setting = target_reference or matches_group(config_string, TARGET_SETTINGS) if matches_group(config_string, FLASH_SIZE_SETTINGS): print(" " * (len(current_group) + 1), "flash size") last_flash_size_group = add_group( flash_size_settings, last_flash_size_group, current_group ) flash_size_settings.append(config_string) elif matches_group(config_string, FLASH_MODE_SETTINGS): print(" " * (len(current_group) + 1), "flash mode") flash_mode_settings.append(config_string) elif matches_group(config_string, FLASH_FREQ_SETTINGS): print(" " * (len(current_group) + 1), "flash freq") flash_freq_settings.append(config_string) elif matches_group(config_string, PSRAM_SIZE_SETTINGS): print(" " * (len(current_group) + 1), "psram size") psram_size_settings.append(config_string) elif matches_group(config_string, PSRAM_MODE_SETTINGS): print(" " * (len(current_group) + 1), "psram mode") psram_mode_settings.append(config_string) elif ( matches_group(config_string, PSRAM_FREQ_SETTINGS) and "26M" not in config_string and "20M" not in config_string ): # The ESP32S2 has two frequencies (20M and 26M) that aren't on the S3 or ESP32. # So, put those in target settings. print(" " * (len(current_group) + 1), "psram freq") psram_freq_settings.append(config_string) elif matches_esp_default: if print_debug: print(" " * (len(current_group) + 1), "default") # Always document the above settings. Settings below should # be non-default. pass elif matches_group(config_string, PSRAM_SETTINGS) or psram_reference: print(" " * (len(current_group) + 1), "psram shared") last_psram_group = add_group(psram_settings, last_psram_group, current_group) psram_settings.append(config_string) elif matches_group(config_string, OPT_SETTINGS): print(" " * (len(current_group) + 1), "opt") last_opt_group = add_group(opt_settings, last_opt_group, current_group) opt_settings.append(config_string) elif target_setting: print(" " * (len(current_group) + 1), "target") last_target_group = add_group( target_settings, last_target_group, current_group ) target_settings.append(config_string) elif matches_group(config_string, BLE_SETTINGS): print(" " * (len(current_group) + 1), "ble") last_ble_group = add_group(ble_settings, last_ble_group, current_group) ble_settings.append(config_string) else: print(" " * (len(current_group) + 1), "all") last_default_group = add_group( default_settings, last_default_group, current_group ) default_settings.append(config_string) else: if item is kconfiglib.COMMENT: print("comment", repr(item)) elif item is kconfiglib.MENU: if node.list: current_group.append(node.prompt[0]) pending_nodes.append(None) pending_nodes.append(node.list) elif isinstance(item, kconfiglib.Choice): # Choices are made up of individual symbols that we need to check. pending_nodes.append(node.list) else: print("unknown", repr(item)) add_group(board_settings, last_board_group, current_group) add_group(opt_settings, last_opt_group, current_group) add_group(flash_size_settings, last_flash_size_group, current_group) add_group(psram_settings, last_psram_group, current_group) add_group(target_settings, last_target_group, current_group) add_group(ble_settings, last_ble_group, current_group) add_group(default_settings, last_default_group, current_group) board_config.write_text("\n".join(board_settings)) if update_all: # Add empty strings to get trailing newlines flash_mode_settings.append("") flash_freq_settings.append("") flash_size_config.write_text("\n".join(flash_size_settings)) flash_mode_config.write_text("\n".join(flash_mode_settings)) flash_freq_config.write_text("\n".join(flash_freq_settings)) if psram_size != "0": psram_size_settings.append("") psram_mode_settings.append("") psram_freq_settings.append("") psram_config.write_text("\n".join(psram_settings)) psram_size_config.write_text("\n".join(psram_size_settings)) psram_mode_config.write_text("\n".join(psram_mode_settings)) psram_freq_config.write_text("\n".join(psram_freq_settings)) opt_config.write_text("\n".join(opt_settings)) default_config.write_text("\n".join(default_settings)) target_config.write_text("\n".join(target_settings)) if ble_settings: ble_config.write_text("\n".join(ble_settings)) if __name__ == "__main__": update()