[Read the English version]
Abhängigkeiten gibt es viele in Software-Entwicklungsprojekten. Von besonderem Interesse sind die Build-Abhängigkeiten der Komponenten, die beim Bauen, Ausliefern und Warten des Produktes beachtet und verwaltet werden müssen. Mit Visual Studio Ultimate können Build-Abhängigkeiten einer Assembly oder einer Solution mit Hilfe des Architecture Explorer ausgewertet und dargestellt werden. Nicht wenige größere Projekte müssen mit einer Vielzahl von Solutions und Visual Studio-Projekten umgehen. Dieser Beitrag zeigt Ihnen, wie Sie die Erweiterungschnittstellen von Visual Studio und MSBuild nutzen können, um das Dependency Management zu einem “First-Class Citizen” zu machen.
Update
Wir haben eine Visual Studio Extension – den "AIT Dependency Manager" – veröffentlicht. Damit wird es in Zukunft möglich sein, Abhängigkeiten in Team Explorer und Visual Studio zu verwalten und zu nutzen, ohne selbst Anpassungen an Visual Studio vornehmen zu müssen. Sie finden den kostenfreien AIT Dependency Manager unter https://www.aitgmbh.de/DependencyManager.
Wo ist das Problem?
Doch zurück zur Solution. Eine Solution besteht vereinfacht aus Projekten, die untereinander und nach außen Abhängigkeiten besitzen. Die folgende Abbildung verdeutlicht das.
Abbildung 1: Solution-Struktur
Bei den äußeren Abhängigkeiten handelt es sich neben Referenzen auf das .NET Framework und Komponenten von Drittanbietern auch um In-House gebaute Komponenten – also Zulieferungen anderer Teams oder Abteilungen. Dies geschieht aus Gründen der Wiederverwendung oder aber auch der besseren Arbeitsteilung wegen. Christian Binder von Microsoft Deutschland arbeitet zusammen mit uns an einem Whitepaper welches die verschiedenen Optionen zur Lösung von internen Abhängigkeiten aufzeigen soll.
Ein Branch-zentrischer Ansatz
Eine ganz konkrete Lösungsmöglichkeiten soll nun im folgenden anhand eines Beispiels erarbeitet werden.
Dabei gehen wir von einem Branch-zentrischen Ansatz aus. Das Projekt ist in verschiedene Teilbereiche (wie z.B. Plattform und Applikation) aufgeteilt. Jeder Teilbereich agiert als Zulieferer für konsumierende Teilbereiche – besitzt also ein eigenes Release-Management und wird binär über Build-Ergebnisse an die Konsumenten übergeben.
Die folgende Grafik zeigt diese Abstraktionsebene:
Abbildung 2: Branch-Struktur im Kontext der Teamprojekte
Mit den Boardmitteln von .NET Framework und TFS kommt man nun ncht mehr aus, wenn Abhängigkeiten zwischen Solutions verschiedener Teamprojekte und Branches komfortabel verwaltet werden sollen. Einige Anpassungen und Erweiterungen sind nötig, um den entstehenden Abhängigkeitsbaum verwalten zu können.
ComponentDefinition.targets
Zum einen müssen die Abhängigkeiten modelliert und irgendwo gespeichert werden. Da die Abhängigkeiten einer Solution beschrieben werden sollen, eignet sich die Solution auch als Ablageort. Dafür definieren wir eine Datei namens ComponentDefinition.targets innerhalb der Solutions Items:
Abbildung 3: Die Datei ComponentDefinition.targets als Solution Item
Diese Datei enthält nun 2 wesentlich Beschreibungen.
- Die Liste der zu bauenden Solutions in ihrer Reihenfolge, wenn Abhänigkeiten als zu bauender Quellcode vorliegen – vor allem bei aufgeteilten Solutions der Fall.
- Die Abhängigkeiten die entsprechend noch geladen werden müssen – entsprechend ihrem Typ entweder aus der Versionskontrolle, vom Build-Share oder 3rdParty-Share.
Die unterschiedlichen Abhängigkeitstypen bedingen auch verschiedene Identifizierungsarten.
- Binäre Abhängigkeiten zu Drittanbieterbibliotheken (3rdParty)
- Werden über einen Pfad auf das entsprechende Netzwerkverzeichnis identifiziert.
- Alle Dateien müssen ins lokale Build-Ausgabeverzeichnis kopiert werden.
- Binäre Abhängigkeiten zu eigens erstellten Bibliotheken und Komponenten (BuildResult)
- Werden über das Teamprojekt und die Buildnummer identifiziert
- Alle Dateien aus dem Build-Drop müssen ins lokale Build-Ausgabeverzeichnis kopiert werden.
- Quellcodeabhängigkeiten zu anderen Solutions (Source)
- Werden über den Pfad in der Versionskontrolle und der Version identifiziert
- Das Verzeichnis muss lokal im Workspace gemappt und die entsprechende Version aus dem Repository abgerufen werden
Das bedingt dann den folgenden Aufbau der ComponentDefinition.targets-Datei:
Listing 1: ComponentDefinition.targets Inhalt
Es handelt sich dabei um eine Datei im MSBuild-Format. Damit lässt sich die Liste der zu bauenden Solutions “nativ” in Form einer sogenannten ItemGroup namens ItemsToBuild (Zeilen 6-9) abbilden. Die Solution Project2.sln ist dabei die eigentliche Solution der betrachteten Komponente. Vorher muss aber noch Project1.sln aus der Komponente Platform.Common gebaut werden (Zeile 7).
Um nun Bauen zu können muss die Komponente Platform.Common aber zunächst einmal da sein. Zudem werden Dlls der Komponente Platform.Compression benötigt Dafür sorgt die ItemGroup Dependencies (Zeilen 11-25). Platform.Common wird als Source (Zeile 13) in den lokalen Workspace gemappt und in einer bestimmten Version – hier einem Build-Label (Zeile 16) – abgerufen.
Platform.Compression liegt als Build-Ergebnis vor und wird über das Teamprojekt, die Build-Definition und Nummer “geortet” und in das lokale Ausgabeverzeichnis kopiert.
Für die beschriebenen Mapping und Kopieraktionen sorgt dabei ein eigens entwickelter Build-Tasks GetComponents (Zeile 4 in Listing 2). In Zeile 4 im Listing 1 wird diese Logik importiert – sie muss also auf den Entwicklungs- und Build-Rechnern ausgerollt sein.
Listing 2: Auszug aus der zentralen Logik
Die ItemsToBuild Liste wird in Listing 2 Zeile 12 verwendet, um direkt MSBuild aufzurufen. Dieses sorgt dafür, dass die Solutions der Reihe nach gebaut werden. Das Skript ist hier vereinfacht. Normalerweise müssten noch entsprechende Parameter wie z.B. zum Ausgabeverzeichnis und der Plattform übergeben werden.
Abbildung 3 zeigte, dass die Datei ComponentDefinition.targets zur Solution hinzugefügt wurde. Doch wo liegt diese im Gesamtkontext Teamprojekt und Branches? Nun, am Ort der Solution unterhalb eines Branches. Nur so kann gewährleistet werden, dass für verschiedene Varianten oder Entwicklungszweige unabhängig die Liste der Abhängigkeiten gepflegt aber dennoch gemergt werden kann. Abbildung 4 zeigt einen Ausschnitt aus der Versionskontrolle des Beispiels.
Abbildung 4: Ort der ComponentDefinition.targets in der Versionskontrolle
Der Arbeitsablauf für den Anwender gestaltet sich dann wie folgt:
- Workspace im TFS mappen (auf die Komponente im Unterverzeichnis eines Branches – hier: Platform.Base unterhalb von Branch Team Stuttgart)
- Solution öffnen – hier: Project2.sln
- Abhängigkeiten über ComponentDefinition.targets abrufen (starten mit MSBuild)
Das Starten der ComponentDefinition.targets über MSBuild ist zwar von Kommandozeile aus möglich. Geschickter ist aber der Aufruf direkt aus dem Solution Explorer über das Kontextmenü. Dazu gehen Sie wie folgt vor.
Legen Sie zwei neue External Tools im Visual Studio an:
- Menü Tools > External Tools
- Legen Sie das External Tool “Get Dependencies” an:
- Legen Sie das External Tool “Build All” an – beachten Sie die Arguments-Angaben:
- Um die Tools aus dem Kontextmenü heraus aufzurufen, gehen Sie in das Menü Tools > Customize… und dort in den Reiter Commands
Dort selektieren Sie den Radio Button “Context Menus” und wählen aus der Liste “Project and Solution Context Menu | Item” aus
Nun fügen Sie die Kommandos “External Command X” und “External Command Y” hinzu, wobei X und Y den Indizes der angelegten Tools in der obigen Liste (beginnend bei 1) entspricht.
Das Kontextmenü im Solution Explorer sollte nun folgendes Anzeigen:
Abbildung 8: Kontextmenü im Solution Explorer
Somit lassen sich die Abhängigkeiten elegant abrufen und im Anschluss bauen. Die Ausgabe der Tools (also MSBuild) erfolgt dabei komfortabel im Ausgabefenster von Visual Studio.
Abbildung 9: Ergebnisse im Ausgabefenster
Damit befinden Sie alle Abhängigkeiten auf dem lokalen Arbeitsplatz. Mühevolles Zusammensuchen der Referenzen ist damit passé.
Zentrale Build-Prozesse
Die lokale Anlage des Arbeitsplatzes ist aber nicht alles, was während des Entwicklungsprozesses benötigt wird. Im zentralen Build müssen ebenfalls Abhängigkeiten vorhanden sein. Mit Team Foundation Build können die oben beschriebenen Arbeitsabläufe auf dem Build-Rechner autmatisiert ablaufen.
Das Mapping auf die Komponente wird im Workspace-Template der Build-Definition angegeben:
Abbildung 10: Mappen der Komponente im Build-Workspace
Die ComponentDefinition.targets-Datei wird dabei als zentrales Item to Build in der Build-Definition definiert (siehe Abbildung 11). Mit Hilfe der AIT Build Suite lässt sich zudem nach dem Abrufen der Sourcen das Build-Skript starten, um vorab die Abhängigkeiten abzurufen.
Abbildung 11: Definition der Build-Prozessparameter mit der AIT Build Suite
Visualisierung der Abhängigkeiten
Bleibt die Verwaltung der Abhängigkeiten. Die dezentralen Definitionsdateien müssen zentral überwacht werden können. Dazu dient in unserem Fall der Architecture Explorer und DGML – eine Möglichkeit, Graphen zu visualisieren und zu analysieren.
Vorab noch ein Blick auf die eingebauten Möglichkeiten der Tools. Der Architecture Explorer ermöglicht zum Beispiel über den sogenannten Solution View, eine Solution auszuwerten.
Abbildung 12: Der Solution View im Architecture Explorer
Die erfragten Informaitonen lassen sich per Drag’n’Drop auf eine DGML-Datei ziehen und werden dann visualisiert wie in Abbildung 13.
Abbildung 13: Visualisierung der Abhängigkeiten einer Solution
Eine Information zu Build-Ergebnisabhängigkeiten oder zum Source-Code gibt es nicht.
Informationen aus der Versionskontrolle sind in der Branch-Visualisierung des Team Explorers dargestellt. Allerdings nur die Branch- und Merge-Beziehungen zwischen den Zweigen im Repository und keine Abhängigkeiten.
Abbildung 14: Der Branch-Baum im Team Exporer
Diese Lücke gilt es mit einem eignen Tooling zu schließen. Wie bereits angedeutet, eignet sich dafür die erweiterbare Schnittstelle des Architecture Explorers – die Progression API. Der Architecture Explorer bietet ein flexibles Provider-Modell, bei dem lediglich eine Dll mit einer Implementierung des Interface Microsoft.VisualStudio.Progression.IProvider in das Verzeichnis "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Providers” abgelegt werden muss.
Über eigene Abfrage-Typen kann nun ein mächtiges Werkzeug zur Analyse der Abhängigkeiten geschaffen werden (siehe Abbildung 15). Dabei kommuniziert das Visual Studio direkt mit der Versionskontrolle des TFS – lädt zum Beispiel die ComponentDefinition.targets-Dateien direkt in den Speicher, um diese weiter zu analysieren.
Abbildung 15: Die eigene Dependency View im Architecture Explorer
Die einzelnen Komponenten lassen sich dann via DGML darstellen. Doch auch die Verbindungen – die Links – bieten über zusätzliche Eigenschaften nützliche Informationen.
Abbildung 16: Visualisierte Abhängigkeit mit referenzierter Version
So können nicht nur die Visualisierungsfunktionen von Visual Studio via DGML genutzt werden. Auch die Analysefunktionen des Architecture Explorer zum Beispiel für das Aufsuchen von zirkulären Referenzen im Graphen lassen sich verwenden wie Abbildung 17 abschließend zeigt.
Abbildung 17: Analyse auf zirkuläre Abhängigkeiten und die markierten Treffer
Weitere Informationen
Aufzeichnung des TechEd 2010 Vortrags: Extended Dependency Management using TFS 2010
Fazit
Das beschriebene Beispiel gibt einen kleinen Einblick in die Möglichkeiten zur Verwaltung von Abhängigkeiten sozusagen über Zweig- un Baumgrenzen hinweg. Die Erweiterbarkeit von Visual Studio, MSBuild und Team Foundation Server lässt viele weitere Möglichkeiten offen. Die hier dargestellte Lösung kommt bereits in ähnlicher Form bei einigen unserer Kunden zum Einsatz.
Wenn Sie Details und Code-Snippets benötigen sollten, können Sie gerne auf uns zurückkommen. Schicken Sie uns doch eine Nachricht oder rufen Sie uns an.
Über den Autor
Sven Hubert ist für die AIT als Berater im AIT Team SystemPro Team tätig und Most Valuable Professional (MVP) für Visual Studio ALM. Zu seinen Schwerpunkten gehört neben der Projektleitung das Application-Lifecylce-Management. Er berät Unternehmen bei der Einführung und Anpassung des Visual Studio Team Foundation Server. Schreiben Sie ihm an Sven.Hubert (at) aitgmbh.de oder hinterlassen Sie einfach einen Kommentar. |