tools: add target consistency checks to cmake

Extend target checks in cmake, in case it's run directly and not via
idf.py or if idf.py misses something. This may happen
for example if cmake variables are set in project's CMakeLists.txt.

Some clean-ups are included along with the new checks and tests.

1. __target_check() function is removed. IIUC it should never fail,
   because the selected target is explicitly passed as environmental
   variable to kconfgen. Meaning the IDF_TARGET from environment variable may
   not be actually used in kconfgen if IDF_TARGET is already set it cmake cache.
   Note that the IDF_TARGET environment variable used for kconfgen is not
   based on the actual IDF_TARGET environment variable set for idf.py, but
   rather on the value set in __target_init() with

	set(IDF_TARGET ${env_idf_target} CACHE STRING "IDF Build Target")

   My understanding is that the original check was introduced to handle
   situation, where IDF_TARGET was already set in cmake's cache and
   the IDF_TARGET from environment variable was different. Since
   the kconfgen would use the original environment variable(not
   explicitly passed as it is now) the IDF_TARGET in cmake and in
   sdkconfig could differ. IOW I think the original check was introduced
   to cope with the following cmake behaviour

	set(VARIABLE "value1" CACHE STRING "test variable")
	set(VARIABLE "value2" CACHE STRING "test variable")
	message("Variable value: ${VARIABLE}")
	output: Variable value: value1

2. I scratched by head how it is possible that the following code
   in __target_check()

   	if(NOT ${IDF_TARGET} STREQUAL ${env_idf_target})

   could fail if IDF_TARGET is not set. For example in clean project

	IDF_TARGET=esp32 idf.py reconfigure

   Here env_idf_target==esp32 and IDF_TARGET is not set, so I would
   expect that cmake will fail with error message that the cache
   and env target do not match. The thing is that the variable
   evaluation is done before the if command, so it actually
   sees this

   	if(NOT  STREQUAL esp32)

   which is false and the error is not printed. It can be seen
   with 'cmake --trace-expand' command. I don't know if this
   was used on purpose or it worked just as a coincidence, but
   I find it very confusing, so I added explicit check if the
   IDF_TARGET is defined before the actual check. Same for
   CMAKE_TOOLCHAIN_FILE.

3. Error messages are not formated(line-wrapped) by cmake's markup
   so it's easier to check the output in tests.

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
This commit is contained in:
Frantisek Hrbata 2023-02-28 18:15:12 +01:00
parent 8e912faad1
commit 1ca9e63e79
4 changed files with 85 additions and 31 deletions

View File

@ -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

View File

@ -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})

View File

@ -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:

View File

@ -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))