diff --git a/components/esp_app_format/CMakeLists.txt b/components/esp_app_format/CMakeLists.txt index 05d7c9ce3c..c25ced06f2 100644 --- a/components/esp_app_format/CMakeLists.txt +++ b/components/esp_app_format/CMakeLists.txt @@ -18,7 +18,7 @@ if(NOT BOOTLOADER_BUILD) target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_app_desc") if(CONFIG_APP_PROJECT_VER_FROM_CONFIG) - # Ignore current PROJECT_VER (which was set in __project_get_revision()). + # Ignore current PROJECT_VER (which was set in project.cmake) # Gets the version from the CONFIG_APP_PROJECT_VER. idf_build_set_property(PROJECT_VER "${CONFIG_APP_PROJECT_VER}") endif() diff --git a/docs/en/api-guides/build-system.rst b/docs/en/api-guides/build-system.rst index 3ca033ea2e..de708a7834 100644 --- a/docs/en/api-guides/build-system.rst +++ b/docs/en/api-guides/build-system.rst @@ -361,6 +361,7 @@ The following are some project/build variables that are available as build prope * If :ref:`CONFIG_APP_PROJECT_VER_FROM_CONFIG` option is set, the value of :ref:`CONFIG_APP_PROJECT_VER` will be used. * Else, if ``PROJECT_VER`` variable is set in project CMakeLists.txt file, its value will be used. * Else, if the ``PROJECT_DIR/version.txt`` exists, its contents will be used as ``PROJECT_VER``. + * Else, if ``VERSION`` argument is passed to the ``project()`` call in the CMakeLists.txt file as ``project(... VERSION x.y.z.w )`` then it will be used as ``PROJECT_VER``. The ``VERSION`` argument must be compilant with the `cmake standard `_. * Else, if the project is located inside a Git repository, the output of git description will be used. * Otherwise, ``PROJECT_VER`` will be "1". - ``EXTRA_PARTITION_SUBTYPES``: CMake list of extra partition subtypes. Each subtype description is a comma-separated string with ``type_name, subtype_name, numeric_value`` format. Components may add new subtypes by appending them to this list. diff --git a/docs/zh_CN/api-guides/build-system.rst b/docs/zh_CN/api-guides/build-system.rst index 344c8782fb..dfe2c1fa6d 100644 --- a/docs/zh_CN/api-guides/build-system.rst +++ b/docs/zh_CN/api-guides/build-system.rst @@ -361,6 +361,7 @@ ESP-IDF 在搜索所有待构建的组件时,会按照 ``COMPONENT_DIRS`` 指 * 如果设置 :ref:`CONFIG_APP_PROJECT_VER_FROM_CONFIG` 选项,将会使用 :ref:`CONFIG_APP_PROJECT_VER` 的值。 * 或者,如果在项目 CMakeLists.txt 文件中设置了 ``PROJECT_VER`` 变量,则该变量值可以使用。 * 或者,如果 ``PROJECT_DIR/version.txt`` 文件存在,其内容会用作 ``PROJECT_VER`` 的值。 + * 或者,如果在 CMakeLists.txt 文件中将 ``VERSION`` 参数传递给 ``project()`` 调用,形式为 ``project(... VERSION x.y.z.w )``,那么 ``VERSION`` 参数将用作为 ``PROJECT_VER`` 的值。``VERSION`` 参数必须符合 `cmake 标准 `_。 * 或者,如果项目位于某个 Git 仓库中,则使用 ``git describe`` 命令的输出作为 ``PROJECT_VER`` 的值。 * 否则,``PROJECT_VER`` 的值为 1。 - ``EXTRA_PARTITION_SUBTYPES``:CMake 列表,用于创建额外的分区子类型。子类型的描述由字符串组成,以逗号为分隔,格式为 ``type_name, subtype_name, numeric_value``。组件可通过此列表,添加新的子类型。 diff --git a/tools/cmake/project.cmake b/tools/cmake/project.cmake index 2ebb31ff35..e92e5855d5 100644 --- a/tools/cmake/project.cmake +++ b/tools/cmake/project.cmake @@ -64,30 +64,88 @@ endif() idf_build_set_property(__COMPONENT_MANAGER_INTERFACE_VERSION 2) # -# Get the project version from either a version file or the Git revision. This is passed -# to the idf_build_process call. Dependencies are also set here for when the version file -# changes (if it is used). +# Parse and store the VERSION argument provided to the project() command. # -function(__project_get_revision var) - set(_project_path "${CMAKE_CURRENT_LIST_DIR}") - if(NOT DEFINED PROJECT_VER) - if(EXISTS "${_project_path}/version.txt") - file(STRINGS "${_project_path}/version.txt" PROJECT_VER) - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_project_path}/version.txt") - else() - git_describe(PROJECT_VER_GIT "${_project_path}") - if(PROJECT_VER_GIT) - set(PROJECT_VER ${PROJECT_VER_GIT}) - else() - message(STATUS "Could not use 'git describe' to determine PROJECT_VER.") - set(PROJECT_VER 1) - endif() +function(__parse_and_store_version_arg) + # The project_name is the fisrt argument that was passed to the project() command + set(project_name ${ARGV0}) + + # Parse other arguments passed to the project() call + set(options) + set(oneValueArgs VERSION) + set(multiValueArgs) + cmake_parse_arguments(PROJECT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # If the VERSION keyword exists but no version string is provided then raise a warning + if((NOT PROJECT_VERSION + OR PROJECT_VERSION STREQUAL "NOTFOUND") + AND NOT PROJECT_VERSION STREQUAL "0") + message(STATUS "VERSION keyword not followed by a value or was followed by a value that expanded to nothing.") + # Default the version to 1 in this case + set(project_ver 1) + else() + # Check if version is valid. cmake allows the version to be in the format [.[.[.]]]] + string(REGEX MATCH "^([0-9]+(\\.[0-9]+(\\.[0-9]+(\\.[0-9]+)?)?)?)?$" version_valid ${PROJECT_VERSION}) + if(NOT version_valid AND NOT PROJECT_VERSION STREQUAL "0") + message(SEND_ERROR "Version \"${PROJECT_VERSION}\" format invalid.") + return() endif() + + # Split the version string into major, minor, patch, and tweak components + string(REPLACE "." ";" version_components ${PROJECT_VERSION}) + list(GET version_components 0 PROJECT_VERSION_MAJOR) + list(LENGTH version_components version_length) + if(version_length GREATER 1) + list(GET version_components 1 PROJECT_VERSION_MINOR) + endif() + if(version_length GREATER 2) + list(GET version_components 2 PROJECT_VERSION_PATCH) + endif() + if(version_length GREATER 3) + list(GET version_components 3 PROJECT_VERSION_TWEAK) + endif() + + # Store the version string in cmake specified variables to access the version + set(PROJECT_VERSION ${PROJECT_VERSION} PARENT_SCOPE) + set(PROJECT_VERSION_MAJOR ${PROJECT_VERSION_MAJOR} PARENT_SCOPE) + if(PROJECT_VERSION_MINOR) + set(PROJECT_VERSION_MINOR ${PROJECT_VERSION_MINOR} PARENT_SCOPE) + endif() + if(PROJECT_VERSION_PATCH) + set(PROJECT_VERSION_PATCH ${PROJECT_VERSION_PATCH} PARENT_SCOPE) + endif() + if(PROJECT_VERSION_TWEAK) + set(PROJECT_VERSION_TWEAK ${PROJECT_VERSION_TWEAK} PARENT_SCOPE) + endif() + + # Also store the version string in the specified variables for the project_name + set(${project_name}_VERSION ${PROJECT_VERSION} PARENT_SCOPE) + set(${project_name}_VERSION_MAJOR ${PROJECT_VERSION_MAJOR} PARENT_SCOPE) + if(PROJECT_VERSION_MINOR) + set(${project_name}_VERSION_MINOR ${PROJECT_VERSION_MINOR} PARENT_SCOPE) + endif() + if(PROJECT_VERSION_PATCH) + set(${project_name}_VERSION_PATCH ${PROJECT_VERSION_PATCH} PARENT_SCOPE) + endif() + if(PROJECT_VERSION_TWEAK) + set(${project_name}_VERSION_TWEAK ${PROJECT_VERSION_TWEAK} PARENT_SCOPE) + endif() + endif() +endfunction() + +# +# Get the project version from a version file. This is passed to the idf_build_process call. +# Dependencies are also set here for when the version file changes (if it is used). +# +function(__project_get_revision_from_version_file var) + set(_project_path "${CMAKE_CURRENT_LIST_DIR}") + if(EXISTS "${_project_path}/version.txt") + file(STRINGS "${_project_path}/version.txt" PROJECT_VER) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_project_path}/version.txt") endif() set(${var} "${PROJECT_VER}" PARENT_SCOPE) endfunction() - # paths_with_spaces_to_list # # Replacement for spaces2list in cases where it was previously used on @@ -598,7 +656,54 @@ macro(project project_name) set(build_dir ${CMAKE_BINARY_DIR}) endif() - __project_get_revision(project_ver) + # If PROJECT_VER has not been set yet, look for the version from various sources in the following order of priority: + # + # 1. version.txt file in the top level project directory + # 2. From the VERSION argument if passed to the project() macro + # 3. git describe if the project is in a git repository + # 4. Default to 1 if none of the above conditions are true + # + # PS: PROJECT_VER will get overidden later if CONFIG_APP_PROJECT_VER_FROM_CONFIG is defined. + # See components/esp_app_format/CMakeLists.txt. + if(NOT DEFINED PROJECT_VER) + # Read the version information from the version.txt file if it is present + __project_get_revision_from_version_file(project_ver) + + # If the version is not set from the version.txt file, check other sources for the version information + if(NOT project_ver) + # Check if version information was passed to project() via the VERSION argument + set(version_keyword_present FALSE) + foreach(arg ${ARGN}) + if(${arg} STREQUAL "VERSION") + set(version_keyword_present TRUE) + endif() + endforeach() + + if(version_keyword_present) + __parse_and_store_version_arg(${project_name} ${ARGN}) + set(project_ver ${PROJECT_VERSION}) + + # If the project() command is called from the top-level CMakeLists.txt, + # store the version in CMAKE_PROJECT_VERSION. + if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(CMAKE_PROJECT_VERSION ${PROJECT_VERSION}) + endif() + else() + # Use git describe to determine the version + git_describe(PROJECT_VER_GIT "${CMAKE_CURRENT_LIST_DIR}") + if(PROJECT_VER_GIT) + set(project_ver ${PROJECT_VER_GIT}) + else() + message(STATUS "Could not use 'git describe' to determine PROJECT_VER.") + # None of sources contain the version information. Default PROJECT_VER to 1. + set(project_ver 1) + endif() #if(PROJECT_VER_GIT) + endif() #if(version_keyword_present) + endif() #if(NOT project_ver) + else() + # PROJECT_VER has been set before calling project(). Copy it into project_ver for idf_build_process() later. + set(project_ver ${PROJECT_VER}) + endif() #if(NOT DEFINED PROJECT_VER) message(STATUS "Building ESP-IDF components for target ${IDF_TARGET}") diff --git a/tools/test_build_system/conftest.py b/tools/test_build_system/conftest.py index 94e0348ffb..7c84fe3987 100644 --- a/tools/test_build_system/conftest.py +++ b/tools/test_build_system/conftest.py @@ -38,21 +38,44 @@ def pytest_addoption(parser: pytest.Parser) -> None: ) -@pytest.fixture(name='session_work_dir', scope='session', autouse=True) -def fixture_session_work_dir(request: FixtureRequest) -> typing.Generator[Path, None, None]: +@pytest.fixture(scope='session') +def _session_work_dir(request: FixtureRequest) -> typing.Generator[typing.Tuple[Path, bool], None, None]: work_dir = request.config.getoption('--work-dir') + if work_dir: work_dir = os.path.join(work_dir, datetime.datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S')) logging.debug(f'using work directory: {work_dir}') os.makedirs(work_dir, exist_ok=True) clean_dir = None + is_temp_dir = False else: work_dir = mkdtemp() logging.debug(f'created temporary work directory: {work_dir}') clean_dir = work_dir + is_temp_dir = True - # resolve allows to use relative paths with --work-dir option - yield Path(work_dir).resolve() + # resolve allows using relative paths with --work-dir option + yield Path(work_dir).resolve(), is_temp_dir + + if clean_dir: + logging.debug(f'cleaning up {clean_dir}') + shutil.rmtree(clean_dir, ignore_errors=True) + + +@pytest.fixture(name='func_work_dir', autouse=True) +def work_dir(request: FixtureRequest, _session_work_dir: typing.Tuple[Path, bool]) -> typing.Generator[Path, None, None]: + session_work_dir, is_temp_dir = _session_work_dir + + if request._pyfuncitem.keywords.get('force_temp_work_dir') and not is_temp_dir: + work_dir = Path(mkdtemp()).resolve() + logging.debug('Force using temporary work directory') + clean_dir = work_dir + else: + work_dir = session_work_dir + clean_dir = None + + # resolve allows using relative paths with --work-dir option + yield work_dir if clean_dir: logging.debug(f'cleaning up {clean_dir}') @@ -60,7 +83,7 @@ def fixture_session_work_dir(request: FixtureRequest) -> typing.Generator[Path, @pytest.fixture -def test_app_copy(session_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]: +def test_app_copy(func_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]: # by default, use hello_world app and copy it to a temporary directory with # the name resembling that of the test copy_from = 'tools/test_build_system/build_test_app' @@ -74,7 +97,7 @@ def test_app_copy(session_work_dir: Path, request: FixtureRequest) -> typing.Gen copy_to = mark.args[1] path_from = Path(os.environ['IDF_PATH']) / copy_from - path_to = session_work_dir / copy_to + path_to = func_work_dir / copy_to # if the new directory inside the original directory, # make sure not to go into recursion. @@ -99,13 +122,13 @@ def test_app_copy(session_work_dir: Path, request: FixtureRequest) -> typing.Gen @pytest.fixture -def test_git_template_app(session_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]: +def test_git_template_app(func_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]: copy_to = request.node.name + '_app' - path_to = session_work_dir / copy_to + path_to = func_work_dir / copy_to - logging.debug(f'clonning git-teplate app to {path_to}') + logging.debug(f'cloning git-template app to {path_to}') path_to.mkdir() - # No need to clone full repository, just single master branch + # No need to clone full repository, just a single master branch subprocess.run(['git', 'clone', '--single-branch', '-b', 'master', '--depth', '1', 'https://github.com/espressif/esp-idf-template.git', '.'], cwd=path_to, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -122,7 +145,7 @@ def test_git_template_app(session_work_dir: Path, request: FixtureRequest) -> ty @pytest.fixture -def idf_copy(session_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]: +def idf_copy(func_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]: copy_to = request.node.name + '_idf' # allow overriding the destination via pytest.mark.idf_copy() @@ -131,7 +154,7 @@ def idf_copy(session_work_dir: Path, request: FixtureRequest) -> typing.Generato copy_to = mark.args[0] path_from = EXT_IDF_PATH - path_to = session_work_dir / copy_to + path_to = func_work_dir / copy_to # if the new directory inside the original directory, # make sure not to go into recursion. diff --git a/tools/test_build_system/pytest.ini b/tools/test_build_system/pytest.ini index fad075f0bb..0db692d048 100644 --- a/tools/test_build_system/pytest.ini +++ b/tools/test_build_system/pytest.ini @@ -16,3 +16,4 @@ junit_log_passing_tests = False markers = test_app_copy: specify relative path of the app to copy, and the prefix of the destination directory name idf_copy: specify the prefix of the destination directory where IDF should be copied + force_temp_work_dir: force temporary folder as the working directory diff --git a/tools/test_build_system/test_build.py b/tools/test_build_system/test_build.py index 35d59bf2be..5529aab277 100644 --- a/tools/test_build_system/test_build.py +++ b/tools/test_build_system/test_build.py @@ -19,9 +19,9 @@ def assert_built(paths: Union[List[str], List[Path]]) -> None: assert os.path.exists(path) -def test_build_alternative_directories(idf_py: IdfPyFunc, session_work_dir: Path, test_app_copy: Path) -> None: +def test_build_alternative_directories(idf_py: IdfPyFunc, func_work_dir: Path, test_app_copy: Path) -> None: logging.info('Moving BUILD_DIR_BASE out of tree') - alt_build_dir = session_work_dir / 'alt_build' + alt_build_dir = func_work_dir / 'alt_build' idf_py('-B', str(alt_build_dir), 'build') assert os.listdir(alt_build_dir) != [], 'No files found in new build directory!' default_build_dir = test_app_copy / 'build' diff --git a/tools/test_build_system/test_git.py b/tools/test_build_system/test_git.py index 25109c0ce1..57cdf3e431 100644 --- a/tools/test_build_system/test_git.py +++ b/tools/test_build_system/test_git.py @@ -8,7 +8,7 @@ import subprocess import typing from pathlib import Path -from test_build_system_helpers import EnvDict, IdfPyFunc, run_idf_py +from test_build_system_helpers import EnvDict, run_idf_py def run_git_cmd(*args: str, @@ -25,13 +25,6 @@ def run_git_cmd(*args: str, stdout=subprocess.PIPE, stderr=subprocess.PIPE) -def test_get_version_from_git_describe(test_git_template_app: Path, idf_py: IdfPyFunc) -> None: - logging.info('Get the version of app from git describe. Project is not inside IDF and do not have a tag only a hash commit.') - idf_ret = idf_py('reconfigure') - git_ret = run_git_cmd('describe', '--always', '--tags', '--dirty', workdir=test_git_template_app) - assert f'App "app-template" version: {git_ret.stdout.decode("utf-8")}' in idf_ret.stdout, 'Project version should have a hash commit' - - # In this test, the action needs to be performed in ESP-IDF that is valid git directory # Copying ESP-IDF is not possible def test_git_custom_tag() -> None: diff --git a/tools/test_build_system/test_kconfig.py b/tools/test_build_system/test_kconfig.py index 4db2dfdf0a..ddcdba2132 100644 --- a/tools/test_build_system/test_kconfig.py +++ b/tools/test_build_system/test_kconfig.py @@ -124,12 +124,3 @@ def test_kconfig_multiple_and_target_specific_options(idf_py: IdfPyFunc, test_ap idf_py('set-target', 'esp32s2') assert all([file_contains((test_app_copy / 'sdkconfig'), x) for x in ['CONFIG_TEST_NEW_OPTION=y', 'CONFIG_TEST_OLD_OPTION=y']]) - - -def test_kconfig_get_version_from_describe(idf_py: IdfPyFunc, test_app_copy: Path) -> None: - logging.info('Get the version of app from Kconfig option') - (test_app_copy / 'version.txt').write_text('project_version_from_txt') - (test_app_copy / 'sdkconfig.defaults').write_text('\n'.join(['CONFIG_APP_PROJECT_VER_FROM_CONFIG=y', - 'CONFIG_APP_PROJECT_VER="project_version_from_Kconfig"'])) - ret = idf_py('build') - assert 'App "build_test_app" version: project_version_from_Kconfig' in ret.stdout diff --git a/tools/test_build_system/test_versions.py b/tools/test_build_system/test_versions.py new file mode 100644 index 0000000000..b4109fd574 --- /dev/null +++ b/tools/test_build_system/test_versions.py @@ -0,0 +1,182 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import logging +import os +import subprocess +import typing +from pathlib import Path + +import pytest +from test_build_system_helpers import EnvDict, IdfPyFunc, append_to_file, replace_in_file + + +############################################################################################# +# Test Case: Test that the build-system can set the default version for an IDF app +# +# Test Steps: +# 1. Copy the base build_test_app +# 2. Run idf.py reconfigure +# 3. Verify that the app version takes the default value of 1 +# +# Note: This test must run outside a git repository for it to pass. Hence we force the test +# to use a temporary work directory. +############################################################################################# +@pytest.mark.force_temp_work_dir +def test_versions_get_default_version(idf_py: IdfPyFunc, test_app_copy: Path) -> None: + logging.info('Verify the default version of an app') + ret = idf_py('reconfigure') + assert 'App "build_test_app" version: 1' in ret.stdout + + +############################################################################################# +# Test Case: Test that the build-system can set the version of an IDF app from git describe +# +# Test Steps: +# 1. Clone the idf template app from https://github.com/espressif/esp-idf-template.git +# 2. Run idf.py reconfigure +# 3. Run git describe in the cloned app git repository +# 4. Verify that the app version is picked up from the git describe command +# +############################################################################################# +def test_versions_get_version_from_git_describe(idf_py: IdfPyFunc, + test_git_template_app: Path, + env: typing.Optional[EnvDict] = None) -> None: + logging.info('Verify that the version of app can be set from git describe') + idf_ret = idf_py('reconfigure') + env_dict = dict(**os.environ) + if env: + env_dict.update(env) + git_ret = subprocess.run(['git', 'describe', '--always', '--tags', '--dirty'], + cwd=test_git_template_app, env=env_dict, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + assert f'App "app-template" version: {git_ret.stdout.decode("utf-8")}' in idf_ret.stdout + + +############################################################################################# +# Test Case: Test that the build-system can set the version for an IDF app from the VERSION argument +# +# Test Steps: +# 1. Clone the idf template app from https://github.com/espressif/esp-idf-template.git +# 2. Replace the default project() command in the top level CMakeLists.txt file to call the version parsing +# function __parse_and_store_version_arg() +# 3. Append several calls to __parse_and_store_version_arg() with different inputs for the VERSION argument +# 4. Append a project() call with valid arguments at the end of the CMakeLists.txt file +# 5. Run idf.py reconfigure +# 6. Verify that cmake correctly flags invalid inputs for the VERSION argument and accepts valid inputs for the same +# +############################################################################################# +def test_versions_get_version_from_version_arg(idf_py: IdfPyFunc, test_git_template_app: Path) -> None: + logging.info('Verify that the VERSION argument in project() is correctly parsed by cmake') + + # empty VERSION argument + replace_in_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template)', + '__parse_and_store_version_arg(app-template VERSION)') + # Invalid VERSION argument format + append_to_file((test_git_template_app / 'CMakeLists.txt'), + '\n__parse_and_store_version_arg(app-tempplate VERSION 1..2)') + # Invalid VERSION argument format + append_to_file((test_git_template_app / 'CMakeLists.txt'), + '\n__parse_and_store_version_arg(app-template VERSION version_text)') + # Invalid VERSION argument format + append_to_file((test_git_template_app / 'CMakeLists.txt'), + '\n__parse_and_store_version_arg(app-template VERSION 1.2.3.4.5)') + append_to_file((test_git_template_app / 'CMakeLists.txt'), + '\n__parse_and_store_version_arg(app-template VERSION 0)') + # Valid VERSION argument format + append_to_file((test_git_template_app / 'CMakeLists.txt'), + '\n__parse_and_store_version_arg(app-template VERSION 0.1)') + # Valid VERSION argument format + append_to_file((test_git_template_app / 'CMakeLists.txt'), + '\n__parse_and_store_version_arg(app-template VERSION 0.1.2)') + # Valid VERSION argument format + append_to_file((test_git_template_app / 'CMakeLists.txt'), + '\n__parse_and_store_version_arg(app-template VERSION 0.1.2.3)') + # project() call with valid VERSION argument format + append_to_file((test_git_template_app / 'CMakeLists.txt'), + '\nproject(app-template VERSION 0.1.2.3)') + + with pytest.raises(subprocess.CalledProcessError) as e: + idf_py('reconfigure') + + assert 'VERSION keyword not followed by a value or was followed by a value that expanded to nothing.' in e.stdout + assert 'Version "1..2" format invalid' in e.stderr + assert 'Version "version_text" format invalid' in e.stderr + assert 'Version "1.2.3.4.5" format invalid' in e.stderr + assert 'Version "1.2.3.4.5" format invalid' in e.stderr + assert 'Version "0" format invalid' not in e.stderr + assert 'Version "0.1" format invalid' not in e.stderr + assert 'Version "0.1.2" format invalid' not in e.stderr + assert 'Version "0.1.2.3" format invalid' not in e.stderr + assert 'App "app-template" version: 0.1.2.3' in e.stdout + + +############################################################################################# +# Test Case: Test that the build-system can set the version of an IDF app from version.txt file +# +# Test Steps: +# 1. Clone the idf template app from https://github.com/espressif/esp-idf-template.git +# 2. Replace the default project() command in the top level CMakeLists.txt file to include VERSION argument +# 3. Copy version.txt file into the cloned app repository +# 4. Updated the version in version.txt file to a known value +# 5. Run idf.py reconfigure +# 6. Verify that the app version is picked up from the version.txt file +# +############################################################################################# +def test_versions_get_version_from_version_file(idf_py: IdfPyFunc, test_git_template_app: Path) -> None: + logging.info('Verify that the version of app can be set from version.txt file') + replace_in_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template)', + 'project(app-template VERSION 0.1.2.3)') + (test_git_template_app / 'version.txt').write_text('project_version_from_txt') + idf_ret = idf_py('reconfigure') + + assert f'App "app-template" version: project_version_from_txt' in idf_ret.stdout + + +############################################################################################# +# Test Case: Test that the build-system can set the version of an IDF app if PROJECT_VER is set in the CMakeLists.txt +# +# Test Steps: +# 1. Clone the idf template app from https://github.com/espressif/esp-idf-template.git +# 2. Update CMakeLists.txt file to set PROJECT_VER before calling project() +# 3. Replace the default project() command in the top level CMakeLists.txt file to include VERSION argument +# 4. Copy version.txt file into the cloned app repository +# 5. Updated the version in version.txt file to a known value +# 6. Run idf.py reconfigure +# 7. Verify that the app version is picked up from the CMakeLists.txt file +# +############################################################################################# +def test_versions_get_version_from_top_level_cmake(idf_py: IdfPyFunc, test_git_template_app: Path) -> None: + logging.info('Verify that the version of app can be set from PROJECT_VER in CMakeLists.txt') + replace_in_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template)', + 'set(PROJECT_VER project_version_from_CMakeLists)') + append_to_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template VERSION 0.1.2.3)') + (test_git_template_app / 'version.txt').write_text('project_version_from_txt') + idf_ret = idf_py('reconfigure') + + assert f'App "app-template" version: project_version_from_CMakeLists' in idf_ret.stdout + + +############################################################################################# +# Test Case: Test that the build-system can set the version of an IDF app from Kconfig option +# +# Test Steps: +# 1. Clone the idf template app from https://github.com/espressif/esp-idf-template.git +# 2. Update CMakeLists.txt file to set PROJECT_VER before calling project() +# 3. Replace the default project() command in the top level CMakeLists.txt file to include VERSION argument +# 4. Copy version.txt file into the cloned app repository +# 5. Updated the version in version.txt file to a known value +# 6. Run idf.py reconfigure +# 7. Updated sdkconfig.defaults to configure CONFIG_APP_PROJECT_VER_FROM_CONFIG and CONFIG_APP_PROJECT_VER +# 8. Run idf.py reconfigure +# 9. Verify that the app version is picked up from the Kconfig option +# +############################################################################################# +def test_versions_get_version_from_kconfig_option(idf_py: IdfPyFunc, test_git_template_app: Path) -> None: + logging.info('Verify that the version of app can be set from Kconfig option') + replace_in_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template)', + 'set(PROJECT_VER project_version_from_CMakeLists)') + append_to_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template VERSION 0.1.2.3)') + (test_git_template_app / 'sdkconfig.defaults').write_text('\n'.join(['CONFIG_APP_PROJECT_VER_FROM_CONFIG=y', + 'CONFIG_APP_PROJECT_VER="project_version_from_Kconfig"'])) + idf_ret = idf_py('reconfigure') + + assert f'App "app-template" version: project_version_from_Kconfig' in idf_ret.stdout