diff --git a/tools/cmake/build.cmake b/tools/cmake/build.cmake index 3968dae1de..2f74364ca6 100644 --- a/tools/cmake/build.cmake +++ b/tools/cmake/build.cmake @@ -383,9 +383,6 @@ macro(__build_process_project_includes) set(${build_property} "${val}") endforeach() - # Check that the CMake target value matches the Kconfig target value. - __target_check() - idf_build_get_property(build_component_targets __BUILD_COMPONENT_TARGETS) # Include each component's project_include.cmake diff --git a/tools/cmake/targets.cmake b/tools/cmake/targets.cmake index ade74db9cc..75b466f34c 100644 --- a/tools/cmake/targets.cmake +++ b/tools/cmake/targets.cmake @@ -89,35 +89,42 @@ macro(__target_init) message(STATUS "IDF_TARGET not set, using default target: ${env_idf_target}") endif() endif() - else() - # IDF_TARGET set both in environment and in cache, must be the same - if(NOT ${IDF_TARGET} STREQUAL ${env_idf_target}) - message(FATAL_ERROR "IDF_TARGET in CMake cache does not match " - "IDF_TARGET environment variable. To change the target, clear " - "the build directory and sdkconfig file, and build the project again") + endif() + + # Check if selected target is consistent with CMake cache + if(DEFINED CACHE{IDF_TARGET}) + if(NOT $CACHE{IDF_TARGET} STREQUAL ${env_idf_target}) + message(FATAL_ERROR " IDF_TARGET '$CACHE{IDF_TARGET}' in CMake" + " cache does not match currently selected IDF_TARGET '${env_idf_target}'." + " To change the target, clear the build directory and sdkconfig file," + " and build the project again.") endif() endif() - # IDF_TARGET will be used by Kconfig, make sure it is set + if(SDKCONFIG) + get_filename_component(sdkconfig "${SDKCONFIG}" ABSOLUTE) + else() + set(sdkconfig "${CMAKE_SOURCE_DIR}/sdkconfig") + endif() + + # Check if selected target is consistent with sdkconfig + __target_from_config("${sdkconfig}" sdkconfig_target where) + if(sdkconfig_target) + if(NOT ${sdkconfig_target} STREQUAL ${env_idf_target}) + message(FATAL_ERROR " Target '${sdkconfig_target}' in sdkconfig '${where}'" + " does not match currently selected IDF_TARGET '${IDF_TARGET}'." + " To change the target, clear the build directory and sdkconfig file," + " and build the project again.") + endif() + endif() + + # IDF_TARGET will be used by component manager, make sure it is set set(ENV{IDF_TARGET} ${env_idf_target}) # Finally, set IDF_TARGET in cache set(IDF_TARGET ${env_idf_target} CACHE STRING "IDF Build Target") endmacro() -# -# Check that the set build target and the config target matches. -# -function(__target_check) - # Should be called after sdkconfig CMake file has been included. - idf_build_get_property(idf_target IDF_TARGET) - if(NOT ${idf_target} STREQUAL ${CONFIG_IDF_TARGET}) - message(FATAL_ERROR "CONFIG_IDF_TARGET in sdkconfig does not match " - "IDF_TARGET environment variable. To change the target, delete " - "sdkconfig file and build the project again.") - endif() -endfunction() - # # Used by the project CMake file to set the toolchain before project() call. # @@ -133,12 +140,12 @@ macro(__target_set_toolchain) else() set(env_idf_toolchain gcc) endif() - else() + elseif(DEFINED CACHE{IDF_TOOLCHAIN}) # IDF_TOOLCHAIN set both in environment and in cache, must be the same - if(NOT ${IDF_TOOLCHAIN} STREQUAL ${env_idf_toolchain}) - message(FATAL_ERROR "IDF_TOOLCHAIN in CMake cache does not match " - "IDF_TOOLCHAIN environment variable. To change the toolchain, clear " - "the build directory and sdkconfig file, and build the project again") + if(NOT $CACHE{IDF_TOOLCHAIN} STREQUAL ${env_idf_toolchain}) + message(FATAL_ERROR " IDF_TOOLCHAIN '$CACHE{IDF_TOOLCHAIN}' in CMake cache does not match" + " currently selected IDF_TOOLCHAIN '${env_idf_toolchain}'. To change the toolchain, clear" + " the build directory and sdkconfig file, and build the project again.") endif() endif() @@ -149,6 +156,18 @@ macro(__target_set_toolchain) set(toolchain_type "clang-") endif() + # Check if selected target is consistent with toolchain file in CMake cache + if(DEFINED CMAKE_TOOLCHAIN_FILE) + string(FIND "${CMAKE_TOOLCHAIN_FILE}" "-${toolchain_type}${IDF_TARGET}.cmake" found) + if(${found} EQUAL -1) + get_filename_component(toolchain "${CMAKE_TOOLCHAIN_FILE}" NAME_WE) + message(FATAL_ERROR " CMAKE_TOOLCHAIN_FILE '${toolchain}'" + " does not match currently selected IDF_TARGET '${IDF_TARGET}'." + " To change the target, clear the build directory and sdkconfig file," + " and build the project again.") + endif() + endif() + # First try to load the toolchain file from the tools/cmake/directory of IDF set(toolchain_file_global ${idf_path}/tools/cmake/toolchain-${toolchain_type}${IDF_TARGET}.cmake) if(EXISTS ${toolchain_file_global}) diff --git a/tools/test_build_system/test_build_system_helpers/idf_utils.py b/tools/test_build_system/test_build_system_helpers/idf_utils.py index 61d347536a..7578c17ee0 100644 --- a/tools/test_build_system/test_build_system_helpers/idf_utils.py +++ b/tools/test_build_system/test_build_system_helpers/idf_utils.py @@ -94,7 +94,8 @@ def run_idf_py(*args: str, text=True, encoding='utf-8', errors='backslashreplace') -def run_cmake(*cmake_args: str, env: typing.Optional[EnvDict] = None) -> None: +def run_cmake(*cmake_args: str, env: typing.Optional[EnvDict] = None, + check: bool = True) -> subprocess.CompletedProcess: """ Run cmake command with given arguments, raise an exception on failure :param cmake_args: arguments to pass cmake @@ -108,9 +109,10 @@ def run_cmake(*cmake_args: str, env: typing.Optional[EnvDict] = None) -> None: cmd = ['cmake'] + list(cmake_args) logging.debug('running {} in {}'.format(' '.join(cmd), workdir)) - subprocess.check_call( + return subprocess.run( cmd, env=env, cwd=workdir, - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + check=check, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True, encoding='utf-8', errors='backslashreplace') def check_file_contains(filename: Union[str, Path], what: Union[str, Pattern]) -> None: diff --git a/tools/test_build_system/test_non_default_target.py b/tools/test_build_system/test_non_default_target.py index e55812acb2..d7a3988b88 100644 --- a/tools/test_build_system/test_non_default_target.py +++ b/tools/test_build_system/test_non_default_target.py @@ -72,6 +72,42 @@ def test_target_from_environment_idf_py(idf_py: IdfPyFunc, default_idf_env: EnvD ['-D', 'IDF_TARGET={}'.format(ESP32_TARGET)]) +def test_target_consistency_cmake(default_idf_env: EnvDict, test_app_copy: Path) -> None: + def reconfigure_and_check_return_values(errmsg: str, opts: Optional[List[str]] = None) -> None: + opts = opts or [] + ret = run_cmake(*opts, '-G', 'Ninja', '..', env=default_idf_env, check=False) + assert ret.returncode == 1 + assert errmsg in ret.stderr + + run_cmake('-G', 'Ninja', '..') + + cfg_path = (test_app_copy / 'sdkconfig') + + logging.info("cmake fails if IDF_TARGET settings don't match the environment") + default_idf_env.update({'IDF_TARGET': ESP32S2_TARGET}) + reconfigure_and_check_return_values(f"IDF_TARGET '{ESP32_TARGET}' in CMake cache does not " + f"match currently selected IDF_TARGET '{ESP32S2_TARGET}'") + + logging.info("cmake fails if IDF_TARGET settings don't match the sdkconfig") + default_idf_env.pop('IDF_TARGET') + (test_app_copy / 'sdkconfig').write_text(f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"') + reconfigure_and_check_return_values(f"Target '{ESP32S2_TARGET}' in sdkconfig '{cfg_path}' does not " + f"match currently selected IDF_TARGET '{ESP32_TARGET}'.") + + logging.info("cmake fails if IDF_TOOLCHAIN settings don't match the environment") + (test_app_copy / 'sdkconfig').write_text(f'CONFIG_IDF_TARGET="{ESP32_TARGET}"') + default_idf_env.update({'IDF_TOOLCHAIN': 'clang'}) + reconfigure_and_check_return_values("IDF_TOOLCHAIN 'gcc' in CMake cache does not match " + "currently selected IDF_TOOLCHAIN 'clang'") + + logging.info("cmake fails if IDF_TARGET settings don't match CMAKE_TOOLCHAIN_FILE") + default_idf_env.pop('IDF_TOOLCHAIN') + reconfigure_and_check_return_values("CMAKE_TOOLCHAIN_FILE 'toolchain-esp32' does not " + f"match currently selected IDF_TARGET '{ESP32S2_TARGET}'", + ['-D', f'IDF_TARGET={ESP32S2_TARGET}', + '-D', 'SDKCONFIG=custom_sdkconfig']) + + def test_target_precedence(idf_py: IdfPyFunc, default_idf_env: EnvDict, test_app_copy: Path) -> None: logging.info('IDF_TARGET takes precedence over the value of CONFIG_IDF_TARGET in sdkconfig.defaults') (test_app_copy / 'sdkconfig.defaults').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))