Skip to content
On this page

GitHub Actions矩阵构建

矩阵构建是GitHub Actions中的一项强大功能,允许您在单个工作流中为不同的配置组合运行相同的作业。这在测试应用程序在不同环境下的兼容性、构建不同架构的软件包或并行执行多个相关任务时非常有用。

矩阵构建基础

基本矩阵配置

yaml
# .github/workflows/matrix-example.yml
name: Matrix Build Example

on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test
      - name: Report environment
        run: |
          echo "OS: ${{ runner.os }}"
          echo "Node version: ${{ matrix.node-version }}"

矩阵构建的优势

矩阵构建的主要优势包括:

  • 并行执行:多个配置组合同时运行,节省总执行时间
  • 一致性:相同的步骤在不同环境中执行
  • 全面覆盖:确保应用程序在各种环境下正常工作
  • 易于维护:单一工作流配置管理多个环境

矩阵策略配置

基本策略选项

yaml
jobs:
  matrix-test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node-version: [16, 18, 20]
      fail-fast: true      # 任一矩阵作业失败时停止所有作业(默认)
      # fail-fast: false  # 所有矩阵作业继续运行,即使有失败
      max-parallel: 2      # 最大并行运行的矩阵作业数(默认为所有)
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm test

故障容忍策略

yaml
jobs:
  fault-tolerant-matrix:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
      fail-fast: false  # 即使一个作业失败,其他作业继续运行
      max-parallel: 4   # 限制并行作业数以控制资源使用
    steps:
      - name: Run test with error handling
        run: |
          npm test || echo "Tests failed but continuing..."

复杂矩阵配置

包含和排除配置

yaml
jobs:
  complex-matrix:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
        architecture: [x64, arm64]
        # 包含特定的组合
        include:
          - os: ubuntu-latest
            node-version: 16
            architecture: x64
            experimental: true
          - os: windows-latest
            node-version: 20
            architecture: x64
            experimental: false
        # 排除特定的组合
        exclude:
          - os: windows-latest
            node-version: 16
          - os: macos-latest
            architecture: arm64
          - os: ubuntu-latest
            node-version: 20
            architecture: arm64
    steps:
      - name: Setup environment
        run: |
          echo "OS: ${{ matrix.os }}"
          echo "Node: ${{ matrix.node-version }}"
          echo "Architecture: ${{ matrix.architecture }}"
          if [ "${{ matrix.experimental }}" = "true" ]; then
            echo "Running experimental configuration"
          fi
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm test

动态矩阵配置

yaml
jobs:
  dynamic-matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - id: set-matrix
        run: |
          # 根据某些条件动态设置矩阵
          if [ "${{ github.event_name }}" = "push" ]; then
            # 推送时运行所有配置
            MATRIX='{"os":["ubuntu-latest","windows-latest","macos-latest"],"node":["16","18","20"]}'
          else
            # PR时只运行关键配置
            MATRIX='{"os":["ubuntu-latest"],"node":["18"]}'
          fi
          echo "matrix=$MATRIX" >> $GITHUB_OUTPUT

  run-tests:
    needs: dynamic-matrix
    runs-on: ${{ matrix.os }}
    strategy:
      matrix: ${{ fromJSON(needs.dynamic-matrix.outputs.matrix) }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm test

多维度矩阵

操作系统和运行时矩阵

yaml
jobs:
  os-runtime-matrix:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        runtime: 
          - { name: 'node', version: '16' }
          - { name: 'node', version: '18' }
          - { name: 'python', version: '3.9' }
          - { name: 'python', version: '3.11' }
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        if: matrix.runtime.name == 'node'
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.runtime.version }}
      
      - name: Setup Python
        if: matrix.runtime.name == 'python'
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.runtime.version }}
      
      - name: Run tests
        run: |
          if [ "${{ matrix.runtime.name }}" = "node" ]; then
            npm ci
            npm test
          elif [ "${{ matrix.runtime.name }}" = "python" ]; then
            pip install -r requirements.txt
            python -m pytest
          fi

数据库兼容性矩阵

yaml
jobs:
  database-matrix:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        database: 
          - { name: 'postgres', version: '13', port: '5432' }
          - { name: 'postgres', version: '14', port: '5433' }
          - { name: 'mysql', version: '8.0', port: '3306' }
          - { name: 'mysql', version: '5.7', port: '3307' }
    services:
      database:
        image: ${{ matrix.database.name }}:${{ matrix.database.version }}
        env:
          POSTGRES_PASSWORD: postgres
          MYSQL_ROOT_PASSWORD: root
        options: >-
          --health-cmd "pg_isready -U postgres || mysqladmin ping -u root -proot"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - name: Install and test with ${{ matrix.database.name }} ${{ matrix.database.version }}
        run: |
          npm ci
          npm run test:db
        env:
          DB_HOST: localhost
          DB_PORT: ${{ matrix.database.port }}
          DB_NAME: test
          DB_USER: postgres
          DB_PASSWORD: postgres

矩阵与条件执行

矩阵条件执行

yaml
jobs:
  conditional-matrix:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
        experimental: [false, true]
        include:
          - os: ubuntu-latest
            node-version: 20
            experimental: true
            run-experimental: true
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      
      - name: Regular tests
        run: npm test
      
      - name: Experimental tests
        if: matrix.run-experimental == true
        run: npm run test:experimental

矩阵依赖作业

yaml
jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      build-id: ${{ steps.build-info.outputs.id }}
    steps:
      - id: build-info
        run: |
          BUILD_ID=$(date +%s)
          echo "id=$BUILD_ID" >> $GITHUB_OUTPUT
  
  test:
    needs: setup
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - name: Run tests with build ID
        run: |
          npm ci
          npm test
        env:
          BUILD_ID: ${{ needs.setup.outputs.build-id }}
  
  deploy:
    needs: [setup, test]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy with matrix results
        run: |
          echo "Deploying build ${{ needs.setup.outputs.build-id }}"
          echo "All matrix tests passed"

高级矩阵用法

自定义矩阵键名

yaml
jobs:
  custom-matrix-keys:
    runs-on: ${{ matrix.runner }}
    strategy:
      matrix:
        runner: [ubuntu-latest, windows-latest]
        language: 
          - { name: 'javascript', version: '18', command: 'npm test' }
          - { name: 'python', version: '3.11', command: 'python -m pytest' }
          - { name: 'go', version: '1.21', command: 'go test ./...' }
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup JavaScript
        if: matrix.language.name == 'javascript'
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.language.version }}
      
      - name: Setup Python
        if: matrix.language.name == 'python'
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.language.version }}
      
      - name: Setup Go
        if: matrix.language.name == 'go'
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.language.version }}
      
      - name: Run tests
        run: ${{ matrix.language.command }}

矩阵与环境变量

yaml
jobs:
  matrix-env-vars:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        env-type: [development, staging, production]
    steps:
      - name: Set environment-specific variables
        run: |
          if [ "${{ matrix.env-type }}" = "production" ]; then
            echo "API_URL=https://api.example.com" >> $GITHUB_ENV
            echo "LOG_LEVEL=error" >> $GITHUB_ENV
          elif [ "${{ matrix.env-type }}" = "staging" ]; then
            echo "API_URL=https://staging-api.example.com" >> $GITHUB_ENV
            echo "LOG_LEVEL=warn" >> $GITHUB_ENV
          else
            echo "API_URL=https://dev-api.example.com" >> $GITHUB_ENV
            echo "LOG_LEVEL=debug" >> $GITHUB_ENV
          fi
        shell: bash
      
      - name: Run with environment
        run: |
          echo "Testing with API: $API_URL"
          echo "Log level: $LOG_LEVEL"
          # 实际测试命令

矩阵构建最佳实践

1. 合理控制矩阵大小

yaml
# 避免过大的矩阵
# 不好的做法:10x10x5 = 500个作业
# strategy:
#   matrix:
#     os: [all-possible-os]
#     node-version: [all-possible-versions]
#     database: [all-possible-databases]

# 好的做法:控制在可管理的范围内
strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]  # 限制操作系统数量
    node-version: [18, 20]              # 只测试关键版本
    # 总作业数:2x2 = 4个作业

2. 使用并行限制

yaml
jobs:
  controlled-parallelism:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
      max-parallel: 3  # 限制同时运行的作业数
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm test

3. 优化矩阵配置

yaml
jobs:
  optimized-matrix:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          # 定义最常用的组合
          - os: ubuntu-latest
            node-version: '18'
            test-suite: 'full'
          - os: windows-latest
            node-version: '18'
            test-suite: 'core'  # Windows上只运行核心测试
          - os: macos-latest
            node-version: '20'
            test-suite: 'full'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - name: Run ${{ matrix.test-suite }} tests
        run: |
          if [ "${{ matrix.test-suite }}" = "full" ]; then
            npm test
          else
            npm run test:core
          fi

实际应用示例

完整的跨平台测试矩阵

yaml
name: Cross-Platform Testing

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test-matrix:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16.x, 18.x, 20.x]
        # 排除某些不兼容的组合
        exclude:
          # Node 16在macOS上可能有问题
          - os: macos-latest
            node-version: 16.x
      fail-fast: false
      max-parallel: 6
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linter
        run: npm run lint
      
      - name: Run unit tests
        run: npm run test:unit
      
      - name: Run integration tests
        run: npm run test:integration
      
      - name: Generate coverage report
        run: npm run test:coverage
      
      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage-${{ matrix.os }}-node${{ matrix.node-version }}
          path: coverage/
        if: runner.os == 'Linux'  # 只上传Linux的覆盖率报告

  security-scan:
    runs-on: ubuntu-latest
    needs: test-matrix
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - name: Security scan
        run: npm audit --audit-level moderate

  deploy:
    runs-on: ubuntu-latest
    needs: [test-matrix, security-scan]
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
      - name: Deploy to production
        run: echo "Deploying production build"
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

构建多个架构的发布包

yaml
name: Build and Release

on:
  push:
    tags: ['v*']

jobs:
  build-matrix:
    runs-on: ${{ matrix.runner }}
    strategy:
      matrix:
        include:
          - runner: ubuntu-latest
            os: linux
            arch: x64
            target: x86_64-unknown-linux-musl
          - runner: ubuntu-latest
            os: linux
            arch: arm64
            target: aarch64-unknown-linux-musl
          - runner: windows-latest
            os: windows
            arch: x64
            target: x86_64-pc-windows-msvc
          - runner: macos-latest
            os: darwin
            arch: x64
            target: x86_64-apple-darwin
          - runner: macos-latest
            os: darwin
            arch: arm64
            target: aarch64-apple-darwin
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      
      - name: Build for ${{ matrix.target }}
        run: |
          rustup target add ${{ matrix.target }}
          cargo build --release --target ${{ matrix.target }}
      
      - name: Package binary
        run: |
          BINARY_NAME="myapp-${{ matrix.os }}-${{ matrix.arch }}"
          if [ "${{ matrix.os }}" = "windows" ]; then
            BINARY_NAME="${BINARY_NAME}.exe"
            cp "target/${{ matrix.target }}/release/myapp.exe" "$BINARY_NAME"
          else
            cp "target/${{ matrix.target }}/release/myapp" "$BINARY_NAME"
          fi
          chmod +x "$BINARY_NAME"
        shell: bash
      
      - name: Upload binary
        uses: actions/upload-artifact@v4
        with:
          name: myapp-${{ matrix.os }}-${{ matrix.arch }}
          path: myapp-*
  
  release:
    runs-on: ubuntu-latest
    needs: build-matrix
    steps:
      - name: Download all binaries
        uses: actions/download-artifact@v4
        
      - name: Create release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          draft: false
          prerelease: false

浏览器兼容性测试矩阵

yaml
name: Browser Compatibility Tests

on: [push, pull_request]

jobs:
  e2e-test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        browser: [chrome, firefox, safari]
        node-version: [18]
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      
      - name: Install dependencies
        run: npm ci
      
      - name: Install Playwright browsers
        run: npx playwright install --with-deps ${{ matrix.browser }}
      
      - name: Run E2E tests on ${{ matrix.browser }}
        run: npm run test:e2e -- --browser=${{ matrix.browser }}
      
      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results-${{ matrix.browser }}
          path: test-results/

通过合理使用矩阵构建,可以高效地在多种配置下测试和构建应用程序,确保其在不同环境下的兼容性和稳定性。