版本号管理是软件开发中至关重要的一环,它有助于标识和跟踪软件的发布历史、功能改进和修复。在本篇博客中,我们将探索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进制, 0x1000000
  • APP_VERSION_NUMBER: (VERSION_MAJOR<<16)|(VERSION_MINOR<<8)| PATCHLEVEL, 16进制, 0x10000
  • APP_VERSION_MAJOR: VERSION_MAJOR, 10进制, 1
  • APP_VERSION_MINOR: VERSION_MINOR, 10进制, 0
  • APP_PATCHLEVEL: PATCHLEVEL, 10进制, 0
  • APP_VERSION_TWEAK : VERSION_TWEAK, 10进制, 0
  • APP_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

上面为分析版本生成的简要指南,下面的付费内容将详细解释这一流程:

顶级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()