Spis treści
Końcowy kod z opisanymi tutaj elementami, i kilkoma więcej, znajdziesz na moim GitHubie: Ztr.AI.
Wstęp
Wyobraź sobie, że całe CI/CD swojej aplikacji możesz opisać za pomocą C#. Bez potrzeby nauki środowiska graficznego TeamCity, czy YAML’i GitHuba.
Nuke jest jednym ze sposobów opisu budowania naszej aplikacji w języku C#. Jeżeli chcemy, aby nasz projekt był cały czas łatwo zarządzalny, musimy mieć sposób na opis wszystkich wymagań stawianych przed nim. I tak oto, możemy opisać:
-
(Czyszczenie) jak powinny wyglądać katalogi przed rozpoczęciem budowania projektu,
-
(Przygotowanie) jakich zależności potrzebuje aplikacja do kompilacji,
-
(Budowanie) w jakiej kolejności budować poszczególne elementy,
-
(Testowanie) jak uruchamiać testy jednostkowe i jakie są wymagania co do pokrycia nimi naszego kodu,
-
(Publikacja) jak spakować aplikację i wysłać ją na środowisko produkcyjne.
Nie jest to kompletna lista, tego, co możemy zrobić w trakcie procesu ciągłej integracji i dostarczania. Na pewno pokazuje ona podstawowe zagadnienia, przed którymi jesteśmy stawiani podczas wydawania kolejnej wersji naszego oprogramowania. I jak to w życiu bywa, jeśli czegoś nie zapiszemy, to pewno o tym zapomnimy. A po co zapisywać coś na skrawku papieru, gdy można to zrobić za pomocą kodu, który wykona się sam?
Wszystkie wyżej opisane kroki możemy opisywać w różnych językach. Robimy to często w tak wyspecjalizowanych formach, jak projekty budujące na TeamCity, czy GitHub Actions. Nuke przekonał mnie tym, że nie muszę uczyć się tych wszystkich konfiguracji osobno – mogę opisać wszystko, co niezbędne za pomocą dobrze mi znanego języka C# i wykorzystywać tam, gdzie potrzebuję. W przypadku GitHub Actions mogę bez problemu również wykorzystać raz napisany kod w wielu różnych przepływach, co stanowczo upraszcza pracę i testowanie.
Wstępna konfiguracja z pomocą kreatora
Wstępną konfigurację naszego projektu warto zacząć od instalacji narzędzia Nuke, które stanowczo ułatwia pracę dewelopera. Co ważne, nie jest ono niezbędne na serwerze budującym.
dotnet tool install Nuke.GlobalTool --global

Narzędzie poprosi o podjęcie szeregu decyzji, jak widać na ilustracji powyżej:
-
Wybierz nazwę projektu budującego.
-
Katalog, w którym będzie on zapisany.
-
Wersję Nuke.
-
Domyślne rozwiązanie, które będzie budowane.
-
Czy chcesz, aby podstawowe polecenia budujące zostały już umieszczone w nowym projekcie.
-
Środowisko, które będzie budować twoje rozwiązanie.
-
Miejsce, gdzie są umieszczone twoje projekty (w tym katalogu będzie przeprowadzane czyszczenie dodatkowe).
-
Gdzie mają trafiać artefakty, czyli pliki wynikowe budowania, jak paczki nuget.
-
Gdzie są twoje projekty, które testują rozwiązanie.
-
Czy używasz GitVersion. Ja wybrałem, że tak, jednak opis tego narzędzia znajdzie się w innym artykule.
Rezultatem takich wyborów, jest klasa C#, która będzie wyglądać mniej więcej tak, jak poniżej.
Obok tego dostaniemy kilka plików build.sh, build.cmd i build.ps1, które pozwalają nam na budowę naszej aplikacji nawet w środowisku, które nie ma zainstalowanego środowiska .Net.
Pojawi się również katalog .nuke
, który przechowuje kilka ustawień.
[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 ArtifactsDirectory => RootDirectory / "artifacts";
Target Clean => _ => _
.Before(Restore)
.Executes(() =>
{
SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory);
EnsureCleanDirectory(ArtifactsDirectory);
});
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());
});
}
Jak budować projekt z pomocą Nuke
Projekt budujący Nuke możemy uruchomić przynajmniej na trzy sposoby:
Niezależnie od wybranej metody, często, aby zmiany w kodzie budującym zostały zastosowane, niezbędne jest przebudowanie projektu. Samo budowanie, bez czyszczenia, rzadko daje efekty. |
Z konsoli
-
dotnet run - Budować możesz poleceniem
dotnet run
wywołanym w katalogu, gdzie znajduje się nasz projekt budujący (u mnie jest to katalog CICD). -
Narzędziem nuke - Jeśli zainstalowałeś wcześniej globalne narzędzie nuke, to możesz użyć również go. Wywołaj w konsoli polecenie
nuke
. Spowoduje ono wywołanie domyślnego celu budowania, czyli kompilację. Podejście to jest bardziej elastyczne, ponieważ zadziała niezależnie od katalogu, w którym je wywołasz. Potrafi ono samo znaleźć katalog główny rozwiązania i tam poszukać odpowiednich plików.
Niezależnie od podejścia, pamiętaj, że przy uruchomieniu możesz podawać własne parametry uruchomieniowe.
Możesz spróbować poprzez dodanie flagi --Configuration Release
, co spowoduje zbudowanie aplikacji w trybie release.
Więcej o definiowaniu własnych parametrów znajdziesz w dalszej części artykułu, w sekcji na temat CI/CD.
Jeśli chcesz wywołać inny cel, wystarczy, że podasz jego nazwę: nuke restore
(dotnet run restore
).
Plugin do Visual Studio 2022
Plugin do Visual Studio pozwala nam na wywoływanie akcji budowania prosto z IDE. Do tego dochodzi możliwość debugowania. Plugin ściągniesz tutaj.
Po instalacji zobaczysz dodatkową ikonkę obok każdego celu budowania:

Testy jednostkowe
Mając już przygotowane środowisko, możemy dodać testy jednostkowe.
Target Tests => _ => _
.DependsOn(Compile) (1)
.TriggeredBy(Compile) (2)
.Executes(() =>
{
EnsureCleanDirectory(TestResultDirectory); (3)
DotNetTest(new DotNetTestSettings()
.SetConfiguration(Configuration) (4)
.EnableNoBuild() (5)
.SetProjectFile(Solution)); (6)
});
Powyższy kod w zupełności wystarczy, aby uruchomić testy jednostkowe znajdujące się w całym naszym rozwiązaniu.
1 | Najpierw określamy, że testy muszą zostać wykonane po kompilacji. |
2 | Następnie, że są one wywoływane po zakończeniu kompilacji. Więcej na temat tych dwóch metod przeczytasz w ramkach poniżej. |
3 | W tym miejscu upewniamy się, że folder wynikowy testów jednostkowych jest pusty. Czasem potrafią znaleźć się tam ciekawe rzeczy, zwłaszcza gdy coś nie działa. |
4 | W tym miejscu ustawiamy konfigurację, czyli to, w jaki sposób chcemy budować naszą aplikację, czy w trybie debug , czy release .
Jak spojrzysz na kod wygenerowany przez konfigurator, zobaczysz właściwość o nazwie Configuration , która dostarcza nam takową informację.
Zawsze możesz go nadpisać, używając parametru --Configuration [Debug|Release] . |
5 | Ustawiamy flagę, informującą o tym, że mechanizm testowy ma nie budować ponownie naszych projektów. Zrobiliśmy to w kroku Compile` , więc powinno nam to zaoszczędzić trochę czasu. |
6 | Określamy projekt, a w tym przypadku całe rozwiązanie, które chcemy przetestować. |
Mając dodane te kilka linijek do naszej klasy Build.cs
możemy wywołać polecenie nuke Compile
.
Powinniśmy ostatecznie uzyskać wynik na kształt:
═══════════════════════════════════════
Target Status Duration
───────────────────────────────────────
Clean Succeeded < 1sec
Restore Succeeded < 1sec
Compile Succeeded 0:02
Tests Succeeded 0:02
───────────────────────────────────────
Total 0:15
═══════════════════════════════════════
Build succeeded on 29.05.2022 18:38:46. \(^ᴗ^)/
Dodatkowe informacje
Pomoc
W każdym momencie możesz wywołać pomoc przy budowaniu. Można zrobić to na wiele rożnych sposobów:
-
nuke help
w dowolnym katalogu rozwiązania, jeśli masz zainstalowane narzędzie Nuke. -
dotnet run — --help
w katalogu projektu budujacego. -
.\build.ps1 --help
w katalogu, gdzie znajduje się skrypt budujący.
Przykładowy rezultat takiego polecenia jest widoczny ponizej. Zwróć uwagę na to, że widoczne są wszystkie wcześniej określone cele budowania oraz parametry wraz z opisem. Daje nam to bardzo fajną odkrywalność naszego procesu budującego.
███╗ ██╗██╗ ██╗██╗ ██╗███████╗
████╗ ██║██║ ██║██║ ██╔╝██╔════╝
██╔██╗ ██║██║ ██║█████╔╝ █████╗
██║╚██╗██║██║ ██║██╔═██╗ ██╔══╝
██║ ╚████║╚██████╔╝██║ ██╗███████╗
╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
NUKE Execution Engine version 6.0.3 (Windows,.NETCoreApp,Version=v6.0)
Targets (with their direct dependencies):
Clean
Restore
Compile (default) -> Clean, Restore
Tests -> Compile
Publish -> Compile
PushToNetlify -> Publish
TestCoverage -> Tests
Parameters:
--configuration Configuration to build - Default is 'Debug' (local) or
'Release' (server).
--netlify-site-access-token <no description>
--netlify-site-id <no description>
--continue Indicates to continue a previously failed build attempt.
--help Shows the help text for this build assembly.
--host Host for execution. Default is 'automatic'.
--no-logo Disables displaying the NUKE logo.
--plan Shows the execution plan (HTML).
--profile Defines the profiles to load.
--root Root directory during build execution.
--skip List of targets to be skipped. Empty list skips all
dependencies.
--target List of targets to be invoked. Default is 'Compile'.
--verbosity Logging verbosity during build execution. Default is
'Normal'.
Jaka jest kolejność?
Gdy już ilość celów budowania będzie duża, a zależności między nimi będzie co niemiara, warto pamiętać o narzędziu, które w przejrzysty sposób wyświetli nam, co będzie się działo.
Do tego służy flaga plan
, która używamy w następujący sposób: nuke --plan
, lub, jeśli chcemy zobaczyć plan dla niestandardowego wywołania, to możemy podać dodatkowe parametry, jak na przykład nazwę celu budowania: nuke PushToNetlify --plan
.
Pamiętaj, że podobnie jak polecenie help, również to można wywołać na analogiczne sposoby.
nuke --plan
Podsumowanie
W następnej części zamierzam pokazać Ci jak wymusić odpowiednie pokrycie kodu testami jednostkowymi oraz jak przygotować aplikację do publikacji. Opiszę również sposób przygotowania CI/CD dla Github Actions z uwzględnieniem parametrów pobierania sekretów repozytorium. Jeśli masz pomysły, co mógłbym jeszcze opisać w sprawie Nuke, daj znać w komentarzu!
Końcowy kod z opisanymi tutaj elementami, i kilkoma więcej, znajdziesz na moim GitHubie: Ztr.AI.
Photo by Burgess Milner on Unsplash.