feat(tools/cmake): Added VERSION argument to the project() macro in cmake

This commit enables the standad VERSION argument for the project() macro
in ESP-IDF. The VERSION argument is compilant with the requirements of
cmake 3.16. This commit also adds new test cases for verifying the
validity of the version argument.

Merges https://github.com/espressif/esp-idf/pull/12461

Co-authored-by: Sudeep Mohanty <sudeep.mohanty@espressif.com>
This commit is contained in:
kohait00 2023-10-25 22:05:31 +02:00 committed by Sudeep Mohanty
parent 105f6dd22c
commit 9beda4ce48
10 changed files with 348 additions and 51 deletions

View File

@ -18,7 +18,7 @@ if(NOT BOOTLOADER_BUILD)
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_app_desc") target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_app_desc")
if(CONFIG_APP_PROJECT_VER_FROM_CONFIG) 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. # Gets the version from the CONFIG_APP_PROJECT_VER.
idf_build_set_property(PROJECT_VER "${CONFIG_APP_PROJECT_VER}") idf_build_set_property(PROJECT_VER "${CONFIG_APP_PROJECT_VER}")
endif() endif()

View File

@ -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. * 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 ``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 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 <https://cmake.org/cmake/help/v3.16/command/project.html>`_.
* Else, if the project is located inside a Git repository, the output of git description will be used. * Else, if the project is located inside a Git repository, the output of git description will be used.
* Otherwise, ``PROJECT_VER`` will be "1". * 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. - ``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.

View File

@ -361,6 +361,7 @@ ESP-IDF 在搜索所有待构建的组件时,会按照 ``COMPONENT_DIRS`` 指
* 如果设置 :ref:`CONFIG_APP_PROJECT_VER_FROM_CONFIG` 选项,将会使用 :ref:`CONFIG_APP_PROJECT_VER` 的值。 * 如果设置 :ref:`CONFIG_APP_PROJECT_VER_FROM_CONFIG` 选项,将会使用 :ref:`CONFIG_APP_PROJECT_VER` 的值。
* 或者,如果在项目 CMakeLists.txt 文件中设置了 ``PROJECT_VER`` 变量,则该变量值可以使用。 * 或者,如果在项目 CMakeLists.txt 文件中设置了 ``PROJECT_VER`` 变量,则该变量值可以使用。
* 或者,如果 ``PROJECT_DIR/version.txt`` 文件存在,其内容会用作 ``PROJECT_VER`` 的值。 * 或者,如果 ``PROJECT_DIR/version.txt`` 文件存在,其内容会用作 ``PROJECT_VER`` 的值。
* 或者,如果在 CMakeLists.txt 文件中将 ``VERSION`` 参数传递给 ``project()`` 调用,形式为 ``project(... VERSION x.y.z.w )``,那么 ``VERSION`` 参数将用作为 ``PROJECT_VER`` 的值。``VERSION`` 参数必须符合 `cmake 标准 <https://cmake.org/cmake/help/v3.16/command/project.html>`_
* 或者,如果项目位于某个 Git 仓库中,则使用 ``git describe`` 命令的输出作为 ``PROJECT_VER`` 的值。 * 或者,如果项目位于某个 Git 仓库中,则使用 ``git describe`` 命令的输出作为 ``PROJECT_VER`` 的值。
* 否则,``PROJECT_VER`` 的值为 1。 * 否则,``PROJECT_VER`` 的值为 1。
- ``EXTRA_PARTITION_SUBTYPES``CMake 列表,用于创建额外的分区子类型。子类型的描述由字符串组成,以逗号为分隔,格式为 ``type_name, subtype_name, numeric_value``。组件可通过此列表,添加新的子类型。 - ``EXTRA_PARTITION_SUBTYPES``CMake 列表,用于创建额外的分区子类型。子类型的描述由字符串组成,以逗号为分隔,格式为 ``type_name, subtype_name, numeric_value``。组件可通过此列表,添加新的子类型。

View File

@ -64,30 +64,88 @@ endif()
idf_build_set_property(__COMPONENT_MANAGER_INTERFACE_VERSION 2) 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 # Parse and store the VERSION argument provided to the project() command.
# 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 var) 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 <major>[.<minor>[.<patch>[.<tweak>]]]]
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}") set(_project_path "${CMAKE_CURRENT_LIST_DIR}")
if(NOT DEFINED PROJECT_VER)
if(EXISTS "${_project_path}/version.txt") if(EXISTS "${_project_path}/version.txt")
file(STRINGS "${_project_path}/version.txt" PROJECT_VER) file(STRINGS "${_project_path}/version.txt" PROJECT_VER)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_project_path}/version.txt") 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()
endif()
endif() endif()
set(${var} "${PROJECT_VER}" PARENT_SCOPE) set(${var} "${PROJECT_VER}" PARENT_SCOPE)
endfunction() endfunction()
# paths_with_spaces_to_list # paths_with_spaces_to_list
# #
# Replacement for spaces2list in cases where it was previously used on # 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}) set(build_dir ${CMAKE_BINARY_DIR})
endif() 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}") message(STATUS "Building ESP-IDF components for target ${IDF_TARGET}")

View File

@ -38,21 +38,44 @@ def pytest_addoption(parser: pytest.Parser) -> None:
) )
@pytest.fixture(name='session_work_dir', scope='session', autouse=True) @pytest.fixture(scope='session')
def fixture_session_work_dir(request: FixtureRequest) -> typing.Generator[Path, None, None]: def _session_work_dir(request: FixtureRequest) -> typing.Generator[typing.Tuple[Path, bool], None, None]:
work_dir = request.config.getoption('--work-dir') work_dir = request.config.getoption('--work-dir')
if work_dir: if work_dir:
work_dir = os.path.join(work_dir, datetime.datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S')) 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}') logging.debug(f'using work directory: {work_dir}')
os.makedirs(work_dir, exist_ok=True) os.makedirs(work_dir, exist_ok=True)
clean_dir = None clean_dir = None
is_temp_dir = False
else: else:
work_dir = mkdtemp() work_dir = mkdtemp()
logging.debug(f'created temporary work directory: {work_dir}') logging.debug(f'created temporary work directory: {work_dir}')
clean_dir = work_dir clean_dir = work_dir
is_temp_dir = True
# resolve allows to use relative paths with --work-dir option # resolve allows using relative paths with --work-dir option
yield Path(work_dir).resolve() 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: if clean_dir:
logging.debug(f'cleaning up {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 @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 # by default, use hello_world app and copy it to a temporary directory with
# the name resembling that of the test # the name resembling that of the test
copy_from = 'tools/test_build_system/build_test_app' 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] copy_to = mark.args[1]
path_from = Path(os.environ['IDF_PATH']) / copy_from 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, # if the new directory inside the original directory,
# make sure not to go into recursion. # 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 @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' 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() 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', '.'], 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) 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 @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' copy_to = request.node.name + '_idf'
# allow overriding the destination via pytest.mark.idf_copy() # 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] copy_to = mark.args[0]
path_from = EXT_IDF_PATH 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, # if the new directory inside the original directory,
# make sure not to go into recursion. # make sure not to go into recursion.

View File

@ -16,3 +16,4 @@ junit_log_passing_tests = False
markers = markers =
test_app_copy: specify relative path of the app to copy, and the prefix of the destination directory name 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 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

View File

@ -19,9 +19,9 @@ def assert_built(paths: Union[List[str], List[Path]]) -> None:
assert os.path.exists(path) 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') 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') idf_py('-B', str(alt_build_dir), 'build')
assert os.listdir(alt_build_dir) != [], 'No files found in new build directory!' assert os.listdir(alt_build_dir) != [], 'No files found in new build directory!'
default_build_dir = test_app_copy / 'build' default_build_dir = test_app_copy / 'build'

View File

@ -8,7 +8,7 @@ import subprocess
import typing import typing
from pathlib import Path 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, def run_git_cmd(*args: str,
@ -25,13 +25,6 @@ def run_git_cmd(*args: str,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) 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 # In this test, the action needs to be performed in ESP-IDF that is valid git directory
# Copying ESP-IDF is not possible # Copying ESP-IDF is not possible
def test_git_custom_tag() -> None: def test_git_custom_tag() -> None:

View File

@ -124,12 +124,3 @@ def test_kconfig_multiple_and_target_specific_options(idf_py: IdfPyFunc, test_ap
idf_py('set-target', 'esp32s2') idf_py('set-target', 'esp32s2')
assert all([file_contains((test_app_copy / 'sdkconfig'), x) for x in ['CONFIG_TEST_NEW_OPTION=y', assert all([file_contains((test_app_copy / 'sdkconfig'), x) for x in ['CONFIG_TEST_NEW_OPTION=y',
'CONFIG_TEST_OLD_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

View File

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