После прочтения статьи “Как готовить Cake, используя только Frosting” мне пришла в голову мысль: “Какой большой проект для такого простого процесса сборки”. После этого я и решил написать мини-статью про аналог Cake — Nuke.

Если я правильно понимаю историю проекта, Nuke появился как более простой и удобный аналог Cake, Psake и Fake. Автору Nuke хотелось писать скрипты сборки именно на C#, поэтому ему не подошли Psake и Fake. Судя по всему, когда создавался Nuke, не существовал Frosting, и все Cake скрипты были странными файлами с DSL на основе C#, которые можно было писать и отлаживать только в VS Code. Скрипт сборки Nuke изначально задумывался как обычное консольное приложение, которое легко можно писать и отлаживать в любой IDE.

Давайте создадим простейший скрипт сборки на Nuke и соберём с его помощью приложение!

1. Создаём приложение

Создадим новое приложение в папке src репозитория. Это не обязательно, но далее станет понятно, почему удобнее использовать для этого отдельную папку.

2. Устанавливаем Global Tool

dotnet tool install Nuke.GlobalTool --global

3. Создаём проект со скриптом сборки

Для этого необходимо выполнить команду:

nuke :setup

Это запустит консольный конфигуратор проекта сборки. Привожу анимацию его работы из документации Nuke.

Давайте посмотрим, что изменилось в репозитории:

Если не трогать настройки по умолчанию, то скрипт установки nuke создаст проект сборки в отдельной папке build репозитория. Таким образом, скрипты сборки лежат в build, а исходники в src. Те же настройки по умолчанию предполагают использование папки output в корне репозитория в качестве выходной директории для сборки.

Разберём изменения по отдельности:

  • .nuke — файл-маркер, по которому nuke определяет корневую папку

  • build.* — бутстрапперы для запуска сборки. При необходимости могут и .net установить, если его нет на машине, на которой происходит сборка

  • _build.csproj — файл проекта сборки. Имеет такое странное название для того, чтобы всегда быть первым в списке проектов в Solution Explorer

  • .editorconfig и _build.csproj.DotSettings — просто настройки стиля

А теперь — самое интересное:

3.1. Изменения в sln

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

1>------ Rebuild All started: Project: Demo, Configuration: Debug Any CPU ------
2>------ Skipped Rebuild All: Project: _build, Configuration: Debug Any CPU ------
2>Project not selected to build for this solution configuration 
1>Demo -> C:Usersbuldosourcereposnuke-examplesrcDemoDemobinDebugnetcoreapp3.1Demo.dll
========== Rebuild All: 1 succeeded, 0 failed, 1 skipped ==========

То есть при пересборке решения проект _build не собирается. Действительно, если бы скрипт сборки при работе вызывал ещё и пересборку самого себя, это могло бы привести к проблемам.

3.2. Build.cs

Приведу полный код сгенерированного файла и объясню основные моменты.

[CheckBuildProjectConfigurations]
[ShutdownDotNetAfterServerBuild]
class Build : NukeBuild
{
    public static int Main () => Execute<Build>(x => x.Compile);

    [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
    readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;

    [Solution] readonly Solution Solution;
    [GitRepository] readonly GitRepository GitRepository;
    [GitVersion] readonly GitVersion GitVersion;

    AbsolutePath SourceDirectory => RootDirectory / "src";
    AbsolutePath OutputDirectory => RootDirectory / "output";

    Target Clean => _ => _
        .Before(Restore)
        .Executes(() =>
        {
            SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory);
            EnsureCleanDirectory(OutputDirectory);
        });

    Target Restore => _ => _
        .Executes(() =>
        {
            DotNetRestore(s => s
                .SetProjectFile(Solution));
        });

    Target Compile => _ => _
        .DependsOn(Restore)
        .Executes(() =>
        {
            DotNetBuild(s => s
                .SetProjectFile(Solution)
                .SetConfiguration(Configuration)
                .SetAssemblyVersion(GitVersion.AssemblySemVer)
                .SetFileVersion(GitVersion.AssemblySemFileVer)
                .SetInformationalVersion(GitVersion.InformationalVersion)
                .EnableNoRestore());
        });

}

Данный класс является точкой входа в приложения. В методе Main запускается сборка и выбирается цель сборки по умолчанию.

Цели сборки представлены свойствами типа Target. Синтаксис с кучей стрелочек сначала пугает, но потом привыкаешь. Для целей сборки можно задавать зависимости (DependsOn()), явный порядок выполнения (Before(), After()), условия выполнения (как статические, так и динамические), а главное — указывать с помощью Executes(), что вообще делает этот Target.

Следующая важная вещь для начинающего пользователя Nuke — параметры сборки. Достаточно просто пометить поле атрибутом [Parameter], и можно передавать значение этого поля через командную строку. В данном случае — конфигурацию сборки Debug или Release. Механизм очень гибок — можно сделать параметр необходимым, и без него сборка не запустится. Также параметр можно делать необходимым только для конкретного Target.

Nuke предоставляет необходимые абстракции над решениями и проектами. Несколько решений в проекте? Нет проблем. Добавьте новое свойство типа Solution и пометьте его атрибутом [Solution] — Nuke найдёт решение, распарсит его и предоставит доступ его содержимому.

Также в этом файле можно заметить пример расширения Nuke — модуль для работы с git установлен отдельным пакетом. С помощью полей GitRepository и GitVersion можно легко оперировать состоянием репозитория. Например, ориентируясь на имя ветки (master/не master), выбрать — ставить или нет preview метку при сборке nuget пакета.

4. Запуск сборки

Сборка запускается с помощью бутстраппера:

.build.ps1 [targets] [arguments]

В данном случае для того, чтобы собрать приложение, необходимо выполнить следующую команду:

.build.ps1 --target Compile --configuration release

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

═══════════════════════════════════════
Target             Status      Duration
───────────────────────────────────────
Restore            Executed        0:01
Compile            Executed        0:03
───────────────────────────────────────
Total                              0:04
═══════════════════════════════════════

Build succeeded on 29.12.2020 21:38:10.

На самом деле, если установлен NET 5, сборка скорее всего завершится ошибкой. Волшебный фикс — заменить атрибут поля GitVersion на [GitVersion(Framework = "netcoreapp3.1")].

5. Делаем что-то полезное

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

Далее я приведу код цели сборки с подробными комментариями:

Target Publish => _ => _
    .Executes(() =>
    {
        var rids = new[] {"win-x64", "linux-x64"}; // Перечисляем RID'ы, для которых собираем приложение

        DotNetPublish(s => s // Теперь вызываем dotnet publish
            .SetAssemblyVersion(GitVersion.AssemblySemVer)
            .SetFileVersion(GitVersion.AssemblySemFileVer)
            .SetInformationalVersion(GitVersion.InformationalVersion)
            .SetProject(Solution.GetProject("Demo")) // Для dotnet publish желательно указывать проект
            .SetPublishSingleFile(true) // Собираем в один файл
            .SetSelfContained(true)     // Вместе с рантаймом
            .SetConfiguration(Configuration) // Для определённой конфигурации
            .CombineWith(rids, (s, rid) => s // Но нам нужны разные комбинации параметров
                .SetRuntime(rid) // Устанавливаем RID
                .SetOutput(OutputDirectory/rid))); // Делаем так, чтобы сборки с разными RID попали в разные папки
    });

Заключение

Nuke — удобная система сборки. Она проста и минималистична в начале вашего проекта, но достаточно мощна для того, чтобы развиваться вместе с ним.

Ссылки

Let’s block ads! (Why?)

Read More

Recent Posts

Apple возобновила переговоры с OpenAI и Google для интеграции ИИ в iPhone

Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…

2 дня ago

Российская «дочка» Google подготовила 23 иска к крупнейшим игрокам рекламного рынка

Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…

2 дня ago

Google завершил обновление основного алгоритма March 2024 Core Update

Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…

2 дня ago

Нейросети будут писать тексты объявления за продавцов на Авито

У частных продавцов на Авито появилась возможность составлять текст объявлений с помощью нейросети. Новый функционал доступен в категории «Обувь, одежда,…

3 дня ago

Объявлены победители международной премии Workspace Digital Awards-2024

24 апреля 2024 года в Москве состоялась церемония вручения наград международного конкурса Workspace Digital Awards. В этом году участниками стали…

3 дня ago

Яндекс проведет гик-фестиваль Young Con

27 июня Яндекс проведет гик-фестиваль Young Con для студентов и молодых специалистов, которые интересуются технологиями и хотят работать в IT.…

4 дня ago