May 1, 2011, 9:39 p.m.
posted by fullstack
Customize Your Project Build Process
Find out how to automate tasks that occur prior to, during, or after a build.
While the Visual Studio build process is familiar for all .NET developers, not everyone realizes the build customization capabilities that can be accomplished with just a little work. You can easily put together tasks that occur before, after, and even during a build.
Pre- and Post-Build Event Commands
In nonweb projects, you can create pre- and post-build event commands. As their names suggest, these are commands that you can customize to run just prior to or after a build. You can even control whether the post-build event executes at all depending on the outcome of the build. You can find these settings on the project properties under the Build Events tab (see Figure).
Project build events
Just type in the command you want to occur for each event type. When you select the text box to enter the command, you can click on the button on the righthand side of the text box to expand the command window and gain access to the list of macros or variables available. These variables help provide some shortcuts to including pathnames, project output locations, and other information into your event commands. Note that the variables are sensitive to the type of build you are compiling, so if you switch to a release build, the variables referencing the output directory will be correctly pointed at the \bin\release directory (or wherever you have set up the output directory to be). Figure provides a few examples of the macros available.
The full directory path to the output directory, including the project output filename. Example: c:\CSharpWinFormApp\bin\debug\CSharpWinFormApp.exe.
The full directory path to the output directory. Example: c:\CSharpWinFormApp\bin\debug\.
The full directory path to where the project file exists. Example: c:\CSharpWinFormApp\.
The build event command lines are simply command-line operations that will be executed at the specified time. In fact, the command line you enter in the properties tab actually gets copied out into a batch file (stored in the output directory for the project). These batch files will be named PreBuildEvent.bat or PostBuildEvent.bat and are handy to help troubleshoot your build events if necessary.
Note that a failing build event will be shown in the build output and listed in the Build Error task list along with any other build errors. A failing pre-build event will cause the build to stop, while a failing post-build event will still mark the build as a failure even if the build of the code succeeded without errors. You can choose to have the post-build event occur every build, only if the build succeeded, or when the project output (the resulting .dll or .exe) actually changes.
A great example of using a custom post-build event is when you have multiple configuration files. When you add an app.config file to your project, it will automatically be copied to the output directory of the project and renamed to match up with the project output file (e.g., CSharpWinFormApp.exe.config), but the IDE will not do the same with other custom configuration files. What if your project had multiple .config files to break up the settings? For example, you have app.config to store application configuration but then you also have an EndUser.config file to store user-specific settings that you didn't want to mix with the application settings. By default, this configuration file would not be copied to your output directory, and the application would receive an exception if it went looking for the file during execution. To get around this, a custom post-build event can copy the EndUser.config file to the output directory every time a build succeeds.
In the Build Events tab, choose to run a Post-Build event when a successful build occurs and add a Post-Build event command line of:
copy "$(ProjectDir)EndUser.config" "$(TargetDir)"
Note the use of quotes around the variables and commands—the quotes are there because there may be spaces in the file and pathnames. Also, the variables that generate pathnames will append an ending slash automatically. After a successful build of this project the current version of the EndUser.config file in the project directory will be copied to the output directory.
Copying files after a build is just one example of how these build events can be useful. More complex operations can be performed as well. For example, a Windows script or executable could be run when a build completes to kick off a set of test scripts against the newly built output. The build event could just as easily automatically create and register an output type library for use with COM or even register a newly built component with the GAC.
Handling Build Events
Some of the limitations of the custom pre- and post-build command events are that they are available for only nonweb projects and are only project specific. They don't give you the flexibility to deal with building a multiproject solution or provide a way of handling more complex build outcomes other than a successful build, a failed build, or a changed project output. For more advanced handling of build events, we turn to the Visual Studio Macro Environment.
To hook into the IDE build events, all we need to do is create some simple macros. Open up your Macro IDE (from Tools Macros Macro IDE) [Hack #51] . If you haven't used the Macro IDE before, the first thing you'll notice is that it resembles the regular VS.NET IDE, except the Macro IDE is used solely for the creation, editing, and compiling of macros that can be used within your Visual Studio IDE.
Open up the MyMacros project and see if it already contains an EnvironmentEvents module. If it doesn't, go ahead and copy the one from the Samples directory into your MyMacros project. The code looks like this:
Option Strict Off Option Explicit Off Imports EnvDTE Imports System.Diagnostics Public Module EnvironmentEvents #Region "Automatically generated code, do not modify" 'Automatically generated code, do not modify 'Event Sources Begin <System.ContextStaticAttribute( )> Public WithEvents DTEEvents As EnvDTE.DTEEvents <System.ContextStaticAttribute( )> Public WithEvents DocumentEvents As EnvDTE.DocumentEvents <System.ContextStaticAttribute( )> Public WithEvents WindowEvents As EnvDTE.WindowEvents <System.ContextStaticAttribute( )> Public WithEvents TaskListEvents As EnvDTE.TaskListEvents <System.ContextStaticAttribute( )> Public WithEvents FindEvents As EnvDTE.FindEvents <System.ContextStaticAttribute( )> Public WithEvents OutputWindowEvents As EnvDTE.OutputWindowEvents <System.ContextStaticAttribute( )> Public WithEvents SelectionEvents As EnvDTE.SelectionEvents <System.ContextStaticAttribute( )> Public WithEvents SolutionItemsEvents _ As EnvDTE.ProjectItemsEvents <System.ContextStaticAttribute( )> Public WithEvents MiscFilesEvents As EnvDTE.ProjectItemsEvents <System.ContextStaticAttribute( )> Public WithEvents DebuggerEvents As EnvDTE.DebuggerEvents 'Event Sources End 'End of automatically generated code #End Region End Module
This sets up the types of IDE events you can hook into. Now if you open the EnvironmentEvents module in your MyMacros folder, you can select the BuildEvents from the Class Name drop-down (the left drop-down box above the code). Then from the right Method Name drop-down, you can select one of four build events to hook into:
Will fire when any build operation is fired from the IDE. It fires only once for a full solution or multiproject build operation.
Will fire when a build operation completes. This event fires only once for a full solution or multiproject build operation.
Will fire when a project build begins. This event is used to catch each project build event within a solution or multiproject build operation.
Will fire when a project build completes. This event is used to catch the completion of each project build within a solution or multiproject build operation.
For an example, you can add the capability to stop the build process when a project fails and display a message box in the IDE to note when the build process is complete (in case someone isn't paying attention to the Build output).
2.1 Canceling a failed build
The normal behavior of Visual Studio is to build every project in a solution, even if one or more of those projects fails. If you have a half-dozen projects in your solution, then this can sometimes take a little while. It is much faster if the build quits when any of the projects fails. By handling a build event, you can ensure that.
Start by selecting the OnBuildProjConfigDone event from the Method Name drop-down above the code. The Macro IDE will automatically generate the method signature to hook the event:
Private Sub BuildEvents_OnBuildProjConfigDone( _ ByVal Project As String, _ ByVal ProjectConfig As String, _ ByVal Platform As String, ByVal SolutionConfig As String, _ ByVal Success As Boolean) _ Handles BuildEvents.OnBuildProjConfigDone End Sub
Since this event will fire whenever a project completes a build, you can easily stop the rest of the build from occurring by using the ExecuteCommand method of the DTE object to send a Build.Cancel command to the IDE. The DTE object [Hack #86] is a reference to the Visual Studio IDE, and using DTE.ExecuteCommand method to cancel the build is the same as hitting Ctrl-Break during a build or typing Build.Cancel into the IDE command window. Using the Success parameter of the event signature, you can determine whether you want to stop the build. The code looks like this:
Private Sub BuildEvents_OnBuildProjConfigDone( _ ByVal Project As String, ByVal ProjectConfig As String, _ ByVal Platform As String, ByVal SolutionConfig As String, _ ByVal Success As Boolean) _ Handles BuildEvents.OnBuildProjConfigDone If Success = False Then 'The build failed...cancel any further builds. DTE.ExecuteCommand("Build.Cancel") End If End Sub
2.2 Alert the user on a successful build
Now, the idea was to capture the end of the build process to alert the user that the build was complete. For that you hook into the OnBuildDone event the same way you did for the project build complete. This time, you'll use a standard Windows message box to display an informative alert:
Private Sub BuildEvents_OnBuildDone( _ ByVal Scope As EnvDTE.vsBuildScope, _ ByVal Action As EnvDTE.vsBuildAction) _ Handles BuildEvents.OnBuildDone 'Alert that we finished building! System.Windows.Forms.MessageBox.Show("Build is complete!") End Sub
These are some pretty simple examples, but you have the power of the .NET Framework in your hands for any of these events so you are limited only by what you can come up with to handle. You can share these with other developers by just sending them the code to include in their own macro project or take it a step further and create a Visual Studio add-in that hooks these events and distribute it that way. If you take a look at the other IDE events you can tap into, you will understand why the Visual Studio IDE is so extensible.
Something to consider when you are implementing custom build events is that they are handled within the IDE only. Build events implemented using the extensibility of the IDE (macros or add-ins) will not fire if the build is run from the command line [Hack #78] . However, pre- and post-build event commands will execute even if the build is being run via the command line with devenv.exe /build. This could be important if you want to automate your build process on a build machine.