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/project.cmake b/tools/cmake/project.cmake index 00d94c8a6a..469a573488 100644 --- a/tools/cmake/project.cmake +++ b/tools/cmake/project.cmake @@ -1,12 +1,29 @@ # Designed to be included from an IDF app's CMakeLists.txt file cmake_minimum_required(VERSION 3.16) +# Get the currently selected sdkconfig file early, so this doesn't +# have to be done multiple times on different places. +if(SDKCONFIG) + get_filename_component(sdkconfig "${SDKCONFIG}" ABSOLUTE) +else() + set(sdkconfig "${CMAKE_SOURCE_DIR}/sdkconfig") +endif() + +# Check if the cmake was started as part of the set-target action. +# If so, check for existing sdkconfig file and rename it. +# This is done before __target_init, so the existing IDF_TARGET from sdkconfig +# is not considered for consistence checking. +if("$ENV{_IDF_PY_SET_TARGET_ACTION}" EQUAL "1" AND EXISTS "${sdkconfig}") + file(RENAME "${sdkconfig}" "${sdkconfig}.old") + message(STATUS "Existing sdkconfig '${sdkconfig}' renamed to '${sdkconfig}.old'.") +endif() + include(${CMAKE_CURRENT_LIST_DIR}/targets.cmake) # Initialize build target for this build using the environment variable or # value passed externally. -__target_init() +__target_init("${sdkconfig}") -# The mere inclusion of this CMake file sets up some interal build properties. +# The mere inclusion of this CMake file sets up some internal build properties. # These properties can be modified in between this inclusion the the idf_build_process # call. include(${CMAKE_CURRENT_LIST_DIR}/idf.cmake) @@ -316,7 +333,7 @@ function(__project_init components_var test_components_var) set(${test_components_var} "${test_components}" PARENT_SCOPE) endfunction() -# Trick to temporarily redefine project(). When functions are overriden in CMake, the originals can still be accessed +# Trick to temporarily redefine project(). When functions are overridden in CMake, the originals can still be accessed # using an underscore prefixed function of the same name. The following lines make sure that __project calls # the original project(). See https://cmake.org/pipermail/cmake/2015-October/061751.html. function(project) @@ -431,12 +448,6 @@ macro(project project_name) list(APPEND sdkconfig_defaults ${sdkconfig_default}) endforeach() - if(SDKCONFIG) - get_filename_component(sdkconfig "${SDKCONFIG}" ABSOLUTE) - else() - set(sdkconfig "${CMAKE_CURRENT_LIST_DIR}/sdkconfig") - endif() - if(BUILD_DIR) get_filename_component(build_dir "${BUILD_DIR}" ABSOLUTE) if(NOT EXISTS "${build_dir}") diff --git a/tools/cmake/targets.cmake b/tools/cmake/targets.cmake index 23d3ff4848..e4c3f195b1 100644 --- a/tools/cmake/targets.cmake +++ b/tools/cmake/targets.cmake @@ -1,7 +1,76 @@ +# +# Get target from single sdkconfig file +# +function(__target_from_config config target_out file_out) + set(${target_out} NOTFOUND PARENT_SCOPE) + set(${file_out} NOTFOUND PARENT_SCOPE) + + if(NOT EXISTS "${config}") + return() + endif() + + file(STRINGS "${config}" lines) + foreach(line ${lines}) + if(NOT "${line}" MATCHES "^CONFIG_IDF_TARGET=\"[^\"]+\"$") + continue() + endif() + + string(REGEX REPLACE "CONFIG_IDF_TARGET=\"([^\"]+)\"" "\\1" target "${line}") + set(${target_out} ${target} PARENT_SCOPE) + set(${file_out} ${config} PARENT_SCOPE) + return() + endforeach() +endfunction() + +# +# Get target from list of sdkconfig files +# +function(__target_from_configs configs target_out file_out) + set(target NOTFOUND) + set(file NOTFOUND) + + foreach(config ${configs}) + message(DEBUG "Searching for target in '${config}'") + get_filename_component(config "${config}" ABSOLUTE) + __target_from_config("${config}" target file) + if(target) + break() + endif() + endforeach() + + set(${target_out} ${target} PARENT_SCOPE) + set(${file_out} ${file} PARENT_SCOPE) +endfunction() + +# +# Search for target in config files in the following order. +# SDKCONFIG cmake var, default sdkconfig, SDKCONFIG_DEFAULTS cmake var +# if non-empty or SDKCONFIG_DEFAULTS env var if non-empty or +# sdkconfig.defaults. +# +function(__target_guess target_out file_out) + # Select sdkconfig_defaults to look for target + if(SDKCONFIG_DEFAULTS) + set(defaults "${SDKCONFIG_DEFAULTS}") + elseif(DEFINED ENV{SDKCONFIG_DEFAULTS}) + set(defaults "$ENV{SDKCONFIG_DEFAULTS}") + endif() + + if(NOT defaults) + set(defaults "${CMAKE_SOURCE_DIR}/sdkconfig.defaults") + endif() + + set(configs "${SDKCONFIG}" "${CMAKE_SOURCE_DIR}/sdkconfig" "${defaults}") + message(DEBUG "Searching for target in '${configs}'") + __target_from_configs("${configs}" target file) + set(${target_out} ${target} PARENT_SCOPE) + set(${file_out} ${file} PARENT_SCOPE) +endfunction() + # # Set the target used for the standard project build. # -macro(__target_init) +macro(__target_init config_file) # Input is IDF_TARGET environement variable set(env_idf_target $ENV{IDF_TARGET}) @@ -10,38 +79,46 @@ macro(__target_init) if(IDF_TARGET) set(env_idf_target ${IDF_TARGET}) else() - set(env_idf_target esp32) - message(STATUS "IDF_TARGET not set, using default target: ${env_idf_target}") - 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") + # Try to guess IDF_TARGET from sdkconfig files while honoring + # SDKCONFIG and SDKCONFIG_DEFAULTS values + __target_guess(env_idf_target where) + if(env_idf_target) + message(STATUS "IDF_TARGET is not set, guessed '${env_idf_target}' from sdkconfig '${where}'") + else() + set(env_idf_target esp32) + message(STATUS "IDF_TARGET not set, using default target: ${env_idf_target}") + endif() endif() endif() - # IDF_TARGET will be used by Kconfig, make sure it is set + # 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() + + # Check if selected target is consistent with sdkconfig + __target_from_config("${config_file}" 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. # @@ -57,12 +134,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() @@ -73,6 +150,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/idf_py_actions/core_ext.py b/tools/idf_py_actions/core_ext.py index 7fb66556ba..3068130d64 100644 --- a/tools/idf_py_actions/core_ext.py +++ b/tools/idf_py_actions/core_ext.py @@ -155,14 +155,9 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: "%s is still in preview. You have to append '--preview' option after idf.py to use any preview feature." % idf_target) args.define_cache_entry.append('IDF_TARGET=' + idf_target) - sdkconfig_path = os.path.join(args.project_dir, 'sdkconfig') - sdkconfig_old = sdkconfig_path + '.old' - if os.path.exists(sdkconfig_old): - os.remove(sdkconfig_old) - if os.path.exists(sdkconfig_path): - os.rename(sdkconfig_path, sdkconfig_old) - print('Set Target to: %s, new sdkconfig created. Existing sdkconfig renamed to sdkconfig.old.' % idf_target) - ensure_build_directory(args, ctx.info_name, True) + print(f'Set Target to: {idf_target}, new sdkconfig will be created.') + env = {'_IDF_PY_SET_TARGET_ACTION': '1'} + ensure_build_directory(args, ctx.info_name, True, env) def reconfigure(action: str, ctx: Context, args: PropertyDict) -> None: ensure_build_directory(args, ctx.info_name, True) diff --git a/tools/idf_py_actions/tools.py b/tools/idf_py_actions/tools.py index f756fcce09..f2c546f8fa 100644 --- a/tools/idf_py_actions/tools.py +++ b/tools/idf_py_actions/tools.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import asyncio +import json import os import re import subprocess @@ -384,19 +385,27 @@ def _parse_cmakecache(path: str) -> Dict: return result -def _new_cmakecache_entries(cache_path: str, new_cache_entries: List) -> bool: - if not os.path.exists(cache_path): - return True +def _parse_cmdl_cmakecache(entries: List) -> Dict[str, str]: + """ + Parse list of CMake cache entries passed in via the -D option. - if new_cache_entries: - current_cache = _parse_cmakecache(cache_path) + Returns a dict of name:value. + """ + result: Dict = {} + for entry in entries: + key, value = entry.split('=', 1) + value = _strip_quotes(value) + result[key] = value - for entry in new_cache_entries: - key, value = entry.split('=', 1) - current_value = current_cache.get(key, None) - if current_value is None or _strip_quotes(value) != current_value: - return True + return result + +def _new_cmakecache_entries(cache: Dict, cache_cmdl: Dict) -> bool: + for entry in cache_cmdl: + if entry not in cache: + return True + if cache_cmdl[entry] != cache[entry]: + return True return False @@ -410,7 +419,8 @@ def _detect_cmake_generator(prog_name: str) -> Any: raise FatalError("To use %s, either the 'ninja' or 'GNU make' build tool must be available in the PATH" % prog_name) -def ensure_build_directory(args: 'PropertyDict', prog_name: str, always_run_cmake: bool=False) -> None: +def ensure_build_directory(args: 'PropertyDict', prog_name: str, always_run_cmake: bool=False, + env: Dict=None) -> None: """Check the build directory exists and that cmake has been run there. If this isn't the case, create the build directory (if necessary) and @@ -444,12 +454,14 @@ def ensure_build_directory(args: 'PropertyDict', prog_name: str, always_run_cmak cache_path = os.path.join(build_dir, 'CMakeCache.txt') cache = _parse_cmakecache(cache_path) if os.path.exists(cache_path) else {} - # Validate or set IDF_TARGET - _guess_or_check_idf_target(args, prog_name, cache) - args.define_cache_entry.append('CCACHE_ENABLE=%d' % args.ccache) - if always_run_cmake or _new_cmakecache_entries(cache_path, args.define_cache_entry): + cache_cmdl = _parse_cmdl_cmakecache(args.define_cache_entry) + + # Validate IDF_TARGET + _check_idf_target(args, prog_name, cache, cache_cmdl) + + if always_run_cmake or _new_cmakecache_entries(cache, cache_cmdl): if args.generator is None: args.generator = _detect_cmake_generator(prog_name) try: @@ -469,7 +481,7 @@ def ensure_build_directory(args: 'PropertyDict', prog_name: str, always_run_cmak cmake_args += [project_dir] hints = not args.no_hints - RunTool('cmake', cmake_args, cwd=args.build_dir, hints=hints)() + RunTool('cmake', cmake_args, cwd=args.build_dir, env=env, hints=hints)() except Exception: # don't allow partially valid CMakeCache.txt files, # to keep the "should I run cmake?" logic simple @@ -522,6 +534,27 @@ def merge_action_lists(*action_lists: Dict) -> Dict: return merged_actions +def get_sdkconfig_filename(args: 'PropertyDict', cache_cmdl: Dict=None) -> str: + """ + Get project's sdkconfig file name. + """ + if not cache_cmdl: + cache_cmdl = _parse_cmdl_cmakecache(args.define_cache_entry) + config = cache_cmdl.get('SDKCONFIG') + if config: + return os.path.abspath(config) + + proj_desc_path = os.path.join(args.build_dir, 'project_description.json') + try: + with open(proj_desc_path, 'r') as f: + proj_desc = json.load(f) + return str(proj_desc['config_file']) + except (OSError, KeyError): + pass + + return os.path.join(args.project_dir, 'sdkconfig') + + def get_sdkconfig_value(sdkconfig_file: str, key: str) -> Optional[str]: """ Return the value of given key from sdkconfig_file. @@ -549,48 +582,48 @@ def is_target_supported(project_path: str, supported_targets: List) -> bool: return get_target(project_path) in supported_targets -def _guess_or_check_idf_target(args: 'PropertyDict', prog_name: str, cache: Dict) -> None: +def _check_idf_target(args: 'PropertyDict', prog_name: str, cache: Dict, cache_cmdl: Dict) -> None: """ - If CMakeCache.txt doesn't exist, and IDF_TARGET is not set in the environment, guess the value from - sdkconfig or sdkconfig.defaults, and pass it to CMake in IDF_TARGET variable. - - Otherwise, cross-check the three settings (sdkconfig, CMakeCache, environment) and if there is + Cross-check the three settings (sdkconfig, CMakeCache, environment) and if there is mismatch, fail with instructions on how to fix this. """ - # Default locations of sdkconfig files. - # FIXME: they may be overridden in the project or by a CMake variable (IDF-1369). - # These are used to guess the target from sdkconfig, or set the default target by sdkconfig.defaults. - idf_target_from_sdkconfig = get_target(args.project_dir) - idf_target_from_sdkconfig_defaults = get_target(args.project_dir, 'sdkconfig.defaults') + sdkconfig = get_sdkconfig_filename(args, cache_cmdl) + idf_target_from_sdkconfig = get_sdkconfig_value(sdkconfig, 'CONFIG_IDF_TARGET') idf_target_from_env = os.environ.get('IDF_TARGET') idf_target_from_cache = cache.get('IDF_TARGET') + idf_target_from_cache_cmdl = cache_cmdl.get('IDF_TARGET') - if not cache and not idf_target_from_env: - # CMakeCache.txt does not exist yet, and IDF_TARGET is not set in the environment. - guessed_target = idf_target_from_sdkconfig or idf_target_from_sdkconfig_defaults - if guessed_target: - if args.verbose: - print("IDF_TARGET is not set, guessed '%s' from sdkconfig" % (guessed_target)) - args.define_cache_entry.append('IDF_TARGET=' + guessed_target) - - elif idf_target_from_env: + if idf_target_from_env: # Let's check that IDF_TARGET values are consistent if idf_target_from_sdkconfig and idf_target_from_sdkconfig != idf_target_from_env: - raise FatalError("Project sdkconfig was generated for target '{t_conf}', but environment variable IDF_TARGET " + raise FatalError("Project sdkconfig '{cfg}' was generated for target '{t_conf}', but environment variable IDF_TARGET " "is set to '{t_env}'. Run '{prog} set-target {t_env}' to generate new sdkconfig file for target {t_env}." - .format(t_conf=idf_target_from_sdkconfig, t_env=idf_target_from_env, prog=prog_name)) + .format(cfg=sdkconfig, t_conf=idf_target_from_sdkconfig, t_env=idf_target_from_env, prog=prog_name)) if idf_target_from_cache and idf_target_from_cache != idf_target_from_env: raise FatalError("Target settings are not consistent: '{t_env}' in the environment, '{t_cache}' in CMakeCache.txt. " "Run '{prog} fullclean' to start again." .format(t_env=idf_target_from_env, t_cache=idf_target_from_cache, prog=prog_name)) - elif idf_target_from_cache and idf_target_from_sdkconfig and idf_target_from_cache != idf_target_from_sdkconfig: + if idf_target_from_cache_cmdl and idf_target_from_cache_cmdl != idf_target_from_env: + raise FatalError("Target '{t_cmdl}' specified on command line is not consistent with " + "target '{t_env}' in the environment." + .format(t_cmdl=idf_target_from_cache_cmdl, t_env=idf_target_from_env)) + elif idf_target_from_cache_cmdl: + # Check if -DIDF_TARGET is consistent with target in CMakeCache.txt + if idf_target_from_cache and idf_target_from_cache != idf_target_from_cache_cmdl: + raise FatalError("Target '{t_cmdl}' specified on command line is not consistent with " + "target '{t_cache}' in CMakeCache.txt. Run '{prog} set-target {t_cmdl}' to re-generate " + 'CMakeCache.txt.' + .format(t_cache=idf_target_from_cache, t_cmdl=idf_target_from_cache_cmdl, prog=prog_name)) + + elif idf_target_from_cache: # This shouldn't happen, unless the user manually edits CMakeCache.txt or sdkconfig, but let's check anyway. - raise FatalError("Project sdkconfig was generated for target '{t_conf}', but CMakeCache.txt contains '{t_cache}'. " - "To keep the setting in sdkconfig ({t_conf}) and re-generate CMakeCache.txt, run '{prog} fullclean'. " - "To re-generate sdkconfig for '{t_cache}' target, run '{prog} set-target {t_cache}'." - .format(t_conf=idf_target_from_sdkconfig, t_cache=idf_target_from_cache, prog=prog_name)) + if idf_target_from_sdkconfig and idf_target_from_cache != idf_target_from_sdkconfig: + raise FatalError("Project sdkconfig '{cfg}' was generated for target '{t_conf}', but CMakeCache.txt contains '{t_cache}'. " + "To keep the setting in sdkconfig ({t_conf}) and re-generate CMakeCache.txt, run '{prog} fullclean'. " + "To re-generate sdkconfig for '{t_cache}' target, run '{prog} set-target {t_cache}'." + .format(cfg=sdkconfig, t_conf=idf_target_from_sdkconfig, t_cache=idf_target_from_cache, prog=prog_name)) class TargetChoice(click.Choice): 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 0d0cbe2af7..d7a3988b88 100644 --- a/tools/test_build_system/test_non_default_target.py +++ b/tools/test_build_system/test_non_default_target.py @@ -4,10 +4,14 @@ import logging import shutil from pathlib import Path +from typing import List, Optional import pytest from test_build_system_helpers import EnvDict, IdfPyFunc, check_file_contains, run_cmake +ESP32C3_TARGET = 'esp32c3' +ESP32C2_TARGET = 'esp32c2' +ESP32S3_TARGET = 'esp32s3' ESP32S2_TARGET = 'esp32s2' ESP32_TARGET = 'esp32' @@ -28,17 +32,21 @@ def test_target_from_environment_cmake(default_idf_env: EnvDict) -> None: def test_target_from_environment_idf_py(idf_py: IdfPyFunc, default_idf_env: EnvDict, test_app_copy: Path) -> None: - def reconfigure_and_check_return_values(errmsg: str) -> None: - ret = idf_py('reconfigure', check=False) + def reconfigure_and_check_return_values(errmsg: str, opts: Optional[List[str]] = None) -> None: + opts = opts or [] + ret = idf_py(*opts, 'reconfigure', check=False) assert ret.returncode == 2 assert errmsg in ret.stderr idf_py('set-target', ESP32S2_TARGET) default_idf_env.update({'IDF_TARGET': ESP32_TARGET}) + cfg_path = (test_app_copy / 'sdkconfig') + logging.info("idf.py fails if IDF_TARGET settings don't match the environment") - reconfigure_and_check_return_values("Project sdkconfig was generated for target '{}', but environment " - "variable IDF_TARGET is set to '{}'.".format(ESP32S2_TARGET, ESP32_TARGET)) + reconfigure_and_check_return_values("Project sdkconfig '{}' was generated for target '{}', but environment " + "variable IDF_TARGET is set to '{}'.".format(cfg_path, ESP32S2_TARGET, + ESP32_TARGET)) logging.info("idf.py fails if IDF_TARGET settings in CMakeCache.txt don't match the environment") (test_app_copy / 'sdkconfig').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32_TARGET)) @@ -47,8 +55,57 @@ def test_target_from_environment_idf_py(idf_py: IdfPyFunc, default_idf_env: EnvD logging.info("idf.py fails if IDF_TARGET settings in CMakeCache.txt don't match the sdkconfig") default_idf_env.pop('IDF_TARGET') - reconfigure_and_check_return_values("Project sdkconfig was generated for target '{}', but CMakeCache.txt " - "contains '{}'.".format(ESP32_TARGET, ESP32S2_TARGET)) + reconfigure_and_check_return_values("Project sdkconfig '{}' was generated for target '{}', but CMakeCache.txt " + "contains '{}'.".format(cfg_path, ESP32_TARGET, ESP32S2_TARGET)) + + logging.info('idf.py fails if IDF_TARGET is set differently in environment and with -D option') + (test_app_copy / 'sdkconfig').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET)) + default_idf_env.update({'IDF_TARGET': ESP32S2_TARGET}) + reconfigure_and_check_return_values("Target '{}' specified on command line is not consistent with target '{}' " + 'in the environment.'.format(ESP32_TARGET, ESP32S2_TARGET), + ['-D', 'IDF_TARGET={}'.format(ESP32_TARGET)]) + + logging.info('idf.py fails if IDF_TARGET is set differently in CMakeCache.txt and with -D option') + default_idf_env.pop('IDF_TARGET') + reconfigure_and_check_return_values("Target '{}' specified on command line is not consistent with " + "target '{}' in CMakeCache.txt.".format(ESP32_TARGET, ESP32S2_TARGET), + ['-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: @@ -102,3 +159,41 @@ def test_target_using_sdkconfig(idf_py: IdfPyFunc, test_app_copy: Path) -> None: idf_py('reconfigure') check_file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET)) check_file_contains('sdkconfig', 'CONFIG_IDF_TARGET_{}=y'.format(ESP32S2_TARGET.upper())) + + +def test_target_guessing(idf_py: IdfPyFunc, test_app_copy: Path, default_idf_env: EnvDict) -> None: + """ + Tests are performed from the lowest to the highest priority, while + configs, except from the sdkconfig, and parameters of previous tests are + preserved. + """ + + logging.info('Can guess target from sdkconfig.defaults') + (test_app_copy / 'sdkconfig.defaults').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32_TARGET)) + idf_py('reconfigure') + check_file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32_TARGET)) + check_file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32_TARGET)) + + logging.info('Can guess target from SDKCONFIG_DEFAULTS environment variable') + (test_app_copy / 'sdkconfig1').write_text('NOTHING HERE') + (test_app_copy / 'sdkconfig2').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET)) + clean_app(test_app_copy) + default_idf_env.update({'SDKCONFIG_DEFAULTS': 'sdkconfig1;sdkconfig2'}) + idf_py('reconfigure') + check_file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET)) + check_file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S2_TARGET)) + + logging.info('Can guess target from SDKCONFIG_DEFAULTS using -D') + (test_app_copy / 'sdkconfig3').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET)) + (test_app_copy / 'sdkconfig4').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S3_TARGET)) + clean_app(test_app_copy) + idf_py('-D', 'SDKCONFIG_DEFAULTS=sdkconfig4;sdkconfig3', 'reconfigure') + check_file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S3_TARGET)) + check_file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S3_TARGET)) + + logging.info('Can guess target from custom sdkconfig') + (test_app_copy / 'sdkconfig5').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32C3_TARGET)) + clean_app(test_app_copy) + idf_py('-D', 'SDKCONFIG=sdkconfig5', '-D', 'SDKCONFIG_DEFAULTS=sdkconfig4;sdkconfig3', 'reconfigure') + check_file_contains('sdkconfig5', 'CONFIG_IDF_TARGET="{}"'.format(ESP32C3_TARGET)) + check_file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32C3_TARGET))