管道条件

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

本文介绍 Azure Pipelines 阶段、作业或步骤运行的条件,以及如何指定不同的条件。 有关阶段、作业和步骤的更多上下文,请参阅 Azure Pipelines 的关键概念

  • 默认情况下,如果作业或阶段不依赖于任何其他作业或阶段,或者其所有依赖项都已成功完成,它们就会运行。 此要求不仅适用于直接依赖项,也适用于以递归方式计算的间接依赖项。

  • 默认情况下,如果步骤的作业中没有任何内容失败并且紧接在其前面的步骤已完成,则该步骤将运行。

可以通过强制阶段、作业或步骤运行(即使先前的依赖项失败)或通过指定自定义条件来替代或自定义此行为。

注意

本文讨论 YAML 管道功能。 对于经典管道,可以在每个任务的控制选项中和发布管道中的作业的附加选项中,指定任务或作业运行的某些条件。

阶段、作业或步骤运行的条件

在管道定义 YAML 中,可以指定阶段、作业或步骤运行的以下条件:

  • 仅当具有相同代理池的所有先前直接和间接依赖项都成功时。 如果有不同的代理池,这些阶段或作业将并发运行。 如果 YAML 中没有设置条件,则此条件是默认条件。

  • 即使以前的依赖项失败,除非运行已取消。 将 YAML 中的 succeededOrFailed() 用于此条件。

  • 即使以前的依赖项失败,即使运行已取消。 将 YAML 中的 always() 用于此条件。

  • 仅当以前的依赖项失败时。 将 YAML 中的 failed() 用于此条件。

  • 自定义条件。

默认情况下,如果所有直接和间接依赖项都成功,将运行阶段、作业和步骤。 此状态与指定 condition: succeeded() 相同。 有关详细信息,请参阅成功状态函数

为阶段、作业或步骤指定 condition 属性时,将覆盖其默认 condition: succeeded()。 即使生成被取消,指定自己的条件也会导致阶段、作业或步骤运行。 确保编写的条件考虑到父阶段或作业的状态。

下面的 YAML 示例显示了always()failed() 条件。 即使依赖项失败或生成被取消,第一个作业中的步骤也会运行。 只有当第一个作业失败时,第二个作业才会运行。

jobs:
- job: Foo

  steps:
  - script: echo Hello!
    condition: always() # this step runs, even if the build is canceled

- job: Bar
  dependsOn: Foo
  condition: failed() # this job runs only if Foo fails

还可以在条件中设置和使用变量。 以下示例设置并使用一个 isMain 变量将 main 指定为 Build.SourceBranch

variables:
  isMain: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]

stages:
- stage: A
  jobs:
  - job: A1
    steps:
      - script: echo Hello Stage A!

- stage: B
  condition: and(succeeded(), eq(variables.isMain, true))
  jobs:
  - job: B1
    steps:
      - script: echo Hello Stage B!
      - script: echo $(isMain)

重要

评估条件以确定是启动阶段、作业还是步骤。 因此,在该工作单元内运行时计算的内容将不可用。 例如,如果你有一个作业,该作业根据 $[ ] 语法使用运行时表达式来设置变量,那么你无法在该作业的自定义条件中使用该变量。

自定义条件

如果内置条件不能满足你的需求,可以指定自定义条件。 可以将条件作为表达式写入 YAML 管道定义中。

代理从最内部的函数开始计算表达式,然后向外执行。 最终结果是一个布尔值,用于确定是否应运行任务、作业或阶段。 有关语法的完整指南,请参阅表达式

如果任一条件使任务即使在取消生成后也能运行,请为取消超时指定一个合理的值,以便这些任务在用户取消运行后有足够的时间完成。

取消生成时的条件结果

取消生成并不意味着其所有阶段、作业或步骤都停止运行。 哪些阶段、作业或步骤停止运行取决于你指定的条件,以及在你取消生成时管道执行的进度。 如果跳过阶段、作业或步骤的父级,则无论其条件如何,任务都不会运行。

只要一个阶段、作业或步骤的条件评估为 true,它就会运行。 如果条件没有考虑到任务父级的状态,即使其父级被取消,任务也可能运行。 要控制在取消生成时是否运行带有条件的阶段、作业或步骤,请确保在条件中包含作业状态检查函数

以下示例显示了在取消生成时在阶段、作业或步骤上设置的各种条件的结果。

阶段示例 1

在下面的管道中,默认情况下,stage2将依赖于 stage1,但 stage2 有一个 condition 设置,只要源分支为 main,无论 stage1 状态如何,都会运行。

如果在 main 分支上将某个生成进行排队,并且你在 stage1 仍在运行时取消了此生成,则 stage2 仍会运行,因为 eq(variables['Build.SourceBranch'], 'refs/heads/main') 的计算结果为 true

stages:
- stage: stage1
  jobs:
  - job: A
    steps:
      - script: echo 1; sleep 30
- stage: stage2
  condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
  jobs:
  - job: B
    steps:
      - script: echo 2

阶段示例 2

在以下管道中,默认情况下,stage2 依赖于 stage1stage2 中的作业 B 已设置 condition。 如果在 main 分支上将某个生成进行排队,并且你在 stage1 仍在运行的情况下取消了此生成,则 stage2 将不会运行,即使它包含的某个作业的条件计算结果为 true

这是因为 stage2 具有默认的 condition: succeeded(),而在 stage1 被取消时,后者的计算结果为 false。 因此,stage2 将被跳过,并且其作业都不会运行。

stages:
- stage: stage1
  jobs:
  - job: A
    steps:
      - script: echo 1; sleep 30
- stage: stage2
  jobs:
  - job: B
    condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
    steps:
      - script: echo 2

阶段示例 3

在以下管道中,默认情况下,stage2 依赖于 stage1,作业 B 中的步骤有一个 condition 集。

如果在 main 分支上将某个生成进行排队,并且你在 stage1 仍在运行的情况下取消了此生成,则 stage2 将不会运行,即使它包含的作业 B 中的某一步骤的条件计算结果为 true。 原因是跳过了 stage2 以响应 stage1 被取消的操作。

stages:
- stage: stage1
  jobs:
  - job: A
    steps:
      - script: echo 1; sleep 30
- stage: stage2
  jobs:
  - job: B
    steps:
      - script: echo 2
        condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')

作业示例 1

在以下 YAML 管道中,默认情况下,作业 B 依赖于作业A;但是作业 B 有一个 condition 集,当源分支为 main 时运行。 如果在 main 分支上将某个生成进行排队,并且你在作业 A 仍在运行的情况下取消了此生成,则作业 B 仍会运行,因为 eq(variables['Build.SourceBranch'], 'refs/heads/main') 的计算结果为 true

jobs:
- job: A
  steps:
  - script: sleep 30
- job: B
  dependsOn: A 
  condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
  steps:
    - script: echo step 2.1

如果你希望作业 B 仅在作业 A 成功时才运行,生成源是 main 分支,condition 应该是 and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))

作业示例 2

在以下管道中,默认情况下,作业 B 依赖于作业 A。 如果你在分支 main 上将生成排队,并在作业 A 仍在运行的情况下取消了此生成,则作业 B 不会运行,即使其步骤 condition 的计算结果为 true

这是因为作业 B 具有默认的 condition: succeeded(),而在作业 A 被取消时,后者的计算结果为 false。 因此,将跳过作业 B,并且不会运行任何步骤。

jobs:
- job: A
  steps:
  - script: sleep 30
- job: B
  dependsOn: A 
  steps:
    - script: echo step 2.1
      condition: eq(variables['Build.SourceBranch'], 'refs/heads/main', succeeded())
      

步骤示例

还可以对步骤施加条件。

在以下管道中,每当源分支为 main 时,步骤 2.3 都有一个要运行的 condition 集。 如果你在 main 分支上将生成排队,并在步骤 2.1 或 2.2 运行时将其取消,则步骤 2.3 仍会运行,因为 eq(variables['Build.SourceBranch'], 'refs/heads/main') 计算结果为 true

steps:
  - script: echo step 2.1
  - script: echo step 2.2; sleep 30
  - script: echo step 2.3
    condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')

条件设置

下表显示了生成各种结果的示例 condition 设置。

注意

Release.Artifacts.{artifact-alias}.SourceBranch 等效于 Build.SourceBranch

所需结果 示例条件设置
如果源分支是主分支,即使父级或上一阶段、作业或步骤失败或已取消,也会运行。 eq(variables['Build.SourceBranch'], 'refs/heads/main')
如果源分支是主分支,并且父级或上一阶段、作业或步骤成功,则会运行。 and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
如果源分支不是主分支,并且父级或上一阶段、作业或步骤成功,则会运行。 and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/main'))
如果父级或上一阶段、作业或步骤成功,则为用户主题分支运行。 and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/heads/users/'))
如果父级或上一阶段、作业或步骤成功,则为持续集成 (CI) 生成运行。 and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI'))
如果生成是由拉取请求的分支策略触发的,并且父级或上一阶段、作业或步骤失败,则会运行。 and(failed(), eq(variables['Build.Reason'], 'PullRequest'))
即使父级或上一阶段、作业或步骤失败或已取消,也为计划生成运行。 eq(variables['Build.Reason'], 'Schedule')
如果变量设置为 true,则即使父级或上一阶段、作业或步骤失败或已取消,也会运行。 eq(variables['System.debug'], true)

注意

如果变量为 null(空字符串),则可以设置一个运行条件。 由于在 Azure Pipelines 中,所有变量都被视为字符串,因此以下管道中的空字符串等效于 null

variables:
- name: testEmpty
  value: ''

jobs:
  - job: A
    steps:
    - script: echo testEmpty is blank
    condition: eq(variables.testEmpty, '')

条件中的参数

参数展开发生在考虑条件之前。 因此,当在同一管道中声明一个参数作为条件时,可以将该参数嵌入到条件中。 以下 YAML 中的脚本运行是因为 parameters.doThing 为 true。

parameters:
- name: doThing
  default: true
  type: boolean

steps:
- script: echo I did a thing
  condition: and(succeeded(), ${{ eq(parameters.doThing, true) }})

前面管道中的 condition 结合了两个函数:succeeded()${{ eq(parameters.doThing, true) }}succeeded() 函数检查上一步是否成功。 succeeded() 函数返回 true,因为没有上一步。

${{ eq(parameters.doThing, true) }}函数检查 doThing 参数是否等于 true。 由于 doThing 的默认值是 true,因此除非管道设置了不同的值,否则默认情况下条件返回 true

条件中的模板参数

在你将参数传递给模板时,需要在模板中设置参数的值,或使用 templateContext 将参数传递给模板

例如,以下 parameters.yml 文件声明 doThing 参数和默认值:

# parameters.yml
parameters:
- name: doThing
  default: true # value passed to the condition
  type: boolean

jobs:
  - job: B
    steps:
    - script: echo I did a thing
    condition: ${{ eq(parameters.doThing, true) }}

管道代码引用 parameters.yml 模板。 管道的输出是 I did a thing,因为参数 doThing 为 true。

# azure-pipeline.yml
parameters:
- name: doThing
  default: true 
  type: boolean

trigger:
- none

extends:
  template: parameters.yml

有关更多模板参数示例,请参阅模板使用情况参考

后续作业条件中使用的作业输出变量

可以将变量提供给将来的作业,并在条件中指定此变量。 必须使用 isOutput=true 将未来作业可用的变量标记为多作业输出变量,如下代码所示:

jobs:
- job: Foo
  steps:
  - bash: |
      echo "This is job Foo."
      echo "##vso[task.setvariable variable=doThing;isOutput=true]Yes" #set variable doThing to Yes
    name: DetermineResult
- job: Bar
  dependsOn: Foo
  condition: eq(dependencies.Foo.outputs['DetermineResult.doThing'], 'Yes') #map doThing and check the value
  steps:
  - script: echo "Job Foo ran and doThing is Yes."

在后续步骤条件中使用的步骤中创建的变量

可以创建一个变量,以供将来的步骤在条件中指定。 默认情况下,从步骤创建的变量可用于将来的步骤,并且不需要标记为多作业输出变量

关于从步骤中创建的作用域变量,需要注意一些重要事项。

  • 在作业的步骤中创建的变量的作用域将限定为同一作业中的步骤。
  • 在步骤中创建的变量仅在后续步骤中作为环境变量提供。
  • 在某一步骤中创建的变量不能在定义这些变量的步骤中使用。

下面的示例显示将在步骤中创建管道变量并在后续步骤的条件和脚本中使用该变量。

steps:

# This step creates a new pipeline variable: doThing. This variable is available to subsequent steps.
- bash: |
    echo "##vso[task.setvariable variable=doThing]Yes"
  displayName: Step 1

# This step is able to use doThing, so it uses doThing in its condition
- script: |
    # Access the variable from Step 1 as an environment variable.
    echo "Value of doThing (as DOTHING env var): $DOTHING."
  displayName: Step 2
  condition: and(succeeded(), eq(variables['doThing'], 'Yes')) # or and(succeeded(), eq(variables.doThing, 'Yes'))

常见问题解答

如果以前的作业成功出现问题,如何触发作业?

可以在条件中使用上一个作业的结果。 例如,在以下 YAML 中,条件 eq(dependencies.A.result,'SucceededWithIssues') 允许作业 B 运行,因为作业 A 成功,但存在问题。

jobs:
- job: A
  displayName: Job A
  continueOnError: true # next job starts even if this one fails
  steps:
  - script: echo Job A ran
  - script: exit 1

- job: B
  dependsOn: A
  condition: eq(dependencies.A.result,'SucceededWithIssues') # targets the result of the previous job 
  displayName: Job B
  steps:
  - script: echo Job B ran

我取消了我的生成,但它仍在运行。 为什么?

如果在阶段中配置的条件不包含作业状态检查函数,则会遇到此问题。 要解决此问题,请向条件中添加作业状态检查函数。

如果某个作业在队列阶段中但未在运行,而此时你取消此作业,则会取消整个作业,包括所有其他阶段。 有关详细信息,请参阅本文前面的取消生成时的条件结果