Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Automatyzacja projektu z MSBuild-em - 2. Dlaczego MSBuild ?

Aktualnie do dyspozycji mamy kilka możliwych narzędzi wspomagających budowę aplikacji w NET:

Mój ostateczny wybór, trochę metodą eliminacji padł na MSBuild.

A dlaczego nie Bake ?

Bake, poprzednio Boobs, jest to narzędzie wzorowane na Rake, systemie do automatyzacji zadań w środowisku Ruby. Oto przykład Bake:

Task "remove build dir":
    RmDir("build", true) if Exist("build")
 
Task "init build dir":
    MkDir("build")
    Cp(["lib/*.dll"], "build", true)
 
Task "build Bake", ["build engine", "build extensions", "build win32 helper"]:
    Booc(
        SourcesSet   : ["tools/Bake/**/*.boo"],
        OutputFile   : "build/Bake.exe",
        ReferencesSet: ["build/Bake.engine.dll", "build/boo.lang.useful.dll"]
        ).Execute()

Jego główną zaletą jest to, że skrypty są tworzone za pomocą języka Boo wzbogaconego o dodatkowa semantykę Domain Specific Language. Hmm co w tym nowego ? Stare, poczciwe skrypty dla "make" również zawierały swój własny DSL, który był zdefiniowany w zewnętrznych bibliotekach. NAnt i MSBuild to też DSL tylko że XML-owy. Definicja znaczników XML-owych też znajduje się w zewnętrznych bibliotekach. Skrypt dla Bake natomiast jest czymś w rodzaju "aplikacji" w momencie uruchamiania. Skrypt w naturalny sposób staje się po prostu czytelnym programem do, którego można wstawiać kod w naturalny sposób.  Można by się pokusić o stwierdzenie że NAnt też to potrafi chociażby dzięki taskowi "script":

<script language="C#" >
    <references>
        <include name="System.Data.dll" />
    </references>
    <imports>
        <import namespace="System.Data.SqlClient" />
    </imports>
    <code>
      <![CDATA[
        public static void ScriptMain(Project project) {
            string dbUserName = "nant";
            string dbPassword = "nant";
            string dbServer = "(local)";
            string dbDatabaseName = "NAntSample";
            string connectionString = String.Format("Server={0};uid={1};pwd={2};", dbServer, dbUserName, dbPassword);
            
            SqlConnection connection = new SqlConnection(connectionString);
            string createDbQuery = "CREATE DATABASE " + dbDatabaseName;
            SqlCommand createDatabaseCommand = new SqlCommand(createDbQuery);
            createDatabaseCommand.Connection = connection;
            
            connection.Open();
            
            try {
                createDatabaseCommand.ExecuteNonQuery();
                project.Log(Level.Info, "Database added successfully: " + dbDatabaseName);
            } catch (Exception e) {
                project.Log(Level.Error, e.ToString());
            } finally {
                connection.Close();
            }
        }
      ]]>
    </code>
</script>

Jednak można stwierdzić zgodnie, że XML nie powstał z myślą o tworzeniu za jego pomocą czytelnych algorytmów. Niestety projekt Bake jest dopiero we wczesnej fazie rozwojowej i na dodatek od jakiegoś czasu nie jest uaktualniany. Aczkolwiek stanowi interesujący przykład w jakim kierunku powinny pójść narzędzia do zarządzania kompilacją kodu.

Warto również zwrócić uwagę na projekt Mite.Net. Jest on wzorowany na "Rails migrations", czyli zarządzaniu zmianami struktury bazy danych pomiędzy różnymi wersjami aplikacji. Projekt ten również wykorzystuje Boo i posiada własny DSL.

company = "Company"
employee = "Employee"
 
up:
    add_table company:
         string "Name", { max_length = 65, unique = true }
    
    add_table employee:
         string "Name", { max_length = 65 }
         int32 "CompanyId"
    
    add_relation "employment", Cardinality.OneToMany, company, employee + ".CompanyId"
 
down:
    drop_table employee
    drop_table company

Narzędzie to na pewno będzie mogło się przydać nie tyle do kompilacji ale np. do aktualizacji bazy danych przed uruchomieniem testów. Projekt Mite.Net również jest w początkowej fazie rozwoju.

A dlaczego nie NAnt ?

Ależ oczywiście że jestem na "TAK", a raczej byłem. Pierwsze kroki stawiałem właśnie z NAnt. Podczas pisania plików do NAnt-a wzorowałem się rozwiązaniach programistów w innych projektach OpenSource. Świetnym przykładem organizacji plików do NAnta są projekty NHibernate i Castle. W NHibernate każdy projekt Visual Studio ma swój odpowiednik NAnt z rozszerzeniem.build

<?xml version="1.0" ?>
 
<project 
    name="NHibernate" 
    default="build" 
    xmlns="http://nant.sf.net/release/0.85-rc3/nant.xsd"
>
 
    <property name="root.dir" value="../.." />
    <include buildfile="${root.dir}/build-common/common-project.xml" />
 
    <target name="init" depends="common.init">
        <property name="assembly.description" value="An object persistence library for relational databases." />
        <property name="assembly.allow-partially-trusted-callers" value="true" />
        <property name="clover.instrument" value="true" />
 
        <assemblyfileset id="project.references" basedir="${bin.dir}">
            <include name="System.dll" />
            <include name="System.Transactions.dll" />
            <include name="System.Configuration.dll" />
            <include name="System.XML.dll" />
            <include name="System.Data.dll" />
            <include name="System.Data.OracleClient" />
            <include name="System.Web.dll" />
            <include name="Iesi.Collections.dll" />
            <include name="log4net.dll" />
            <include name="Castle.Core.dll" />
            <include name="Castle.DynamicProxy2.dll" />
        </assemblyfileset>
 
        <resourcefileset id="project.resources" prefix="NHibernate" dynamicprefix="true">
            <include name="*.xsd" />
            <include name="**/*.xml" />
            <exclude name="bin/**/*.xml" />
        </resourcefileset>
        
        <fileset id="project.sources">
            <include name="**/*.cs" />
        </fileset>
    </target>
 
    <target name="generate-assemblyinfo" depends="init common.generate-assemblyinfo" />
 
    <target name="build" description="Build NHibernate"
        depends="generate-assemblyinfo common.compile-dll">
        <copy file="${bin.dir}/NHibernate.dll" tofile="${root.dir}/${lib.framework.dir}/NHibernate.dll"/>
    </target>
 
</project>

Następnie w głównym katalogu projektu znajduje się plik default.build konsolidujący ze sobą wszystkie pliki .build w poszczególnych projektach. Dodatkowo jest jeszcze katalog "build-common", który zawiera pliki z parametrami do budowy oraz ze wspólnymi taskami. Dzięki takiemu rozwiązaniu uzyskujemy następujące zalety:

  • możemy niezależnie budować oraz testować aplikacje z parametrami przeznaczonymi dla różnych wersji frameworków np. mono
  • wspólne parametry jak wersjonowanie lub parametry do wspólnych zadań są zarządzane w jednym miejscu
  • każdy projekt może być skompilowany pojedynczo lub wszystkie razem

Czego chcieć więcej ? Zazwyczaj "potrzeba" jest matką wynalazków w moim wypadku to było "lenistwo". W momencie kiedy dodawałem nową bibliotekę do referencji projektu, musiałem pamiętać aby ją uwzględnić również w pliku .build. Podobnie z plikami, które chciałem traktować jako resources w assembly również musiałem dodawać do pliku .build. Jeżeli dodawałem nowy projekt do solution, musiałem pamiętać by dodać ją również do pliku .build w głównym katalogu. Po co robić to dwa razy skoro i tak robimy to używając Visual Studio ? Przecież pliki .csproj są niczym innym jak plikami do MSBuilda a plik .sln służy za zbiór plików z projektami i może być bez problemu parametrem dla MSBuild-a.

msbuild MySolution.sln /t:build

Dodatkowo nie zamierzam kompilować projektów pod mono więc ... wybór padł na MSBuild. Teraz czas udowdnić, że MSBuild z powodzeniem może zastąpić NAnt-a oraz przedstawić techniki z których korzystam w codziennej pracy. A to już w następnych odcinkach.

Opublikowane 14 lipca 2008 14:35 przez rod
Filed under: ,

Komentarze:

14 lipca 2008 17:36 by rod.pl

# Organizacja projektu i automatyzacja z wykorzystaniem MSBuild-a - 1. Struktura

Pierwszy post z cyklu na temat organizacji struktury projektu oraz automatyzacji jego: budowy, testowania, instalacji. W pierwszym odcinku przedstawiona jest struktura projektu.

15 lipca 2008 23:22 by Tarciu

# re: Organizacja projektu i automatyzacja z wykorzystaniem MSBuild-a - 2. Wybór narzędzia

Dublowanie struktury projektu na potrzeby NAnt jest już niepotrzebne. Jest przecież zadanie <msbuild> do wygodnego kompilowania plików .sln i .*proj

http://nantcontrib.sourceforge.net/release/0.85/help/tasks/msbuild.html

W NAnt pisze się tylko to, co jest ponad standardową kompilację.

Zresztą, w czasach VS 2003 w NAnt kompilację .sln czy .*proj można było wykonać poprzez wywołanie VS z odpowiednimi parametrami z poziomu skryptu. Nie wiem, może rozwiązanie które opisałeś (z NHibernate) ma jakieś dodatkowe zalety, ale - jak pośrednio zauważyłeś - konieczność dublowania struktury projektu to koszmar.

Zatem - ta wada obecnie nie występuje.

Ja swoją przygodę z automatyzacją projektów zaczynałem właśnie od NAnt. Nie wszystko mi się w nim podobało i wydawało mi się, że MSBuild to będzie coś o klasę lepszego. W końcu projekt komercyjny, tworzony przez stały zespół z MS no i mogący korzystać z doświadczeń wielu już stworzonych narzędzi tego typu.

Gdy przesiadłem się na MSBuild, kompletnie się rozczarowałem. Jest bardzo surowy, z mocno ograniczającą składnią (nieco ją poprawili w najnowszej wersji), a przede wszystkim - co krok okazuje się, że żeby coś, nawet stosunkowo prostego, osiągnąć, należy pisać własne taski lub stosować koszmarne hacki.

Przykład:

http://forums.msdn.microsoft.com/en-US/msbuild/thread/84242bd4-4209-4429-bd16-0f625938c288/

Kończąc ten zdecydowanie za długi komentarz - bardzo szybko przesiadłem się z powrotem na NAnt i dopiero wtedy w pełni doceniłem jak dobre, mimo swoich wad, jest to narzędzie :)

16 lipca 2008 02:10 by rod

# re: Automatyzacja projektu z MSBuild-em - 2. Dlaczego MSBuild ?

Wiem że jest zadanie <msbuild> :). Zgadzam się również, że dzięki temu można by uniknąć dublowania struktury projektu.

Jednak już w odcinku 3-cim opisuje jak można wzbogacać standardowy "Build", który wykonujemy spod Visual Studio (Ctrl + Shift + B). Nie tylko poprzez "Build Events" dostępne w properties projektu, ale za pomocą centralnego zarządzania dla wszystkich projektów. Tego za pomoc nanta i <msbuild> już bym nie dal rady zrobić.

Natomiast w odcinku 5-tym ( w 4 tym będzie o testach) pokaże dynamiczne dokładanie plików do kompilacji co tez by mi sie nie udało z nant + <msbuild>. Możemy na ten temat podyskutować w komentarzach jak już będzie part 5 :). Może znajdziemy sposób również w nancie

Takie rzeczy byłyby możliwe gdybyśmy mieli się opierać tylko na nant a to znaczy dublowanie struktury projektu.

Doskonale rozumiem Twoje rozczarowanie :). Tez bylem początkowo zniesmaczony MSBuildem. Ale z czasem to minęło :). Co do hacków ... masz racje :(. Pozostaje pisać własne targety :(. Co do nowej wersji, czyli MSBuild 3.5  .. to tak dużo się nie zmieniło aczkolwiek w trakcie pisania dalszych czesci jak bede korzystał z nowych zalet MSbuilda 3.5 to napomknę.

Każdy rzetelny komentarz nie jest za długi :)

16 lipca 2008 07:41 by Tarciu

# re: Automatyzacja projektu z MSBuild-em - 2. Dlaczego MSBuild ?

OK, zatem już wiem po co zastosowano rozwiązanie z  NAntowymi plikami projektów :)

W takim razie czekam na odc.5. Dzięki!

Komentarze anonimowe wyłączone