Generowanie paczek NuGeta przy użyciu FAKE
Efektem tworzenia frameworka (oprócz kodu źródłowego) powinny być oczywiście paczki NuGeta zawierające biblioteki dll. Samo ich stworzenie nie jest problemem, wszakże wystarczy kompilacja i nuget.exe
wywołany z wiersza poleceń. Oczywiście można to robić ręcznie, ale ponieważ jestem leniwym programistą, wolę, aby działo się to automatycznie.
Dlaczego FAKE?
Chyba nie ma nic dziwnego w tym, że pracując samodzielnie nie korzystam z żadnego systemu continous integration. Początkowo kompilowałem projekt, a następnie budowałem paczkę po prostu ręcznie wywołując nugeta z konsoli. Działało to ślicznie, póki nie aktualizowałem zależności oraz nie podzieliłem jednej dllki na cztery. (A w planach mam jeszcze kilka.) Wówczas stwierdziłem, że tak dalej nie da się żyć i postanowiłem zaprząc do tego jakiś automat.
Nie znałem za bardzo takich rozwiązań - w pracy zawsze było TeamCity, które w moim przypadku byłoby strzelaniem z armaty do komara. Poza tym konfigurowanie go jest nudne, a ja nie mam ani środków na zatrudnienie studenta w tym celu. Istnieje MSBuild, który jest jakiś dziwny i wymaga jakiejś dziwnej konfiguracji przez XML. Jest psake, które jest napisane w PowerShellu. Trochę już w życiu pisałem w tym języku - nie jest zły, ma więcej dolarów niż PHP, no ale ma sporo wad typu niewrażliwość na wielkość liter, brak kompilacji oraz typowania. Poza tym już go znam i trochę mi się znudził. Z tego powodu wybrałem FAKE. Nie mam pojęcia o F#, więc w moim przypadku to wybór idealny, przynajmniej się czegoś nauczę.
Cel
Czego oczekuję od systemu automatycznego buildowania i publikowania pakietów?
- Kompilacja wszystkich modułów (bibliotek dll) wchodzących w skład Pizzy.
- Automatyczne wygenerowanie plików
*.nuspec
dla każdego modułu (np. na podstawie szablonu). - W ramach każdego modułu wyszukanie zależności od bibliotek systemowych .NET frameworka i umieszczenie ich w odpowiednim miejscu w pliku
*.nuspec
. - W ramach każdego modułu wyszukanie zależności od innych paczek nugetowych i umieszczenie ich w odpowiednim miejscu w pliku
*.nuspec
. - W ramach każdego modułu wyszukanie zależnośći od innych modułów Pizzy i dodanie ich do listy zależności nugetowych.
- Umieszczenie w paczce treści release notes.
- Wersjonowanie paczki na podstawie numeru z release notes.
- Możliwość wrzucenia paczek na
nuget.org
.
Wykonanie
Co tu dużo mówić - wziąłem jakiś przykładowy skrypt FAKE i zacząłem go dostosowywać do swoich potrzeb. FAKE niemalże wszystkie powyższe wymagania spełnia standardowo. Na przykład, jeśli mamy release notes w formacie zgodnym z:
## New in 0.2.1 (Released 2016/03/13)
* Fixed sorting by nullable enums in grid.
* Audit works also with not authenticated users.
* JS and CSS files moved to framework.
to taki kod: let releaseNotes = LoadReleaseNotes releaseNotesPath
pozwala nam na dobranie się do zapisanej wersji po prostu za pomocą odwołania do wartości: releaseNotes.NugetVersion
.
Podobnie, funkcja getDependencies
przyjmująca ścieżkę do pliku packages.config
wyciąga z niego listę zależności.
Właściwie jedyne rzeczy, które musiałem sam napisać, to znalezienie referencji do bibliotek .NET frameworka. W pliku *.csproj
mają one postać takich węzłów: <Reference Include="System.ComponentModel.DataAnnotations" />
. Zaimplementowałem to w postaci takiej funkcji:
1 let getFrameworkReferencesFromCsproj (csprojContent: XDocument) =
2 csprojContent.Root.Descendants()
3 |> Seq.filter (fun el -> el.Name.LocalName = "Reference" && not el.HasElements)
4 |> Seq.map (fun el -> el.Attribute(XName.Get("Include")))
5 |> Seq.map (fun el -> { AssemblyName = el.Value; FrameworkVersions = [ "4.5" ] } )
6 |> Seq.toList
Kod jest chyba tak prosty, że nie wymaga tłumaczenia.
Z kolei odwołania do pozostałych modułów Pizzy wyglądają w pliku projektu następująco:
1 <ItemGroup>
2 <ProjectReference Include="..\Pizza.Contracts\Pizza.Contracts.csproj">
3 <Project>{10A3EE1D-414C-4A89-9F05-5749F5FEEE7B}</Project>
4 <Name>Pizza.Contracts</Name>
5 </ProjectReference>
6 <ProjectReference Include="..\Pizza.Persistence\Pizza.Persistence.csproj">
7 <Project>{6046F8A1-FDF2-4F58-AE09-76C397580923}</Project>
8 <Name>Pizza.Persistence</Name>
9 </ProjectReference>
10 </ItemGroup>
A funkcja je parsująca tak:
1 let getProjectReferencesFromCsproj (csprojContent: XDocument) =
2 csprojContent.Root.Descendants()
3 |> Seq.filter (fun el -> el.Name.LocalName = "ProjectReference")
4 |> Seq.map (fun el -> el.Element(XName.Get("Name", "http://schemas.microsoft.com/developer/msbuild/2003")))
5 |> Seq.map (fun el -> (el.Value, releaseNotes.NugetVersion))
6 |> Seq.toList
Reszta mojego zadania to poskładanie tego do kupy. Cały kod można zobaczyć tutaj