Рубрики
Uncategorized

Публикация нетривиального .NET с действиями GitHub

Использование действий GitHub для выполнения различных задач, связанных с проектом C#. Создание собственных библиотек для нескольких платформ, автоматических тестов и покрытия кода, а также окончательная упаковка и публикация в Nuget. Tagged с DevOps, Dotnet, GitHub, ShowDev.

За эти годы я использовал несколько разных решений CI для моих скромных общественных проектов: Трэвис , Приложение и в конечном итоге лазурные трубопроводы.

Первоначально мне понравилась идея разделения CI/CD, чтобы избежать страшного блокировки поставщика и освободить меня, чтобы изменить поставщика SCC по желанию, но мне никогда не нравилось всегда посещать внешний сайт. Действия GitHub интересны для проектов, размещенных на GitHub, поскольку это интегрированное решение, аналогичное тому, которое предлагается конкурентами, такими как Gitlab.

Здесь я мигрирую один из моих существующих проектов .NET в GitHub Actions, но только детали специфичны для .NET, и большинство должно быть одинаково применимо к другим проектам.

Основы

В одном из ваших репозиториев вы можете щелкнуть Действия В таблице, а затем Установите этот рабочий процесс кнопка для инициализации .github/Workflows/*. YML Файл на основе содержимого репо. Начальные рабочие процессы происходят из шаблонов в Действия GitHub/стартовые потоки и выглядеть похоже на Этот рабочий процесс для .net :

name: .NET Core

# Trigger event
on:
  # Run on a push or pull request to default branch (usually master)
  push:
    branches: [ $default-branch ]
  pull_request:
    branches: [ $default-branch ]

# Jobs that run in parallel
jobs:
  build:

    runs-on: ubuntu-latest

    # Steps that run sequentially
    steps:
      # git checkout repo
    - uses: actions/checkout@v2
      # Install dotnet
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.101
      # Build and run tests
    - name: Build
      run: |
        dotnet restore
        dotnet build --configuration Release --no-restore
    - name: Test
      run: dotnet test --no-restore --verbosity normal

Рабочие процессы запускаются событиями и запускают один или несколько Работа в параллели. Работа — это набор последовательных шаги это либо сценарий оболочки, либо действие.

на: Определяет события, которые запускают рабочий процесс. События могут быть чем угодно От толкания с клиентом GIT, создание выпуска на GitHub, периодических таймеров и многого другого.

Runs-on: Определяет виртуальную среду, размещающую бегун рабочего процесса (то есть Windows, Linux или MacOS).

использует: Определяет многоразовое действие (например, setup-dotnet ), чтобы позаботиться о шагах шаблонов, общих для сценариев CI/CD. Они могут быть определены в: в том же репо, публичных репо или опубликованных изображений Docker.

с: передает параметры ключевой стоимости действий.

запустить: Выполняет командные линии на оболочке. запустить: | (с трубой) допускает несколько строк команд.

GitHub предоставляет Введение и Синтаксическая документация Анкет

Несколько платформ

Одним из первых изменений, которые я сделал, было создание и тестирование на нескольких платформах:

jobs:
  build:
    strategy:
      matrix:
        os: [windows-2019, ubuntu-18.04, macos-10.15]
    runs-on: ${{ matrix.os }}
    env:
      DOTNET_NOLOGO: true
      DOTNET_CLI_TELEMETRY_OPTOUT: true
      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
    steps:
    # ...
    - name: Code coverage
      if: ${{ matrix.os }} == 'ubuntu-18.04'
      uses: codecov/codecov-action@v1
      # ...

Стратегия: и Матрица: Позвольте вам создать несколько заданий из одного определения. Здесь настройка каждого из «Windows», «Ubuntu» и «macos» значений из ОС: к $ {{matrix.os}} так Runs-on: выполняет задание на всех трех ОС. Аналогично можно использовать для тестирования с несколькими времени запуска/фреймворков/компиляторов, конфигураций отладки/выпуска/профиля и т. Д.

env: Устанавливает переменные среды на всех этапах работы.

if: Включает работу или шаг только при условии, указанном выражение Анкет $ {{}} Необязательно с if: , вы также можете написать if: matrix.os Анкет

Покрытие кода

Я ранее использовал OpenCover к инструменту .NET Projects. У Microsoft есть хорошая статья Детализация покрытия кода для модульных тестов .NET. Они используют Покрытие что теперь, кажется, является решением de-fact .NET, учитывая, что тестовые проекты XUNIT, созданные с помощью dotnet new xunit автоматически добавьте ссылку на Coverlet.collector Анкет

Коверлет имеет 3 различных способа профиля проектов. Первый подход хорошо интегрируется, используя MSBuild ( Docs ):

# Add package reference to coverlet.msbuild in test project
dotnet add tests/tests.csproj package coverlet.msbuild
# Run tests and generate `coverage.json`
dotnet test /p:CollectCoverage=true

Второй подход использует Глобальный инструмент :

# Install global tool
dotnet tool install --global coverlet.console
# Run tests
coverlet tests/bin/Debug/netcoreapp3.1/tests.dll --target dotnet --targetargs "test --no-build"

Обязательно включите --нет-сборка или сгенерированный отчет будет пустым. Согласно Этот блог Инструменты покрытия существующей сборки, так что вы не хотите Dotnet Test Восстановление и замена его.

Есть третий подход, где вы используете «Datacollector» (как показано в статье MS). Коверлет предоставляет информацию Сравнение подходов а также Связанные недостатки Анкет

Теперь, когда у вас есть данные о покрытии, вы захотите создать отчет с чем -то вроде Reportgenerator , или облачное решение, как Codecov Как я буду здесь.

Codecov предоставляет Codecov-Action Для легкой интеграции с действиями GitHub:

    steps:
    # ...
    - name: Test
      run: dotnet test --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=lcov
    - name: Code coverage
      if: matrix.os == 'ubuntu-18.04'
      uses: codecov/codecov-action@v1
      with:
        files: ./tests/coverage.info
        flags: unittests

Если Codecov сообщит «отчеты о покрытии ошибок, вы останетесь, вы будете смотреть на бесполезную:

В моем случае, несмотря на Казалось бы, поддержка json (По умолчанию, произведенный Coverlet) Codecov не смог его обработать. Добавление /п: SoipletOutputFormat = LCOV на тестовый запуск исправил его.

В зависимости от среды CI, структуры тестирования и т. Д. Codecov может быть не в состоянии обработать отчеты о покрытии без Фиксирование пути Анкет Это среди других параметров конфигурации может быть размещено в Codecov.yml (который можно даже размещать в .github/ ). Смотрите Codecov.yml Ссылка Для получения полной информации.

Значки

Поговорим о Flair Анкет Значки обеспечивают простой способ следить за вашим проектом:

Самый простой способ получить значок сборки — это открыть файл рабочего процесса или запустить, а затем нажмите ···> Создать значок статуса Чтобы сгенерировать уценку, аналогичную:

![](https://github.com/USER/PROJECT/workflows/WORKFLOW_NAME/badge.svg)

Проверьте Документация для специфики и дополнительных вариантов.

Для Codecov откройте репозиторий, затем Настройки> Значок Анкет В качестве альтернативы, вы можете Создайте значок непосредственно из действий Анкет

Нативный код

Я также хочу, чтобы действия создали некоторые нативные двоичные файлы. Даже если у вас нет собственного кода, этот же подход может использоваться для многоплатформенного тестирования или для создания посредничных выходов/артефактов.

Как раньше , мы можем использовать Docker и QEMU на Linux, чтобы легко построить бинарные файлы с конкретной платформой без перекрестной компиляции. Суть создает Dockerfile, как:

# Start with Debian for arm32
FROM multiarch/debian-debootstrap:armhf-buster AS arm32v7

# Install required software
RUN apt-get update && apt-get install -y \
    build-essential \
    clang \
    cmake

RUN mkdir -p build && cd build \
    # Build arm32 binary 
    && cmake -G "Unix Makefiles" .. \
    && make \
    # Copy out of container to host
    && cp libnng.so /runtimes

Вместо того, чтобы смешивать мои декларативные действия рабочими процессом с большим количеством ветвления и специфичных для платформы махинаций, мне нравится перемещать логику в отдельный сценарий.

build_nng.ps1 позаботитесь об этих конкретных деталях здания:

if ($IsLinux) {
    # Register QEmu to handle unsupported binaries
    docker run --rm --privileged multiarch/qemu-user-static:register
    # Build our docker image
    docker build -t build-nng Dockerfile
    # Mount `/runtimes` and run image
    docker run -i -t --rm -v "$PWD/nng.NETCore/runtimes:/runtimes" build-nng
}
else {
    if ($is_windows) {
        cmake -A $arch -G "Visual Studio 16 2019" -DBUILD_SHARED_LIBS=ON -DNNG_TESTS=OFF -DNNG_TOOLS=OFF ..
        cmake --build . --config Release
        $dll = "Release/nng.dll"
    } else {
        cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DNNG_TESTS=OFF -DNNG_TOOLS=OFF ..
        make -j2
        $dll = "libnng.dylib"
    }
    Copy-Item $dll "$runtimes/$path/native" -Force
}

Создайте еще один рабочий процесс действия GitHub:

on:
  workflow_dispatch:
    inputs:
      nng_tag:
        description: 'NNG version'
        required: true
jobs:
  build:
    strategy:
      matrix:
        os: [windows-2019, ubuntu-18.04, macos-10.15]
    runs-on: ${{ matrix.os }}
    steps:
    - name: Checkout nng.NET
      uses: actions/checkout@v2
    - name: Build
      run: |
        ./scripts/build_nng.ps1 -nng_tag ${{ github.event.inputs.nng_tag }}

Этот рабочий процесс запускается workflow_dispatch событие с Входные данные: параметры, которые могут быть вручную запустить через ui github:

github.event является частью Контекст о рабочем процессе запуска Анкет Среди других значений он содержит nng_tag: от запуска события.

workflow_dispatch также может быть Запускается http :

curl \
  -X POST \
  -H "Accept: application/vnd.github.v3+json" \
  https://api.github.com/repos/$USER/$REPO/actions/workflows/$WORKFLOW/dispatches \
  -d '{"inputs": {"nng_tag": "v1.3.0"}, "ref": "master"}' \
  # For authorization, must add one of:
  -u $USER:$TOKEN
  #OR
  -H "Authorization: Bearer $TOKEN"
  #OR
  -H "Authorization: Token $TOKEN"

Эквивалент в PowerShell:

$SECURE_TOKEN=ConvertTo-SecureString -String $TOKEN -AsPlainText
Invoke-WebRequest `
    -Method Post `
    -Headers @{accept= 'application/vnd.github.v3+json'} `
    -Uri https://api.github.com/repos/$USER/$REPO/actions/workflows/$WORKFLOW/dispatches `
    -Body (ConvertTo-Json @{inputs=@{nng_tag="v1.3.0"}; ref= "master"}) `
    # For authorization, must add one of:
    -Authentication OAuth -Token $SECURE_TOKEN
    # OR
    -Authentication Bearer -Token $SECURE_TOKEN
    # OR by replacing above `-Headers`
    -Headers @{authorization= "Token $TOKEN"; accept= 'application/vnd.github.v3+json'}

Смотрите «Начало работы с API REST: аутентификация» Для получения дополнительной информации и подробностей о создании токенов GitHub API. Похоже, что ручные события должны работать, даже если рабочий процесс не в филиале по умолчанию, но Похоже, есть проблемы

Если есть промежуточный артефакт, который вы хотите вернуть в проект или сборку, Действия/проверка@v2 Облегчает работу с git :

# ...
    steps:
      - run: |
          git config user.name ${{ username }}
          git config user.email ${{ email }}
          git add .
          git commit -m "generated"
          git push

После настройки user.name и user.email , подтолкните изменения в филиал или сделайте больше всего, что еще больше, подходящее для вашего проекта.

Вы также можете Прикрепите артефакты сборки с Действия/upload-artifact :

      - name: Archive artifacts
        uses: actions/upload-artifact@v2
        with:
        path: |
            nng.NETCore/runtimes/**/*
            !nng.NETCore/runtimes/any/**/*
        if-no-files-found: error

Пакет и опубликовать

Последний шаг — упаковка и выпуск реестра Nuget, как мы используется для вручную Анкет

Упрощенный скрипт:

param(
    [string]$Version,
    [string]$NugetApiKey,
    [string]$CertBase64,
    [string]$CertPassword
)

$ErrorActionPreference = "Stop"

# Strip the leading "v".  E.g. "v1.3.2-rc0" => "1.3.2-rc0"
$Version = $Version -replace "^v",""

# Build nupkg
dotnet pack --configuration Release -p:Version=$Version
# Get list of build nupkgs
$packages = Get-ChildItem "./bin/Release/*.nupkg"

# Download nuget.exe
$Nuget = "./nuget.exe"
Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile $Nuget

# Create temporary code-signing certificate from base64-encoded string
$tempCert = New-TemporaryFile
[IO.File]::WriteAllBytes($tempCert.FullName, [Convert]::FromBase64String($CertBase64))

# Sign nupkg
foreach ($pkg in $packages) {
    & $Nuget sign $pkg -Timestamper http://sha256timestamp.ws.symantec.com/sha256/timestamp -CertificatePath $tempCert.FullName -CertificatePassword $CertPassword -Verbosity quiet
}

# Delete temporary code-signing certificate
$tempCert.Delete()

# Upload nupkg to nuget  
foreach ($pkg in $packages) {
    dotnet nuget push $pkg -k $NugetApiKey -s https://api.nuget.org/v3/index.json
}

Есть Несколько вариантов управления .NET Версия Анкет Я решил установить всю версию здесь, в Time Build с Dotnet Pack -p: версия = $ версия , но вместо этого вы можете сохранить его в файлах проекта под управлением источника, только установите суффикс с Dotnet Build-version-suffix , или какая -то комбинация их.

Чтобы подписать пакет, вы все еще нужно nuget:

nuget.exe sign  -Timestamper http://sha256timestamp.ws.symantec.com/sha256/timestamp -CertificatePath 

Для предоставления сертификата подписания кода действиям, которые вы можете BASE64 кодировать это и рассматривать его как один из секретов сборки (как показано ниже). Это означает, что мы должны декодировать его во временный файл перед запуском Nuget.

Дополнительные ресурсы, связанные с упаковкой/публикацией .NET:

Рабочий процесс упаковки:

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
        version:
          required: true

jobs:
  build:
    runs-on: windows-2019
    # ...
    - name: Package
      shell: pwsh
      run: |

        # Get the version
        $version = "${{ github.event_name }}" == "release" ? "${{ github.event.release.tag_name }}" : "${{ github.event.inputs.version }}"

        # Package and upload to nuget
        ./scripts/nupkg.ps1 -Version $version -CertBase64 ${{ secrets.CODE_SIGN_CERT_BASE64 }} -CertPassword ${{ secrets.CODE_SIGN_CERT_PASSWORD }} -NugetApiKey ${{ secrets.NUGET_API_KEY }}

Здесь я использую В: релиз: запустить рабочий процесс в выпусках GitHub; Публикуйте релиз, и пакеты появляются в Nuget.

Я хочу $ версия исходить из: git тег, связанный с выпуском, или из поставленного inputs.version При отправке вручную. Мы могли бы использовать if: Чтобы обеспечить альтернативные реализации шага, но, поскольку это просто значение одной переменной, кажется, что в скрипте легче разрешить.

Секреты Контекст обеспечивает доступ к значениям, созданным в репозитории GitHub с Настройки> Секреты> Новый секрет репозитория Анкет Идея — Избегайте хранения конфиденциальной информации (Например, пароли) в общественных местах, а действия делают дополнительный шаг, чтобы очистить их из журналов.

Вместо (или в дополнение к) подталкивать вашу посылку в реестр, вы также можете Загрузите его в качестве актива выпуска прикреплено к выпуску.

Плавник

Я думаю, что это служит хорошим началом моей миграции в действии GitHub. Действительно, я едва поцарапал поверхность, так как еще не изучал действия сообщества, написав свои собственные действия, самостоятельные бегуны и гораздо больше.

Оригинал: «https://dev.to/jeikabu/publishing-non-trivial-net-with-github-actions-53ef»