Build Customizations (Custom Build Rules in earlier editions) are commonly used for invoking external tools that translate files from one format to the other, often resulting in C++ sources that can be compiled at the C++ compilation step. In this post, I will explain how to programmatically add, remove, and query for Build Customizations in a VC++ project. I will use the API exposed by the Microsoft.VisualStudio.VCProjectEngine namespace to achieve this goal. If you are creating wizards or project templates that introduce new file types which require custom processing during build, then you will find the contents of this post useful.
In VS2010, Build Customizations (BCs) are represented by 3 files – (1) an .xml file representing the schema information of the Rule and its properties, (2) a .props file contains default values of some/all of these properties and (3) a .targets file that contains build logic and in addition, includes the .xml file. The .targets file is mandatory while the other two files are optional. VS2010 requires that all these three files have the same name and be present in the same location. See Li’s blog post for more details on BCs and their file format in VS2010.
For the purpose of this post, I will assume that you have a BC called MyBC which is represented by the three files MyBC.xml, MyBC.props and MyBC.targets in the “C: BuildCustomizations” directory as shown in the snapshot below.
Adding a BC to a VCProject is a two-step process:
1. First you need to get a VCToolFile object for the BC by requesting the VCProjectEngine (you can use the GetBuildCustomizationRepresentation() method listed below for this purpose). A VCToolFile object simply stores the BC name and location internally.
2. Add a BC represented by a VCToolFile object to the VCProject (you can use the AddBuildCustomization() method listed below for this purpose).
Below, I list the C# code for performing 4 operations – get representation for a BC from the VCProjectEngine and add/remove/query(two varieties) a BC from a VC++ project. Personally, I prefer testing code involving VC++ API by first creating a dummy Visual Studio Package (you need to install the VS2010 SDK to get the project template for this project type) and calling my test methods in the Initialize() method. That is what I do, you should use whatever method you are most comfortable with.
To use the code below, you need to add reference to Microsoft.VisualStudio.VCProjectEngine.dll (Version 10) and add a using Microsoft.VisualStudio.VCProjectEngine; statement in your code file. In the code, input parameter checking is not shown for brevity.
Getting a representation object (VCToolFile) for BC
You do this by calling VCProjectEngine.LoadToolFile(). This operation does not change state of the project system. You can call this operation any number of times, even with the same argument. In earlier editions of VS (2008 and prior), a BC was represented by a single .rules file and this method would actually parse that file. Hence the term “load”. However, in VS 2010, a BC is represented by up to three files and the actual parsing is done when you actually add the BC to a VCProject, not when you call the LoadToolFile method.
/// <summary> /// Gets a <see cref="VCToolFile"/> object that stores the location information of /// a Build Customization (BC). /// </summary> /// <param name="targetsFileFullPath"> The full path for the BC targets file. </param> /// <param name="engine"> The VCProjectEngine. </param> /// <returns> A <see cref="VCToolFile"/> object corresponding to the BC. </returns> /// <remarks> /// <para> The .xml and .props files of the BC, if present, must have the same name /// and be present in the same directory as the .targets file. </para> /// <para> The returned <see cref="VCToolFile"/> object is normally used to add this /// BC to one of the containing VC++ projects. </para> /// </remarks> private static VCToolFile GetBuildCustomizationRepresentation(string targetsFileFullPath, VCProjectEngine engine) { // The term "load" in the method name is a legacy artifact; none of the BC files are actually loaded. VCToolFile myBC = engine.LoadToolFile(targetsFileFullPath); return myBC; }
Adding/Removing/Querying for a BC from a VCProject
The following methods demonstrate how we can add, remove and query a BC from a VCProject. Note that both adding a BC that has already been added and removing a BC that has already been removed result in an Exception being thrown. I present two methods of query corresponding to two different types of query objects – one is the VCToolFile representation and another is the string object representing the targets file path.
Adding a BC to a VCProject using AddBuildCustomization() is equivalent to performing the following action in UI: right-click the project node > Build Customizations… > check the BC entry (find it first, if necessary) and hit Ok.
/// <summary> /// Adds a Build Customization (BC) to a VC++ project. /// </summary> /// <param name=”bc”> The BC. </param> /// <param name=”project”> The VC++ project. </param> /// <remarks> /// <para> The .xml and .props files of the BC, if present, must have the same name /// and be present in the same directory as the .targets file. </para> /// <para> You need to first get a representation of the BC by first /// calling <see cref=”GetBuildCustomizationRepresentation”/> and then use the /// returned object as the argument to this method. </para> /// </remarks> private static void AddBuildCustomization(VCToolFile bc, VCProject project) { // Add the BC only if it has not already been added. if (!BuildCustomizationExists(bc, project)) { project.AddToolFile(bc); } }
/// <summary> /// Removes a Build Customization (BC) from a VC++ project. /// </summary> /// <param name=”bc”> The BC. </param> /// <param name=”project”> The VC++ project. </param> private static void RemoveBuildCustomization(VCToolFile bc, VCProject project) { // Remove the BC only if it has been added before. if (BuildCustomizationExists(bc, project)) { project.RemoveToolFile(bc); } }
/// <summary> /// Checks if a Build Customization (BC) exists in a VC++ project. /// </summary> /// <param name=”bc”> The BC. </param> /// <param name=”project”> The VC++ project. </param> /// <returns> true, if the BC exists, else false. </returns> private static bool BuildCustomizationExists(VCToolFile bc, VCProject project) { VCToolFile myBC = GetAddedBuildCustomization(bc.Path, project);
return (myBC != null); }
/// <summary> /// Returns an added Build Customization (BC) from a VC++ project. /// </summary> /// <param name=”targetsFileFullPath”> The full path for the BC targets file. </param> /// <param name=”project”> The VC++ project. </param> /// <returns> A <see cref=”VCToolFile”/> object corresponding to the BC, if it /// exists. Else, null. </returns> private static VCToolFile GetAddedBuildCustomization(string targetsFileFullPath, VCProject project) { // Query for the BC by using its full path. VCToolFile myBC = project.ToolFiles.Item(targetsFileFullPath);
return myBC; }
Finally, I would like to list some driver code which I used to test the above methods.
Test Driver
The below driver method invokes the above methods. Notice that I access the VCProjectEngine and VCProject objects using DTE. Since I wrote my driver in a Visual Studio Package, the DTE object is easily available to me. You should, however, access the project engine and project objects in a way that is most meaningful in your context.
/// <summary> /// Invokes the above methods to test them. /// </summary> /// <param name="serviceProvider"> A Visual Studio service provider. </param> internal static void TestDriver(IServiceProvider serviceProvider) { // Get the DTE object corresponding to the VS instance this code is // running in. I had this code in a dummy Visual Studio Package, so // I could readily access the VS services such as SDTE. DTE vsEnvironment = serviceProvider.GetService(typeof(SDTE)) as DTE; // Get the first project in the solution and assume that it is a VCProject. // You should find the VCProject you are interested in by using a more // meaningful approach. VCProject project = vsEnvironment.Solution.Projects.Item(1).Object as VCProject; // Get a reference to the VCProjectEngine. VCProjectEngine engine = project.VCProjectEngine;
// The full path of the BC .targets file we use in this blog post.
string targetsFileFullPath = @"C:BuildCustomizationsMyBC.targets";
// Get a representation object for the BC by requesting the VCProjectEngine.
VCToolFile myBC = GetBuildCustomizationRepresentation(targetsFileFullPath, engine);
// Add the BC to the VCProject.
AddBuildCustomization(myBC, project);
// Both "exists" and "exists2" will be true.
bool exists = BuildCustomizationExists(myBC, project);
bool exists2 = (GetAddedBuildCustomization(targetsFileFullPath, project) != null);
// Remove the BC from the VCProject.
RemoveBuildCustomization(myBC, project);
// Both "exists" and "exists2" will be false.
exists = BuildCustomizationExists(myBC, project);
exists2 = (GetAddedBuildCustomization(targetsFileFullPath, project) != null);
}
Short Bio: Pavan Adharapurapu is a developer on the Visual Studio Project and Build team.
As part of VS 2010, he has worked on numerous features of the VC++ project system such
as property sheets, filters, property pages, platform and tool extensibility, etc. His long term
focus is on developing a “common project system” infrastructure which could be used to quickly
build richly featured project systems. Prior to joining the Visual Studio team in 2008, he was
working on Workflow technologies in Microsoft Dynamics Axapta. Pavan holds a Masters degree in Computer
Science (CS) from the University of California, Los Angeles (UCLA) and a Bachelors degree in CS from the
Indian Institute of Technology (IIT), Madras. He lives in Redmond, WA with his wife and loves to play soccer
and watch movies in his spare time.
0 comments