Share via


자습서: .NET을 사용하여 GitHub 작업 만들기

GitHub 작업으로 사용할 수 있는 .NET 앱을 만드는 방법을 알아봅니다. GitHub Actions 를 사용하면 워크플로 자동화 및 컴퍼지션이 가능합니다. GitHub Actions를 사용하면 GitHub에서 소스 코드를 빌드, 테스트 및 배포할 수 있습니다. 또한 작업은 프로그래밍 방식으로 문제와 상호 작용하고, 끌어오기 요청을 만들고, 코드 검토를 수행하고, 분기를 관리하는 기능을 노출합니다. GitHub Actions와의 지속적인 통합에 대한 자세한 내용은 .NET 빌드 및 테스트를 참조하세요.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • GitHub Actions용 .NET 앱 준비
  • 작업 입력 및 출력 정의
  • 워크플로 작성

필수 조건

앱의 의도

이 자습서의 앱은 다음을 통해 코드 메트릭 분석을 수행합니다.

  • *.csproj 및 *.vbproj 프로젝트 파일 검색 및 검색

  • 다음 프로젝트 내에서 검색된 소스 코드를 분석하는 방법은 다음과 같습니다.

    • 순환 복잡성
    • 유지 관리 효율성 인덱스
    • 상속 수준
    • 클래스 결합
    • 소스 코드 줄 수
    • 대략적 실행 코드 줄
  • CODE_METRICS.md 파일을 만들거나 업데이트합니다.

앱은 CODE_METRICS.md 파일의 변경 내용으로 끌어오기 요청을 만들 책임이 없습니다. 이러한 변경 내용은 워크플로 컴퍼지션의 일부로 관리됩니다.

이 자습서의 소스 코드에 대한 참조에는 간결하게 하기 위해 생략된 앱의 일부가 있습니다. 전체 앱 코드는 GitHub에서 사용할 수 있습니다.

앱 살펴보기

.NET 콘솔 앱은 NuGet 패키지를 사용하여 CommandLineParser 인수를 개체로 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" 합니다. 및 Branch 속성은 Name 구분된 문자열의 "/" 마지막 세그먼트에서 구문 분석되고 할당됩니다.

정의된 작업 입력 클래스를 사용하여 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 앱이 .에서 인스턴스를 parserargs가져옵니다. 인수를 구문 분석할 수 없는 경우 앱은 0이 아닌 종료 코드로 종료됩니다. 자세한 내용은 작업에 대한 종료 코드 설정을 참조 하세요.

인수가 성공적으로 구문 분석되면 필요한 입력으로 앱이 올바르게 호출되었습니다. 이 경우 기본 기능을 호출합니다 StartAnalysisAsync .

출력 값을 작성하려면 GitHub Actions: 출력 매개 변수 설정에서 인식하는 형식을 따라야 합니다.

GitHub Actions용 .NET 앱 준비

GitHub Actions는 두 가지 앱 개발 변형을 지원합니다.

  • JavaScript(선택적으로 TypeScript)
  • Docker 컨테이너(Docker에서 실행되는 모든 앱)

GitHub 작업이 호스트되는 가상 환경에 .NET이 설치되어 있거나 설치되어 있지 않을 수 있습니다. 대상 환경에 미리 설치된 항목에 대한 자세한 내용은 GitHub Actions Virtual Environments를 참조 하세요. 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

Warning

"docker 지원 추가" 기능에서 만든 표준 Dockerfile과 다르므로 Dockerfile의 모든 단계에 주의하세요. 특히 마지막 몇 단계는 앱ENTRYPOINT의 경로를 변경하는 새 WORKDIR 단계를 지정하지 않음으로써 달라집니다.

위의 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.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 파일은 다음을 정의합니다.

  • name GitHub 작업 및 description
  • 작업을 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.'

Important

컨테이너화된 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. 단계가 값true이 있는 출력 매개 변수 updated-metricsdotnet-code-metrics 지정하면 명명된 Create pull request 조건부 단계가 실행됩니다.

Important

GitHub를 사용하면 암호화된 비밀을 만들 수 있습니다. 비밀을 구문을 사용하여 ${{ secrets.SECRET_NAME }} 워크플로 컴퍼지션 내에서 사용할 수 있습니다. GitHub 작업의 컨텍스트에는 기본적으로 ${{ secrets.GITHUB_TOKEN }}자동으로 채워지는 GitHub 토큰이 있습니다. 자세한 내용은 GitHub Actions에 대한 컨텍스트 및 식 구문을 참조 하세요.

모든 요소 결합

dotnet/samples GitHub 리포지토리에는 이 자습서의 앱을 포함하여 많은 .NET 샘플 소스 코드 프로젝트가 있습니다.

생성된 CODE_METRICS.md 파일은 탐색할 수 있습니다. 이 파일은 분석한 프로젝트의 계층 구조를 나타냅니다. 각 프로젝트에는 최상위 섹션과 중첩된 개체에 대한 가장 높은 순환 복잡성의 전체 상태 나타내는 이모지가 있습니다. 파일을 탐색할 때 각 섹션은 각 영역에 대한 요약과 함께 드릴다운 기회를 노출합니다. markdown에는 추가 편의를 위해 축소 가능한 섹션이 있습니다.

계층 구조는 다음에서 진행됩니다.

  • 어셈블리에 프로젝트 파일
  • 어셈블리에서 네임스페이스로
  • 네임스페이스를 명명된 형식으로
  • 명명된 각 형식에는 테이블이 있으며 각 테이블에는 다음이 있습니다.
    • 필드, 메서드 및 속성의 줄 번호에 대한 링크
    • 코드 메트릭에 대한 개별 등급

예제

워크플로는 분기로 pushmain 작업이 실행되도록 트리거되도록 지정 on 합니다. 실행되면 GitHub의 작업 탭에서 실행의 라이브 로그 스트림을 보고합니다. 다음은 실행의 예제 로그입니다 .NET code metrics .

.NET code metrics - GitHub Actions log

성능 개선 사항

샘플을 따라 진행한 경우 이 작업이 사용될 때마다 해당 이미지에 대한 Docker 빌드수행한다는 것을 알 수 있습니다. 따라서 모든 트리거는 컨테이너를 실행하기 전에 빌드하는 데 약간의 시간이 소요됩니다. Marketplace에 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: Container Registry 작업을 참조하세요.

참고 항목

다음 단계