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?

  1. Kompilacja wszystkich modułów (bibliotek dll) wchodzących w skład Pizzy.
  2. Automatyczne wygenerowanie plików *.nuspec dla każdego modułu (np. na podstawie szablonu).
  3. W ramach każdego modułu wyszukanie zależności od bibliotek systemowych .NET frameworka i umieszczenie ich w odpowiednim miejscu w pliku *.nuspec.
  4. W ramach każdego modułu wyszukanie zależności od innych paczek nugetowych i umieszczenie ich w odpowiednim miejscu w pliku *.nuspec.
  5. W ramach każdego modułu wyszukanie zależnośći od innych modułów Pizzy i dodanie ich do listy zależności nugetowych.
  6. Umieszczenie w paczce treści release notes.
  7. Wersjonowanie paczki na podstawie numeru z release notes.
  8. 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

Opublikowano: