教學課程:使用 .NET 建立 GitHub Action

瞭解如何建立可用來作為 GitHub Action 的 .NET 應用程式。 GitHub Actions 可啟用工作流程自動化與撰寫功能。 透過 GitHub Actions,您可以從 GitHub 建置、測試及部署原始程式碼。 此外,動作會公開以程式設計方式與問題互動、建立提取要求、執行程式碼檢閱及管理分支的能力。 如需持續與 GitHub Actions 整合的詳細資訊,請參閱 建置及測試 .NET

在本教學課程中,您會了解如何:

  • 準備適用于 GitHub Actions 的 .NET 應用程式
  • 定義動作輸入和輸出
  • 撰寫工作流程

必要條件

應用程式的意圖

此教學課程中的應用程式會透過下列方式執行程式碼度量分析:

  • 掃描及探索 *.csproj*.vbproj 專案檔。

  • 分析在這些專案內探索到的原始程式碼以了解:

    • 循環複雜度
    • 可維護性指數
    • 繼承深度
    • 類別結合程度
    • 原始程式碼的行數
    • 可執行檔程式碼的約略行數
  • 建立 (或更新) CODE_METRICS.md 檔案。

應用程式負責建立具有 CODE_METRICS.md 檔案變更的提取要求。 這些變更是當作工作流程撰寫的一部分來管理。

為簡潔起見,此教學課程中原始程式碼的參考省略了應用程式的數個部分。 完整的應用程式程式碼可在 GitHub 上取得

探索應用程式

.NET 主控台應用程式會使用 CommandLineParser NuGet 套件將引數剖析成 ActionInputs 物件。

using CommandLine;

namespace DotNet.GitHubAction;

public class ActionInputs
{
    string _repositoryName = null!;
    string _branchName = null!;

    public ActionInputs()
    {
        if (Environment.GetEnvironmentVariable("GREETINGS") is { Length: > 0 } greetings)
        {
            Console.WriteLine(greetings);
        }
    }

    [Option('o', "owner",
        Required = true,
        HelpText = "The owner, for example: \"dotnet\". Assign from `github.repository_owner`.")]
    public string Owner { get; set; } = null!;

    [Option('n', "name",
        Required = true,
        HelpText = "The repository name, for example: \"samples\". Assign from `github.repository`.")]
    public string Name
    {
        get => _repositoryName;
        set => ParseAndAssign(value, str => _repositoryName = str);
    }

    [Option('b', "branch",
        Required = true,
        HelpText = "The branch name, for example: \"refs/heads/main\". Assign from `github.ref`.")]
    public string Branch
    {
        get => _branchName;
        set => ParseAndAssign(value, str => _branchName = str);
    }

    [Option('d', "dir",
        Required = true,
        HelpText = "The root directory to start recursive searching from.")]
    public string Directory { get; set; } = null!;

    [Option('w', "workspace",
        Required = true,
        HelpText = "The workspace directory, or repository root directory.")]
    public string WorkspaceDirectory { get; set; } = null!;

    static void ParseAndAssign(string? value, Action<string> assign)
    {
        if (value is { Length: > 0 } && assign is not null)
        {
            assign(value.Split("/")[^1]);
        }
    }
}

上述動作輸入類別會定義數個必要的輸入,讓應用程式成功執行。 如果目前執行環境中提供 "GREETINGS" 環境變數值,建構函式將會寫入該環境變數值。 NameBranch 屬性是從 "/" 分隔字串的最後一個區段剖析並指派的。

使用已定義的動作輸入類別,將焦點放在 Program.cs 檔案上。

using System.Text;
using CommandLine;
using DotNet.GitHubAction;
using DotNet.GitHubAction.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using static CommandLine.Parser;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddGitHubActionServices();

using IHost host = builder.Build();

ParserResult<ActionInputs> parser = Default.ParseArguments<ActionInputs>(() => new(), args);
parser.WithNotParsed(
    errors =>
    {
        host.Services
            .GetRequiredService<ILoggerFactory>()
            .CreateLogger("DotNet.GitHubAction.Program")
            .LogError("{Errors}", string.Join(
                Environment.NewLine, errors.Select(error => error.ToString())));

        Environment.Exit(2);
    });

await parser.WithParsedAsync(
    async options => await StartAnalysisAsync(options, host));

await host.RunAsync();

static async ValueTask StartAnalysisAsync(ActionInputs inputs, IHost host)
{
    // Omitted for brevity, here is the pseudo code:
    // - Read projects
    // - Calculate code metric analytics
    // - Write the CODE_METRICS.md file
    // - Set the outputs

    var updatedMetrics = true;
    var title = "Updated 2 projects";
    var summary = "Calculated code metrics on two projects.";

    // Do the work here...

    // Write GitHub Action workflow outputs.
    var gitHubOutputFile = Environment.GetEnvironmentVariable("GITHUB_OUTPUT");
    if (!string.IsNullOrWhiteSpace(gitHubOutputFile))
    {
        using StreamWriter textWriter = new(gitHubOutputFile, true, Encoding.UTF8);
        textWriter.WriteLine($"updated-metrics={updatedMetrics}");
        textWriter.WriteLine($"summary-title={title}");
        textWriter.WriteLine($"summary-details={summary}");
    }

    await ValueTask.CompletedTask;

    Environment.Exit(0);
}

為簡潔起見,Program 檔案經過簡化,若要探索完整的範例來源,請參閱 Program.cs。 現有機制示範使用下列項目所需的未定案程式碼:

您可以使用外部專案或套件參考,並透過相依性插入註冊。 Get<TService> 是靜態區域函式,其需要 IHost 執行個體,並用來解析所需的服務。 透過 CommandLine.Parser.Default 單一資料庫,應用程式會從 args 取得 parser 執行個體。 當無法剖析引數時,應用程式會以非零的結束代碼結束。 如需詳細資訊,請參閱設定動作的結束代碼

成功剖析引數時,會以必要的輸入正確呼叫應用程式。 在此情況下,會呼叫主要功能 StartAnalysisAsync

若要寫入輸出值,您必須遵循 GitHub Actions:設定輸出參數所辨識的格式。

準備適用于 GitHub Actions 的 .NET 應用程式

GitHub Actions 支援兩種應用程式開發變化

  • JavaScript (選擇性 TypeScript)
  • Docker 容器 (Docker 上執行的任何應用程式)

裝載 GitHub Action 的虛擬環境可能或可能尚未安裝 .NET。 如需目標環境中預先安裝項目的相關資訊,請參閱 GitHub Actions 虛擬環境。 雖然您可以從 GitHub Actions 工作流程執行 .NET CLI 命令,但為功能更完整的 。以 NET 為基礎的 GitHub Action,建議您容器化應用程式。 如需詳細資訊,請參閱 容器化 .NET 應用程式

Dockerfile

Dockerfile 是建置映像的一組指令。 對於 .NET 應用程式, Dockerfile 通常位於方案檔旁邊的目錄根目錄。

# Set the base image as the .NET 7.0 SDK (this includes the runtime)
FROM mcr.microsoft.com/dotnet/sdk:7.0 as build-env

# Copy everything and publish the release (publish implicitly restores and builds)
WORKDIR /app
COPY . ./
RUN dotnet publish ./DotNet.GitHubAction/DotNet.GitHubAction.csproj -c Release -o out --no-self-contained

# Label the container
LABEL maintainer="David Pine <david.pine@microsoft.com>"
LABEL repository="https://github.com/dotnet/samples"
LABEL homepage="https://github.com/dotnet/samples"

# Label as GitHub action
LABEL com.github.actions.name="The name of your GitHub Action"
# Limit to 160 characters
LABEL com.github.actions.description="The description of your GitHub Action."
# See branding:
# https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions#branding
LABEL com.github.actions.icon="activity"
LABEL com.github.actions.color="orange"

# Relayer the .NET SDK, anew with the build output
FROM mcr.microsoft.com/dotnet/sdk:7.0
COPY --from=build-env /app/out .
ENTRYPOINT [ "dotnet", "/DotNet.GitHubAction.dll" ]

注意

本教學課程中的 .NET 應用程式依賴 .NET SDK 作為其功能的一部分。 Dockerfile 會建立一組新的 Docker 圖層,與先前的圖層無關。 其會從頭開始使用 SDK 映像,並從先前的一組圖層新增組建輸出。 對於不需要 .NET SDK 作為其功能的一部分的應用程式 ,它們應該只依賴 .NET 執行時間。 這會大幅減少映像的大小。

FROM mcr.microsoft.com/dotnet/runtime:7.0

警告

請密切注意 Dockerfile 中的每個步驟,因為其與從「新增 Docker 支援」功能建立的標準 Dockerfile 不同。 特別是,最後幾個步驟會因未指定新的 WORKDIR 而有所不同,這會變更應用程式 ENTRYPOINT 的路徑。

上述 Dockerfile 步驟包括:

  • mcr.microsoft.com/dotnet/sdk:7.0 的基礎映像設定為別名 build-env
  • 複製內容併發布 .NET 應用程式:
  • 將標籤套用至容器。
  • 轉寄 .NET SDK 映射的來源 mcr.microsoft.com/dotnet/sdk:7.0
  • build-env 複製已發佈的組建輸出。
  • 定義委派給 dotnet /DotNet.GitHubAction.dll 的進入點。

提示

mcr.microsoft.com 中的 MCR 是 "Microsoft Container Registry" 的縮寫,而且是 Microsoft 官方 Docker 中樞同步發佈的容器目錄。 如需詳細資訊,請參閱 Microsoft 同步發佈容器目錄 (英文)。

警告

如果您使用 global.json 檔案來鎖定 SDK 版本,您應該明確地參考 Dockerfile 中的該版本。 例如,如果您已使用 global.json 來鎖定 SDK 版本 5.0.300,您的 Dockerfile 應該使用 mcr.microsoft.com/dotnet/sdk:5.0.300。 這可防止在發行新的次要修訂時中斷 GitHub Actions。

定義動作輸入和輸出

探索應用程式一節中,您已了解 ActionInputs 類別。 此物件代表 GitHub Action 的輸入。 若要讓 GitHub 辨識存放庫是 GitHub Action,您的存放庫根目錄必須有 action.yml 檔案。

name: 'The title of your GitHub Action'
description: 'The description of your GitHub Action'
branding:
  icon: activity
  color: orange
inputs:
  owner:
    description:
      'The owner of the repo. Assign from github.repository_owner. Example, "dotnet".'
    required: true
  name:
    description:
      'The repository name. Example, "samples".'
    required: true
  branch:
    description:
      'The branch name. Assign from github.ref. Example, "refs/heads/main".'
    required: true
  dir:
    description:
      'The root directory to work from. Examples, "path/to/code".'
    required: false
    default: '/github/workspace'
outputs:
  summary-title:
    description:
      'The title of the code metrics action.'
  summary-details:
    description:
      'A detailed summary of all the projects that were flagged.'
  updated-metrics:
    description:
      'A boolean value, indicating whether or not the action updated metrics.'
runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
  - '-o'
  - ${{ inputs.owner }}
  - '-n'
  - ${{ inputs.name }}
  - '-b'
  - ${{ inputs.branch }}
  - '-d'
  - ${{ inputs.dir }}

上述 action.yml 檔案會定義:

  • GitHub Action 的 namedescription
  • branding,其用於 GitHub Marketplace,有助於更唯一識別您的動作
  • inputs,其與 ActionInputs 類別一對一對應
  • outputs,其會寫入 Program 並當作工作流程撰寫的一部分使用
  • runs 節點,可告知 GitHub 應用程式是 docker 應用程式,以及要對其傳遞的引數

如需詳細資訊,請參閱 GitHub Actions 的中繼資料語法 (英文)。

預先定義的環境變數

使用 GitHub Actions,您預設會得到許多環境變數。 例如,變數 GITHUB_REF 將一律包含觸發工作流程執行之分支或標籤的參考。 GITHUB_REPOSITORY 具有擁有者與存放庫名稱,例如 dotnet/docs

您應該探索預先定義的環境變數,並據以使用這些環境變數。

工作流程撰寫

.NET 應用程式容器化 ,以及 已定義的動作輸入和輸出 之後,您就可以取用動作。 GitHub Actions 不需要在 GitHub Marketplace 中發佈,即可使用。 工作流程在存放庫的 .github/workflows 目錄中定義為 YAML 檔案。

# The name of the work flow. Badges will use this name
name: '.NET code metrics'

on:
  push:
    branches: [ main ]
    paths:
    - 'github-actions/DotNet.GitHubAction/**'               # run on all changes to this dir
    - '!github-actions/DotNet.GitHubAction/CODE_METRICS.md' # ignore this file
  workflow_dispatch:
    inputs:
      reason:
        description: 'The reason for running the workflow'
        required: true
        default: 'Manual run'

jobs:
  analysis:

    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write

    steps:
    - uses: actions/checkout@v3

    - name: 'Print manual run reason'
      if: ${{ github.event_name == 'workflow_dispatch' }}
      run: |
        echo 'Reason: ${{ github.event.inputs.reason }}'

    - name: .NET code metrics
      id: dotnet-code-metrics
      uses: dotnet/samples/github-actions/DotNet.GitHubAction@main
      env:
        GREETINGS: 'Hello, .NET developers!' # ${{ secrets.GITHUB_TOKEN }}
      with:
        owner: ${{ github.repository_owner }}
        name: ${{ github.repository }}
        branch: ${{ github.ref }}
        dir: ${{ './github-actions/DotNet.GitHubAction' }}
      
    - name: Create pull request
      uses: peter-evans/create-pull-request@v4
      if: ${{ steps.dotnet-code-metrics.outputs.updated-metrics }} == 'true'
      with:
        title: '${{ steps.dotnet-code-metrics.outputs.summary-title }}'
        body: '${{ steps.dotnet-code-metrics.outputs.summary-details }}'
        commit-message: '.NET code metrics, automated pull request.'

重要

針對容器化的 GitHub Actions,您必須使用 runs-on: ubuntu-latest。 如需詳細資訊,請參閱工作流程語法 jobs.<job_id>.runs-on

上述工作流程 YAML 檔案會定義三個主要節點:

  • 工作流程的 name。 此名稱也是建立工作流程狀態徽章時所使用的名稱。
  • on 節點會定義觸發動作的時機與方式。
  • jobs 節點會概述每個作業內的各種作業與步驟。 個別步驟會取用 GitHub Actions。

如需詳細資訊,請參閱建立您的第一個工作流程

將焦點放在 steps 節點上,撰寫更為明顯:

steps:
- uses: actions/checkout@v3

- name: 'Print manual run reason'
  if: ${{ github.event_name == 'workflow_dispatch' }}
  run: |
    echo 'Reason: ${{ github.event.inputs.reason }}'

- name: .NET code metrics
  id: dotnet-code-metrics
  uses: dotnet/samples/github-actions/DotNet.GitHubAction@main
  env:
    GREETINGS: 'Hello, .NET developers!' # ${{ secrets.GITHUB_TOKEN }}
  with:
    owner: ${{ github.repository_owner }}
    name: ${{ github.repository }}
    branch: ${{ github.ref }}
    dir: ${{ './github-actions/DotNet.GitHubAction' }}
  
- name: Create pull request
  uses: peter-evans/create-pull-request@v4
  if: ${{ steps.dotnet-code-metrics.outputs.updated-metrics }} == 'true'
  with:
    title: '${{ steps.dotnet-code-metrics.outputs.summary-title }}'
    body: '${{ steps.dotnet-code-metrics.outputs.summary-details }}'
    commit-message: '.NET code metrics, automated pull request.'

jobs.steps 代表工作流程撰寫。 步驟經過協調之後,使其成為循序、可溝通且可撰寫的步驟。 透過代表步驟的各種 GitHub Actions,使其每個步驟都有輸入和輸出,即可撰寫工作流程。

在上述步驟中,您可以觀察:

  1. 存放庫已簽出

  2. 手動執行時,系統會將訊息列印至工作流程記錄檔。

  3. 識別為 dotnet-code-metrics 的步驟:

    • uses: dotnet/samples/github-actions/DotNet.GitHubAction@main 是本教學課程中容器化 .NET 應用程式的位置。
    • env 會建立環境變數 "GREETING",這是在應用程式執行時列印的。
    • with 會指定每個必要的動作輸入。
  4. dotnet-code-metrics 步驟指定值為 trueupdated-metrics 輸出參數時,就會執行名為 Create pull request 的條件式步驟。

重要

GitHub 允許建立加密的祕密。 使用 ${{ secrets.SECRET_NAME }} 語法即可在工作流程撰寫內使用祕密。 在 GitHub Action 的內容中,預設會自動填入 GitHub 權杖:${{ secrets.GITHUB_TOKEN }}。 如需詳細資訊,請參閱 GitHub Actions 的內容和運算式語法 (英文)。

組合在一起

dotnet/samples GitHub 存放 庫是許多 .NET 範例原始程式碼專案的首頁,包括 本教學課程 中的應用程式。

產生的 CODE_METRICS.md 檔案是可瀏覽的。 此檔案代表其分析之專案的階層。 每個專案都有最上層區段,以及代表巢狀物件的最高循環複雜度整體狀態的 Emoji。 當您瀏覽檔案時,每個區段都會以每個區域的摘要,公開向下切入機會。 為方便起見,Markdown 包含可摺疊的區段。

階層可從:

  • 專案檔至組件
  • 組件至命名空間
  • 命名空間至具名類型
  • 每個具名類型都有一個資料表,而且每個資料表都有:
    • 指向欄位、方法和屬性之行號的連結
    • 程式碼度量的個別分級

作用中

工作流程會指定 on 一個 pushmain 分支上,會觸發動作來執行。 執行時,GitHub 中的 [動作] 索引標籤會報告其執行的即時記錄資料流。 以下是 .NET code metrics 執行的範例記錄:

.NET code metrics - GitHub Actions log

效能改善

如果您按照範例進行,可能會注意到每次使用此動作時,其都會針對該映像執行 docker build。 因此,每個觸發程序都需要一些時間來建置容器,然後再執行容器。 將 GitHub Actions 發行至 Marketplace 之前,您應該:

  1. (自動) 建置 Docker 映像
  2. 將 Docker 映像推送至 GitHub Container Registry (或其他任何公用容器登錄)
  3. 將動作變更為不建置映像,但使用公用登錄中的映像。
# Rest of action.yml content removed for readability
# using Dockerfile
runs:
  using: 'docker'
  image: 'Dockerfile' # Change this line
# using container image from public registry
runs:
  using: 'docker'
  image: 'docker://ghcr.io/some-user/some-registry' # Starting with docker:// is important!!

如需詳細資訊,請參閱 GitHub Docs:使用容器登錄 (英文)。

另請參閱

下一步