Merge branch 'feat/buildv2_enable_cmake_tests' into 'master'

test(buildv2): Enable all buildv2 tests in CI

Closes IDF-15080

See merge request espressif/esp-idf!46088
This commit is contained in:
Sudeep Mohanty
2026-03-02 11:28:25 +01:00
5 changed files with 128 additions and 95 deletions
-17
View File
@@ -485,23 +485,6 @@ pytest_buildv2_system:
--work-dir ${CI_PROJECT_DIR}/test_build_system --work-dir ${CI_PROJECT_DIR}/test_build_system
--junitxml ${CI_PROJECT_DIR}/XUNIT_RESULT.xml --junitxml ${CI_PROJECT_DIR}/XUNIT_RESULT.xml
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
--
buildv2
test_non_default_target.py
test_component_manager.py
test_build.py
test_bootloader.py
test_git.py
test_kconfig.py
test_partition.py
test_reproducible_build.py
test_sdkconfig.py
test_versions.py
test_common.py
test_components.py
test_cmake.py
test_idf_extension.py
test_rebuild.py
pytest_build_system_macos: pytest_build_system_macos:
extends: extends:
-17
View File
@@ -164,20 +164,3 @@ pytest_buildv2_system_win:
--parallel-index ${CI_NODE_INDEX} --parallel-index ${CI_NODE_INDEX}
--junitxml=${CI_PROJECT_DIR}\XUNIT_RESULT.xml --junitxml=${CI_PROJECT_DIR}\XUNIT_RESULT.xml
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
--
buildv2
test_non_default_target.py
test_component_manager.py
test_build.py
test_bootloader.py
test_git.py
test_kconfig.py
test_partition.py
test_reproducible_build.py
test_sdkconfig.py
test_versions.py
test_common.py
test_components.py
test_cmake.py
test_idf_extension.py
test_rebuild.py
+98 -42
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import json import json
import logging import logging
@@ -22,21 +22,32 @@ from test_build_system_helpers import run_idf_py
# This test checks multiple targets in one test function. It would be better to have each target # This test checks multiple targets in one test function. It would be better to have each target
# tested in a isolated test case, but that would mean doing idf_copy each time, and copying takes most of the time # tested in a isolated test case, but that would mean doing idf_copy each time, and copying takes most of the time
@pytest.mark.usefixtures('idf_copy') @pytest.mark.usefixtures('idf_copy')
def test_build_custom_cmake_project(test_app_copy: Path) -> None: def test_build_custom_cmake_project(test_app_copy: Path, request: pytest.FixtureRequest) -> None:
# Test is compatible with any target. Random targets in the list are selected for performance reasons # Test is compatible with any target. Random targets in the list are selected for performance reasons
idf_path = Path(os.environ['IDF_PATH']) idf_path = Path(os.environ['IDF_PATH'])
is_buildv2 = request.config.getoption('buildv2', False)
if is_buildv2:
idf_as_lib_path = idf_path / 'examples' / 'build_system' / 'cmakev2' / 'features' / 'idf_as_lib'
base_cmake_args = ['-G', 'Ninja', '-DESP_PLATFORM=1']
target_var = 'IDF_TARGET'
else:
idf_as_lib_path = idf_path / 'examples' / 'build_system' / 'cmake' / 'idf_as_lib'
base_cmake_args = ['-G', 'Ninja']
target_var = 'TARGET'
for target in ['esp32', 'esp32c2', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32p4', 'esp32s2', 'esp32s3']: for target in ['esp32', 'esp32c2', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32p4', 'esp32s2', 'esp32s3']:
logging.info(f'Test build ESP-IDF as a library to a custom CMake projects for {target}') logging.info(f'Test build ESP-IDF as a library to a custom CMake projects for {target}')
run_cmake_and_build( run_cmake_and_build(
str(idf_path / 'examples' / 'build_system' / 'cmake' / 'idf_as_lib'), str(idf_as_lib_path),
'-G', *base_cmake_args,
'Ninja',
'-DCMAKE_TOOLCHAIN_FILE={}'.format(idf_path / 'tools' / 'cmake' / f'toolchain-{target}.cmake'), '-DCMAKE_TOOLCHAIN_FILE={}'.format(idf_path / 'tools' / 'cmake' / f'toolchain-{target}.cmake'),
f'-DTARGET={target}', f'-D{target_var}={target}',
) )
assert file_contains((test_app_copy / 'build' / 'compile_commands.json'), '"command"') assert file_contains((test_app_copy / 'build' / 'compile_commands.json'), '"command"')
shutil.rmtree(test_app_copy / 'build') shutil.rmtree(test_app_copy / 'build')
os.remove(idf_path / 'examples' / 'build_system' / 'cmake' / 'idf_as_lib' / 'sdkconfig') sdkconfig_path = idf_as_lib_path / 'sdkconfig'
if sdkconfig_path.exists():
os.remove(sdkconfig_path)
@pytest.mark.skipif( @pytest.mark.skipif(
@@ -45,16 +56,25 @@ def test_build_custom_cmake_project(test_app_copy: Path) -> None:
) )
@pytest.mark.usefixtures('idf_copy') @pytest.mark.usefixtures('idf_copy')
@pytest.mark.usefixtures('test_app_copy') @pytest.mark.usefixtures('test_app_copy')
def test_build_custom_cmake_project_host() -> None: def test_build_custom_cmake_project_host(request: pytest.FixtureRequest) -> None:
logging.info('Test build ESP-IDF as a library to a custom CMake projects for host') logging.info('Test build ESP-IDF as a library to a custom CMake projects for host')
idf_path = Path(os.environ['IDF_PATH']) idf_path = Path(os.environ['IDF_PATH'])
run_cmake_and_build(str(idf_path / 'examples' / 'build_system' / 'cmake' / 'idf_as_lib'), '-G', 'Ninja') is_buildv2 = request.config.getoption('buildv2', False)
if is_buildv2:
idf_as_lib_path = idf_path / 'examples' / 'build_system' / 'cmakev2' / 'features' / 'idf_as_lib'
else:
idf_as_lib_path = idf_path / 'examples' / 'build_system' / 'cmake' / 'idf_as_lib'
run_cmake_and_build(str(idf_as_lib_path), '-G', 'Ninja')
@pytest.mark.buildv2_skip('import_lib example uses cmakev1, not yet updated for buildv2 (IDF-14185)') def test_build_cmake_library_with_toolchain_flags(test_app_copy: Path, request: pytest.FixtureRequest) -> None:
def test_build_cmake_library_with_toolchain_flags(test_app_copy: Path) -> None:
logging.info('Building a project with CMake library imported with modified toolchain flags') logging.info('Building a project with CMake library imported with modified toolchain flags')
idf_path = Path(os.environ['IDF_PATH']) idf_path = Path(os.environ['IDF_PATH'])
is_buildv2 = request.config.getoption('buildv2', False)
if is_buildv2:
import_lib_path = idf_path / 'examples' / 'build_system' / 'cmakev2' / 'features' / 'import_lib'
else:
import_lib_path = idf_path / 'examples' / 'build_system' / 'cmake' / 'import_lib'
# Enable Picolibc to verify that all flags are passed correctly to the external project. # Enable Picolibc to verify that all flags are passed correctly to the external project.
# In case something is missing, the build will fail on linking stage. # In case something is missing, the build will fail on linking stage.
# Note: To enable Picolibc, IDF_EXPERIMENTAL_FEATURES must also be set for now. # Note: To enable Picolibc, IDF_EXPERIMENTAL_FEATURES must also be set for now.
@@ -62,7 +82,6 @@ def test_build_cmake_library_with_toolchain_flags(test_app_copy: Path) -> None:
'\n'.join(['CONFIG_IDF_EXPERIMENTAL_FEATURES=y', 'CONFIG_LIBC_PICOLIBC=y']) '\n'.join(['CONFIG_IDF_EXPERIMENTAL_FEATURES=y', 'CONFIG_LIBC_PICOLIBC=y'])
) )
import_lib_path = idf_path / 'examples' / 'build_system' / 'cmake' / 'import_lib'
run_cmake_and_build( run_cmake_and_build(
str(import_lib_path), str(import_lib_path),
'-G', '-G',
@@ -103,22 +122,37 @@ def check_flag_in_compile_commands(build_dir: Path, flag_to_find: str) -> None:
assert False, f'{flag_to_find} not found in {command}' assert False, f'{flag_to_find} not found in {command}'
@pytest.mark.buildv2_skip('import_lib example uses cmakev1, not yet updated for buildv2 (IDF-14185)') def test_build_cmake_library_psram_workaround(test_app_copy: Path, request: pytest.FixtureRequest) -> None:
def test_build_cmake_library_psram_workaround(test_app_copy: Path) -> None:
logging.info( logging.info(
'Building a project with CMake library imported and PSRAM workaround, all files compile with workaround' 'Building a project with CMake library imported and PSRAM workaround, all files compile with workaround'
) )
idf_path = Path(os.environ['IDF_PATH']) idf_path = Path(os.environ['IDF_PATH'])
is_buildv2 = request.config.getoption('buildv2', False)
(test_app_copy / 'sdkconfig.defaults').write_text( (test_app_copy / 'sdkconfig.defaults').write_text(
'\n'.join(['CONFIG_SPIRAM=y', 'CONFIG_SPIRAM_CACHE_WORKAROUND=y']) '\n'.join(['CONFIG_SPIRAM=y', 'CONFIG_SPIRAM_CACHE_WORKAROUND=y'])
) )
run_cmake( if is_buildv2:
'-G', # Use buildv2_test_app and add esp_psram via CMakeLists placeholder
'Ninja', replace_in_file(
'-DCOMPONENTS=main;esp_psram', test_app_copy / 'main' / 'CMakeLists.txt',
'-DSDKCONFIG_DEFAULTS={}'.format(test_app_copy / 'sdkconfig.defaults'), '# placeholder_inside_idf_component_register',
str(idf_path / 'examples' / 'build_system' / 'cmake' / 'import_lib'), 'REQUIRES esp_psram',
) )
run_cmake(
'..',
'-G',
'Ninja',
'-DSDKCONFIG_DEFAULTS={}'.format(test_app_copy / 'sdkconfig.defaults'),
)
else:
import_lib_path = idf_path / 'examples' / 'build_system' / 'cmake' / 'import_lib'
run_cmake(
'-G',
'Ninja',
'-DCOMPONENTS=main;esp_psram',
'-DSDKCONFIG_DEFAULTS={}'.format(test_app_copy / 'sdkconfig.defaults'),
str(import_lib_path),
)
check_flag_in_compile_commands(test_app_copy, '-mfix-esp32-psram-cache-issue') check_flag_in_compile_commands(test_app_copy, '-mfix-esp32-psram-cache-issue')
@@ -147,38 +181,60 @@ def test_build_cmake_library_psram_strategies(idf_py: IdfPyFunc, test_app_copy:
(test_app_copy / 'sdkconfig').unlink() (test_app_copy / 'sdkconfig').unlink()
def test_defaults_unspecified_build_args(idf_copy: Path) -> None: def test_defaults_unspecified_build_args(idf_copy: Path, request: pytest.FixtureRequest) -> None:
logging.info('Defaults set properly for unspecified idf_build_process args') is_buildv2 = request.config.getoption('buildv2', False)
idf_as_lib_path = idf_copy / 'examples' / 'build_system' / 'cmake' / 'idf_as_lib' if is_buildv2:
logging.info('PROJECT_DIR set correctly by build system v2')
idf_as_lib_path = idf_copy / 'examples' / 'build_system' / 'cmakev2' / 'features' / 'idf_as_lib'
cmake_args = [
'..',
'-G',
'Ninja',
'-DCMAKE_TOOLCHAIN_FILE={}'.format(str(idf_copy / 'tools' / 'cmake' / 'toolchain-esp32.cmake')),
'-DESP_PLATFORM=1',
'-DIDF_TARGET=esp32',
]
else:
logging.info('Defaults set properly for unspecified idf_build_process args')
idf_as_lib_path = idf_copy / 'examples' / 'build_system' / 'cmake' / 'idf_as_lib'
cmake_args = [
'..',
'-G',
'Ninja',
'-DCMAKE_TOOLCHAIN_FILE={}'.format(str(idf_copy / 'tools' / 'cmake' / 'toolchain-esp32.cmake')),
'-DTARGET=esp32',
]
append_to_file( append_to_file(
idf_as_lib_path / 'CMakeLists.txt', idf_as_lib_path / 'CMakeLists.txt',
'\n'.join(['idf_build_get_property(project_dir PROJECT_DIR)', 'message("Project directory: ${project_dir}")']), '\n'.join(['idf_build_get_property(project_dir PROJECT_DIR)', 'message("Project directory: ${project_dir}")']),
) )
ret = run_cmake( ret = run_cmake(*cmake_args, workdir=idf_as_lib_path)
'..',
'-G',
'Ninja',
'-DCMAKE_TOOLCHAIN_FILE={}'.format(str(idf_copy / 'tools' / 'cmake' / 'toolchain-esp32.cmake')),
'-DTARGET=esp32',
workdir=idf_as_lib_path,
)
assert f'Project directory: {str(idf_as_lib_path.as_posix())}' in ret.stderr assert f'Project directory: {str(idf_as_lib_path.as_posix())}' in ret.stderr
def test_build_example_on_host(default_idf_env: EnvDict) -> None: def test_build_example_on_host(default_idf_env: EnvDict, request: pytest.FixtureRequest) -> None:
logging.info('Test if it can build the example to run on host') logging.info('Test if it can build the example to run on host')
idf_path = Path(default_idf_env.get('IDF_PATH')) idf_path = Path(default_idf_env.get('IDF_PATH'))
idf_as_lib_path = Path(idf_path, 'examples', 'build_system', 'cmake', 'idf_as_lib') is_buildv2 = request.config.getoption('buildv2', False)
try: if is_buildv2:
target = 'esp32' idf_as_lib_path = Path(idf_path, 'examples', 'build_system', 'cmakev2', 'features', 'idf_as_lib')
run_cmake( cmake_configure_args = [
'..', '..',
f'-DCMAKE_TOOLCHAIN_FILE={idf_path}/tools/cmake/toolchain-{target}.cmake', f'-DCMAKE_TOOLCHAIN_FILE={idf_path}/tools/cmake/toolchain-esp32.cmake',
f'-DTARGET={target}', '-DESP_PLATFORM=1',
'-DIDF_TARGET=esp32',
'-GNinja', '-GNinja',
workdir=idf_as_lib_path, ]
) else:
idf_as_lib_path = Path(idf_path, 'examples', 'build_system', 'cmake', 'idf_as_lib')
cmake_configure_args = [
'..',
f'-DCMAKE_TOOLCHAIN_FILE={idf_path}/tools/cmake/toolchain-esp32.cmake',
'-DTARGET=esp32',
'-GNinja',
]
try:
run_cmake(*cmake_configure_args, workdir=idf_as_lib_path)
run_cmake('--build', '.', workdir=idf_as_lib_path) run_cmake('--build', '.', workdir=idf_as_lib_path)
finally: finally:
shutil.rmtree(idf_as_lib_path / 'build', ignore_errors=True) shutil.rmtree(idf_as_lib_path / 'build', ignore_errors=True)
@@ -2,11 +2,13 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
from pathlib import Path from pathlib import Path
import pytest
from test_build_system_helpers import append_to_file from test_build_system_helpers import append_to_file
from test_build_system_helpers import get_snapshot from test_build_system_helpers import get_snapshot
from test_build_system_helpers import run_cmake_and_build from test_build_system_helpers import run_cmake_and_build
@pytest.mark.buildv2_skip('Post-ELF dependency API is not used in cmakev2. Component callback method replaces this')
def test_post_elf_dependency_runs_before_bin(test_app_copy: Path) -> None: def test_post_elf_dependency_runs_before_bin(test_app_copy: Path) -> None:
""" """
Verify idf_build_add_post_elf_dependency(<elf_filename> <dep_target>) adds a post-ELF step Verify idf_build_add_post_elf_dependency(<elf_filename> <dep_target>) adds a post-ELF step
+28 -19
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import logging import logging
import os import os
@@ -21,6 +21,7 @@ def clean_app_dir(app_path: Path) -> None:
shutil.rmtree(app_path / 'build', ignore_errors=True) shutil.rmtree(app_path / 'build', ignore_errors=True)
@pytest.mark.buildv2_skip('Examples (spiffsgen, ulp, etc.) not yet ported to buildv2')
@pytest.mark.idf_copy_with_space @pytest.mark.idf_copy_with_space
def test_spaces_bundle1(idf_copy: Path) -> None: def test_spaces_bundle1(idf_copy: Path) -> None:
logging.info('Running test spaces bundle 1') logging.info('Running test spaces bundle 1')
@@ -29,9 +30,12 @@ def test_spaces_bundle1(idf_copy: Path) -> None:
# test build ulp_fsm # test build ulp_fsm
run_idf_py('build', workdir=(idf_copy / 'examples' / 'system' / 'ulp' / 'ulp_fsm' / 'ulp')) run_idf_py('build', workdir=(idf_copy / 'examples' / 'system' / 'ulp' / 'ulp_fsm' / 'ulp'))
# test build ulp_riscv # test build ulp_riscv
run_idf_py('-DIDF_TARGET=esp32s2', 'build', workdir=(idf_copy / 'examples' / 'system' / 'ulp' / 'ulp_riscv' / 'gpio')) run_idf_py(
'-DIDF_TARGET=esp32s2', 'build', workdir=(idf_copy / 'examples' / 'system' / 'ulp' / 'ulp_riscv' / 'gpio')
)
@pytest.mark.buildv2_skip('Examples (flash_encryption, https_x509_bundle) not yet ported to buildv2')
@pytest.mark.idf_copy_with_space @pytest.mark.idf_copy_with_space
def test_spaces_bundle2(idf_copy: Path) -> None: def test_spaces_bundle2(idf_copy: Path) -> None:
logging.info('Running test spaces bundle 2') logging.info('Running test spaces bundle 2')
@@ -41,33 +45,38 @@ def test_spaces_bundle2(idf_copy: Path) -> None:
run_idf_py('build', workdir=(idf_copy / 'examples' / 'protocols' / 'https_x509_bundle')) run_idf_py('build', workdir=(idf_copy / 'examples' / 'protocols' / 'https_x509_bundle'))
@pytest.mark.buildv2_skip('secure_boot test app not yet ported to buildv2')
@pytest.mark.idf_copy_with_space @pytest.mark.idf_copy_with_space
def test_spaces_bundle3(idf_copy: Path) -> None: def test_spaces_bundle3(idf_copy: Path) -> None:
logging.info('Running test spaces bundle 3') logging.info('Running test spaces bundle 3')
secure_boot_app_path = (idf_copy / 'tools' / 'test_apps' / 'security' / 'secure_boot') secure_boot_app_path = idf_copy / 'tools' / 'test_apps' / 'security' / 'secure_boot'
# test secure_boot_v1 # test secure_boot_v1
run_idf_py('-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.00', 'build', run_idf_py('-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.00', 'build', workdir=secure_boot_app_path)
workdir=secure_boot_app_path)
clean_app_dir(secure_boot_app_path) clean_app_dir(secure_boot_app_path)
# test secure_boot_v2 # test secure_boot_v2
run_idf_py('-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.01', 'build', run_idf_py('-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.01', 'build', workdir=secure_boot_app_path)
workdir=secure_boot_app_path)
clean_app_dir(secure_boot_app_path) clean_app_dir(secure_boot_app_path)
# test app_signing # test app_signing
run_idf_py('-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.02', 'build', run_idf_py('-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.02', 'build', workdir=secure_boot_app_path)
workdir=secure_boot_app_path)
clean_app_dir(secure_boot_app_path) clean_app_dir(secure_boot_app_path)
# test secure_boot_release_mode # test secure_boot_release_mode
run_idf_py('-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.04', '-DIDF_TARGET=esp32s2', 'build', run_idf_py(
workdir=secure_boot_app_path) '-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.04',
'-DIDF_TARGET=esp32s2',
'build',
workdir=secure_boot_app_path,
)
@pytest.mark.xfail(sys.platform == 'win32', reason='Bug with reproducible build') @pytest.mark.xfail(sys.platform == 'win32', reason='Bug with reproducible build')
# Use this bundle for tests which can be done with the default build_test_app # Use this bundle for tests which can be done with the default build_test_app
@pytest.mark.parametrize('dummy_', [ @pytest.mark.parametrize(
# Dummy parameter with a space in it, used so that the test directory name contains a space 'dummy_',
pytest.param('test spaces') [
]) # Dummy parameter with a space in it, used so that the test directory name contains a space
pytest.param('test spaces')
],
)
@pytest.mark.idf_copy_with_space @pytest.mark.idf_copy_with_space
@pytest.mark.usefixtures('idf_copy') @pytest.mark.usefixtures('idf_copy')
def test_spaces_bundle4(dummy_: str, idf_py: IdfPyFunc, test_app_copy: Path) -> None: def test_spaces_bundle4(dummy_: str, idf_py: IdfPyFunc, test_app_copy: Path) -> None:
@@ -91,9 +100,9 @@ def test_install_export_unix(idf_copy: Path) -> None:
install_cmd = './install.sh esp32' install_cmd = './install.sh esp32'
export_cmd = '. ./export.sh' export_cmd = '. ./export.sh'
logging.debug('running {} in {}'.format(install_cmd, idf_copy)) logging.debug(f'running {install_cmd} in {idf_copy}')
subprocess.check_call(install_cmd, env=env, shell=True, cwd=idf_copy) subprocess.check_call(install_cmd, env=env, shell=True, cwd=idf_copy)
logging.debug('running {} in {}'.format(export_cmd, idf_copy)) logging.debug(f'running {export_cmd} in {idf_copy}')
# The default shell used by subprocess.Popen on POSIX platforms is '/bin/sh', # The default shell used by subprocess.Popen on POSIX platforms is '/bin/sh',
# which in esp-env Docker image is 'dash'. The export script doesn't support # which in esp-env Docker image is 'dash'. The export script doesn't support
# IDF_PATH detection when used in dash, so we have to override the shell here. # IDF_PATH detection when used in dash, so we have to override the shell here.
@@ -108,7 +117,7 @@ def test_install_export_win(idf_copy: Path) -> None:
install_cmd = 'install.bat esp32' install_cmd = 'install.bat esp32'
export_cmd = 'export.bat' export_cmd = 'export.bat'
logging.debug('running {} in {}'.format(install_cmd, idf_copy)) logging.debug(f'running {install_cmd} in {idf_copy}')
subprocess.check_call(install_cmd, env=env, shell=True, cwd=idf_copy) subprocess.check_call(install_cmd, env=env, shell=True, cwd=idf_copy)
logging.debug('running {} in {}'.format(export_cmd, idf_copy)) logging.debug(f'running {export_cmd} in {idf_copy}')
subprocess.check_call(export_cmd, env=env, shell=True, cwd=idf_copy) subprocess.check_call(export_cmd, env=env, shell=True, cwd=idf_copy)