Managing Build Configurations


Editing and manipulating a project is an important part of the development process, but most of your time is spent building and compiling, not moving around files within a project. Visual Studio provides an object model for building a solution and controlling how the projects contained within that solution should be compiled. The root object for controlling how a solution should be built is named SolutionBuild; you access it by calling the Solution. SolutionBuild property, and you control how each project within the solution should be built by using the ConfigurationManager object, which is accessed through the Project. ConfigurationManager property.

Manipulating Solution Settings

Visual Studio uses solution configurations to manage how a solution is built. A solution configuration is a grouping of project configurations that describe how the projects within the solution should be built. A project configuration, in the simplest terms, tells the various compilers how to create the code for a project. Each project can contain multiple project configurations that you can switch between within the solution configuration to control how the compilers build the code. The most common solution and project configurations are debug and release, which cause a project to be built with debugging information and with code optimizations, respectively. When a Windows Forms project is first created, Visual Studio creates the debug solution configuration containing the Windows Forms debug project configuration and the release solution configuration containing the release Windows Forms project configuration. You can create new solution configurations that contain any of the available project configurations or new project configurations that can be loaded into any solution configuration.

SolutionConfiguration and SolutionContext Objects

Solution configurations are represented in the object model through the SolutionConfigurations collection, which contains SolutionConfiguration objects. Because the SolutionConfigurations object is a collection, you can use the standard techniques for enumerating this collection and use the Item method to find a specific SolutionConfiguration object by name. To create new solution configuration, you use the SolutionConfigurations.Add method, which makes a copy of an existing solution configuration and then renames it to the specified name. The signature of this method is

 public EnvDTE.SolutionConfiguration Add(string NewName,     string ExistingName, bool Propagate) 

Here are the arguments that are passed to this method:

  • NewNameThis is the name of the new solution configuration. It can't be the same as any existing solution configuration name, and it must follow the file system's file-naming rules. (It can't contain characters such as \, /, :, *, ?, ", <, or >.)

  • ExistingNameThis is either the name of an existing solution configuration that is copied to create the new solution configuration or the string "<Default>." If the name "<Default>" is used, the currently active solution configuration is used as the source of what is copied.

  • PropagateIf this parameter is true, when the new solution configuration is created, a copy of each project configuration referenced by the solution configuration is made and assigned the same name as the new solution configuration, and each of these copies of project configurations is loaded into the new solution configuration. If this parameter is false, the new solution configuration is created and the same project configurations that were assigned to the solution configuration source are assigned to the new solution configuration.

The SolutionConfiguration object has one method, Activate, and one property of note, SolutionContexts. When a build is performed, whether through the user interface or through the object model by using the SolutionBuild.Build method, the currently active SolutionConfiguration is the configuration that is built. Therefore, activating a particular solution configuration by using the Activate method causes any build actions to build the active solution configuration. The other item of importance is the SolutionContexts property. As discussed earlier, a SolutionConfiguration is a container of the projects within a solution and the project configuration associated with that solution configuration. The SolutionConfiguration.SolutionContexts property returns a SolutionContexts collection, containing those projects and the configuration of each project to build.

To set the project configuration that is built when the solution is built, you can change the SolutionContext object's ConfigurationName to any project configuration name that the project supports. The following macro changes the debug solution configuration to build the release version of a project that is loaded into the solution:

 Sub ChangeProjectConfiguration()     Dim solutionBuild As EnvDTE.SolutionBuild     Dim solutionCfgs As EnvDTE.SolutionConfigurations     Dim solutionCfg As EnvDTE.SolutionConfiguration     Dim solutionContext As EnvDTE.SolutionContext     'Find the debug solution configuration:     solutionBuild = DTE.Solution.SolutionBuild     solutionCfgs = solutionBuild.SolutionConfigurations     solutionCfg = solutionCfgs.Item("Debug")     'Retrieve the solution context for the first project:     solutionContext = solutionCfg.SolutionContexts.Item(1)     'Change the debug solution context to build the     ' Release project configuration:     solutionContext.ConfigurationName = "Release"     'Reset the build flag for this context:     solutionContext.ShouldBuild = True End Sub 

You can modify a SolutionContext to set the project configuration that should be built for a particular solution configuration and you can also set values such as that specifying whether the project configuration should be built. This is done in the next-to-last line of the preceding macro, where the ShouldBuild property is set to true. In this macro, this property must be set because, as is expected, when the debug solution configuration is first created, it doesn't contain the release project configuration. It, therefore, isn't set to build for that solution configuration, so when the debug solution configuration is set to build the release project configuration, that "do not build" state is carried along with it.

StartupProjects

When you start a solution running (usually by pressing the F5 key), the project builder first verifies that all the projects that need to be built are up-to-date, and then it starts walking the list of projects that are set as startup projects, running each project in turn. You can set the list of startup projects through the user interface by right-clicking on the solution node in Solution Explorer and then choosing Set StartUp Projects from the shortcut menu. You'll see the Solution Property Pages dialog box (shown in Figure 8-3), in which you can set the startup projects for a solution containing four Windows Forms applications.

image from book
Figure 8-3: Setting the projects that will start when you run a solution

You can also set startup projects through the object model by using the SolutionBuild. StartupProjects property. This property is set to a value of type System.Object, which is packed with the projects to start when you run a solution. The value passed to the StartupProjects property can take two forms: a single string that is the unique name of a project (which will set one single project to run) or an array of System.Object (which will be filled with one or more project unique names and will cause multiple projects to be run).

For example, suppose an open solution contains two projects, each of them to be designated as a startup project. You can use code such as the following to set these projects as startup projects:

 Sub SetStartupProjects()     Dim startupProjects(1) As Object     startupProjects(0) = DTE.Solution.Projects.Item(1).UniqueName     startupProjects(1) = DTE.Solution.Projects.Item(2).UniqueName     DTE.Solution.SolutionBuild.StartupProjects = startupProjects End Sub 

If only one project should be set as a startup project, the code looks like this:

 Sub SetStartupProject()     Dim startupProject As String     startupProject = DTE.Solution.Projects.Item(1).UniqueName     DTE.Solution.SolutionBuild.StartupProjects = startupProject End Sub 

When you set the startup projects, you must be careful to supply only buildable projects. If one of the projects supplied to SolutionBuild.StartupProjects is, for example, the unique name for the Miscellaneous Files project or the Solution Items project, an error is generated.

Project Dependencies

When you work with a solution that contains multiple projects, the components built by one project might rely on the output of another project. An example of this is a control project called UserControl, which is placed on the form of a Windows Forms application called WinForm. Because changes to the UserControl project might affect how that control is used by the Windows Forms project, the UserControl project must be compiled before the WinForm project is compiled. To enforce this relationship between the two projects, you can create a project dependency. The dependencies between two or more projects can be depicted using a dependency graph; the dependency graph for the projects WinForm and UserControl is shown in Figure 8-4. The arrow is pointing to the project that another project is dependent on.

image from book
Figure 8-4: A dependency graph showing a WinForm project dependent on a UserControl project

Suppose we add a new project to the solution—a class library called ClassLib that implements functionality used by both the WinForm and the UserControl projects. A dependency graph for this solution is shown in Figure 8-5.

image from book
Figure 8-5: The dependency graph for three projects

You can see in this dependency graph that the WinForm project can't be built until the UserControl and ClassLib projects have been built. The UserControl project relies on only the ClassLib project being built first. When a build of this solution is started, if the build system chooses the UserControl project to start building first, the ClassLib project builds. If the build system chooses the ClassLib project first, because it does not have any dependencies, it can build immediately without needing to build any other projects. When the UserControl project is built, the ClassLib project isn't built again because it is up-to-date. Because it relies upon the output of the other two projects, the last project to be built is the WinForm project because it relies on the output of the other two projects.

A problem can occur with a dependency graph if you create a cyclic dependency, in which one or more projects are mutually dependent. Suppose the WinForm project relies on the UserControl project, the UserControl project relies on the ClassLib project, and the ClassLib project relies on the WinForm project. The cycle shown in Figure 8-6 is generated.

image from book
Figure 8-6: A dependency graph of three projects with a cycle

If the WinForm project is built, the build of the UserControl project is triggered because of the dependency. Building the UserControl project causes the building of the ClassLib project, which is dependent on the WinForm project. If the Visual Studio build system were unable to detect this cycle, the loop would continue forever in an attempt to find the first project to build. But Visual Studio is smart enough to detect dependency cycles, and it disallows them.

You can create dependencies between projects through the user interface by choosing Project | Project Dependencies, which will display the Project Dependencies dialog box (shown in Figure 8-7). The dialog box shows all the projects that can be set as a dependency for the UserControl project. The WinForm check box is shaded because a dependency is set from the WinForm project to the UserControl project, and Visual Studio won't allow a cycle between the WinForm project and the UserControl project to be created.

You can also set build dependencies through the object model. The SolutionBuild. BuildDependencies property returns a BuildDependencies object, which is a collection of BuildDependency objects. You can index this collection by using the Item method—you can pass a numeric index, an EnvDTE.Project object, or the unique name of a project. Each project in the solution has its own EnvDTE.BuildDependency object, whose RequiredProjects property you can use to add, remove, or retrieve dependencies for a project. The following macro displays in the Output window the available projects in the open solution, as well as all the projects it depends on.

image from book
Figure 8-7: Setting project dependencies

 Sub Depends()     Dim projectDep As EnvDTE.BuildDependency     Dim project As EnvDTE.Project     Dim owp As New InsideVSNET.Utilities.OutputWindowPaneEx(DTE,         "Build dependencies")     For Each projectDep In DTE.Solution.SolutionBuild.BuildDependencies         Dim reqProjects As Object()         owp.Write("The project ")         owp.Write(projectDep.Project.Name)         owp.WriteLine(" relies on:")         reqProjects = projectDep.RequiredProjects         If (reqProjects.Length = 0) Then             owp.WriteLine(vbTab + "<None>")         Else             For Each project In reqProjects                 owp.WriteLine(vbTab + project.Name)             Next         End If         owp.WriteLine()     Next End Sub 

Using the BuildDependency object, you can create a macro or an add-in that sets up the dependencies between two or more projects. Suppose, using our current example, that a solution with the projects WinForm, UserControl, and ClassLib is loaded and no dependencies have been set. The BuildDependency object supports three methods for modifying the projects that a project is dependent on: AddProject, RemoveProject, and RemoveAllProjects. AddProject and RemoveProject accept the unique name of a project that should be added or removed as a dependency for a specific project. RemoveAllProjects takes no arguments and removes all project dependencies. The following macro, SetDependencies, builds the correct dependencies for the three-project solution to conform to the dependency graph shown in Figure 8-5:

 Sub SetDependencies()     Dim buildDependencies As EnvDTE.BuildDependencies      Dim buildDependency As EnvDTE.BuildDependency     Dim project As EnvDTE.Project     Dim winFormUniqueName As String     Dim userControlUniqueName As String     Dim classLibUniqueName As String     'Gather up the unique name of each project     For Each project In DTE.Solution.Projects         If (project.Name = "WinForm") Then             winFormUniqueName = project.UniqueName         ElseIf (project.Name = "UserControl") Then              userControlUniqueName = project.UniqueName         ElseIf (project.Name = "ClassLib") Then             classLibUniqueName = project.UniqueName          End If      Next     buildDependencies = DTE.Solution.SolutionBuild.BuildDependencies      For Each buildDependency In buildDependencies         If (buildDependency.Project.Name = "WinForm") Then              buildDependency.RemoveAllProjects()             'Add all projects except the WinForm             ' project as a dependency:             buildDependency.AddProject(userControlUniqueName)              buildDependency.AddProject(classLibUniqueName)         ElseIf (buildDependency.Project.Name = "UserControl") Then              buildDependency.RemoveAllProjects()              'Add a dependency to the ClassLib project:              buildDependency.AddProject(classLibUniqueName)          End If      Next End Sub 

Manipulating Project Settings

Solution configurations are used to group together project configurations. Each project contains a number of configurations that control how the compiler should create the program code for that project. Because a project can have multiple project configurations associated with it, you can generate different versions of a program.

ConfigurationManager Object

You manage project configurations through the ConfigurationManager object, which has a collection of Configuration objects and lets you create new configurations. Configurations for a project are arranged in a grid pattern, with the configuration type, such as debug or release, along one axis of the grid and the platform on which the configuration will be built for on the other axis. The platforms that Visual Studio currently supports are Win32® for 32-bit Microsoft Windows® running on the x86 processor and Any CPU (the configuration name Any CPU is given to programs that will run on the .NET Framework), if the project is being compiled for the Microsoft .NET platform—including .NET applications for the desktop or smart device. Because projects can build only one platform type at a time, the second axis will always have one dimension.

Note 

Previous versions of Visual Studio used the name .NET for the platform name when compiling to MSIL bytecode. If you are upgrading an add-in or macro to Visual Studio, you will need to change your code to use the new name, Any CPU.

You can find a particular project configuration in several ways. The first way is to use the familiar Item method that's available on all collection objects. However, unlike most other Item methods on collection objects, the ConfigurationManager.Item method requires two parameters. The first parameter can be a numerical index and spans the entire grid of platforms and configurations. You can also use Item to directly locate a Configuration by passing the configuration name as the first parameter and the platform name as the second parameter. Suppose a Visual C++ project is open in Solution Explorer. To find the Configuration object for the Win32 debug build, you can use code such as the following:

 Sub RetrieveDebugWin32Configuration()     Dim config As Configuration     Dim project As EnvDTE.Project     project = DTE.Solution.Projects.Item(1)     config = project.ConfigurationManager.Item("Debug", "Win32") End Sub 

Another way to retrieve specific configurations is to use the ConfigurationManager. ConfigurationRow and ConfigurationManager.Platform methods, which take the build type and the platform name, respectively. These methods return a collection of Configuration objects that you can iterate through to find a specific item. The ConfigurationRow method returns a list of all configurations with the passed name; the Platform method returns a list of all configurations belonging to a specific platform. These methods are most useful if you want to modify the settings of configurations that are closely related to one another, such as walking all the Win32 configurations of a Visual C++ project and enabling managed extensions, thus allowing your program to use the .NET Framework in C++ code. The following code sample does just that. After finding the Win32 configurations available to a project, it retrieves the Properties object for that configuration and sets the ManagedExtension property to true, allowing the compiler to generate code that can work with the .NET Framework.

 Sub SetManagedExtensionsProperty()     Dim configManager As ConfigurationManager     Dim configs As Configurations     Dim config As Configuration     Dim project As EnvDTE.Project     project = DTE.Solution.Projects.Item(1)     configManager = project.ConfigurationManager     configs = configManager.Platform("Win32")     For Each config In configs         Dim prop As EnvDTE.Property         prop = config.Properties.Item("ManagedExtensions")         prop.Value = True     Next End Sub 

You can create new configurations based on an existing configuration in the same way that you can create new solution configurations by copying an existing solution configuration. You create new project configurations by using the ConfigurationManager. AddConfigurationRow method. This method takes as its parameters the name of the new configuration and an existing configuration name, which is used as a template for creating the new configuration. AddConfigurationRow also accepts as an argument a Boolean value. This parameter, named Propagate, works in the same way as the Propagate parameter of the SolutionConfigurations.Add method, but in reverse. When the SolutionConfigurations.Add method is called with the Propagate parameter set to true, a copy of the solution configuration and all the project configurations it contains is made. If the AddConfigurationRow method is called with its Propagate parameter set to true, the currently active solution configuration is copied, its name is set to the name passed as the new project configuration, and the new solution configuration is modified to contain the newly created project configuration.

Note 

The ConfigurationManager object contains the method AddPlatform, which works much the same as the AddConfigurationRow method but adds a platform row to the build type configuration grid. If you call this method for any of the current versions of the Microsoft-language products, an exception will be generated because new platforms can't be added for these project types. This doesn't mean that this method won't work for third-party programming language projects or future versions of Microsoft programming languages.

Most project types support only one platform type, but some projects, such as setup projects, are not associated with any platform—what is built is platform-agnostic. A setup project doesn't care whether its contents are intended for Win32 or .NET platforms; its role is to contain files to be installed onto the user's computer, so a platform is not a consideration when you build a setup project. Because the build type configuration grid can't be one-dimensional, a pseudoplatform is generated for these project types, and its name is set to <N/A>.

Project Configuration Properties

Project configurations differ in the property values that are set. For example, one difference between the debug and release configurations is that the debug configuration doesn't optimize the code, which makes debugging easier to perform, and optimization is turned on for the release configuration to make the code run faster. Such properties are set through the object returned by calling the Configuration.Properties property. As you saw earlier in the SetManagedExtensionsProperty macro example, this property returns an EnvDTE.Properties object—the same object that is used throughout Visual Studio to set property values on various objects. The following macro retrieves the debug and release configurations for a project, reads the Boolean Optimize configuration property, negates it, and then stores it back into the configuration. This means that the Optimize property is inverted for all these configurations.

 Sub SwapOptimizationSettings()     Dim project As EnvDTE.Project     Dim configManager As EnvDTE.ConfigurationManager     Dim configs As EnvDTE.Configurations     Dim config As EnvDTE.Configuration     Dim props As EnvDTE.Properties     'Find the ConfigurationManager for the project:     project = DTE.Solution.Projects.Item(1)     configManager = project.ConfigurationManager     'Get the debug configuration manager     configs = configManager.ConfigurationRow("Debug")     'Walk each configuration in the debug configuration row     For Each config In configs         Dim optimize As Boolean         'Get the Optimize property for the configuration         props = config.Properties         optimize = props.Item("Optimize").Value         'Negate the value         props.Item("Optimize").Value = Not optimize     Next     'Repeat for the release configuration     configs = configManager.ConfigurationRow("Release")     For Each config In configs         Dim optimize As Boolean         'Get the Optimize property for the configuration         props = config.Properties         optimize = props.Item("Optimize").Value         'Negate the value         props.Item("Optimize").Value = Not optimize     Next End Sub 

Build Events

As each stage of a build is performed, Visual Studio fires an event that can be captured by a macro or add-in, allowing custom code to be run. Four events are defined. Here are their signatures:

 void OnBuildBegin(EnvDTE.vsBuildScope Scope, EnvDTE.vsBuildAction Action); void OnBuildProjConfigBegin(string Project,     string ProjectConfig, string Platform, string SolutionConfig); void OnBuildProjConfigDone(string Project, string ProjectConfig,     string Platform, string SolutionConfig, bool Success); void OnBuildDone(EnvDTE.vsBuildScope Scope, EnvDTE.vsBuildAction Action); 

These event handlers have the following meanings:

  • OnBuildBeginThis event is fired just before a build is started. Two arguments are passed to the handler of this event. The first argument is an enumeration of type EnvDTE.vsBuildScope, which can be either vsBuildScopeBatch (if you chose to start a batch build of one or more projects), vsBuildScopeProject (if you selected a single project to build by right-clicking a project and choosing Build), or vsBuildScopeSolution (if you chose the active solution configuration to build). The second argument is of type EnvDTE.vsBuildAction and can be either vsBuildActionBuild (if the project or solution configuration is to be compiled), vsBuildActionClean (if the project or solution configuration's build output is to be deleted from disk), vsBuildActionDeploy (if the project or solution configuration is to be deployed to its target), or vsBuildActionRebuildAll (if the project or solution configuration is to be rebuilt, even if the project's dependencies do not warrant a rebuild).

  • OnBuildProjConfigBeginThis event is fired when a project's configuration starts to be built. It is passed four arguments, each of type string. The first argument is the unique name of the project being built, the second is the name of the configuration being built, the third is the name of the platform being built, and last is the name of the solution configuration being built.

  • OnBuildProjConfigDoneThis event handler is fired after a project configuration has been built. It is passed the same arguments as the OnBuildProjConfigBegin event, with the addition of a Boolean value that signals whether the configuration was built successfully (true) or failed to build (false).

  • OnBuildDoneThis event is fired after all build steps have been completed, whether successfully or unsuccessfully.

Among the samples that accompany this book is one called BuildEvents, which demonstrates connecting to each of the build events. As each event handler is called, the information passed to that event handler is displayed within the output window, which contains information about the arguments that were passed to each handler. For example, if we were to create a solution containing two projects, ClassLibrary1 and ClassLibrary2, load the sample add-in, and perform a build on the solution by choosing Build | Build Solution, the following information would be displayed:

 OnBuildBegin     Scope: vsBuildScopeSolution     Action: vsBuildActionBuild OnBuildProjConfigBegin     Project: ClassLibrary1.csproj     Platform: Any CPU     Solution Configuration: Debug OnBuildProjConfigDone     Project: ClassLibrary1.csproj     Platform: Any CPU     Solution Configuration: Debug     Success: True OnBuildProjConfigBegin     Project: ..\ClassLibrary2\ClassLibrary2.csproj     Platform: Any CPU     Solution Configuration: Debug OnBuildProjConfigDone     Project: ..\ClassLibrary2\ClassLibrary2.csproj     Platform: Any CPU     Solution Configuration: Debug     Success: True OnBuildDone     Scope: vsBuildScopeSolution     Action: vsBuildActionBuild 

This output outlines the steps performed to build this two-solution project. It starts with a call to the OnBuildBegin event handler and then builds each project configuration contained within the solution configuration, one after another, with the OnBuildDone event handler being fired to signal that the build process has been completed. With Visual Studio, the OnBuildProjConfigBegin and OnBuildProjConfigEnd events are fired one after another, with no other build events fired between them. However, a macro or add-in should not take advantage of this order of events if you plan to port this code to a future version of Visual Studio because future versions might take advantage of multiprocessor computers, building one project configuration on one processor and another project configuration on another processor. If a macro or an add-in were to rely on this order of events, the code might not work properly.




Working with Microsoft Visual Studio 2005
Working with Microsoft Visual Studio 2005
ISBN: 0735623155
EAN: 2147483647
Year: 2006
Pages: 100

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net