The convoluted build configuration automation model (EnvDTE/EnvDTE80)

One of the most complex things of the automation model (EnvDTE/EnvDTE80) of Visual Studio is the set of classes/properties to manage the build configurations of solutions and projects, because of the complexity itself of the configuration model of Visual Studio with matrixes of configuration names/platform names per solution and per project (nothing to object here, though), but also because of some unfortunate naming, inconsistencies and lacks in the automation model. So, let’s try to understand it (this post will also help me to remember all this in the future):

From the purely visual point of view, Visual Studio offers something named “Configuration Manager” under the “Build” menu. Notice that the “Configuration Manager” is something that belongs to the solution, not to a project (more on this later), and therefore the context menu of a solution in the Solution Explorer offers the “Configuration Manager…” menu while the context menu of a project doesn’t. You can think the (solution) configuration manager as a 2-D matrix of items: one dimension is the configuration name (such as “Debug”, “Release”) and the other dimension is the platform name (such as “Any CPU”, “Itanium”, etc.). Both dimensions are expandable with new values. Now, each item (configuration name, platform name) in the matrix is in turn a collection, where each collection item holds a project name, project configuration name, project platform name and whether to build it or not. Notice that you have solution configuration names and project configuration names (which can be the same or not) and solution platforms and project platforms (which must be the same).

The Configuration Manager of Visual Studio offers a filtered view of this 2-D matrix of collections, where:
  • The two combo boxes at the top, Active Solution Configuration and Active Solution Platform, filter the collection in the 2-D matrix that you see below.
  • The collection is shown in turn in a table where each row is an item of the collection (one row per project in the solution).

The Configuration Manager violates IMHO a principle of user interface design, mixing items (configuration names or platform names) with actions (“New…”, “Edit…”, “Delete…”) in the same combo box. It would be more intuitive to put three separate buttons to the right of the combo box for the actions such as “New…”, “Rename…” (which renames the selected item in the combobox) or “Delete” (which deletes the selected item in the combobox). Notice that in the current implementation, to delete an item you have to “edit” first… weird. Other than that, the Configuration Manager is not so complex. Maybe an alternative design to view the whole matrix at the same time with a tree-listview with a 3-level hierarchy (configuration name \  platform name \ table of projects) would be more complex.

Now, the automation model (EnvDTE/EnvDTE80) to manage all this programmatically:
  • The first thing that you notice is that there is an EnvDTE.ConfigurationManager class, but there is no way to reach it from the EnvDTE.Solution.SolutionBuild object. Strange, isn’t it?
  • Instead, the SolutionBuild class offers a collection of EnvDTE.SolutionConfigurations. Notice that the collection is linear, not a 2-D matrix or array as I modeled it, so you have to make a mental switch in your brain when dealing with this. Say that you want all the solution configurations with the configuration name “Debug” and any platform: you have to iterate the whole collection checking the EnvDTE.SolutionConfiguration.Name property… Say that you want all the solution configurations with the platform name “Itanium” and any configuration name: you have to iterate the whole collection checking the EnvDTE80.SolutionConfiguration2.PlatformName property (next paragraph). Say that you want all the solution configuration names or solution platform names: you have to iterate the whole collection collecting non-duplicated property values!
  • Each EnvDTE.SolutionConfiguration has a Name property but in the EnvDTE automation model of VS.NET 2002/2003 lacks a PlatformName property. Microsoft added it in the EnvDTE80 automation model of VS 2005, where the EnvDTE80.SolutionConfiguration2 class has the PlatformName property. The weird thing about VS.NET 2002/2003 is that it offered platforms for projects (but not for solutions!), although “.NET” was the only possible value for a project platform. The automation team only followed suit providing the EnvDTE.Configuration.PlatformName (which refers to project configuration as we’ll see in a moment) in the automation model of VS.NET 2002.
  • Did I mention EnvDTE.Configuration in the last paragraph? We have solution configurations and project configurations. Which one does EnvDTE.Configuration refer to? Since we have an EnvDTE.SolutionConfiguration, EnvDTE.Configuration can only refer to a project configuration. It would have been much clearer to name it EnvDTE.ProjectConfiguration. Naming it EnvDTE.Configuration forces you to another mental switch in your brain. It happens that it can refers also to a file (ProjectItem) configuration given its Type property with values vsConfigurationTypeProject, vsConfigurationTypeProjectItem, but I don’t care, enter the object-oriented programming world, use an EnvDTE.ConfigurationBase type and inherit two EnvDTE.ProjectConfiguration, EnvDTE.ProjectItemConfiguration classes.
  • Back to the EnvDTE.SolutionConfiguration, which is each item in the 2-D matrix of solution configurations/solution platforms, which in turn is a collection, do you remember?. The actual collection is provided through the SolutionConfiguration.SolutionContexts property, where each EnvDTE.SolutionContext is a row in the visual Configuration Manager, with the project name, project configuration name, project platform name and whether to build/deploy or not. Notice that the visual Configuration Manager says “Project contexts (check the project configurations to build or deploy” while the automation model says “EnvDTE.SolutionContexts”. Another mental switch for your brain…
  • Say that you want to add a new solution configuration name (another item in one of the dimensions of the matrix). The automation model offers the EnvDTE.SolutionConfigurations.Add(newName, existingName, propagate). OK. Say that you want to add a new solution platform name (another item in the other dimension of the matrix). As we said, the EnvDTE automation model has no notion of solution platform names, only project platform names. No problem, go to the EnvDTE80 automation model of VS 2005 which must have an EnvDTE80.SolutionConfigurations2.AddPlatform(…). Oops! There is no such EnvDTE80.SolutionConfigurations2 class, not to mention the AddPlatform method… It seems that the automation model of VS 2005 doesn’t provide a way to add new solution platforms programmatically, at least directly. You may have noticed that each time that you create a new solution configuration name or project configuration name through the visual Configuration Manager the dialog that is shown offers a checkbox to propagate the creation of the configuration name to the project(s) / solution respectively, and the same happen for the platform creation. So, you can create a project platform programmatically (shown later) and propagate the creation of a matching solution platform.
  • Back to the intriguing EnvDTE.ConfigurationManager. If it is not used for solution build configurations (EnvDTE.SolutionConfigurations is used instead), what is for? It happens that you get an EnvDTE.ConfigurationManager from EnvDTE.Project.ConfigurationManager. So, it something that belongs to a project, not to a solution. Just the contrary of the visual interface. It should have been named EnvDTE.ProjectConfigurationManager. Another mental switch for your brain…
  • The EnvDTE.ConfigurationManager manages project build configurations (with project configuration names/project platform names) in a 2-D matrix approach. Instead of using only a linear collection of EnvDTE.Configurations (like the EnvDTE.SolutionBuild.SolutionConfigurations), you can use the Item(configuration, platform) property to get an EnvDTE.Configuration in the matrix. You can get a collection of all the EnvDTE.Configurations for a given configuration (with any platform) with the ConfigurationRow(configuration) property. You can get a collection of all the EnvDTE.Configurations for a given platform (with any configuration) with the Platform(platform) property (not “PlatformColumn” as you would expect, but “Platform”…) and whose help summary is “Returns a collection of ConfigurationAssignment object for the specified platform.”, sorry, ConfigurationAssignment???… You can get all the values of the configurations dimension using the ConfigurationRowNames, which returns an Object instead of an array or collection of strings as the “Names” part of the property name should imply… You can get all the possible values for the platforms dimension using the PlatformNames property, which returns also an Object and not an array of string directly, and that should have been named AvailablePlatformNames, to distinguish it from the SupportedPlatforms property that returns an array with the actually used platforms (correction Aug,30: SupportedPlatforms are the platforms supported by the .NET Framework while PlatformNames are the ones actually created for the project, what did I say about confusing names?), and that  should have been named SupportedPlatformNames but who cares at this point since your brain should be used now to deal with a 2-D matrix of “configuration rows” and “platforms” and not “rows” and “cols” or “configurations” and “platforms”… OK, you don’t like the 2-D approach to manage project build configurations, you miss the linear collection approach of SolutionBuild.SolutionConfigurations (in fact I would prefer the opposite, to manage in a 2D-matrix approach the solution configurations) but no problem, you can! You have the visible ConfigurationManager.Count property that according to the Object Browser help returns “value indicating the count of objects of the collection”, excuse me, since when the ConfigurationManager is a collection? and if you would expect a ConfigurationManager.Configurations property, bad luck, you have the less intuitive and hidden ConfigurationManager.GetEnumerator method, oh my!… The EnvDTE.ConfigurationManager allows you to add values to the dimensions (project configurations or project platforms) using the AddConfigurationRow and AddPlatform methods or delete them using the DeleteConfigurationRow and DeletePlatform methods. The ConfigurationManager class offers also an ActiveConfiguration property which returns an EnvDTE.Configuration. Which is this? The SolutionBuild class has an ActiveConfiguration property which refers to the item filtered by the two comboboxes at the top of the visual configuration manager of Visual Studio. The Project. ConfigurationManager.ActiveConfiguration can only refer to the project configuration for that active solution configuration whose configuration name and platform name matches the values in the row belonging to the project in the filtered view of the visual configuration manager (which in the automation model is a EnvDTE.SolutionContext, not an EnvDTE.Configuration).
  • There are other poorly named properties such as SolutionBuild.LastBuildInfo, which returns an integer with the projects built successfully, or it was the failed ones? You have always to check the help file…
  • One last thing: I mentioned that the EnvDTE.SolutionContext class contains properties such as ShouldBuild, ShouldDeploy that matches the checkboxes in the visual Configuration Manager of Visual Studio. The EnvDTE.Configuration (project configuration) has IsBuildable, IsDeployable properties that I am not sure about their purposes, but don’t confuse them.

I will leave as an exercise for the reader to create a VS 2008 solution with:
  • Four solution configuration names (say “Debug1”, “Debug2”, “Release1”, “Release2”)
  • Two solution platforms (say “Any CPU”, “Itanium”)
  • Two projects (say “Project1” and “Project2”)
  • “Project1” with four project configurations (“Debug1”, “Debug2”, “Release1”, “Release2”) and two project platforms (“Any CPU”, “Itanium”).
  • “Project2” with two project configurations (say “Debug” mapped to solution configurations “Debug1” and “Debug2”, and say “Release” mapped to solution configurations “Release 1” and “Release2”) and two project platforms (“Any CPU”, “Itanium”).

And then create some macros to get all that information programmatically. How is your brain by now? ;-)