[Lesen Sie den Beitrag in deutscher Sprache]
There are many dependencies in software development projects. Build dependencies between components are of special interest. These must be managed during build, delivery and maintenance in order to keep track of them. Using Visual Studio Ultimate, you can visualize and analyze dependencies within solutions in Architecture Explorer. Many bigger projects however have multiple solutions which have to be build in a specific order and are developed on separate branches or even in different team projects. This article demonstrates how to use the extension points of both Visual Studio and MSBuild to make Dependency Management a first-class citizen.
We’ve released a Visual Studio Extension – the”AIT Dependency Manager”. In the future, this will enable you to do extended dependency management without having to customize Visual Studio as described here. You can find the free tool AIT Dependency Manager under https://www.aitgmbh.de/DependencyManager.
What’s the problem?
Back to the Visual Studio solution mentioned above. A solution consists of several projects which might reference other projects or external assemblies. The figure below shows the basic structure of a solution:
Figure 1: Solution structure
External references might be references to .NET Framework, 3rd party libraries or components built in-house – meaning deliveries from other teams or departments. this happens because of reuse or to enable work decomposition. Christian Binder is working with us on a whitepaper which will show the different options you have for handling these dependencies.
A branch-centric approach
One distinct solution option shall be demonstrated here using a basic sample.
We follow a branch-centric approach. A project is decomposed into different “areas” or parts (e.g. a platform and applications built on top of the platform). Every part is acting as a supplier for consuming parts – hence, has its own release cycles and is distributed as binaries via build results to the consumers.
The following graphic visualizes that:
Figure 2: Branching structure underneath a team project
The native function set of .NET Framework and TFS is lacking support to manage inter-solution dependencies across team projects. Some customizations and extensions are necessary to be able to mitigate this scenario.
Dependencies have to be modeled and stored somewhere. Because the dependencies are defined per solution, the solution is the place to store the definition. We define a file called ComponentDefinition.targets in the Solutions Items of the solution:
Figure 3: File ComponentDefinition.targets as Solution Item
This file contains 2 essential parts.
- An ordered list of solutions to be build, in case dependencies are loaded as sources which need to be built before the component solution.
- The dependencies that have to be loaded whether it is sources from the repository or binaries from the build drop.
The different dependency types require different identification mechanisms.
- Binary dependencies to 3rd party libraries (3rdParty)
- Will be identified using a distinct network path
- All files from the network share must be copied to the local output directory
- Binary dependencies to in-house libraries or components (BuildResult)
- Will be identified using team project name and build number
- All files from the build drop must be copied to the local output directory
- Source depdencies to other solutions (Source)
- Will be identified via path and version in source control
- Source control folder has to be mapped to the local workspace and a get operation has to be called
This causes the structure and contents of the ComponentDefinition.targets file:
Listing 1: ComponentDefinition.targets contents
The file is in MSBuild format. The list of solutions to build can be specified as ItemGroup named ItemsToBuild (Lines 6-9). Solution Project2.sln is the actual solution inside the component of interest. Before building that, dependency Project1.sln of component Platform.Common has to be built (Line 7).
To be able to build, component Platform.Common has to be downloaded to the local client. Additionally dlls of component Platform.Compression are required in the output directory of the solution – which should be a central folder common for all projects. This is specified in ItemGroup Dependencies (Lines 11-25).
Hence, Platform.Common will be mapped and downloaded as sources (Line 13) into the local workspace in a specific version – e.g. a build label (Line 16).
Platform.Compression is provided as build result und will be queried using Team Project name, Build Definition name and Build number, and will be copied from the drop location to the local output path.
The described mapping and copy operations are executed by a custom MSBuild task named GetComponents (Line 4 in Listing 2) which is called in a target with the same name. Line 4 in Listing 1 imports the logic – so it has to be deployed to the local developer machine.
Listing 2: Snippet of the central logic
The list ItemsToBuild is used in Listing 2 in line 12 to call MSBuild on all list items directly. This will build all solutions in their order. The script is simplified here; normally you would add additional parameters such as OutDir or Platform to configure the build.
Figure 3 has shown file ComponentDefinition.targets inside the solution. However, where is it stored in the team project or branch context? Basically it is stored next to the solution or underneath the component folder itself (here: Project2) in case there are many solutions which form the component. By using that approach, variants and stages of dependency lists can be managed stream-lined with branches. Figure 4 shows the sample inside Source Control Explorer.
Figure 4: Location of ComponentDefinition.targets in Source Control
The workflow of a user is then like the following:
- Map component into a workspace in TFS mappen (subfolder of the component inside a branch – here: Platform.Base underneath branch Team Stuttgart)
- Open solution – here: Project2.sln
- Get Dependencies via ComponentDefinition.targets (runMSBuild)
Running ComponentDefinition.targets using MSBuild can be done via the command line. However, it’s more convenient to be able to call it from within the Solution Explorer context menu. You can do this in the following way:
Create new External Tools:
- Menu Tools > External Tools
- Add External Tool “Get Dependencies”:
- Add External Tool “Build All” an:
- To add those tools to the context menu, open menu Tools > Customize… and go to the Commands tab.
There select redio button “Context Menus” and select “Project and Solution Context Menu | Item” from the list.
Add the commands “External Command X” and “External Command Y”, where X and Y are indices of the created tools from the list above(starting with 1).
The Solution Explorer context menu should now look like that:
Figure 8: Context menu in Solution Explorer
Dependencies can be retrieved and built easily now. The ouput of the tools (MSBuild) is shown in their own output windows inside Visual Studio.
Figure 9: Result in output window
All dependencies now reside in the local workspace. No more looking around for missing references or how to get the build successfully running.
Central Build Processes
During the development lifecycle, a local workspace as we’ve seen above is not everything that is needed. During central builds, all dependencies have to be available as well. With Team Foundation Build all steps described above can be run automatically.
The workspace template of the build definition contains the mapping to the component of interest:
Figure 10: Map components in build workspace
The ComponentDefinition.targets file will be used as central Item to Build of the process (refer to figure 11). Using AIT Build Suite getting the dependencies can be incorporated be running the script with taret GetComponents after the download of the initial source code.
Figure 11: Definition of build process parameters with AIT Build Suite
This approach makes the team responsible for keeping their component definitions up-to-date. However, the decentralized definition files have to be visualized and controlled centrally. This can be done using Architecture Explorer and DGML – one option to visualize and analyse graphs such as the dependency graph.
Before getting into the details of the customization, let’s have a look into the native functions of the tools. Architecture Explorer comes with a Solution View which let’s you dig into the inner solution references and details.
Figure 12: Solution View in Architecture Explorer
The queried information can be visualized in a DGML file by dragging and dropping the result nodes onto the file (refer to figure 13.
Figure 13: Visualization of dependencies of a solution
However, this view does not provide any information about the branch or version.
Information about branches can be visualized inside the branch hierarchy of Team Explorer. But this only shows branch and merge relationships between branches and nothing about dependencies.
Figure 14: Branch tree in Team Exporer
This gap in functionality needs to be closed. As mentioned above, Architecture Explorer seems to be the tool of choice. The extension point is Microsoft’s Progression API. Architecture Explorer provides a flexible provider model, which allows to place a dll containing an implementation class of interface Microsoft.VisualStudio.Progression.IProvider into the folder “C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Providers”. When launching Architecture Explorer, the dll is loaded.
Using custom DGML query types, you can build a powerful tool for traversing and analysing graphs (refer to figure 15). Visual Studio queries directly TFS source control, e.g. loads ComponentDefinition.targets files into memory to parse and analyse them further.
Figure 15: Custom Dependency View in Architecture Explorer
Every component can now be shown in DGML. Of course, all the dependency relationships are visualized as well.
Figure 16: Visualized dependencies with version information
You can not only use the visualization functions of Visual Studio via DGML, Architecture Explorer provides some analysis features such as finding circular references in graphs.
Figure 17: Analysis of circular references
Recorded TechEd 2010 Session: Extended Dependency Management using TFS 2010
Build Whitepaper: Company-wide build management with TFS 2010
Share components with TF Source Control (German Whitepaper): http://blogs.msdn.com/b/cbinder/…
TFS Branching Guidance: http://tfsbranchingguideiii.codeplex.com/
The sample described above only provides a small view onto a specfic options of handling dependencies across team projects and branches. Extensibility of Visual Studio, MSBuild and Team Foundation Server gives you many options. The provided solution is used in a similar manner at some of our customers.
If you need further details and code snippets, contact us. Feel free to send us an email.