Skip to main content
We publish frequent updates to our documentation, and translation of this page may still be in progress. For the most current information, please visit the English documentation.

使用并发、表达式和测试矩阵

如何使用高级 GitHub Actions 功能进行持续集成 (CI)。

注意:GitHub Enterprise Server 目前不支持 GitHub 托管的运行器。 可以在 GitHub public roadmap 上查看有关未来支持计划的更多信息。

示例概述

本文使用示例工作流演示 GitHub Actions 的某些主要 CI 功能。触发此工作流时,它将使用 npm test 的测试组合矩阵来测试代码。

下图显示了工作流步骤的高级视图以及它们如何在作业中运行:

工作流步骤概述图

此示例中使用的功能

示例工作流演示了 GitHub Actions 的以下功能:

功能实现
从 UI 手动运行工作流:workflow_dispatch

示例工作流

GitHub Docs 工程团队创建了以下工作流。 若要查看 github/docs 存储库中此文件的最新版本,请参阅 test.yml

注意:此工作流的每一行将在下一部分“了解示例”中介绍。

YAML
name: Node.js Tests

# **What it does**: Runs our tests.
# **Why we have it**: We want our tests to pass before merging code.
# **Who does it impact**: Docs engineering, open-source engineering contributors.

on:
  workflow_dispatch:
  pull_request:
  push:
    branches:
      - main

permissions:
  contents: read
  # Needed for the 'trilom/file-changes-action' action
  pull-requests: read

# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
  group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
  cancel-in-progress: true

jobs:
  test:
    # Run on self-hosted if the private repo or ubuntu-latest if the public repo
    # See pull # 17442 in the private repo for context
    runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }}
    timeout-minutes: 60
    strategy:
      fail-fast: false
      matrix:
        # The same array lives in test-windows.yml, so make any updates there too.
        test-group:
          [
            content,
            graphql,
            meta,
            rendering,
            routing,
            unit,
            linting,
            translations,
          ]
    steps:
      # Each of these ifs needs to be repeated at each step to make sure the required check still runs
      # Even if if doesn't do anything
      - name: Check out repo
        uses: actions/checkout@v3
        with:
          # Not all test suites need the LFS files. So instead, we opt to
          # NOT clone them initially and instead, include them manually
          # only for the test groups that we know need the files.
          lfs: ${{ matrix.test-group == 'content' }}
          # Enables cloning the Early Access repo later with the relevant personal access token
          persist-credentials: 'false'

      - name: Figure out which docs-early-access branch to checkout, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        id: check-early-access
        uses: actions/github-script@v6
        env:
          BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
        with:
          github-token: ${{ secrets.DOCUBOT_REPO_PAT }}
          result-encoding: string
          script: |
            // If being run from a PR, this becomes 'my-cool-branch'.
            // If run on main, with the `workflow_dispatch` action for
            // example, the value becomes 'main'.
            const { BRANCH_NAME } = process.env
            try {
              const response = await github.repos.getBranch({
                owner: 'github',
                repo: 'docs-early-access',
                BRANCH_NAME,
              })
              console.log(`Using docs-early-access branch called '${BRANCH_NAME}'.`)
              return BRANCH_NAME
            } catch (err) {
              if (err.status === 404) {
                console.log(`There is no docs-early-access branch called '${BRANCH_NAME}' so checking out 'main' instead.`)
                return 'main'
              }
              throw err
            }

      - name: Check out docs-early-access too, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        uses: actions/checkout@v3
        with:
          repository: github/docs-early-access
          token: ${{ secrets.DOCUBOT_REPO_PAT }}
          path: docs-early-access
          ref: ${{ steps.check-early-access.outputs.result }}

      - name: Merge docs-early-access repo's folders
        if: ${{ github.repository == 'github/docs-internal' }}
        run: |
          mv docs-early-access/assets assets/images/early-access
          mv docs-early-access/content content/early-access
          mv docs-early-access/data data/early-access
          rm -r docs-early-access

      # This is necessary when LFS files where cloned but does nothing
      # if actions/checkout was run with `lfs:false`.
      - name: Checkout LFS objects
        run: git lfs checkout

      - name: Gather files changed
        uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b
        id: get_diff_files
        with:
          # So that `steps.get_diff_files.outputs.files` becomes
          # a string like `foo.js path/bar.md`
          output: ' '

      - name: Insight into changed files
        run: |

          # Must to do this because the list of files can be HUGE. Especially
          # in a repo-sync when there are lots of translation files involved.
          echo "${{ steps.get_diff_files.outputs.files }}" > get_diff_files.txt

      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: 16.14.x
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Cache nextjs build
        uses: actions/cache@v3
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }}

      - name: Run build script
        run: npm run build

      - name: Run tests
        env:
          DIFF_FILE: get_diff_files.txt
          CHANGELOG_CACHE_FILE_PATH: tests/fixtures/changelog-feed.json
        run: npm test -- tests/${{ matrix.test-group }}/

了解示例

 下表介绍了在创建 GitHub Actions 工作流时如何使用这些功能。

代码 解释
YAML
name: Node.js Tests

将显示在 GitHub 存储库的“操作”选项卡中的工作流名称。

YAML
on:

通过 on 关键字,可以定义运行工作流时触发的事件。 可在此处定义多个事件。 有关详细信息,请参阅“触发工作流”。

YAML
  workflow_dispatch:

如果要在 UI 中手动运行此工作流,请添加 workflow_dispatch 事件。 有关详细信息,请参阅 workflow_dispatch

YAML
  pull_request:

添加 pull_request 事件,以便每次创建或更新拉取请求时,工作流都会自动运行。 有关详细信息,请参阅 pull_request

YAML
  push:
    branches:
      - main

添加 push 事件,以便每次将提交推送到匹配筛选器 main 的分支时,工作流都会自动运行。 有关详细信息,请参阅 push

YAML
permissions:
  contents: read
  pull-requests: read

修改授予 GITHUB_TOKEN 的默认权限。 这将因工作流的需求而异。 有关详细信息,请参阅“为作业分配权限”。

YAML
concurrency:
  group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'

为特定事件创建并发组,并使用 || 运算符定义回退值。 有关详细信息,请参阅“使用并发”。

YAML
  cancel-in-progress: true

取消同一并发组中任何当前正在运行的作业或工作流。

YAML
jobs:

将工作流文件中运行的所有作业组合在一起。

YAML
  test:

定义 ID 为 test 的作业,该作业存储在 jobs 键中。

YAML
    runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }}

根据运行工作流的存储库,将作业配置为在 GitHub 托管的运行器或自托管运行器上运行。 在此示例中,如果存储库名为 docs-internal 且位于 github 组织内,则作业将在自托管运行器上运行。 如果存储库与此路径不匹配,则其会在由 GitHub 托管的 ubuntu-latest 运行器上运行。 有关这些选项的详细信息,请参阅“为作业选择运行器”。

YAML
    timeout-minutes: 60

设置作业在自动取消之前运行的最大分钟数。 有关详细信息,请参阅 timeout-minutes

YAML
    strategy:
本部分定义作业的生成矩阵。
YAML
      fail-fast: false

fail-fast 设置为 false 可以防止 GitHub 在矩阵作业失败时取消所有正在进行的作业。

YAML
      matrix:
        test-group:
          [
            content,
            graphql,
            meta,
            rendering,
            routing,
            unit,
            linting,
            translations,
          ]

创建名为 test-group 的矩阵,其中包含测试组数组。 这些值与将由 npm test 运行的测试组的名称匹配。

YAML
    steps:

将作为 test 作业一部分运行的所有步骤组合在一起。 工作流中的每个作业都有其自己的 steps 部分。

YAML
      - name: Check out repo
        uses: actions/checkout@v3
        with:
          lfs: ${{ matrix.test-group == 'content' }}
          persist-credentials: 'false'

uses 关键字指示作业检索名为 actions/checkout 的操作。 这是检出仓库并将其下载到运行器的操作,允许针对您的代码运行操作(例如测试工具)。 只要工作流程针对仓库的代码运行,或者您使用仓库中定义的操作,您都必须使用检出操作。 使用 with 键为操作提供了一些额外的选项。

YAML
      - name: Figure out which docs-early-access branch to checkout, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        id: check-early-access
        uses: actions/github-script@v6
        env:
          BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
        with:
          github-token: ${{ secrets.DOCUBOT_REPO_PAT }}
          result-encoding: string
          script: |
            // If being run from a PR, this becomes 'my-cool-branch'.
            // If run on main, with the `workflow_dispatch` action for
            // example, the value becomes 'main'.
            const { BRANCH_NAME } = process.env
            try {
              const response = await github.repos.getBranch({
                owner: 'github',
                repo: 'docs-early-access',
                BRANCH_NAME,
              })
              console.log(`Using docs-early-access branch called '${BRANCH_NAME}'.`)
              return BRANCH_NAME
            } catch (err) {
              if (err.status === 404) {
                console.log(`There is no docs-early-access branch called '${BRANCH_NAME}' so checking out 'main' instead.`)
                return 'main'
              }
              throw err
            }

如果当前存储库是 github/docs-internal 存储库,此步骤将使用 actions/github-script 操作运行脚本,检查是否存在名为 docs-early-access 的分支。

YAML
      - name: Check out docs-early-access too, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        uses: actions/checkout@v3
        with:
          repository: github/docs-early-access
          token: ${{ secrets.DOCUBOT_REPO_PAT }}
          path: docs-early-access
          ref: ${{ steps.check-early-access.outputs.result }}

如果当前存储库是 github/docs-internal 存储库,此步骤将从上一步中标识的 github/docs-early-access 签出分支。

YAML
      - name: Merge docs-early-access repo's folders
        if: ${{ github.repository == 'github/docs-internal' }}
        run: |
          mv docs-early-access/assets assets/images/early-access
          mv docs-early-access/content content/early-access
          mv docs-early-access/data data/early-access
          rm -r docs-early-access

如果当前存储库是 github/docs-internal 存储库,则此步骤将使用 run 关键字执行 shell 命令,将 docs-early-access 存储库的文件夹移动到主存储库的文件夹中。

YAML
      - name: Checkout LFS objects
        run: git lfs checkout

此步骤运行一个命令,从存储库签出 LFS 对象。

YAML
      - name: Gather files changed
        uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b
        id: get_diff_files
        with:
          # So that `steps.get_diff_files.outputs.files` becomes
          # a string like `foo.js path/bar.md`
          output: ' '

此步骤使用 trilom/file-changes-action 操作收集拉取请求中更改的文件,以便在下一步中分析它们。 此示例使用 a6ca26c14274c33b15e6499323aac178af06ad4b SHA 固定到操作的特定版本。

YAML
      - name: Insight into changed files
        run: |
          echo "${{ steps.get_diff_files.outputs.files }}" > get_diff_files.txt

此步骤运行 shell 命令,该命令使用上一步的输出创建文件,其中包含在拉取请求中更改的文件列表。

YAML
      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: 16.14.x
          cache: npm

此步骤使用 actions/setup-node 操作在运行器上安装指定版本的 node 软件包,以便你可访问 npm 命令。

YAML
      - name: Install dependencies
        run: npm ci

此步骤运行 npm ci shell 命令来安装项目的 npm 软件包。

YAML
      - name: Cache nextjs build
        uses: actions/cache@v3
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }}

此步骤使用 actions/cache 操作来缓存 Next.js 生成,以便工作流将尝试检索生成的缓存,而不是每次都从头重新生成它。 有关详细信息,请参阅“缓存依赖项以加快工作流”。

YAML
      - name: Run build script
        run: npm run build

此步骤运行生成脚本。

YAML
      - name: Run tests
        env:
          DIFF_FILE: get_diff_files.txt
          CHANGELOG_CACHE_FILE_PATH: tests/fixtures/changelog-feed.json
        run: npm test -- tests/${{ matrix.test-group }}/

此步骤使用 npm test 运行测试,测试矩阵为矩阵中的每个作业提供不同的 ${{ matrix.test-group }} 值。 它使用 DIFF_FILE 环境变量来识别已更改的文件,并将 CHANGELOG_CACHE_FILE_PATH 环境变量用于 changelog 缓存文件。

后续步骤