持续集成任务与批处理脚本

发布时间 2023-11-06 23:55:33作者: JobYan

近期我在使用 Jenkins 进行嵌入式软件项目的持续集成[^持续集成]任务时,遇到了一些挑战。该项目是在 Windows 平台上开发的,因此我决定采用批处理脚本作为构建工具。本文将详细介绍我在解决相关问题时遇到的难点以及我的解决方案。希望能为大家提供一些参考和借鉴。

问题背景

待构建的软件包含多种语言和配置选项,包括但不限于 CN_STD, CN_NEU, EN_STD, EN_NEU. 各个字段含义如下:

  • CN_STD: 中文标配(即语言为中文,产品带有公司信息)
  • CN_NEU: 中文中性(即语言为中文,产品不带有公司信息)
  • EN_STD: 英文标配(即语言为英文,产品带有公司信息)
  • EN_NEU: 英文中性(即语言为英文,产品不带有公司信息)

这些选项可以通过环境变量 language_config 将其以不同的组合方式传递给我们的构建脚本。在一个构建过程中,我们可能要生成多种具有不同语言和配置的软件包。例如,language_config 可能是 CN_STD, EN_STD, CN_STD,EN_NEU, CN_NEU,EN_STD 等等。我的目标是如何利用批处理脚本实现多配置项的构建流程,最终输出多个配置项对应的软件成果物。
为了实现这个目标,我们需要先将语言和配置选项提取出来,放入一个数组之中。以下是实现这一目标的代码片段:

@echo off
setlocal enabledelayedexpansion

set language_config=CN_STD,EN_NEU
set /a index=0

rem 遍历所有子字符串并存储到数组中
for %%a in (%language_config%) do (
    set array[!index!]=%%a
    set /a index+=1
)

set /a upperBound=index-1

rem 输出数组中的所有元素
for /l %%i in (0, 1, %upperBound%) do (
    echo array[%%i]=!array[%%i]!
)

上述代码中,有几点需要注意的地方:

  • setlocal enabledelayedexpansion 的作用是什么?这里为什么要使用它?如果不使用它会怎么样呢?
  • 为什么变量在引用的过程中,有的表达式使用 %value_name% 而有的表达式使用 !value_name! 呢?

问题 1:在批处理脚本中,setlocal enabledelayedexpansion 是为了打开延迟环境变量扩展。这是一种允许在批处理文件内更改变量值的选项。如果只是想要暂时改变一个变量的值,并且希望在脚本结束后恢复到原始状态,那么就可以使用 setlocal。使用 setlocal 后,所有的变量赋值都会被限制在本地范围之内,当你退出批处理文件时,这些变量会被重置。比如,如果你想设置一个变量并且在整个批处理脚本中只保留这一个变量的值,但是你不希望影响脚本外部的环境,就可以这样做:

setlocal
set myVar=myValue
echo My var is: !myVar!
endlocal
echo My var is still the same as before.

同时,启用 delayed expansion 意味着变量的值将在被引用的地方计算,而不是在批处理被解析的时候。这就意味着如果你在一个 for 循环中更改了一个变量的值,仍然可以在循环中看到这个更改。
问题 2:关于为何在同一代码块中有时使用%value%有时候使用!value!的原因是,这是因为在批处理中,对于使用 setlocal enabledelayedexpansion 的语句后面的行,你会想要延迟其变量扩展以便能够在每一步中使用更新后的变量值。而对 !value! 的引用是对延迟环境变量进行评估,以获得它们最新的值。所以,对于 !value! 的使用是非常有用的,特别是在 for 循环中。
如下面的例子所示,通过使用 !value! ,可以正确地迭代变量 changes 的值:

setlocal enabledelayedexpansion
set changes=a,b,c,d,e
for %%x in (!changes!) do (
   set value=%%x
   echo !value!
 )
endlocal

而在这段代码中,不能使用 %value%,因为变量会在 for 循环开始前就解析其值,而不是每次迭代时都解析。因此它只会显示一次原始值。

setlocal enabledelayedexpansion
set changes=a,b,c,d,e
for %%x in ("%changes%") do (
   set value=%%x
   echo %value%
 )
endlocal

通过上述的解释说明,我相信你对 cmd 批处理脚本的 for 循环有了更深的了解。
经过上述代码的处理,我们可以将语言和配置项提取到名为 array 的数组中。接下来,我们计划对数组中的每个元素进行单独处理,最终生成每个配置项的结果,简单的处理流程如下:

rem 对array中的每个配置项分别进行软件构建操作
for /l %%i in (0, 1, %upperBound%) do (
    rem TODO:构建软件
)

在进行软件构建的过程中,我发现我始终依赖于 cmd 批处理脚本来完成这项任务。然而,回顾整个过程,我认为可能有更好的方法。以下是我做出的思考和结论。

思考与改进

  1. 虽然 cmd 批处理脚本在一定程度上能够满足需求,但我认为它可以做得更好。具体来说,我觉得可以用 Python 脚本来替代 cmd 脚本。原因如下:

    • Python 语法优雅且易读,相比 cmd 脚本,更容易理解和维护;
    • Python 提供强大的 IDE 支持和丰富的调试工具,便于我们在出现问题时定位和修复错误。
  2. 如何提高批处理脚本的可读性和可维护性?在这个阶段,我可以引入注释,解释关键部分的含义,以便其他开发者理解。同时,可以添加文档说明,使流程更加清晰明了。

  3. 我该如何改进现有的批处理脚本?考虑到性能和可靠性,我考虑重新组织代码结构,使其更易于阅读和理解。

针对这些问题,我打算改进现有方案,将其升级为 Python 脚本。下面是具体的实施方案:

实施方案

首先,将 cmd 脚本转换为 Python 脚本:

# 定义配置选项和环境变量
language_config = 'CN_STD, EN_NEU'

# 将环境变量转化为列表
config_list = language_config.split(',')

# 遍历列表,对每个配置项进行处理
for config in config_list:
    # TODO: 构建软件

效果评估

经过改进之后,代码变得更加简洁易懂,方便他人理解和维护。此外,利用 Python 的优势,可以更好地阅读和调试程序,提高开发效率。
总结一下,针对在 Windows 平台下的持续集成任务,使用 Python 脚本替换 cmd 脚本有许多优点。这不仅可以提高代码质量,还能提高工作效率,减少不必要的调试时间。希望通过这篇文章,你能够有所收获!