Einführung in Microsofts Build-Management-Technik MSBuild

Werkzeuge  –  Kommentare

Dass das Kompilieren und dabei anfallende Arbeiten wie das Kopieren und das Bearbeiten von Dateien automatisiert werden müssen, ist seit langem gängiges Wissen in der Softwareentwicklung. Doch was passiert, wenn der Programmierer in "seinem" Visual Studio auf F5 drückt? Hat man damit nicht schon alles aus der Hand gegeben? Gut zu wissen, was unter der Haube vor sich geht.

MSBuild steuert das Kompilieren bei allen Microsoft-Projekten und ist für den Aufruf des Compilers verantwortlich. Physikalisch besteht MSBuild im Wesentlichen aus der MSBuild.exe und den Micosoft.MSBuild.*.dll-Dateien im .NET Framework. Visual Studio benutzt etwa die DLL-Dateien, die im GAC (Global Assembly Cache) liegen, für das Ausführen der Projekt-Dateien. Alle *proj-Dateien bei Visual Studio sind demnach MSBuild-Dateien.

Wenn der Visual-Studio-Entwickler eine solche Datei mit der Bedienfolge Menu | File | Open | File öffnet, lässt sich danach die XML-Datei editieren. Das XML steuert die Microsoft Build Engine. Ant beziehungsweise NAnt waren das Vorbild für diese Architektur. Bei MSBuild handelt es sich aber nicht um eine Skript-Sprache in XML-Form, sondern um eine Auszeichnungssprache, die erst als Ganzes eingelesen und dann ausgeführt wird. Die Reihenfolge zur Ausführung der Anweisungen lässt sich daher nicht immer durch die Position in der Datei bestimmen.

MSBuild.exe bietet den Einstiegspunkt für die Kommandozeile. Zum Build-System gehören neben DLL-Dateien wie Microsoft.Build.Tasks.v3.5.dll noch Response-(.rsp-), Target-, Task- und XSD-Dateien. Die DLLs werden beim Setup in den GAC kopiert, damit sie für Visual Studio zur Verfügung stehen.

Alle Entwicklerprodukte Microsofts haben dieselbe Edition von MSBuild, vom einfachen SDK (Software Development Kit) bis zur Team-Suite, und die gleichen Funktionen. Die Versionsangaben richten sich nach der jeweiligen Version des .NET Framework und damit auch nach dem jeweiligen Release von Visual Studio.

MSBuild ist Teil der Redistributables, das heißt, die ausführbaren Dateien lassen sich mit der eigenen Software verteilen, solange der Hinweis auf das Urheberrecht bestehen bleibt. Des Weiteren ist MSBuild kostenlos erhältlich, etwa mit den Express-Versionen von Visual Studio oder mit den .NET Redistributable Frameworks. Nach der Installation mit dem .NET Framework steht MSBuild zur Verfügung. Damit es in der Kommandozeile bei jedem Pfad bereitsteht, sollte der Entwickler das Installationsverzeichnis über System Properties | Advanced tab | Environment Variables Button | System variable in den "Path" eintragen. Nach dem Öffnen einer neuen Kommandozeile lässt sich MSBuild verwenden.

Die Ausgabe ist eingebettet in viele Metainformationen (Abb. 1).

Der Anwendung MSBuild.exe übergibt man eine .msbuild-Datei als Parameter. Die Endung ist nicht zwingend, .xy würde auch funktionieren. .msbuild ist aber anzuraten, da bei großen Projekten immer viele Dateiendungen benutzt werden, und diese Festlegung führt zu einer schnellen Unterscheidbarkeit. Zur Veranschaulichung hier ein erstes helloworld.msbuild-Beispiel, das zum Ergebnis in Abbildung 1 führt:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="A">
<Message Text="Hello World!"/>
</Target>
</Project>

Wenn sich eine Datei mit der Endung "proj" oder eine .sln-Datei im Verzeichnis befindet, verwendet MSBuild diese als Eingabe. Angenommen sei, dass im Verzeichnis D:\Development\ConsoleApplication1 eine Solution liegt, dann kompiliert der Aufruf "msbuild" aus diesem Pfad die Solution mit den darin angegebenen Einstellungen.

MSBuild im Build-Management zu verwenden kann im einfachsten Fall so aussehen, dass man per cd-Befehl zu der Solution-Datei des Softwareprojekts navigiert und darauf MSBuild aufruft. Die Solution-Datei erstellt man mit Visual Studio, aber ihre Ausführung erfordert nicht diese IDE, das Framework-SDK reicht. Der Aufruf von MSBuild durch die Übergabe einer Projektmappendatei ist ein einfaches Mittel, Projekte jeder Größenordnung zu kompilieren. Ab einer gewissen Größenordnung ist das aber nicht zu empfehlen, etwa im Kontext von Continuous Integration.

Das erste Target wird ausgeführt, nicht alle (Abb. 2).

Wie man an dem HelloWorld-Beispiel sehen kann, ist die grundlegende Einheit eines MSBuild-Skripts das Target. In der Regel gibt es davon mehrere in einer MSBuild-Datei. Die Namensgebung Target stammt aus der ursprünglichen Verwendung: Es handelt sich um eine bestimmte Zielplattform (32-Bit, Prozessortyp, Betriebssystem), für die kompiliert wird. Mittlerweile steht der Begriff jedoch nicht nur für die Zielplattform, sondern für jede Art von Zwischenergebnis. Targets unterstützen auch die Schachtelung und Verkettung. Das folgende HelloWorld2.msbuild

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="A">
<Message Text="Hello World! A"/>
</Target>
<Target Name="B">
<Message Text="Hello World! B"/>
</Target>
</Project>

führt bei einem einfachen Aufruf nur zur Ausführung des ersten Targets (siehe Abbildung 2).

Nur das benannte Target wird ausgeführt (Abb. 3).

Will man das Target "B" aufrufen, ist das ebenfalls anzugeben. Die Option /target:mytarget oder /t:mytarget definiert das gewünschte Target.

Wenn im Root-Tag das Default-Target steht und sonst keines definiert ist, wird dieses als Einziges aufgerufen.

<Project DefaultTargets="B" xmlns=
"http://schemas.microsoft.com/developer/msbuild/2003">

Nur das Default-Target wird aufgerufen (Abb. 4).

Bei einem gleichen Aufruf führt man nur das Target B aus.

Der Target-Tag hat das Attribut "DependsOnTargets". Das bedeutet, dass MSBuild die im Attribut genannten Targets zuerst ausführt. Das Beispiel definiert kein Default-Target, fügt aber ein "DependsOnTarget" ein. Es wird unter helloworld3.msbuild gespeichert:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="A">
<Message Text="Hello World! A"/>
</Target>
<Target Name="B" DependsOnTargets="A">
<Message Text="Hello World! B"/>
</Target>
</Project>

Ist ein Target von einem anderen abhängig, wird das andere zuerst ausgeführt (Abb. 5).

Schließlich muss man die anderen Targets durchführen, um das abhängige Target auszuführen.