运行跨平台脚本

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

借助 Azure Pipelines,可以在 macOS、Linux 和 Windows 计算机上运行你的生成。 如果使用跨平台技术(例如 .NET Core、Node.js 和 Python)进行开发,这些功能既会带来优势,也会带来挑战。

例如,大多数管道包含一个或多个要在生成过程中运行的脚本。 但是,脚本在不同平台上的运行方式往往不同。 可以使用 script 关键字快捷方式简化写入脚本,还可以使用条件来确定脚本的具体平台目标。

使用脚本步骤运行跨平台工具

script 关键字是命令行任务的捷径。 script 关键字在 Linux 和 macOS 上运行 Bash,在 Windows 上运行 cmd.exe。

当任务只是将参数传递给跨平台工具时,使用 script 会很有帮助。 例如,结合一组参数调用 npm 可以通过一个 script 步骤来轻松完成。 script 在每个平台的本机脚本解释器中运行:macOS 和 Linux 上的 Bash,Windows 上的 cmd.exe。

steps:
- script: |
    npm install
    npm test

处理环境变量

环境变量是编写跨平台脚本时面临的第一个难题。 命令行、PowerShell 和 Bash 各自有不同的环境变量读取方法。 如果你需要访问操作系统提供的值(例如 PATH),则需要在每个平台上采用不同的方法。

但是,Azure Pipelines 提供了一种跨平台方式来引用它所知道的变量,这种方式称为宏语法。 将变量名称括在 $( ) 中后,该名称就会扩展,然后平台的 shell 就能识别到它。 例如,如果你要回显管道的 ID,可以使用以下跨平台友好的脚本:

steps:
- script: echo This is pipeline $(System.DefinitionId)

这也适用于在管道中指定的变量。

variables:
  Example: 'myValue'

steps:
- script: echo The value passed in is $(Example)

考虑使用 Bash 还是 pwsh

如果你的脚本需求比上面的示例更复杂,请考虑使用 Bash 编写脚本。 大多数 macOS 和 Linux 代理都提供 Bash 作为可用的 shell,而 Windows 代理则提供 Git Bash 或适用于 Linux 的 Windows 子系统 Bash。

对于 Azure Pipelines,Microsoft 托管的代理始终提供 Bash。

例如,如果你需要决定生成是否由拉取请求触发,请执行以下操作:

trigger:
    batch: true
    branches:
        include:
        - main
steps:
- bash: |
    echo "Hello world from $AGENT_NAME running on $AGENT_OS"
    case $BUILD_REASON in
            "Manual") echo "$BUILD_REQUESTEDFOR manually queued the build." ;;
            "IndividualCI") echo "This is a CI build for $BUILD_REQUESTEDFOR." ;;
            "BatchedCI") echo "This is a batched CI build for $BUILD_REQUESTEDFOR." ;;
        *) $BUILD_REASON ;;
    esac
  displayName: Hello world

PowerShell Core (pwsh) 也是一个选择。 它要求在每个代理上安装 PowerShell Core。

根据平台切换

一般情况下,我们建议避免使用特定于平台的脚本,以避免出现管道逻辑重复等问题。 重复会导致额外的工作和额外的 bug 风险。 但是,如果无法避免特定于平台的脚本,则可以使用 condition 来检测所在的平台。

例如,假设出于某种原因需要获取生成代理的 IP 地址。 在 Windows 上,ipconfig 可获取该信息。 在 macOS 上,相应的命令是 ifconfig。 在 Ubuntu Linux 上,相应的命令是 ip addr

设置以下管道,然后尝试针对不同平台上的代理运行它。

steps:
# Linux
- bash: |
    export IPADDR=$(ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1  -d'/')
    echo "##vso[task.setvariable variable=IP_ADDR]$IPADDR"
  condition: eq( variables['Agent.OS'], 'Linux' )
  displayName: Get IP on Linux
# macOS
- bash: |
    export IPADDR=$(ifconfig | grep 'en0' -A3 | grep inet | tail -n1 | awk '{print $2}')
    echo "##vso[task.setvariable variable=IP_ADDR]$IPADDR"
  condition: eq( variables['Agent.OS'], 'Darwin' )
  displayName: Get IP on macOS
# Windows
- powershell: |
    Set-Variable -Name IPADDR -Value ((Get-NetIPAddress | ?{ $_.AddressFamily -eq "IPv4" -and !($_.IPAddress -match "169") -and !($_.IPaddress -match "127") } | Select-Object -First 1).IPAddress)
    Write-Host "##vso[task.setvariable variable=IP_ADDR]$IPADDR"
  condition: eq( variables['Agent.OS'], 'Windows_NT' )
  displayName: Get IP on Windows

# now we use the value, no matter where we got it
- script: |
    echo The IP address is $(IP_ADDR)