Русский
Русский
English
Статистика
Реклама

Msbuild

Из песочницы По мотивам youtube-dl C 9 и музыка ВК

28.10.2020 06:04:03 | Автор: admin
image
В 2020ом мы пользуемся разными музыкальными сервисами, но как реликт ушедшей эпохи, в забытом профиле ВК, у многих хранится музыка. Функции для загрузки нет, но что если позарез нужно спасти аудиозапись?
Под катом рассмотрен процесс создания self-hosted утилиты для загрузки своих аудио, не сливающей данные профиля сторонним сервисам и демонстрирующей мощь экосистемы современного и кроссплатформенного .NET.

Работать утилита будет так:
 dotnet vkm [login] [password] [audio-lemma]

Перво-наперво создадим репозиторий и опишем в одном файле csproj зависимости проекта
<Project Sdk="Microsoft.NET.Sdk">    <PropertyGroup>        <!--Утилита будет работать из консоли-->        <OutputType>Exe</OutputType>        <TargetFramework>netcoreapp3.1</TargetFramework>        <!--Строго запрещаем null -->        <Nullable>enable</Nullable>        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>        <!--И включаем C# 9 -->        <LangVersion>9</LangVersion>    </PropertyGroup>    <ItemGroup>        <!--Зависимость от VK API без необходимости вручную получать токен-->        <PackageReference Include="VkNet" Version="1.56.0" />        <!--Доступ к своим сообщениям, комментариям и музыке-->        <PackageReference Include="VkNet.AudioBypassService" Version="1.7.0" />    </ItemGroup></Project>

После этого с чистой совестью можно приступать к написанию кода. Нам потребуется авторизация утилиты в ВК с полным доступом к своему профилю. И как мы видим, благодаря экосистеме .NET, сделать это невероятно просто:
static class Vk{    internal static VkApi LoginToVkApi(string login, string password)    {        // Включаем доступ к своим сообщениям, комментариям и аудиозаписям        var api = new VkApi(new ServiceCollection().AddAudioBypass());        api.Authorize(new ApiAuthParams        {             ApplicationId = 1980660,            Login = login,             Password = password,             Settings = All         });        $"Login as vk.com/id{api.UserId}".Println(DarkBlue);        return api;    }}

Опишем точку входа и фильтр загружаемых аудиозаписей. Используем для этого top-level programs и прямо в файле Application.cs валидируем аргументы, одновременно инициализируя api
var vk = args.Length switch{    3 => LoginToVkApi(args[0], args[1]),    _ => throw new ArgumentException("Invalid arguments. Usage:\n" +        "  dotnet vkm [login] [password] [audio]\n" +    )};

Приводим лемму для поиска аудиозаписи к upper-case
var lemma = args.Last().ToUpperInvariant();

И грепаем с помощью Linq все аудиозаписи с её вхождением. Отдельное спасибо хабраюзеру SuperHackerVk за способ получения mp3-ссылки регуляркой.
var audios = vk.Audio.Get(new AudioGetParams { Count = 6000 })    .Where(x => x.Title.ToUpperInvariant().Contains(lemma))    .Select(x => (x.Title, Url: Regex.Replace(        x.Url.ToString(),        @"/[a-zA-Z\d]{6,}(/.*?[a-zA-Z\d]+?)/index.m3u8()",        @"$1$2.mp3"    )));

Наконец остается только загрузить свои найденные аудио:
using var http = new HttpClient();foreach (var (title, url) in audios){    $"Downloading {title}...".Println(DarkBlue);    await WriteAllBytesAsync($"{title}.mp3", await http.GetByteArrayAsync(url));}

Вот и все! Утилита написана и готова к использованию в личных целях. Заметно как C# с каждым годом все больше превращается в хороший мультитул, позволяющий решать любой спектр задач. Расширения синтаксических возможностей которые при анонсах кажутся загромождающими язык, на практике напротив, позволяют сократить код и сделать его простым и понятным.

Репозиторий на GitHub c небольшими дополнениями и документацией по запуску.
Всем удачного дня!
Подробнее..

Из песочницы По мотивам youtube-dl музыка ВК

28.10.2020 10:05:26 | Автор: admin
image
В 2020ом мы пользуемся разными музыкальными сервисами, но как реликт ушедшей эпохи, в забытом профиле ВК, у многих хранится музыка. Функции для загрузки нет, но что если позарез нужно спасти аудиозапись?
Поскольку такого софта в открытом доступе не обнаружилось, кроме парочки веб-сервисов требующих авторизацию через ВК (что не очень то и безопасно), под катом мы рассмотрим процесс создания self-hosted утилиты на современном C# для загрузки своих аудио, не сливающей данные профиля сторонним сервисам.

Одной из ценностей работы программиста является простота и по возможности лаконичность кода. Поэтому мы склеим несколько уже существующих библиотек чтобы получить нужное решение.
Работать утилита будет так:
 dotnet vkm [login] [password] [audio-lemma]

Перво-наперво создадим репозиторий и опишем в одном файле csproj зависимости проекта
<Project Sdk="Microsoft.NET.Sdk">    <PropertyGroup>        <!--Утилита будет работать из консоли-->        <OutputType>Exe</OutputType>        <TargetFramework>netcoreapp3.1</TargetFramework>        <!--Строго запрещаем null на этапе компиляции, чтобы застраховаться от NRE -->        <Nullable>enable</Nullable>        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>        <!--И включаем C# 9 который понадобится нам для top-level точки входа -->        <LangVersion>9</LangVersion>    </PropertyGroup>    <ItemGroup>        <!--Зависимость от VK API без необходимости вручную получать токен-->        <PackageReference Include="VkNet" Version="1.56.0" />        <!--Доступ к своим сообщениям, комментариям и музыке-->        <PackageReference Include="VkNet.AudioBypassService" Version="1.7.0" />    </ItemGroup></Project>

После этого с чистой совестью можно приступать к написанию кода. Нам потребуется авторизация утилиты в ВК с полным доступом к своему профилю. И как мы видим, благодаря экосистеме .NET, сделать это невероятно просто:
static class Vk{    internal static VkApi LoginToVkApi(string login, string password)    {        // Включаем доступ к своим сообщениям, комментариям и аудиозаписям        var api = new VkApi(new ServiceCollection().AddAudioBypass());        api.Authorize(new ApiAuthParams        {             ApplicationId = 1980660,            Login = login,             Password = password,             Settings = All         });        $"Login as vk.com/id{api.UserId}".Println(DarkBlue);        return api;    }}

Опишем точку входа и фильтр загружаемых аудиозаписей. Используем для этого top-level programs и прямо в файле Application.cs валидируем аргументы, одновременно инициализируя api
var vk = args.Length switch{    3 => LoginToVkApi(args[0], args[1]),    _ => throw new ArgumentException("Invalid arguments. Usage:\n" +        "  dotnet vkm [login] [password] [audio]\n" +    )};

Приводим лемму для поиска аудиозаписи к upper-case
var lemma = args.Last().ToUpperInvariant();

И грепаем с помощью Linq все аудиозаписи с её вхождением. Отдельное спасибо хабраюзеру SuperHackerVk за способ получения mp3-ссылки регуляркой.
var audios = vk.Audio.Get(new AudioGetParams { Count = 6000 })    .Where(x => x.Title.ToUpperInvariant().Contains(lemma))    .Select(x => (x.Title, Url: Regex.Replace(        x.Url.ToString(),        @"/[a-zA-Z\d]{6,}(/.*?[a-zA-Z\d]+?)/index.m3u8()",        @"$1$2.mp3"    )));

Наконец остается только загрузить свои найденные аудио:
using var http = new HttpClient();foreach (var (title, url) in audios){    $"Downloading {title}...".Println(DarkBlue);    await WriteAllBytesAsync($"{title}.mp3", await http.GetByteArrayAsync(url));}

Вот и все! Утилита написана и готова к использованию в личных целях. Заметно как C# с каждым годом все больше превращается в хороший мультитул, позволяющий решать любой спектр задач. Расширения синтаксических возможностей которые при анонсах кажутся загромождающими язык, на практике напротив, позволяют сократить код и сделать его простым и понятным.

Репозиторий на GitHub c небольшими дополнениями и документацией по запуску.
Всем удачного дня!
Подробнее..

Пошаговая инструкция по настройке и использованию Gitlab CI Visual Studio для сборки приложения .NET Framework

12.03.2021 14:20:14 | Автор: admin

По натуре своей многие разработчики слишком ленивые не любят делать одно и то же действие много раз. Нам проще научить компьютер, чтобы он делал монотонные действия за нас.


Как только кто-либо из нашей команды вносит изменения в код (читай мерджит feature-ветку в develop), наш билд-сервер:


  • Собирает исходный код и установщик приложения
    • проставляет номер сборки, каждый раз увеличивая последнюю цифру. Например, текущая версия нашего ПО 3.3.0.202 часть 3.3.0 когда-то ввёл разработчик (привет, SemVer), а 202 проставляется в процессе сборки.
    • В процессе анализирует качество кода (с использованием SonarQube) и отправляет отчёт во внутренний SonarQube,
  • Сразу после сборки запускает автотесты (xUnit) и анализирует покрытие тестами (OpenCover),

Также, в зависимости от ветки, в которую были внесены изменения, могут быть выполнены:


  • отправка сборки (вместе с changelog-ом) в один или несколько телеграм-каналов (иногда удобнее брать сборки оттуда).
  • публикация файлов в систему автообновления ПО.

Под катом о том, как мы научили Gitlab CI делать за нас бОльшую часть этой муторной работы.


Оглавление


  1. Устанавливаем и регистрируем Gitlab Runner.
  2. Что нужно знать про .gitlab-ci.yml и переменные сборки.
  3. Активируем режим Developer PowerShell for VS.
  4. Используем CI для проставления версии во все сборки решения.
  5. Добавляем отправку данных в SonarQube.
  6. Причёсываем автотесты xUnit + добавляем вычисление покрытия тестами через OpenCover.
  7. Послесловие.

Перед началом


Чтобы быть уверенными, что написанное ниже работает, мы взяли на github небольшой проект, написанный на WPF и имеющий unit-тесты, и воспроизвели на нём описанные в статье шаги. Самые нетерпеливые могут сразу зайти в созданный на сайте gitlab.com репозиторий и посмотреть, как это выглядит.


Устанавливаем и регистрируем Gitlab Runner


Для того чтобы Gitlab CI мог что-либо собрать, сначала установите и настройте Gitlab Runner на машине, на которой будет осуществляться сборка. В случае проекта на .Net Framework это будет машина с ОС Windows.


Чтобы настроить Gitlab Runner, выполните следующие шаги:


  1. Установите Git для Windows с сайта git.
  2. Установите Visual Studio с сайта Microsoft. Мы поставили себе Build Tools для Visual Studio 2019. Чтобы скачать именно его, разверните список Инструменты для Visual Studio2019.
  3. Создайте папку C:\GitLab-Runner и сохраните в неё программу gitlab runner. Скачать её можно со страницы [документации Gitlab] (https://docs.gitlab.com/runner/install/windows.html) ссылки скрыты прямо в тексте: Download the binary for x86 or amd64.
  4. Запустите cmd или powershell в режиме администратора, перейдите в папку C:\GitLab-Runner и запустите скачанный файл с параметром install (Gitlab runner установится как системная служба).

.\gitlab-runner.exe install

  1. Посмотрите токен для регистрации Runner-а. В зависимости от того, где будет доступен ваш Runner:


    • только в одном проекте смотрите токен в меню проекта Settings > CI/CD в разделе Runners,
    • в группе проектов смотрите токен в меню группы Settings > CI/CD в разделе Runners,
    • для всех проектов Gitlab-а смотрите токен в секции администрирования, меню Overview > Runners.

  2. Выполните регистрацию Runner-а, с помощью команды



.\gitlab-runner.exe register

Далее надо ввести ответы на вопросы мастера регистрации Runner-а:


  • coordinator URL http или https адрес вашего сервера gitlab;
  • gitlab-ci token введите токен, полученный на предыдущем шаге;
  • gitlab-ci description описание Runner-а, которое будет показываться в интерфейсе Gitlab-а;
  • gitlab-ci tags через запятую введите тэги для Runner-а. Если вы не знакомы с этим механизмом, оставьте поле пустым отредактировать его можно позднее через интерфейс самого gitlab-а. Тэги можно использовать для того, чтобы определённые задачи выполнялись на определённых Runner-ах (например, чтобы настроить сборку ПО на Runner-е, развёрнутом на копьютере ОС Windows, а подготовку документации на Runner-е с ОС Linux);
  • enter the executor ответьте shell. На этом шаге указывается оболочка, в которой будут выполняться команды; при указании значения shell под windows выбирается оболочка powershell, а последующие скрипты написаны именно для неё.

Что нужно знать про .gitlab-ci.yml и переменные сборки


В процессе соей работы Gitlab CI берёт инструкции о том, что делать в процессе сборки того или иного репозитория из файла .gitlab-ci.yml, который следует создать в корне репозитория.


Вбив в поиске содержимое .gitlab-ci.yml для сборки приложения .NET Framework можно найти несколько шаблонов: 1, 2. Выглядят они примерно так:


variables:  # Максимальное количество параллельно собираемых проектов при сборке решения; зависит от количества ядер ПК, выбранного для сборки  MSBUILD_CONCURRENCY: 4  # Тут куча путей до утилит, которые просто оябзаны лежать там, где ожидается  NUGET_PATH: 'C:\Tools\Nuget\nuget.exe'  MSBUILD_PATH: 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\msbuild.exe'  XUNIT_PATH: 'C:\Tools\xunit.runner.console.2.3.1\xunit.console.exe'  TESTS_OUTPUT_FOLDER_PATH: '.\tests\CiCdExample.Tests\bin\Release\'# Тут указываются стадии сборки. Указывайте любые названия которые вам нравятся, но по умолчанию используют три стадии: build, test и deploy.# Стадии выполняются именно в такой последовательности.stages:  - build  - test# Далее описываются задачи (job-ы)build_job:  stage: build # указание, что задача принадлежит этапу build  # tags: windows # если тут указать тэг, задача будет выполняться только на Runner-е с указанным тэгом   only: # для каких сущностей требуется выполнять задачу    - branches  script: # код шага    - '& "$env:NUGET_PATH" restore'    - '& "$env:MSBUILD_PATH" /p:Configuration=Release /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly' # сборка; ключ clp:ErrorsOnlyоставляет только вывод ошибок; ключ nr:false завершает инстансы msbuild   artifacts: # где по завершении задачи будут результаты, которые надо сохранить в gitlab (т.н. артефакты) и которые можно будет передать другим задачам по цепочке    expire_in: 2 days # сколько хранить артефакты    paths: # список путей, по которым находятся файлы для сохранения      - '$env:TESTS_OUTPUT_FOLDER_PATH'test_job:  stage: test  only:    - branches  script:    - '& "$env:XUNIT_PATH" "$env:TESTS_OUTPUT_FOLDER_PATH\CiCdExample.Tests.dll"'  dependencies: # указание, что для запуска этой задачи требуется успешно завершенная задача build_job    - build_job

И последнее: если нам требуется передавать в скрипт значение параметра, который мы не хотим хранить в самом скрипте (например, пароль для подключения куда-либо), мы можем использовать для этого объявление параметров в gitlab. Для этого зайдите в проекте (или в группе проекта) в Settings > CI/CD и найдите раздел Variables. Прописав в нём параметр с именем (key) SAMPLE_PARAMETER, вы сможете получить его значение в в скрипте .gitlab-ci.yml через обращение $env:SAMPLE_PARAMETER.
Также в этом разделе можно настроить передачу введенных параметров только при сборке защищённых веток (галочка Protected) и/или скрытие значения параметра из логов (галочка Masked).
Подробнее о параметрах окружения сборки смотрите в документации к Gitlab CI.


Активируем режим Developer PowerShell for VS


Скрипт, приведённый выше, уже можно использовать для сборки и вызова тестов. Правда, присутствует НО: крайне неудобно прописывать абсолютные пути к разным установкам Visual Studio. К примеру, если на одной билд-машине стоит Visual Studio 2017 BuildTools, а на другой Visual Studio Professional 2019, то такой скрипт будет работать только для одной из двух машин.


К счастью, с версии Visual Studio 2017 появился способ поиска всех инсталляций Visual Studio на компьютере. Для этого существует утилита vswhere, путь к которой не привязан ни к версии Visual Studio, ни к её редакции. А в Visual Studio 2019 (в версии 16.1 или более новой) есть библиотека, которая умеет трансформировать консоль Powershell в режим Developer Powershell, в котором уже прописаны пути к утилитам из поставки VS.


Как применить


Дописываем переменную к секции Variables:


variables:  VSWHERE_PATH: '%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe'

Затем создаём новую секцию before_msbuild якорем enter_vsdevshell и следующим текстом:


.before_msbuild: &enter_vsdevshell  before_script:    - '$vsWherePath = [System.Environment]::ExpandEnvironmentVariables($env:VSWHERE_PATH)'    - '& $vsWherePath -latest -format value -property installationPath -products Microsoft.VisualStudio.Product.BuildTools | Tee-Object -Variable visualStudioPath'    - 'Join-Path "$visualStudioPath" "\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" | Import-Module'    - 'Enter-VsDevShell -VsInstallPath:"$visualStudioPath" -SkipAutomaticLocation'

И всюду, где нам надо использовать утилиты Visual Studio, добавляем этот якорь. После этого задача сборки начинает выглядеть намного более опрятно:


build_job:  <<: *enter_vsdevshell  stage: build  only:    - branches  script:    - 'msbuild /t:restore /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'    - 'msbuild /p:Configuration=Release /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'  artifacts:    expire_in: 2 days    paths:      - '$env:TESTS_OUTPUT_FOLDER_PATH'

Подробно о том, что написано в .before_msbuild
  1. Утилита vswhere.exe умеет находить и выдавать список найденных инсталляций Visual Studio. Расположен она всегда по одному и тому же пути (этот путь записан в переменной VSWHERE_PATH). Поскольку в переменной фигурирует подстановка %programfiles%, её требуется раскрыть до пути к этой папке. Такое раскрытие проще всего сделать через статический метод .NET System.Environment.ExpandEnvironmentVariables.

Результат: мы имеем путь к vswhere.


  1. Вызовом vswhere получим путь к папке с установленной Visual Studio.
    Все параметры утилиты можно посмотреть, если запустить vswhere.exe с параметром -help, в статье же только перечислю использованные:
    • -latest (искать самые свежие установки),
    • -property installationPath (вывести параметр пути установки),
    • -format value (при печати параметра вывести только значение параметра, без его имени),
    • -products <список искомых инсталляций Visual Studio, пробел считается разделителем элементов списка> (указание искомых редакций Visual Studio). Например, при запуске с параметром -products Microsoft.VisualStudio.Product.Community Microsoft.VisualStudio.Product.BuildTools утилита попробует найти Visual Studio редакций Community или BuildTools. Подробнее об идентификаторах продуктов смотрите по ссылке https://aka.ms/vs/workloads.

Результат: в переменную $visualStudioPath записан путь к Visual Studio или пустая строка, если инсталляций Visual Studio не найдено (обработку этой ситуации мы ещё не добавили).


  1. Команда Import-Module загружает библиотеку Microsoft.VisualStudio.DevShell.dll, в которой прописаны командлеты трансформации консоли Powershell в Developer-консоль. А командлет Join-Path формирует путь к этой библиотеке относительно пути установки Visual Studio.
    На этом шаге нам прилетит ошибка, если библиотека Microsoft.VisualStudio.DevShell.dll отсутствует или путь к установке Visual Studio нужной редакции не был найден Import-Module сообщит, что не может загрузить библиотеку.

Результат: загружен модуль Powershell с командлетом трансформации.


  1. Запускаем передёлку консоли в Developer Powershell. Чтобы корректно прописать пути к утилитам, командлету требуется путь к установленной Visual Studio (параметр -VsInstallPath). А указаниеSkipAutomaticLocation требует от командлета не менять текущее расположение (без этого параметра путь меняется на <домашнаяя папка пользователя>\source\repos.

Результат: мы получили полноценную консоль Developer Powershell с прописанными путями к msbuild и многим другим утилитам, которые можноиспользовать при сборке.


Используем CI для проставления версии во все сборки решения


Раньше мы использовали t4 шаблоны для проставления версий: номер версии собиралась из содержимого файла в формате <major>.<minor>.<revision>, далее к ней добавлялся номер сборки из Gitlab CI и он передавался в tt-шаблон, добавляющий в решение везде, где требуется, номер версии. Однако некоторое время назад был найден более оптимальный способ использование команд git tag и git describe.


Команда git tag устанавливает коммиту метку (тэг). Отметить таким образом можно любой коммит в любой момент времени. В отличие от веток, метка не меняется. То есть если после помеченного коммита вы добавите ещё один, метка останется на помеченном коммите. Если попробуете переписать отмеченный коммит командами git rebase или git commit --amend, метка также продолжит указывать на исходный коммит, а не на изменённый. Подробнее о метках смотрите в git book.


Команда git describe, к сожалению, в русскоязычном gitbook не описана. Но работает она примерно так: ищет ближайшего помеченного родителя текущего коммита. Если такого коммита нет команда возвращает ошибку fatal: No tags can describe '<тут хэш коммита>'. А вот если помеченный коммит нашёлся тогда команда возвращает строку, в которой участвует найденная метка, а также количество коммитов между помеченным и текущим.


На заметку: чтобы данная команда работала корректно во всех случаях, автор gitflow даже чуть-чуть поменял скрипты finish hotfix и finish release. Если кому интересно посмотреть обсуждение с автором gitflow, а также увидеть что изменилось (картинка с актуальной схемой в последнем сообщении в треде).


Кстати, по этой же причине если вы используете gitflow, требуется после вливания feature-ветки в develop требуется удалить влитую локальную ветку, после чего пересоздать её от свежего develop:


Не забывайте пересоздавать ветки от develop
(обратите внимание на историю git в левой части картинки: из-за отсутствия пути из текущего коммита до коммита с меткой 1.0.5, команда git describe выдаст неверный ответ)


Но вернёмся к автопроставлению версии. В нашем репозитории царит gitflow (точнее его rebase-версия), метки расставляются в ветке master и мы не занываем пересоздавать feature-ветки от develop, а также merge-ить master в develop после каждого релиза или хотфикса.


Тогда получить версию для любого коммита и сразу передать её в msbuild можно добавив всего пару строк к задаче сборки:


build_job:  <<: *enter_vsdevshell  stage: build  only:    - branches  script:    - 'msbuild /t:restore /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'    - '$versionGroup = git describe --long | Select-String -Pattern "(?<major>[0-9]+)\.(?<minor>[0-9]*)\.(?<patch>[0-9]*)\-(?<commit>[0-9]+)\-g[0-9a-f]+" | Select-Object -First 1'    - '[int]$major, [int]$minor, [int]$patch, [int]$commit = $versionGroup.Matches[0].Groups["major", "minor", "patch", "commit"].Value'    - '[string]$version = "$major.$minor.$patch.$commit"'    - 'msbuild /p:Configuration=Release /p:AssemblyVersionNumber=$version /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'  artifacts:    expire_in: 2 days    paths:      - '$env:TESTS_OUTPUT_FOLDER_PATH'

Как это работает:


  1. Мы проставляем метки в формате <major>.<minor>.<revision>.
  2. Тогда git describe --long возвращает нам строку, описывающую версию в формате <major>.<minor>.<revision>-<количество новых коммитов>-g<хэш текущего коммита>.
  3. Парсим полученную строку через регулярные выражения, выделяя нужные нам части и записывем части в $versionGroup.
  4. Преобразовываем четыре найденные подстроки в 4 числа и пишем их в переменные $major, $minor, $patch, $commit, после чего собираем из них строку уже в нужном нам формате.
  5. Передаём указанную строку в msbuild чтобы он сам проставил версию файлов при сборке.

Обратите внимание: если вы, согласно gitflow, будете отмечать (тэгировать) ветку master после вливания в неё release или hofix, будьте внимательны: до простановки метки автосборка будет вестись относительно последней существующей ветки. Например, сейчас опубликована версия 3.4, а вы создаёте release-ветку для выпуска версии 3.5. Так вот: всё время существования этой ветки, а также после её вливания в master, но до простановки тэга, автосборка будет проставлять версию 3.4.


Добавляем отправку данных в SonarQube


SonarQube это мощный инструмент контроля качества кода.


SonarQube имеет бесплатную Community-версию, которая способна проводить полный анализ. Правда, только одной ветки. Чтобы настроить её на контроль качества ветки разработки (develop), требуется выполнить следующие шаги (разумеется, помимо установки и развёртывания сервера SonarQube):


  1. Создайте в SonarQube новый проект, после чего запомнить его ключ.


  2. Скачайте SonarScanner for MSBuild (с сайта sonarqube.org)[https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-msbuild/] мы используем версию .NET Framework 4.6+.


  3. Распакуйте содержимое архива в папку. Например, в C:\Tools\SonarScanner.



На заметку: эту утилиту также можно скачать на сборочную машину через NuGet, но тогда надо будет чуть по-иному указывать её путь.


  1. Зайдите в параметры CI/CD в свойствах проекта в Gitlab следующие параметры:
    • SONARQUBE_PROJECT_KEY ключ проекта,
    • SONARQUBE_AUTH_TOKEN токен авторизации.

Прописывать параметр ключа проекта необходимо для каждого проекта отдельно (ведь у каждого проекта свой уникальный ключ). А параметр токена авторизации желательно скрыть (отметить как Masked) чтобы он не был доступен всем, кто имете доступ к репозиторию или логам сборки.


  1. Допишите переменные к секции Variables:


    variables:SONARSCANNER_MSBUILD_PATH: 'C:\Tools\SonarScanner\SonarScanner.MSBuild.exe'SONARQUBE_HOST_URL: 'url вашего сервера SonarQube'
    

  2. Допишите в задачу тестирования ветки разработки (test_job) команды для запуска анализа кода и уберите зависимость от задачи build_job:


    test_job:stage: testonly:- /^develop$/<<: *enter_vsdevshellscript:- '$versionGroup = git describe --long | Select-String -Pattern "(?<major>[0-9]+)\.(?<minor>[0-9]*)\.(?<patch>[0-9]*)\-(?<commit>[0-9]+)\-g[0-9a-f]+" | Select-Object -First 1'- '[int]$major, [int]$minor, [int]$patch, [int]$commit = $versionGroup.Matches[0].Groups["major", "minor", "patch", "commit"].Value'- '[string]$version = "$major.$minor.$patch.$commit"'- '& "$env:SONARSCANNER_MSBUILD_PATH" begin /key:$env:SONARQUBE_PROJECT_KEY /d:sonar.host.url=$env:SONARQUBE_HOST_URL /d:sonar.login=$env:SONARQUBE_AUTH_TOKEN /d:sonar.gitlab.project_id=$CI_PROJECT_PATH /d:sonar.gitlab.ref_name=develop /v:$version /d:sonar.dotnet.excludeGeneratedCode=true'- 'msbuild /t:rebuild /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'- '& "$env:SONARSCANNER_MSBUILD_PATH" end /d:sonar.login=$env:SONARQUBE_AUTH_TOKEN'- '& "$env:XUNIT_PATH" "$env:TESTS_OUTPUT_FOLDER_PATH\CiCdExample.Tests.dll"'
    


Теперь при каждой сборке ветки develop в SonarQube будет отправляться подробный анализ нашего кода.


На заметку: вообще команда msbuild /t:rebuild полностью пересобирает решение. Вероятно, в большинстве проектов анализ можно было бы встроить прямо в стадию сборки. Но сейчас у нас анализ в отдельной задаче.


Пара слов об использованных параметрах:


  • key ключ проекта на сервере SonarQube,
  • v собираемая версия. ИМХО отлично комбинируется с предыдущим шагом автопроставления версии,
  • sonar.gitlab.project_id ID проекта на сервере Gitlab,
  • sonar.gitlab.ref_name название ветки, которое получает сервер SonarQube при передаче результатов анализа,
  • sonar.dotnet.excludeGeneratedCode не включать в анализ объекты, отмеченные атрибутом System.CodeDom.Compiler.GeneratedCode (чтобы не оценивать качество автосгенерированного кода).

Причёсываем автотесты xUnit + добавляем вычисление покрытия тестами через OpenCover


Со сборкой более-менее разобрались теперь приступаем к тестам. Доработаем код прогона тестов, чтобы он:


  • сам находил библиотеки с тестами,
  • прогонял их пачкой через xUnit,
  • вычислял тестовое покрытие через OpenConver,
  • отправлял результаты покрытия тестами в SonarQube.

На заметку: обычно в паре с OpenCover используют ReportGenerator, но при наличии SonarQube мы с тем же успехом можем смотреть результаты через его интерфейс.


Для настройки выполним следующие шаги:


  1. Скачайте OpenCover в виде zip-файла с сайта github.


  2. Распакуйте содержимое архива в папку. Например, в C:\Tools\OpenCover.



На заметку: эту утилиту также можно скачать на сборочную машину через NuGet, но тогда надо будет чуть по-иному указывать её путь.


  1. Допишите переменные к секции Variables:


    variables:OBJECTS_TO_TEST_REGEX: '^Rt[^\n]*\.(dll|exe)$'OPENCOVER_PATH: 'C:\Tools\opencover-4.7.922\xunit.console.exe'OPENCOVER_FILTER: '+[Rt.*]* -[*UnitTests]* -[*AssemblyInfo]*'OPENCOVER_REPORT_FILE_PATH: '.\cover.xml'
    

  2. Модифицируйте задачу тестирования ветки разработки (test_job), чтобы она включала и команды вызова OpenCover:



test_job:  stage: test  only:    - /^develop$/  <<: *enter_vsdevshell  script:    - '$versionGroup = git describe --long | Select-String -Pattern "(?<major>[0-9]+)\.(?<minor>[0-9]*)\.(?<patch>[0-9]*)\-(?<commit>[0-9]+)\-g[0-9a-f]+" | Select-Object -First 1'    - '[int]$major, [int]$minor, [int]$patch, [int]$commit = $versionGroup.Matches[0].Groups["major", "minor", "patch", "commit"].Value'    - '[string]$version = "$major.$minor.$patch.$commit"'    - '& "$env:SONARSCANNER_MSBUILD_PATH" begin /key:$env:SONARQUBE_PROJECT_KEY /d:sonar.host.url=$env:SONARQUBE_HOST_URL /d:sonar.login=$env:SONARQUBE_AUTH_TOKEN /d:sonar.gitlab.project_id=$CI_PROJECT_PATH /d:sonar.gitlab.ref_name=develop /v:$version /d:sonar.cs.opencover.reportsPaths="$env:OPENCOVER_REPORT_FILE_PATH" /d:sonar.dotnet.excludeGeneratedCode=true'    - 'msbuild /t:rebuild /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'    - '$dllsToRunUnitTesting = @(Get-ChildItem "$env:TESTS_OUTPUT_FOLDER_PATH" -Recurse) | Where-Object {$_.Name -match $env:OBJECTS_TO_TEST_REGEX} | ForEach-Object { """""$_""""" } | Join-String -Separator " "'    - '& "$env:OPENCOVER_PATH" -register -target:"$env:XUNIT_PATH" -targetargs:"$dllsToRunUnitTesting -noshadow" -filter:"$env:OPENCOVER_FILTER" -output:"$env:OPENCOVER_REPORT_FILE_PATH" | Write-Host'    - 'if ($?) {'    - '[xml]$coverXml = Get-Content "$env:OPENCOVER_REPORT_FILE_PATH"'    - '$sequenceCoverage = $coverXml.CoverageSession.Summary.sequenceCoverage'    - '$branchCoverage = $coverXml.CoverageSession.Summary.branchCoverage'    - 'Write-Host "Total Sequence Coverage <!<$sequenceCoverage>!>"'    - 'Write-Host "Total Branch Coverage [![$branchCoverage]!]"'    - '} else {'    - 'Write-Host "One or more tests failed!"'    - 'Throw'    - '}'    - '& "$env:SONARSCANNER_MSBUILD_PATH" end /d:sonar.login=$env:SONARQUBE_AUTH_TOKEN'

Обратите внимание: в begin-команде запуска sonar scanner-а появился дополнительный параметр /d:sonar.cs.opencover.reportsPaths.


  1. (необязательный пункт) Ненадолго возврващаемся в Gitlab, заходим в меню проекта Settings > CI/CD и находим на странице настроек параметр Test coverage parsing. Указываем в нём регулярное выражение, которое позволит Gitlab-у также получать информацию о покрытии тестами приложения:
    • если хочется видеть значение покрытия тестов по строкам кода (его ешё называют Sequence Coverage или Statement Coverage), указываем выражение <!<([^>]+)>!>,
    • если хочется видеть значение покрытия тестов по веткам условных операторов (его называют Decision Coverage или Branch Coverage), указываем выражение \[!\[([^>]+)\]!\].

А теперь комментарии по изменениям в скрипте.


Длинная строка, начинающаяся с объявления переменной $dllsToRunUnitTesting, нужна для того, чтобы найти все библиотеки нашего приложения, которые потом будут участвовать в расчёте тестового покрытия. При этом выбираются они по регулярному выражению, заданному в параметре $env:OBJECTS_TO_TEST_REGEX (мы же не хотим, например, учитывать в покрытии библиотеки .net или сторонних nuget-пакетов). Пути ко всем найденным библиотекам склеиваются в строку с разделителем пробелом и двумя двойными кавычками для каждого параметра. Две двойные кавычки были добавлены потому, что OpenCover при вызове приложения xunit съедает одни из кавычек, а вторые кавычки нужны на случай наличия пробелов в абсолютных путях.


Следующая строка запуск утилиты OpenConver с передачей ей списка библиотек нашего приложения, фильтра, по которому OpenCover исключает из покрытия библиотеки с unit-тестами, а также часть классов, не требующих расчёта покрытия (например, AssemblyInfo). Конвейер и Write-Host были добавлены в порыве вдохновения, так как без него у нас не работал вывод OpenConver-а.


И следующий if проверяет, успешно ли завершился запуск OpenConver. Если не успешно кладём скрипт; если же успешно парсим получившийся xml-файлик с отчётом и печатаем значения покрытия тестами, чтобы затем его легко распарсил gitlab через указанное в настройках регулярное выражение.


Послесловие


Как известно, нет предела совершенству. Мы продолжим добавлять новые функции в используемые нами процессы сборки, тестирования и публикации. На момент написания тестируется переезд нашего ПО на .NET 5 (с ним OpenCover уже не работает, сборка выполняется через команду dotnet), а также мы переходим на другой способ публикации (к сожалению, пока не могу дать более подробный комментарий).


Если у вас появятся вопросы или предложения пишите в комментариях.


И спасибо за прочтение!

Подробнее..

Наследование в Nuget-пакетах

21.01.2021 14:15:10 | Автор: admin
image

Nuget-пакет это не только архив с переиспользуемыми сборками, но и контент с target-скриптами, которые задают поведение MsBuild при сборке приложения. Это дает нам возможность рассматривать nuget-пакет в качестве самостоятельного объекта, у которого есть состояние и поведение.

А раз у нас есть объект, то что мешает попробовать посмотреть на работу с ним со стороны объектно-ориентированной парадигмы? Давайте попробуем применить для nuget-пакетов один из основных принципов ООП наследование.

Предположим, вам нужно сделать nuget-пакет на основе уже существующего, немного изменив его поведение.

Для примера, рассмотрим nuget-пакет с драйверами базы данных DB2: IBM.Data.DB2.Core.

Этот пакет обладает специальным поведением. При сборке использующего его проекта происходит копирование unmanaged-библиотеки с драйверами в результирующую папку билда: в проект-потребитель пакета в процессе сборки автоматически добавляются контент-ссылки на файлы unmanaged-библиотек с драйверами.

Предположим, что у вас есть фреймворк, который использует ORM, например NHibernate. Он содержит специальную обвязку драйвера DB2, которая нужна для обеспечения доступа к этой базе данных через этот ORM: ViennaNET.Orm.DB2.Win.

Сборки внутри ViennaNET.Orm.DB2.Win конечно же ссылаются на IBM.Data.DB2.Core. Но если вы в каком-то новом проекте подключите только пакет ViennaNET.Orm.DB2.Win, то IBM.Data.DB2.Core автоматически не подключится. То есть, драйвера, необходимые для работы с БД, не появятся, если вы не используете менеджер пакетов, который разрешает транзитивные зависимости. В качестве примера такого менеджера можно упомянуть Paket.

Здесь есть несколько решений.

  1. Предложить потребителю пакета ViennaNET.Orm.DB2.Win учитывать транзитивную зависимость от пакета IBM.Data.DB2.Core. Это можно сделать вручную или с помощью автоматизированного инструмента типа ранее упоминаемого мною Paket.
  2. Полностью продублировать в проекте пакета ViennaNET.Orm.DB2.Win содержимое и поведение пакета IBM.Data.DB2.Core.
  3. Реализовать наследование содержимого и поведения пакета IBM.Data.DB2.Core в пакете ViennaNET.Orm.DB2.Win.

Решения 1 и 2 лежат на поверхности, поэтому здесь я опишу только решение 3. Оно позволит упростить использование пакета конечным потребителем и снизит возможные риски копирайта, которые могут возникнуть при дублировании содержимого одного проекта в другом.

После того, как вы скачаете и разархивируете пакет IBM.Data.DB2.Core, вы увидите примерно такую структуру каталога:

image

В папке build находится папка clidriver с unmanaged-драйвером DB2 и targets-скрипт IBM.Data.DB2.Core.targets, который будет выполняться при сборке проекта, использующего этот пакет. Скрипт содержит следующие инструкции:

<Project xmlns="http://personeltest.ru/away/schemas.microsoft.com/developer/msbuild/2003">    <ItemGroup>        <None Include="$(MSBuildThisFileDirectory)clidriver\**" >            <Link>clidriver\%(RecursiveDir)%(FileName)%(Extension)</Link>            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>        </None>    </ItemGroup></Project>

В скрипте говорится, что в проект, который использует данный пакет, на все файлы из папки build\clidriver нужно рекурсивно добавить контент-ссылки. Это необходимо, чтобы в процессе билда файлы с драйверами были помещены в итоговую папку сборки проекта.

После сборки проекта в нем появиться папка с контент-ссылками. Причем это именно ссылки на файлы находящиеся в папке пакета IBM.Data.DB2.Core, в папку проекта они не копируются:

image

Теперь, хотелось бы повторить такое же поведение для пакета ViennaNET.Orm.DB2.Win в отношении его потребителя.

Тут пришлось изрядно порыть MSDN и StackOverflow. Как мне кажется, в итоге сформировалось решение достойное того, чтобы поведать о нем общественности.

Для реализации в вашем пакете наследования контента и поведения от другого пакета достаточно выполнить следующие действия в файле проекта вашего пакета.

  1. В ссылке на базовый пакет вам нужно указать атрибут GeneratePathProperty="true". Это позволит создать переменную процесса сборки с именем, соответствующим имени пакета. Она нам нужна, так как будет указывать путь к папке с содержимым этого пакета. В самом имени символ '.' будет заменен на символ '_'.

    <ItemGroup>    <PackageReference Include="IBM.Data.DB2.Core"         Version="1.3.0.100"         GeneratePathProperty="true" /></ItemGroup>
    
  2. Добавить контент, ссылающийся на контент базового пакета. Символы '**' обозначают рекурсивное использование всех файлов.

    <Content Include="$(PkgIBM_Data_DB2_Core)\build\clidriver\**"     Pack="true" PackagePath="build\clidriver"     PackageCopyToOutput="false" />
    
  3. Добавить контент, ссылающийся на target-скрипт базового проекта. При этом, чтобы его выполнил MsBuild, необходимо переименовать target-скрипт по имени текущего проекта. Это можно сделать в атрибуте PackagePath.

    <Content Include="$(PkgIBM_Data_DB2_Core)\build\*.targets"     Pack="true" PackagePath="build\$(TargetName).targets"     PackageCopyToOutput="false" />
    

На этом всё.

Теперь при сборке пакета ViennaNET.Orm.DB2.Win в него будут добавлены файлы unmanaged-драйвера DB2 и target-скрипт из пакета IBM.Data.DB2.Core. Это позволит при подключении пакета ViennaNET.Orm.DB2.Win к новому проекту обеспечить размещение драйверов DB2 в папке сборки так, как это происходило бы при подключении пакета IBM.Data.DB2.Core.

Общий вид файла проекта будет выглядеть так:

<Project Sdk="Microsoft.NET.Sdk">  <PropertyGroup>    <TargetFramework>netstandard2.0</TargetFramework>    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>    <RuntimeIdentifiers>win-x64</RuntimeIdentifiers>  </PropertyGroup>  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">    <OutputPath>..\Bin</OutputPath>    <DocumentationFile>..\Bin\ViennaNET.Orm.DB2.Win.xml</DocumentationFile>  </PropertyGroup>  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">    <OutputPath>..\Bin</OutputPath>    <DocumentationFile>..\Bin\ViennaNET.Orm.DB2.Win.xml</DocumentationFile>  </PropertyGroup>  <ItemGroup>    <ProjectReference Include="..\ViennaNET.Orm\ViennaNET.Orm.csproj" />    <ProjectReference Include="..\ViennaNET.Protection\ViennaNET.Protection.csproj" />  </ItemGroup>  <ItemGroup>    <PackageReference Include="IBM.Data.DB2.Core" Version="1.3.0.100"         GeneratePathProperty="true" />  </ItemGroup>  <ItemGroup>    <Content Include="$(PkgIBM_Data_DB2_Core)\build\clidriver\**"         Pack="true" PackagePath="build\clidriver"         PackageCopyToOutput="false" />    <Content Include="$(PkgIBM_Data_DB2_Core)\build\*.targets"         Pack="true" PackagePath="build\$(TargetName).targets"         PackageCopyToOutput="false" />  </ItemGroup></Project>

Таким способом вы сможете обеспечить наследование контента и поведения в ваших nuget-пакетах от других nuget-пакетов.

Практическую реализацию решения с наследованием контента и поведения nuget-пакетов можно посмотреть в проекте ViennaNET на GitHub.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru