- | rssFeed | My book on MSBuild and Team Build | Archives and Categories Thursday, January 18, 2007

MSBuild: Find Many Project References

Ok, here is the challenge you have several project files that comprise a set of products. Now you want list every reference that each project file has. How do you do it? I came up with a pretty nifty way of doing it. If you look a project file for a managed project (I’ll be using C# projects for this example) you’ll see that you can see all the references listed in an Item named Reference. So there’s got to be a way to extract out that information for each project. I was thinking that it would be nice if each project had a target that would simply output that item. But that would involve changing each project to add the target to perform that. Which obviously is not an option. But what we can do is achieve the exact same thing, by using the Import element. Take a look at the simple targets file below:

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

 

   <Import Project="$(ProjectFile)"/>

 

   <Target Name="GetReferences" Outputs ="@(ProjReferences)">

      <Message Text="Getting references for project $(ProjectFile)"/>

      <CreateItem Include="@(Reference)">

         <Output ItemName="ProjReferences" TaskParameter="Include"/>

      </CreateItem>

   </Target>

  

</Project>

What this will do is import an MSBuild project using the Import statement, this file is driven off of a property file. After that it will merge a new target GetReferences with the project declared in ProjectFile. Now we simply need a way to specify that ProjectFile. To achieve this we will actually change the above statements to include the condition Condition="$(ProjectFile)!=''". And we add the following statements to find all project files, enumerate references and write a file containing all of them.

<!-- Project files to examine -->

<ItemGroup>

   <ProjectFiles Include="**\*.csproj"/>

</ItemGroup>

<!-- File to save project references to -->

<PropertyGroup>

   <RefFile>SolutionReferences.txt</RefFile>

</PropertyGroup>

  

<Target Name="SetupFindRef">

   <Delete Files="$(RefFile)"/>

   <WriteLinesToFile File="$(RefFile)" Lines="ProjectName%09Reference"/>

</Target>

 

<Target Name="FindReferences" Outputs="%(ProjectFiles.FullPath)" DependsOnTargets="SetupFindRef">

   <MSBuild Projects="$(MSBuildProjectFile)" Properties="ProjectFile=%(ProjectFiles.FullPath)" Targets="GetReferences">

      <Output ItemName="ProjReferences" TaskParameter="TargetOutputs"/>

   </MSBuild>

           

   <WriteLinesToFile File="$(RefFile)" Lines="%(ProjectFiles.FullPath)"/>

   <WriteLinesToFile File="$(RefFile)" Lines="@(ProjReferences->'%09%(Identity)')"/>

</Target>

 

If we execute the FindReferences target on this project file it will batch over each project file. It will then execute the same project file with the property ProjectFile defined, and execute the GetReferences target. So what is really happening is that we are effectively extending the existing MSBuild project file and injecting a new target to an existing  project file. Pretty nifty.

Here is the entire project file so you can see the end result, also you can download it below.

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

   <!-- Project files to examine -->

   <ItemGroup>

      <ProjectFiles Include="**\*.csproj"/>

   </ItemGroup>

   <!-- File to save project references to -->

   <PropertyGroup>

      <RefFile>SolutionReferences.txt</RefFile>

   </PropertyGroup>

  

   <Target Name="SetupFindRef">

      <Delete Files="$(RefFile)"/>

      <WriteLinesToFile File="$(RefFile)" Lines="ProjectName%09Reference"/>

   </Target>

 

   <Target Name="FindReferences" Outputs="%(ProjectFiles.FullPath)" DependsOnTargets="SetupFindRef">

      <MSBuild Projects="$(MSBuildProjectFile)" Properties="ProjectFile=%(ProjectFiles.FullPath)" Targets="GetReferences">

         <Output ItemName="ProjReferences" TaskParameter="TargetOutputs"/>

      </MSBuild>

           

      <WriteLinesToFile File="$(RefFile)" Lines="%(ProjectFiles.FullPath)"/>

      <WriteLinesToFile File="$(RefFile)" Lines="@(ProjReferences->'%09%(Identity)')"/>

   </Target>

 

  

   <Import Project="$(ProjectFile)" Condition="$(ProjectFile)!=''"/>

   <Target Name="GetReferences" Outputs ="@(ProjReferences)" Condition="$(ProjectFile)!=''">

      <Message Text="Getting references for project $(ProjectFile)"/>

      <CreateItem Include="@(Reference)">

         <Output ItemName="ProjReferences" TaskParameter="Include"/>

      </CreateItem>

   </Target>

</Project>

Here is what the result looks like (for the some of the Enterprise Library projects):

ProjectName           Reference

C:\TEMP\EnterpriseLibrary\src\Caching\Caching.csproj

   System

   System.configuration

   System.Configuration.Install

   System.Data

   System.Management

   System.Xml

C:\TEMP\EnterpriseLibrary\src\Caching\Configuration\Design\Caching.Configuration.Design.csproj

   System

   System.configuration

   System.Data

   System.Design

   System.Drawing

   System.Windows.Forms

   System.XML

C:\TEMP\EnterpriseLibrary\src\Caching\Cryptography\Caching.Cryptography.csproj

   System

   System.configuration

C:\TEMP\EnterpriseLibrary\src\Caching\Cryptography\Configuration\Design\Caching.Cryptography.Configuration.Design.csproj

   System

   System.configuration

   System.Data

   System.Drawing

   System.Xml

The above project file will actually generate a tab delimited file, so you can open it up in Excel and take a closer look.

 GetAllReferences.proj

Sayed Ibrahim Hashimi

msbuild | Visual Studio Thursday, January 18, 2007 7:05:31 AM (GMT Standard Time, UTC+00:00)  #     |