次の方法で共有


チュートリアル: .NET を使用して GitHub Actionsを作成する

GitHub Actionsとして使用できる .NET アプリを作成する方法について説明します。 GitHub Actions により、ワークフローの自動化と構成が可能になります。 GitHub Actions を使用すると、GitHub からソース コードをビルド、テスト、デプロイできます。 さらに、アクションによって、プログラムで問題を操作したり、プル要求を作成したり、コード レビューを実行したり、ブランチを管理したりする機能が公開されます。 GitHub Actions を使用した継続的インテグレーションの詳細については、.NET のビルドとテストに関するページを参照してください。

このチュートリアルでは、以下の内容を学習します。

  • .NET アプリを GitHub Actions 用に準備する
  • アクションの入力と出力を定義する
  • ワークフローを構成する

前提条件

アプリの意図

このチュートリアルのアプリでは、次の操作を行うことによってコード メトリック分析を実行します。

  • *.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" 環境変数の値を書き込みます (この値が現在の実行環境で使用可能な場合)。 Name および Branch プロパティが解析され、"/" で区切られた文字列の最後のセグメントから割り当てられます。

定義されたアクション入力クラスを使用して、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 インスタンスを取得します。 これらの引数を解析できない場合、このアプリは 0 以外の終了コードで終了します。 詳細については、アクションの終了コードの設定に関するページを参照してください。

引数が正常に解析された場合、このアプリは、必要な入力で正しく呼び出されました。 この場合は、主要な機能 StartAnalysisAsync への呼び出しが行われます。

出力値を書き込むには、GitHub Actions: 出力パラメーターの設定によって認識される形式に従う必要があります。

.NET アプリを GitHub Actions 用に準備する

GitHub Actions では、アプリ開発として次の 2 つのバリエーションをサポートしています。

  • JavaScript (オプションで TypeScript)
  • Docker コンテナー (Docker 上で実行される任意のアプリ)

GitHub Actionsがホストされている仮想環境には、.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@sha256:d32bd65cf5843f413e81f5d917057c82da99737cb1637e905a1a4bc2e7ec6c8d 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@sha256:d32bd65cf5843f413e81f5d917057c82da99737cb1637e905a1a4bc2e7ec6c8d
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

警告

これは、"docker サポートの追加" 機能から作成された標準の Dockerfile とは異なるため、Dockerfile 内のすべての手順に細心の注意を払ってください。 特に、最後のいくつかの手順は、アプリの ENTRYPOINT に対するパスを変更する新しい WORKDIR を指定しないことで異なります。

前の Dockerfile のステップには、次のものが含まれます。

  • mcr.microsoft.com/dotnet/sdk:7.0 の基本イメージのエイリアス build-env としての設定。
  • コンテンツのコピーと .NET アプリの発行。
    • このアプリは、dotnet publish コマンドを使用して発行されます。
  • コンテナーへのラベルの適用。
  • mcr.microsoft.com/dotnet/sdk:7.0 の .NET SDK イメージの再階層化。
  • 発行されたビルド出力の build-env からのコピー。
  • dotnet /DotNet.GitHubAction.dll に委任されるエントリ ポイントの定義。

ヒント

mcr.microsoft.com の MCR は "Microsoft Container Registry" を表し、公式の Docker Hub の Microsoft のシンジケート化されたコンテナー カタログです。 詳細については、Microsoft によるコンテナー カタログのシンジケート化に関するページを参照してください。

注意事項

global.json ファイルを使用して SDK のバージョンを固定する場合は、Dockerfile でそのバージョンを明示的に参照する必要があります。 たとえば、global.json を使用して SDK のバージョンを 5.0.300 に固定した場合、Dockerfilemcr.microsoft.com/dotnet/sdk:5.0.300 を使用する必要があります。 このようにすると、新しいマイナー リビジョンがリリースされても、GitHub Actions が損なわれることがなくなります。

アクションの入力と出力を定義する

アプリを探索する」のセクションでは、ActionInputs クラスについて学習しました。 このオブジェクトは、GitHub Actionsの入力を表します。 GitHub でリポジトリが GitHub Actionsであることを認識できるようにするには、そのリポジトリのルートに 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 Actionsの namedescription
  • branding。これは、アクションをより一意に識別しやすくするために GitHub Marketplace で使用されます。
  • inputs。これは、ActionInputs クラスに 1 対 1 でマップします。
  • outputs。これは、Programワークフローの構成に書き込まれ、その一部として使用されます。
  • runs ノード。これは、そのアプリが docker アプリケーションであり、それにどのような引数を渡すかを GitHub に通知します。

詳細については、「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 ファイルでは、次の 3 つのプライマリ ノードを定義します。

  • ワークフローの 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 ステップで true の値を持つ updated-metrics の出力パラメーターが指定されると、Create pull request という名前の条件付きステップが実行されます。

重要

GitHub では、暗号化されたシークレットの作成が許可されます。 シークレットは、${{ secrets.SECRET_NAME }} 構文を使用して、ワークフローの構成内で使用できます。 GitHub Actionsのコンテキストには、既定値 ${{ secrets.GITHUB_TOKEN }} が自動的に設定される GitHub トークンが存在します。 詳細については、GitHub Actions のコンテキストと式の構文に関するページを参照してください。

すべてをまとめた配置

dotnet/samples GitHub リポジトリには、このチュートリアルのアプリを含む多数の .NET サンプル ソース コード プロジェクトがあります。

生成された CODE_METRICS.md ファイルはナビゲート可能です。 このファイルは、分析されたプロジェクトの階層を表します。 各プロジェクトには最上位のセクションがあり、絵文字は、入れ子になったオブジェクトの最も高いサイクロマティック複雑度の全体的な状態を表します。 ファイル内を移動すると、各セクションでは、各領域の概要と共にドリル ダウンの機会が提供されます。 マークダウンには、利便性の向上として折りたたみ可能なセクションがあります。

この階層は、次のように進行します。

  • プロジェクト ファイルからアセンブリへ
  • アセンブリから名前空間へ
  • 名前空間から名前付き型へ
  • 各名前付き型にはテーブルがあり、各テーブルには次のものがあります。
    • フィールド、メソッド、プロパティの行番号へのリンク
    • コード メトリックに関する個々の評価

操作の実例

このワークフローは、main ブランチへの push に関する on 操作によって、アクションの実行がトリガーされることを指定します。 これが実行されると、GitHub の [アクション] タブによって、その実行のライブ ログ ストリームが報告されます。 .NET code metrics 実行のログの例を次に示します。

.NET code metrics - GitHub Actions log

パフォーマンスの向上

サンプルに従った場合、このアクションが使用されるたびに、そのイメージの docker build が実行されることに気付く可能性があります。 そのため、すべてのトリガーは、コンテナーを実行する前にそれをビルドする時間が発生します。 GitHub Actions をマーケットプレースにリリースする前に、次の操作を行う必要があります。

  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: コンテナー レジストリの操作」を参照してください。

関連項目

次のステップ