diff --git a/tools/cmake/kconfig.cmake b/tools/cmake/kconfig.cmake index 2839275a8a2..892e981f6a3 100644 --- a/tools/cmake/kconfig.cmake +++ b/tools/cmake/kconfig.cmake @@ -1,5 +1,13 @@ include(ExternalProject) +# Minimum esp-idf-kconfig version that ships the esp_menuconfig module name +# (renamed from menuconfig in the 3.x series). +set(ESP_MENUCONFIG_MIN_KCONFIG_VERSION "3.1.0") + +# Minimum esp-idf-kconfig version required for the --menuconfig kconfgen flag +# (fused menuconfig + deprecated-options post-processing in a single invocation). +set(MENUCONFIG_INLINE_MIN_KCONFIG_VERSION "3.9.0") + function(__kconfig_init) idf_build_get_property(idf_path IDF_PATH) @@ -266,19 +274,16 @@ function(__kconfig_generate_config sdkconfig sdkconfig_defaults) idf_build_set_property(SDKCONFIG_JSON_MENUS ${sdkconfig_json_menus}) idf_build_set_property(CONFIG_DIR ${config_dir}) - # newer versions of esp-idf-kconfig renamed menuconfig to esp_menuconfig - # Order matters here, we want to use esp_menuconfig if it is available - execute_process( - COMMAND ${python} -c "import esp_menuconfig" - RESULT_VARIABLE ESP_MENUCONFIG_AVAILABLE - OUTPUT_QUIET ERROR_QUIET - ) - if(ESP_MENUCONFIG_AVAILABLE EQUAL 0) + __check_python_package_min_version( + ${python} esp-idf-kconfig "${ESP_MENUCONFIG_MIN_KCONFIG_VERSION}" _has_esp_menuconfig) + if(_has_esp_menuconfig) set(MENUCONFIG_CMD ${python} -m esp_menuconfig) else() set(MENUCONFIG_CMD ${python} -m menuconfig) endif() + __check_python_package_min_version( + ${python} esp-idf-kconfig "${MENUCONFIG_INLINE_MIN_KCONFIG_VERSION}" MENUCONFIG_INLINE_SUPPORTED) set(TERM_CHECK_CMD ${python} ${idf_path}/tools/check_term.py) @@ -286,44 +291,72 @@ function(__kconfig_generate_config sdkconfig sdkconfig_defaults) return() endif() - # Generate the menuconfig target - # WARNING: If you change anything here, please ensure that only the necessary files are touched! - # If unnecessary files (those not affected by the change from menuconfig) are touched - # (their timestamp changed), it will cause unnecessary rebuilds of the whole project! - add_custom_target(menuconfig - ${menuconfig_depends} - # create any missing config file, with defaults if necessary - COMMAND ${prepare_kconfig_files_command} - COMMAND ${kconfgen_basecommand} - --env "IDF_TARGET=${idf_target}" - --env "IDF_TOOLCHAIN=${idf_toolchain}" - --env "IDF_ENV_FPGA=${idf_env_fpga}" - --env "IDF_INIT_VERSION=${idf_init_version}" - --env "KCONFIG_REPORT_VERBOSITY=quiet" - --dont-write-deprecated - --output config ${sdkconfig} # Do NOT regenerate the rest of the config files! - COMMAND ${TERM_CHECK_CMD} - COMMAND ${CMAKE_COMMAND} -E env - "COMPONENT_KCONFIGS_SOURCE_FILE=${kconfigs_path}" - "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=${kconfigs_projbuild_path}" - "KCONFIG_CONFIG=${sdkconfig}" - "IDF_TARGET=${idf_target}" - "IDF_TOOLCHAIN=${idf_toolchain}" - "IDF_ENV_FPGA=${idf_env_fpga}" - "IDF_INIT_VERSION=${idf_init_version}" - "IDF_MINIMAL_BUILD=${idf_minimal_build}" - ${MENUCONFIG_CMD} ${root_kconfig} - USES_TERMINAL - # additional run of kconfgen ensures that the deprecated options will be inserted into config files - # (for backward compatibility) - COMMAND ${kconfgen_basecommand} - --env "IDF_TARGET=${idf_target}" - --env "IDF_TOOLCHAIN=${idf_toolchain}" - --env "IDF_ENV_FPGA=${idf_env_fpga}" - --env "IDF_INIT_VERSION=${idf_init_version}" - ${kconfgen_output_options} - --env "KCONFIG_REPORT_VERBOSITY=${kconfig_report_verbosity}" - ) + ################################################################################## + # WARNING: If you change the menuconfig target below, ensure that ONLY the + # necessary files are touched! The target writes sdkconfig, sdkconfig.h, + # sdkconfig.cmake, sdkconfig.json and kconfig_menus.json. Any stray timestamp + # change on files not affected by the user's config edit will cause unnecessary + # rebuilds of the whole project! + ################################################################################## + if(MENUCONFIG_INLINE_SUPPORTED) + add_custom_target(menuconfig + # Ensure kconfig.in and kconfig_projbuild.in are present and up to date. + # This is not necessary under normal circumstances, but if the files are manually removed, + # it may be possible to regenerate them. + COMMAND ${prepare_kconfig_files_command} + COMMAND ${TERM_CHECK_CMD} + COMMAND ${CMAKE_COMMAND} -E env + "COMPONENT_KCONFIGS_SOURCE_FILE=${kconfigs_path}" + "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=${kconfigs_projbuild_path}" + "KCONFIG_CONFIG=${sdkconfig}" + "IDF_TARGET=${idf_target}" + "IDF_TOOLCHAIN=${idf_toolchain}" + "IDF_ENV_FPGA=${idf_env_fpga}" + "IDF_INIT_VERSION=${idf_init_version}" + "IDF_MINIMAL_BUILD=${idf_minimal_build}" + "KCONFIG_REPORT_VERBOSITY=${kconfig_report_verbosity}" + ${kconfgen_basecommand} + --menuconfig + ${kconfgen_output_options} + USES_TERMINAL + ) + else() + message(WARNING "esp-idf-kconfig >= ${MENUCONFIG_INLINE_MIN_KCONFIG_VERSION} is required " + "for the optimised menuconfig target. Please update your Python packages by re-running the install script.") + add_custom_target(menuconfig + # Ensure kconfig.in and kconfig_projbuild.in are present and up to date. + # This is not necessary under normal circumstances, but if the files are manually removed, + # it may be possible to regenerate them. + COMMAND ${prepare_kconfig_files_command} + COMMAND ${kconfgen_basecommand} + --env "IDF_TARGET=${idf_target}" + --env "IDF_TOOLCHAIN=${idf_toolchain}" + --env "IDF_ENV_FPGA=${idf_env_fpga}" + --env "IDF_INIT_VERSION=${idf_init_version}" + --env "KCONFIG_REPORT_VERBOSITY=quiet" + --dont-write-deprecated + --output config ${sdkconfig} + COMMAND ${TERM_CHECK_CMD} + COMMAND ${CMAKE_COMMAND} -E env + "COMPONENT_KCONFIGS_SOURCE_FILE=${kconfigs_path}" + "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=${kconfigs_projbuild_path}" + "KCONFIG_CONFIG=${sdkconfig}" + "IDF_TARGET=${idf_target}" + "IDF_TOOLCHAIN=${idf_toolchain}" + "IDF_ENV_FPGA=${idf_env_fpga}" + "IDF_INIT_VERSION=${idf_init_version}" + "IDF_MINIMAL_BUILD=${idf_minimal_build}" + ${MENUCONFIG_CMD} ${root_kconfig} + USES_TERMINAL + COMMAND ${kconfgen_basecommand} + --env "IDF_TARGET=${idf_target}" + --env "IDF_TOOLCHAIN=${idf_toolchain}" + --env "IDF_ENV_FPGA=${idf_env_fpga}" + --env "IDF_INIT_VERSION=${idf_init_version}" + ${kconfgen_output_options} + --env "KCONFIG_REPORT_VERBOSITY=${kconfig_report_verbosity}" + ) + endif() # Custom target to generate configuration report to JSON add_custom_target(config-report diff --git a/tools/cmake/utilities.cmake b/tools/cmake/utilities.cmake index ed0e9002d98..4580c30e2a9 100644 --- a/tools/cmake/utilities.cmake +++ b/tools/cmake/utilities.cmake @@ -45,6 +45,32 @@ function(lines2list variable_name) endfunction() +# __check_python_package_min_version +# +# Check whether a Python package is installed with at least the given version. +# +# @param python_exe Python interpreter to use (e.g. ${python} or ${PYTHON}) +# @param package_name Pip package name (e.g. esp-idf-kconfig) +# @param min_version Minimum version required (e.g. 3.4.2) +# @param result_var Output variable name; set to TRUE if package version >= min_version, +# FALSE otherwise (package missing, older version, or pip unavailable) +# +function(__check_python_package_min_version python_exe package_name min_version result_var) + execute_process( + COMMAND ${python_exe} -c + "from importlib.metadata import version; print(version('${package_name}'))" + OUTPUT_VARIABLE _ver + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _rc + ERROR_QUIET + ) + set(${result_var} FALSE PARENT_SCOPE) + if(_rc EQUAL 0 AND _ver VERSION_GREATER_EQUAL "${min_version}") + set(${result_var} TRUE PARENT_SCOPE) + endif() +endfunction() + + # move_if_different # # If 'source' has different md5sum to 'destination' (or destination diff --git a/tools/cmakev2/kconfig.cmake b/tools/cmakev2/kconfig.cmake index 856e542c8e4..484ff9d106e 100644 --- a/tools/cmakev2/kconfig.cmake +++ b/tools/cmakev2/kconfig.cmake @@ -10,6 +10,14 @@ include(utilities) include(build) include(component) +# Minimum esp-idf-kconfig version that ships the esp_menuconfig module name +# (renamed from menuconfig in the 3.x series). +set(ESP_MENUCONFIG_MIN_KCONFIG_VERSION "3.1.0") + +# Minimum esp-idf-kconfig version required for the --menuconfig kconfgen flag +# (fused menuconfig + deprecated-options post-processing in a single invocation). +set(MENUCONFIG_INLINE_MIN_KCONFIG_VERSION "3.9.0") + #[[ __init_kconfig() @@ -785,6 +793,10 @@ endfunction() Create a menuconfig target with the name specified by the ``TARGET`` option for an ``executable``. + + When ``esp-idf-kconfig >= MENUCONFIG_INLINE_MIN_KCONFIG_VERSION``, the + target runs a single ``kconfgen --menuconfig`` invocation (fused UI and + post-processing). Older installs fall back to the legacy three-step flow. #]] function(idf_create_menuconfig executable) set(options) @@ -805,69 +817,101 @@ function(idf_create_menuconfig executable) idf_build_get_property(kconfgen_cmd __BASE_KCONFGEN_CMD) idf_build_get_property(sdkconfig SDKCONFIG) idf_build_get_property(root_kconfig __ROOT_KCONFIG) - idf_build_get_property(build_dir BUILD_DIR) idf_build_get_property(target IDF_TARGET) idf_build_get_property(toolchain IDF_TOOLCHAIN) idf_build_get_property(idf_init_version __IDF_INIT_VERSION) idf_build_get_property(idf_env_fpga __IDF_ENV_FPGA) idf_build_get_property(kconfgen_outputs_cmd __KCONFGEN_OUTPUTS_CMD) - # Newer versions of esp-idf-kconfig renamed menuconfig to esp_menuconfig - # Order matters here, we want to use esp_menuconfig if it is available - execute_process( - COMMAND "${python}" -c "import esp_menuconfig" - RESULT_VARIABLE ESP_MENUCONFIG_AVAILABLE - OUTPUT_QUIET ERROR_QUIET - ) - if(ESP_MENUCONFIG_AVAILABLE EQUAL 0) - set(MENUCONFIG_CMD "${python}" -m esp_menuconfig) - else() - set(MENUCONFIG_CMD "${python}" -m menuconfig) - endif() - __create_executable_config_env_file("${executable}") get_target_property(config_env_dir "${executable}" CONFIG_ENV_DIR) - add_custom_target("${ARG_TARGET}" - # Prepare Kconfig source files - COMMAND ${python} "${idf_path}/tools/kconfig_new/prepare_kconfig_files.py" - --list-separator=semicolon - --env-file "${config_env_dir}/config.env" - # Generate config with current settings - COMMAND ${kconfgen_cmd} - --env "IDF_TARGET=${target}" - --env "IDF_TOOLCHAIN=${toolchain}" - --env "IDF_ENV_FPGA=${idf_env_fpga}" - --env "IDF_INIT_VERSION=${idf_init_version}" - --dont-write-deprecated - ${kconfgen_outputs_cmd} - --env-file "${config_env_dir}/config.env" - # Check terminal capabilities - COMMAND ${python} "${idf_path}/tools/check_term.py" - # Run menuconfig - COMMAND ${CMAKE_COMMAND} -E env - "COMPONENT_KCONFIGS_SOURCE_FILE=${config_env_dir}/kconfigs.in" - "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=${config_env_dir}/kconfigs_projbuild.in" - "COMPONENT_KCONFIGS_EXCLUDED_SOURCE_FILE=${config_env_dir}/kconfigs_excluded.in" - "COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED_SOURCE_FILE=${config_env_dir}/kconfigs_projbuild_excluded.in" - "KCONFIG_CONFIG=${sdkconfig}" - "IDF_TARGET=${target}" - "IDF_TOOLCHAIN=${toolchain}" - "IDF_ENV_FPGA=${idf_env_fpga}" - "IDF_INIT_VERSION=${idf_init_version}" - "IDF_BUILD_V2=y" - ${MENUCONFIG_CMD} "${root_kconfig}" - # Post-menuconfig: insert deprecated options for backward compatibility - COMMAND ${kconfgen_cmd} - --env "IDF_TARGET=${target}" - --env "IDF_TOOLCHAIN=${toolchain}" - --env "IDF_ENV_FPGA=${idf_env_fpga}" - --env "IDF_INIT_VERSION=${idf_init_version}" - ${kconfgen_outputs_cmd} - --env-file "${config_env_dir}/config.env" - USES_TERMINAL - COMMENT "Running menuconfig..." - ) + __check_python_package_min_version( + ${python} esp-idf-kconfig "${ESP_MENUCONFIG_MIN_KCONFIG_VERSION}" _has_esp_menuconfig) + if(_has_esp_menuconfig) + set(_menuconfig_module_args -m esp_menuconfig) + else() + set(_menuconfig_module_args -m menuconfig) + endif() + + __check_python_package_min_version( + ${python} esp-idf-kconfig "${MENUCONFIG_INLINE_MIN_KCONFIG_VERSION}" _menuconfig_inline_ok) + + set(kconfig_report_verbosity "$ENV{KCONFIG_REPORT_VERBOSITY}") + if(NOT kconfig_report_verbosity) + set(kconfig_report_verbosity "default") + endif() + + if(_menuconfig_inline_ok) + add_custom_target("${ARG_TARGET}" + COMMAND ${python} "${idf_path}/tools/kconfig_new/prepare_kconfig_files.py" + --list-separator=semicolon + --env-file "${config_env_dir}/config.env" + COMMAND ${python} "${idf_path}/tools/check_term.py" + COMMAND ${CMAKE_COMMAND} -E env + "COMPONENT_KCONFIGS_SOURCE_FILE=${config_env_dir}/kconfigs.in" + "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=${config_env_dir}/kconfigs_projbuild.in" + "COMPONENT_KCONFIGS_EXCLUDED_SOURCE_FILE=${config_env_dir}/kconfigs_excluded.in" + "COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED_SOURCE_FILE=${config_env_dir}/kconfigs_projbuild_excluded.in" + "KCONFIG_CONFIG=${sdkconfig}" + "IDF_TARGET=${target}" + "IDF_TOOLCHAIN=${toolchain}" + "IDF_ENV_FPGA=${idf_env_fpga}" + "IDF_INIT_VERSION=${idf_init_version}" + "IDF_BUILD_V2=y" + "KCONFIG_REPORT_VERBOSITY=${kconfig_report_verbosity}" + ${kconfgen_cmd} + --env "IDF_TARGET=${target}" + --env "IDF_TOOLCHAIN=${toolchain}" + --env "IDF_ENV_FPGA=${idf_env_fpga}" + --env "IDF_INIT_VERSION=${idf_init_version}" + --menuconfig + ${kconfgen_outputs_cmd} + --env-file "${config_env_dir}/config.env" + USES_TERMINAL + COMMENT "Running menuconfig..." + ) + else() + message(WARNING "esp-idf-kconfig >= ${MENUCONFIG_INLINE_MIN_KCONFIG_VERSION} is required " + "for the optimised menuconfig target. Please update your Python packages by re-running the install script.") + add_custom_target("${ARG_TARGET}" + COMMAND ${python} "${idf_path}/tools/kconfig_new/prepare_kconfig_files.py" + --list-separator=semicolon + --env-file "${config_env_dir}/config.env" + COMMAND ${kconfgen_cmd} + --env "IDF_TARGET=${target}" + --env "IDF_TOOLCHAIN=${toolchain}" + --env "IDF_ENV_FPGA=${idf_env_fpga}" + --env "IDF_INIT_VERSION=${idf_init_version}" + --env "KCONFIG_REPORT_VERBOSITY=quiet" + --dont-write-deprecated + --output config "${sdkconfig}" + --env-file "${config_env_dir}/config.env" + COMMAND ${python} "${idf_path}/tools/check_term.py" + COMMAND ${CMAKE_COMMAND} -E env + "COMPONENT_KCONFIGS_SOURCE_FILE=${config_env_dir}/kconfigs.in" + "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=${config_env_dir}/kconfigs_projbuild.in" + "COMPONENT_KCONFIGS_EXCLUDED_SOURCE_FILE=${config_env_dir}/kconfigs_excluded.in" + "COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED_SOURCE_FILE=${config_env_dir}/kconfigs_projbuild_excluded.in" + "KCONFIG_CONFIG=${sdkconfig}" + "IDF_TARGET=${target}" + "IDF_TOOLCHAIN=${toolchain}" + "IDF_ENV_FPGA=${idf_env_fpga}" + "IDF_INIT_VERSION=${idf_init_version}" + "IDF_BUILD_V2=y" + ${python} ${_menuconfig_module_args} "${root_kconfig}" + COMMAND ${kconfgen_cmd} + --env "IDF_TARGET=${target}" + --env "IDF_TOOLCHAIN=${toolchain}" + --env "IDF_ENV_FPGA=${idf_env_fpga}" + --env "IDF_INIT_VERSION=${idf_init_version}" + ${kconfgen_outputs_cmd} + --env "KCONFIG_REPORT_VERBOSITY=${kconfig_report_verbosity}" + --env-file "${config_env_dir}/config.env" + USES_TERMINAL + COMMENT "Running menuconfig..." + ) + endif() endfunction() #[[ diff --git a/tools/cmakev2/utilities.cmake b/tools/cmakev2/utilities.cmake index a2804964184..329f960652f 100644 --- a/tools/cmakev2/utilities.cmake +++ b/tools/cmakev2/utilities.cmake @@ -143,6 +143,32 @@ function(deprecate_variable var) endif() endfunction() +#[[ +.. cmakev2:function:: __check_python_package_min_version + + .. code-block:: cmake + + __check_python_package_min_version( ) + + Check whether a Python package is installed with at least the given version + via ``importlib.metadata.version``. Sets ```` to TRUE or FALSE + in the parent scope. +#]] +function(__check_python_package_min_version python_exe package_name min_version result_var) + execute_process( + COMMAND ${python_exe} -c + "from importlib.metadata import version; print(version('${package_name}'))" + OUTPUT_VARIABLE _ver + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _rc + ERROR_QUIET + ) + set(${result_var} FALSE PARENT_SCOPE) + if(_rc EQUAL 0 AND _ver VERSION_GREATER_EQUAL "${min_version}") + set(${result_var} TRUE PARENT_SCOPE) + endif() +endfunction() + #[[ __get_real_target(TARGET OUTPUT )