版本号管理是软件开发中至关重要的一环,它有助于标识和跟踪软件的发布历史、功能改进和修复。在本篇博客中,我们将探索Zephyr的版本号的管理和引用。 Zephyr支持两种版本号,一种是标识Zephyr内核的版本,内核版本随着Zephyr的发布而修改,另一种是应用版本号,Zephyr提供应用版本号的生成机制,通过该机制应用开发者设定的应用版本号可以被Zephyr识别并引用。
版本号构成 链接到标题
无论是内核还是应用的版本都由下面字段构成
- VERSION_MAJOR: 主版本号,表示软件的重大功能改进和结构性变化,只能是数字
- VERSION_MINOR = 次版本号,表示较小的功能增加、改进或修复,只能是数字
- PATCHLEVEL = 补丁级别,表示针对软件中的错误或问题进行的修复,只能是数字
- VERSION_TWEAK = 微调版本号,表示对软件进行微小的调整或修订,通常不包含重大变化,只能是数字
- EXTRAVERSION = 额外版本信息,用于标识特殊版本或预发布版本,由数字和小写字母(a-z)构成
例如下面内容对应的版本号就是3.4.0-rc3, 在Zephyr中一般不将VERSION_TWEAK显示在版本号字符串中
VERSION_MAJOR = 3
VERSION_MINOR = 4
PATCHLEVEL = 0
VERSION_TWEAK = 0
EXTRAVERSION = rc3
Zephyr内核版本 链接到标题
Zephyr内核版本被定义在文件zephyr/VERSION
中,目前的版本号如下
VERSION_MAJOR = 3
VERSION_MINOR = 4
PATCHLEVEL = 99
VERSION_TWEAK = 0
EXTRAVERSION =
也就是3.4.99
管理方式 链接到标题
每次LTS发布后VERSION_MAJOR
变化一次,例如第一个LTS为1.14.1,之后就开始发布2.0.0, 第二个LTS为2.7.0,之后就发布3.0.0,按计划下一个LTS是3.7.0, 之后就升到4.0.0
每次发布VERSION_MINOR
变化一次,例如目前最新的发布版本3.4.0,下一个发布版本就是3.5.0
开发周期内PATCHLEVEL
为99,例如现在最新的发布版本是3.4.0,下一个要发布的版本是3.5.0,目前处于3.5.0的开发周期,目前VERSION文件中PATCHLEVEL就是99.LTS发布后,LTS内维护Patch的发布一次PATCHLEVEL
,例如LTS2中目前已经到了2.7.5
VERSION_TWEAK
在zephyr中基本没有使用
EXTRAVERSION
正式发布前使用,例如3.4.0发布前,有3.4.0-rc1, 3.4.0-rc2, 3.4.0-rc3
引用访问 链接到标题
Zephyr在构建期通过cmake脚本将zephyr/VERSION
文件中设置的版本号信息生成为c头文件build/zephyr/include/generated/version.h
, 通过包含version.h
文件引用版本信息。
对于下面VERSION
文件
VERSION_MAJOR = 3
VERSION_MINOR = 4
PATCHLEVEL = 0
VERSION_TWEAK = 0
EXTRAVERSION = rc3
生成的version.h
文件内容为:
#ifndef _KERNEL_VERSION_H_
#define _KERNEL_VERSION_H_
/* values come from cmake/version.cmake
* BUILD_VERSION related values will be 'git describe',
* alternatively user defined BUILD_VERSION.
*/
#define ZEPHYR_VERSION_CODE 197632
#define ZEPHYR_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define KERNELVERSION 0x3040000
#define KERNEL_VERSION_NUMBER 0x30400
#define KERNEL_VERSION_MAJOR 3
#define KERNEL_VERSION_MINOR 4
#define KERNEL_PATCHLEVEL 0
#define KERNEL_VERSION_STRING "3.4.0-rc3"
#define BUILD_VERSION v3.4.0-rc3
#endif /* _KERNEL_VERSION_H_ */
zephyr/include/zephyr/kernel_version.h
提供对外接口,访问这些信息
//取版本号中的信息
#define SYS_KERNEL_VER_MAJOR(ver) (((ver) >> 24) & 0xFF)
#define SYS_KERNEL_VER_MINOR(ver) (((ver) >> 16) & 0xFF)
#define SYS_KERNEL_VER_PATCHLEVEL(ver) (((ver) >> 8) & 0xFF)
//获取版本号
extern uint32_t sys_kernel_version_get(void);
sys_kernel_version_get
实现在zephyr/kernel/version.c
中就是直接返回KERNEL_VERSION_STRING
uint32_t sys_kernel_version_get(void)
{
return KERNELVERSION;
}
也可以不使用Zephyr提供的API,在应用代码中#include "version.h"
就可以直接访问里面的版本信息。
Zephyr应用版本号 链接到标题
Zephyr提供应用程序版本管理系统,该系统和Zephyr内核版本系统管理方式相同。在应用代码的文件夹下放置VERSION
文件,格式和内核的一致
VERSION_MAJOR = 1
VERSION_MINOR = 0
PATCHLEVEL = 0
VERSION_TWEAK = 0
EXTRAVERSION = rc1
引用访问 链接到标题
应用的版本号可以在源代码,CMake, Kconfig中被引用。
源代码中引用
Zephyr在构建期通过脚本zephyr/cmake/module/version.cmake
将应用文件夹下的VERSION
文件中设置的版本号信息生成为c头文件build/zephyr/include/generated/app_version.h
, 通过包含app_version.h
文件引用版本信息。上面示例VERSION
产生的文件如下
#ifndef _APP_VERSION_H_
#define _APP_VERSION_H_
/* values come from cmake/version.cmake
* BUILD_VERSION related values will be 'git describe',
* alternatively user defined BUILD_VERSION.
*/
/* #undef ZEPHYR_VERSION_CODE */
/* #undef ZEPHYR_VERSION */
#define APPVERSION 0x1000000
#define APP_VERSION_NUMBER 0x10000
#define APP_VERSION_MAJOR 1
#define APP_VERSION_MINOR 0
#define APP_PATCHLEVEL 0
#define APP_VERSION_STRING "1.0.0"
#define APP_BUILD_VERSION 26645bfa6b53
#endif /* _APP_VERSION_H_ */
包含app_version.h
文件即可使用,对应关系如下
APPVERSION
:(VERSION_MAJOR<<24)|(VERSION_MINOR<<16)|(PATCHLEVEL<<8)| VERSION_TWEAK
APP_VERSION_NUMBER
:(VERSION_MAJOR<<16)|(VERSION_MINOR<<8)| PATCHLEVEL
APP_VERSION_MAJOR
:VERSION_MAJOR
APP_VERSION_MINOR
:VERSION_MINOR
APP_PATCHLEVEL
:PATCHLEVEL
APP_VERSION_STRING
:"VERSION_MAJOR.VERSION_MINOR.PATCHLEVEL-EXTRAVERSION"
APP_BUILD_VERSION
: app所在git仓库当前的版本号
Kconfig中引用 Kconfig通过访问下面变量访问应用版本信息
$(VERSION_MAJOR)
:VERSION_MAJOR
, 1$(VERSION_MINOR)
:VERSION_MINOR
, 0$(PATCHLEVEL)
:PATCHLEVEL
, 0$(VERSION_TWEAK)
:VERSION_TWEAK
, 0$(APPVERSION)
:VERSION_MAJOR.VERSION_MINOR.PATCHLEVEL-EXTRAVERSION
,“1.0.0-rc1”
**Cmake中引用 Cmake通过访问下面变量访问应用版本信息
APPVERSION
:(VERSION_MAJOR<<24)|(VERSION_MINOR<<16)|(PATCHLEVEL<<8)| VERSION_TWEAK
, 16进制, 0x1000000APP_VERSION_NUMBER
:(VERSION_MAJOR<<16)|(VERSION_MINOR<<8)| PATCHLEVEL
, 16进制, 0x10000APP_VERSION_MAJOR
:VERSION_MAJOR
, 10进制, 1APP_VERSION_MINOR
:VERSION_MINOR
, 10进制, 0APP_PATCHLEVEL
:PATCHLEVEL
, 10进制, 0APP_VERSION_TWEAK
:VERSION_TWEAK
, 10进制, 0APP_VERSION_STRING
:"VERSION_MAJOR.VERSION_MINOR.PATCHLEVEL-EXTRAVERSION"
, 字符串,“1.0.0-rc1”
参考 链接到标题
https://docs.zephyrproject.org/latest/kernel/services/other/version.html#version https://docs.zephyrproject.org/latest/build/version/index.html#version-file
版本生成分析 链接到标题
前面提到过版本信息的生成是通过cmake脚本读取VERSION文件转化完成c头文件,下面这幅图说明了这一过程
图中涉及代码的作用和路径:
zephyr/CMakeLists.txt
根CMakeList,调用gen_version_h.cmake脚本zephyr/cmake/gen_version_h.cmake
版本头文件生成脚本,读取模板zephyr/version.h.in,并按照模板将版本CMake变量写入头文件zephyr/cmake/modules/version.cmake
读取VERSION文件生成CMake变量VERSION_TYPE
:生成版本文件的类型,决定VERSION_FILE
从何处读取VERSION和生成那种版本头文件的名称- KERNEL: 读取
zephyr/VERSION
, 生成version.h - APP: 读取
${APPLICATION_SOURCE_DIR}/VERSION
,生成app_version.h
- KERNEL: 读取
上面为分析版本生成的简要指南,下面的付费内容将详细解释这一流程:
顶级CMake 链接到标题
Zephyr的顶级CMake文件zephyr/CMakeLists.txt
指定执行的生成版本信息命令脚本gen_version_h.cmake
,其它参数信息通过-D进行变量设置传递
# 生成内核版本信息
add_custom_command(
OUTPUT ${PROJECT_BINARY_DIR}/include/generated/version.h
COMMAND ${CMAKE_COMMAND} -DZEPHYR_BASE=${ZEPHYR_BASE} # 执行cmake命令
-DOUT_FILE=${PROJECT_BINARY_DIR}/include/generated/version.h # 指定输出文件路径
-DVERSION_TYPE=KERNEL # 指定生成版本信息的类型为内核
-DVERSION_FILE=${ZEPHYR_BASE}/VERSION # 指定要读取的VERSION文件路径为zephyr/VERSION
${build_version_argument} # 指定build版本参数
-P ${ZEPHYR_BASE}/cmake/gen_version_h.cmake # 要执行的cmake脚本
DEPENDS ${ZEPHYR_BASE}/VERSION ${git_dependency}
)
add_custom_target(version_h DEPENDS ${PROJECT_BINARY_DIR}/include/generated/version.h)
# 生成应用版本信息
if(EXISTS ${APPLICATION_SOURCE_DIR}/VERSION)
add_custom_command(
OUTPUT ${PROJECT_BINARY_DIR}/include/generated/app_version.h
COMMAND ${CMAKE_COMMAND} -DZEPHYR_BASE=${ZEPHYR_BASE} # 执行cmake命令
-DOUT_FILE=${PROJECT_BINARY_DIR}/include/generated/app_version.h # 指定输出文件路径
-DVERSION_TYPE=APP # 指定生成版本信息的类型为应用
-DVERSION_FILE=${APPLICATION_SOURCE_DIR}/VERSION # 指定要读取的VERSION文件路径为应用下面的VERSION
${build_version_argument} # 指定build版本参数
-P ${ZEPHYR_BASE}/cmake/gen_version_h.cmake # 要执行的cmake脚本
DEPENDS ${APPLICATION_SOURCE_DIR}/VERSION ${git_dependency}
)
add_custom_target(app_version_h DEPENDS ${PROJECT_BINARY_DIR}/include/generated/app_version.h)
add_dependencies(zephyr_interface app_version_h)
endif()
可以看到内核和应用都是使用通一个脚本zephyr/cmake/gen_version_h.cmake
来生成,只是指定的参数不一样而已。
cmake生成脚本 链接到标题
zephyr/cmake/gen_version_h.cmake
读取输入的版本文件VERSION通过包含zephyr/cmake/modules/version.cmake
按照模板zephyr/version.h.in
进行生成。
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
# 设置BUILD_VERSION_NAME
if(VERSION_TYPE STREQUAL KERNEL)
set(BUILD_VERSION_NAME BUILD_VERSION)
else()
set(BUILD_VERSION_NAME ${VERSION_TYPE}_BUILD_VERSION)
endif()
# 如果不存在BUILD_VERSION_NAME,就通过git获取VERSION_FILE所在目录的git版本信息作为BUILD_VERSION_NAME
if(NOT DEFINED ${BUILD_VERSION_NAME})
cmake_path(GET VERSION_FILE PARENT_PATH work_dir)
find_package(Git QUIET)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --abbrev=12 --always
WORKING_DIRECTORY ${work_dir}
OUTPUT_VARIABLE ${BUILD_VERSION_NAME}
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
ERROR_VARIABLE stderr
RESULT_VARIABLE return_code
)
if(return_code)
message(STATUS "git describe failed: ${stderr}")
elseif(NOT "${stderr}" STREQUAL "")
message(STATUS "git describe warned: ${stderr}")
endif()
endif()
endif()
# 包含版本信息生成cmake,生成version.h.in中需要的变量
include(${ZEPHYR_BASE}/cmake/modules/version.cmake)
# 读取模板
file(READ ${ZEPHYR_BASE}/version.h.in version_content)
# 将模板内的变量进行替换
string(CONFIGURE "${version_content}" version_content)
string(CONFIGURE "${version_content}" version_content)
# 将替换后的内容写到输出文件中
file(WRITE ${OUT_FILE} "${version_content}")
BUILD_VERSION_NAME 链接到标题
对于内核来源于变量BUILD_VERSION
,对应应用来源于APP_BUILD_VERSION
, 按照正常的编译是不会为CMake指定这两个变量的,因此BUILD_VERSION_NAME
也就不存在会去VERSION
文件所在的文件夹下将git describe --abbrev=12 --always
的结果保存在BUILD_VERSION_NAME
内。
也可以在编译的时候通过-D
指定,但不推荐这种做法,例如:
west build -b esp32c3_zgp -S remap-uart zephyr_sample/ -- -DBUILD_VERSION="3.4.0-rc2" -DAPP_BUILD_VERSION="1.0.0"
模板 链接到标题
version.h.in
是version.h和app_version.h的内容模板,version.cmake生成模板内变量的内容
#ifndef _@VERSION_TYPE@_VERSION_H_
#define _@VERSION_TYPE@_VERSION_H_
/* @templates@ values come from cmake/version.cmake
* BUILD_VERSION related @template@ values will be 'git describe',
* alternatively user defined BUILD_VERSION.
*/
#cmakedefine ZEPHYR_VERSION_CODE @ZEPHYR_VERSION_CODE@
#cmakedefine ZEPHYR_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define @VERSION_TYPE@VERSION @@VERSION_TYPE@VERSION@
#define @VERSION_TYPE@_VERSION_NUMBER @@VERSION_TYPE@_VERSION_NUMBER@
#define @VERSION_TYPE@_VERSION_MAJOR @@VERSION_TYPE@_VERSION_MAJOR@
#define @VERSION_TYPE@_VERSION_MINOR @@VERSION_TYPE@_VERSION_MINOR@
#define @VERSION_TYPE@_PATCHLEVEL @@VERSION_TYPE@_PATCHLEVEL@
#define @VERSION_TYPE@_VERSION_STRING "@@VERSION_TYPE@_VERSION_STRING@"
#define @BUILD_VERSION_NAME@ @@BUILD_VERSION_NAME@@
#endif /* _@VERSION_TYPE@_VERSION_H_ */
@变量@
表示要替换的变量,例如VERSION_TYPE=KERNEL
, KERNELVERSION=0x3040000
,对#define @VERSION_TYPE@VERSION @@VERSION_TYPE@VERSION@
进行替换。
第一次替换@VERSION_TYPE@
,结果为#define KERNELVERSION @KERNELVERSION@
第二次替换@KERNELVERSION@
,结果为#define KERNELVERSION 0x3040000
变量生成 链接到标题
version.cmake生成模板内变量的内容
# 变量VERSION_TYPE和VERSION_FILE变量,这两个变量已经在前面的顶级CMakeLists.txt中指定
foreach(type file IN ZIP_LISTS VERSION_TYPE VERSION_FILE)
if(NOT EXISTS ${file})
break()
endif()
# 读取VERSION文件
file(READ ${file} ver)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${file})
# 从VERSION文件中解析出VERSION_MAJOR,VERSION_MINOR,PATCHLEVEL,VERSION_TWEAK,EXTRAVERSION 并进行规则判断
string(REGEX MATCH "VERSION_MAJOR = ([0-9]*)" _ ${ver})
set(${type}_VERSION_MAJOR ${CMAKE_MATCH_1})
string(REGEX MATCH "VERSION_MINOR = ([0-9]*)" _ ${ver})
set(${type}_VERSION_MINOR ${CMAKE_MATCH_1})
string(REGEX MATCH "PATCHLEVEL = ([0-9]*)" _ ${ver})
set(${type}_PATCHLEVEL ${CMAKE_MATCH_1})
string(REGEX MATCH "VERSION_TWEAK = ([0-9]*)" _ ${ver})
set(${type}_VERSION_TWEAK ${CMAKE_MATCH_1})
string(REGEX MATCH "EXTRAVERSION = ([a-z0-9]*)" _ ${ver})
set(${type}_VERSION_EXTRA ${CMAKE_MATCH_1})
# 生成变量
set(${type}_VERSION_WITHOUT_TWEAK ${${type}_VERSION_MAJOR}.${${type}_VERSION_MINOR}.${${type}_PATCHLEVEL})
set(MAJOR ${${type}_VERSION_MAJOR}) # Temporary convenience variable
set(MINOR ${${type}_VERSION_MINOR}) # Temporary convenience variable
set(PATCH ${${type}_PATCHLEVEL}) # Temporary convenience variable
set(TWEAK ${${type}_VERSION_TWEAK}) # Temporary convenience variable
math(EXPR ${type}_VERSION_NUMBER_INT "(${MAJOR} << 16) + (${MINOR} << 8) + (${PATCH})")
math(EXPR ${type}VERSION_INT "(${MAJOR} << 24) + (${MINOR} << 16) + (${PATCH} << 8) + (${TWEAK})")
to_hex(${${type}_VERSION_NUMBER_INT} ${type}_VERSION_NUMBER)
to_hex(${${type}VERSION_INT} ${type}VERSION)
if(${type}_VERSION_EXTRA)
set(${type}_VERSION_STRING "${${type}_VERSION_WITHOUT_TWEAK}-${${type}_VERSION_EXTRA}")
else()
set(${type}_VERSION_STRING "${${type}_VERSION_WITHOUT_TWEAK}")
endif()
if(type STREQUAL KERNEL)
set(PROJECT_VERSION_MAJOR ${${type}_VERSION_MAJOR})
set(PROJECT_VERSION_MINOR ${${type}_VERSION_MINOR})
set(PROJECT_VERSION_PATCH ${${type}_PATCHLEVEL})
set(PROJECT_VERSION_TWEAK ${${type}_VERSION_TWEAK})
set(PROJECT_VERSION_EXTRA ${${type}_VERSION_EXTRA})
if(PROJECT_VERSION_EXTRA)
set(PROJECT_VERSION_EXTRA_STR "-${PROJECT_VERSION_EXTRA}")
endif()
if(${type}_VERSION_TWEAK)
set(PROJECT_VERSION ${${type}_VERSION_WITHOUT_TWEAK}.${${type}_VERSION_TWEAK})
else()
set(PROJECT_VERSION ${${type}_VERSION_WITHOUT_TWEAK})
endif()
set(PROJECT_VERSION_STR ${PROJECT_VERSION}${PROJECT_VERSION_EXTRA_STR})
set(ZEPHYR_VERSION_CODE ${${type}_VERSION_NUMBER_INT})
set(ZEPHYR_VERSION TRUE)
if(DEFINED BUILD_VERSION)
set(BUILD_VERSION_STR ", build: ${BUILD_VERSION}")
endif()
# 构建期显示内核版本
if (NOT NO_PRINT_VERSION)
message(STATUS "Zephyr version: ${PROJECT_VERSION_STR} (${ZEPHYR_BASE})${BUILD_VERSION_STR}")
endif()
endif()
# Cleanup convenience variables
unset(MAJOR)
unset(MINOR)
unset(PATCH)
unset(${type}_VERSION_WITHOUT_TWEAK)
endforeach()