关于 dotnet-gha-patterns
为 .NET 项目设计可组合 GitHub Actions 工作流的技能,侧重超越基础构建与测试模板的工作流组合及 CI/CD 控制。
主要功能
- 01使用 workflow_call 复用工作流
- 02用复合操作共享步骤
- 03构建多 TFM 与多操作系统矩阵
- 04按路径触发并管理并发组
- 05配置环境保护规则与密钥
- 06缓存 NuGet 和 SDK;设置手动输入
适用场景
- 01在多个仓库共享构建任务
- 02跨操作系统测试不同 .NET 版本
- 03文件无关时跳过 CI
- 04部署前要求人工审批
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-gha-patterns安装后在 Claude Code 中输入/dotnet-gha-patterns确认已加载。
详细介绍
dotnet-gha-patterns
适用于 .NET 项目的可组合 GitHub Actions 工作流模式:使用 workflow_call 创建可复用工作流,使用复合操作封装共享步骤序列,跨 TFM 和操作系统进行矩阵构建,基于路径触发,使用并发组取消重复运行,设置环境保护规则,采用 NuGet 和 SDK 缓存策略,以及使用 workflow_dispatch 输入手动触发。
**版本假设:**GitHub Actions 工作流语法 v2。使用 actions/setup-dotnet@v4 支持 .NET 8/9/10。使用 actions/cache@v4 缓存依赖项。
**范围边界:**本技能负责 GitHub Actions 中可组合的 CI/CD 工作流设计模式。入门 CI 模板(基本构建/测试/打包)由 [skill:dotnet-add-ci] 负责——本技能通过高级组合扩展这些模板。CLI 专用发布流水线(用于 CLI 二进制文件的 build-package-release)由 [skill:dotnet-cli-release-pipeline] 负责——本技能涵盖 CLI 流水线所使用的通用工作流模式。基准测试 CI 集成由 [skill:dotnet-ci-benchmarking] 负责。
**不在范围内:**入门 CI/CD 模板——参见 [skill:dotnet-add-ci]。CLI 发布流水线(由标签触发、用于 CLI 工具的 build-package-release)——参见 [skill:dotnet-cli-release-pipeline]。基准测试 CI 工作流——参见 [skill:dotnet-ci-benchmarking]。Azure DevOps 流水线模式——参见 [skill:dotnet-ado-patterns]。构建/测试细节——参见 [skill:dotnet-gha-build-test]。发布工作流——参见 [skill:dotnet-gha-publish]。部署模式——参见 [skill:dotnet-gha-deploy]。
交叉引用:[skill:dotnet-add-ci] 提供本模式所扩展的入门模板;[skill:dotnet-cli-release-pipeline] 提供 CLI 专用发布自动化;[skill:dotnet-ci-benchmarking] 提供基准测试专用 CI 集成。
可复用工作流(workflow_call)
定义可复用工作流
可复用工作流允许调用方将整个工作流作为一个步骤调用。为明确约定定义输入、输出和密钥:
# .github/workflows/build-reusable.yml
name: Build (Reusable)
on:
workflow_call:
inputs:
dotnet-version:
description: '.NET SDK version to install'
required: false
type: string
default: '8.0.x'
configuration:
description: 'Build configuration'
required: false
type: string
default: 'Release'
project-path:
description: 'Path to solution or project file'
required: true
type: string
outputs:
artifact-name:
description: 'Name of the uploaded build artifact'
value: ${{ jobs.build.outputs.artifact-name }}
secrets:
NUGET_AUTH_TOKEN:
description: 'NuGet feed authentication token'
required: false
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact-name: build-${{ github.sha }}
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.dotnet-version }}
- name: Restore
run: dotnet restore ${{ inputs.project-path }}
- name: Build
run: dotnet build ${{ inputs.project-path }} -c ${{ inputs.configuration }} --no-restore
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build-${{ github.sha }}
path: |
**/bin/${{ inputs.configuration }}/**
retention-days: 7
调用可复用工作流
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
uses: ./.github/workflows/build-reusable.yml
with:
dotnet-version: '8.0.x'
project-path: MyApp.sln
secrets:
NUGET_AUTH_TOKEN: ${{ secrets.NUGET_AUTH_TOKEN }}
test:
needs: build
uses: ./.github/workflows/test-reusable.yml
with:
dotnet-version: '8.0.x'
project-path: MyApp.sln
跨仓库可复用工作流
使用完整路径引用其他仓库中的工作流:
jobs:
build:
uses: my-org/.github-workflows/.github/workflows/dotnet-build.yml@v1
with:
dotnet-version: '9.0.x'
secrets: inherit # pass all secrets from caller
如果可复用工作流需要访问与调用工作流相同的密钥,且不想逐一列出,请使用 secrets: inherit。
复合操作
创建复合操作
复合操作将多个步骤打包成一个可复用操作。对于多个工作流中共用的步骤序列,可使用复合操作:
# .github/actions/dotnet-setup/action.yml
name: 'Setup .NET Environment'
description: 'Install .NET SDK and restore NuGet packages with caching'
inputs:
dotnet-version:
description: '.NET SDK version'
required: false
default: '8.0.x'
project-path:
description: 'Path to solution or project'
required: true
runs:
using: 'composite'
steps:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.dotnet-version }}
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Restore dependencies
shell: bash
run: dotnet restore ${{ inputs.project-path }}
使用复合操作
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET environment
uses: ./.github/actions/dotnet-setup
with:
dotnet-version: '9.0.x'
project-path: MyApp.sln
- name: Build
run: dotnet build MyApp.sln -c Release --no-restore
可复用工作流与复合操作的比较
| 特性 | 可复用工作流 | 复合操作 |
|---|---|---|
| 范围 | 包含运行器的整个任务 | 任务中的步骤 |
| 运行器选择 | 自带 runs-on | 调用方的运行器 |
| 密钥访问 | 显式指定或使用 inherit | 调用方的上下文 |
| 输出 | 任务级输出 | 步骤级输出 |
| 适用场景 | 完整的构建/测试/部署任务 | 共享的设置/清理步骤序列 |
矩阵构建
多 TFM 和多操作系统矩阵
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
dotnet-version: ['8.0.x', '9.0.x']
include:
- os: ubuntu-latest
dotnet-version: '10.0.x'
exclude:
- os: macos-latest
dotnet-version: '8.0.x'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup .NET ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Test
run: dotnet test --framework net${{ matrix.dotnet-version == '8.0.x' && '8.0' || matrix.dotnet-version == '9.0.x' && '9.0' || '10.0' }}
关键决策:
fail-fast: false可确保即使某个组合失败,也会运行所有矩阵组合,从而全面了解哪些平台/TFM 存在问题include可添加笛卡尔积中没有的特定组合exclude可移除不必要或不受支持的组合
从 JSON 动态生成矩阵
针对复杂场景动态生成矩阵值:
jobs:
compute-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- id: set-matrix
shell: bash
run: |
set -euo pipefail
# Extract TFMs from Directory.Build.props or csproj files
TFMS=$(grep -rh '<TargetFrameworks\?>' **/*.csproj | \
sed 's/.*<TargetFrameworks\?>//' | sed 's/<.*//' | \
tr ';' '\n' | sort -u | jq -R . | jq -sc .)
echo "matrix={\"tfm\":$TFMS}" >> "$GITHUB_OUTPUT"
test:
needs: compute-matrix
strategy:
matrix: ${{ fromJson(needs.compute-matrix.outputs.matrix) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: dotnet test --framework ${{ matrix.tfm }}
基于路径的触发器
选择性执行工作流
仅在相关文件发生更改时触发工作流,从而降低 CI 成本并缩短反馈时间:
on:
push:
branches: [main]
paths:
- 'src/**'
- 'tests/**'
- '*.sln'
- 'Directory.Build.props'
- 'Directory.Packages.props'
- '.github/workflows/ci.yml'
pull_request:
branches: [main]
paths:
- 'src/**'
- 'tests/**'
- '*.sln'
- 'Directory.Build.props'
- 'Directory.Packages.props'
忽略非代码更改
使用 paths-ignore 跳过仅涉及文档更改的构建:
on:
push:
branches: [main]
paths-ignore:
- 'docs/**'
- '*.md'
- 'LICENSE'
- '.editorconfig'
选择 paths 或 paths-ignore,不要同时使用。 在同一事件中同时指定两者时,paths-ignore 会被忽略。对于范围明确的工作流,请使用 paths(允许列表);对于范围较广的工作流,请使用 paths-ignore(拒绝列表)。
并发组
取消重复运行
当向同一分支或 PR 推送新提交时,取消正在进行的运行,避免浪费 CI 时间:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
按环境设置并发
避免同时向同一环境部署:
concurrency:
group: deploy-production
cancel-in-progress: false # queue, do not cancel deployments
对于构建/测试,使用 cancel-in-progress: true(新提交会取代旧提交);对于部署,使用 cancel-in-progress: false(不要取消正在进行的部署)。
环境保护规则
配置环境
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- name: Deploy to staging
run: echo "Deploying..."
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- name: Deploy to production
run: echo "Deploying..."
在 GitHub Settings > Environments 中配置保护规则:
| 规则 | 用途 |
|---|---|
| 必需审核者 | 部署前手动批准 |
| 等待时间 | 冷却期(例如 15 分钟) |
| 分支限制 | 仅允许 main 或 release/* 分支部署 |
| 自定义部署保护规则 | 第三方集成(监控检查) |
环境机密
环境可以拥有自己的机密,并覆盖仓库级机密。使用环境级机密存储部署凭据:
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- name: Deploy
env:
# These resolve to environment-specific values
CONNECTION_STRING: ${{ secrets.CONNECTION_STRING }}
API_KEY: ${{ secrets.API_KEY }}
run: ./deploy.sh
缓存策略
NuGet 包缓存
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: |
nuget-${{ runner.os }}-
restore-keys 前缀匹配可确保在 csproj 文件发生变化时仍能部分命中缓存(大多数包仍会保留在缓存中)。
.NET SDK 缓存
对于自托管运行器,或 SDK 安装缓慢的场景:
- name: Setup .NET with cache
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
cache: true
cache-dependency-path: '**/packages.lock.json'
actions/setup-dotnet@v4 中的 cache: true 选项会启用内置 NuGet 缓存,并使用 packages.lock.json 作为缓存键。
构建输出缓存(.NET 9+)
.NET 9 引入了 MSBuild 构建检查缓存。对于增量 CI 构建:
- name: Cache build output
uses: actions/cache@v4
with:
path: |
**/bin/
**/obj/
key: build-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.cs') }}
restore-keys: |
build-${{ runner.os }}-
谨慎使用构建输出缓存——过期缓存可能掩盖构建错误。优先将 NuGet 缓存用作 CI 加速的主要优化方式。
workflow_dispatch 输入
带参数的手动触发
on:
workflow_dispatch:
inputs:
environment:
description: 'Target deployment environment'
required: true
type: choice
options:
- staging
- production
default: staging
version:
description: 'Version to deploy (e.g., 1.2.3)'
required: true
type: string
dry-run:
description: 'Simulate deployment without applying changes'
required: false
type: boolean
default: false
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
with:
ref: v${{ inputs.version }}
- name: Deploy
env:
DRY_RUN: ${{ inputs.dry-run }}
run: |
set -euo pipefail
if [ "$DRY_RUN" = "true" ]; then
echo "DRY RUN: would deploy v${{ inputs.version }} to ${{ inputs.environment }}"
else
./deploy.sh --version ${{ inputs.version }}
fi
输入类型:string、boolean、choice、environment(从已配置的环境中选择)。
Agent 易错点
- 不要在同一事件中混用
paths和paths-ignore——同时指定时,paths-ignore会被静默忽略。二者选其一。 - 在矩阵构建中设置
fail-fast: false——默认的fail-fast: true会在某个作业失败时取消其他关联作业,导致无法发现其他哪些组合也会失败。 - 在所有 bash 步骤中使用
set -euo pipefail——如果不使用pipefail,管道命令中的非零退出状态(例如script | tee)不会导致步骤失败。 - 可复用工作流的输入默认是字符串——必须在
workflow_call输入中显式使用type:声明布尔值和数字类型。 - 缓存键必须包含
runner.os——NuGet 包依赖操作系统;在 Windows 上恢复由 Linux 构建的缓存会导致还原失败。 - 不要在工作流文件中硬编码 TFM——使用矩阵变量或从 csproj 中提取,以便工作流与项目配置保持同步。
secrets: inherit会传递调用方的所有机密——对于涉及安全的可复用工作流,应显式声明机密以限制暴露范围。- 部署的并发组必须使用
cancel-in-progress: false——取消正在进行的部署可能导致基础设施处于不一致状态。