An Automatic Build Number Incrementer for Visual Studio .NET

This article was contributed by: JamesB.

Environment: Visual Studio .NET

Introduction

Each of us who migrate to VS.NET encounters a few bumps in the road. One bump that caught me off guard was that my macros no longer worked.

My existing build number incrementing macro was a combination of bits and pieces of code scavenged from countless authors over the years. Although my build numbering system is not complicated, and does not require features found in similar macros, it will provide a solid foundation to start from.

Goals

These are my requirements for automating build numbers:

  1. On each release build, automatically increment a build number contained in a separate file that will be included into my project.
  2. Automatically track build date and number in a log file.

Code Discussion

My first task was to find a replacement for the .Application_BeforeBuildStart(). event handler used in VS 6. The designers of VS.NET provide us with four easy-to-use build events:

  • OnBuildBegin
  • OnBuildDone
  • OnBuildProjConfigBegin
  • OnBuildProjConfigDone

The naming convention used for the event handlers is somewhat misleading. We don’t want to use OnBuildBegin because it is called one time, even if we are building multiple configurations (release, debug, and so forth), making it hard to increment the build number for release builds only. OnBuildProjConfigBegin is called for each configuration being built, and supplies a ProjectConfig string containing the name of the project configuration used (release, debug, and so on).

Most of the work is done in the OnBuildProjConfigBegin event handler with the aid of two helper macros:

  • WriteToLogFile
  • WriteToOutputBuildPane

Both helper methods could be folded into the OnBuildProjConfigBegin event handler, or removed if not required.

Code

' ------------------------------------
' OnBuildProjConfigBegin event handler
' ------------------------------------

Private Sub BuildEvents_OnBuildProjConfigBegin(
        ByVal Project As String,
        ByVal ProjectConfig As String,
        ByVal Platform As String,
        ByVal SolutionConfig As String)
        Handles BuildEvents.OnBuildProjConfigBegin

  ' abort if build type is debug

  If InStr(1, ProjectConfig, "Debug", 1) Then Exit Sub

  ' get ver filename

  Dim res_filename As String

  res_filename = DTE.Solution.FullName
  res_filename = Path.ChangeExtension(res_filename, ".ver")

  ' open VERSION FILE and increment build number

  Dim msg_text As String

  If File.Exists(res_filename) Then

    Dim line As String

    Try

      Dim sr As StreamReader = New StreamReader(res_filename)
      line = sr.ReadLine()
      sr.Close()

    Catch ex As Exception

      Module1.WriteToOutputBuildPane(vbCrLf & _
                                "Version file read failed : " &
      ex.Message & vbCrLf)

    End Try

    line = Right(line, line.Length - 18)

    Try

      Dim sw As StreamWriter = File.CreateText(res_filename)
      sw.WriteLine("#define BUILD_NUM {0}", line + 1)
      sw.Close()

    Catch ex As Exception

      Module1.WriteToOutputBuildPane(vbCrLf & _
                                "Version file write failed : " &
      ex.Message & vbCrLf)

    End Try

    msg_text = "Build number : " & line + 1 & ", " & Now

    Module1.WriteToOutputBuildPane(vbCrLf & msg_text & vbCrLf)
    Module1.WriteToLogFile(msg_text)

  Else

    Try

      Dim sw As StreamWriter = File.CreateText(res_filename)
      sw.WriteLine("#define BUILD_NUM 1")
      sw.Close()

    Catch ex As Exception

      Module1.WriteToOutputBuildPane(vbCrLf & _
                                "Version file write failed : " &
      ex.Message & vbCrLf)

    End Try

    msg_text = "Build number : 1, " & Now

    Module1.WriteToOutputBuildPane(vbCrLf & msg_text & vbCrLf)
    Module1.WriteToLogFile(msg_text)

  End If

End Sub




' ----------------------------------
' write text message to a log file
' ----------------------------------

Sub WriteToLogFile(ByVal msg_text As String)

  Dim log_filename As String

  log_filename = DTE.Solution.FullName
  log_filename = Path.ChangeExtension(log_filename, ".log.txt")

  Try

    Dim sw As StreamWriter = File.AppendText(log_filename)
    sw.WriteLine(msg_text)
    sw.Close()

  Catch ex As Exception

    MsgBox("Log file write failed : " & ex.Message)

  End Try

End Sub




' ----------------------------------------------------------------
' write a text message to the build pane of the output tool window
' ----------------------------------------------------------------

Sub WriteToOutputBuildPane(ByVal msg_text As String)

  ' Create a tool window handle for the Output window.

  Dim win As Window = DTE.Windows.Item(EnvDTE.Constants. _
                                        qvsWindowKindOutput)

  ' Create handles to the Output window and its build pane.

  Dim OW As OutputWindow = win.Object
  Dim OWp As OutputWindowPane

  OWp = OW.OutputWindowPanes.Item("Build")

  ' Add a line of text to the output pane.

  OWp.OutputString(msg_text & vbCrLf)

End Sub

Integration

Note! If you do not have a macro project, create one before continuing.

You need a macro project opened and displayed in the VS Macros IDE.

  1. Each macro project has an EnvironmentEvents page. Double-click to open the page.
  2. Select BuildEvents from the class name dropdown list.
  3. Select OnBuildProjConfigBegin from the method name dropdown list.
  4. Add two imports statements to the list of name spaces to use.

    The .System. namespace is required for exceptions and .SystemIO. is for the stream reader and writer classes.

  5. Add the working code to the OnBuildProjConfigBegin method.
  6. Double click to open the Module1 page.

    Module1 is a default page name the IDE uses. If you use a different name, be sure to resolve namespace issues in the OnBuildProjConfigBegin method.

  7. Add the two helper methods.
  8. Add the .System and .SystemIO. imports statements to the list of namespaces to use.

Sample Output

The following samples are the result of building a small MFC dialog based application.

Contents of the version file: (MfcApp.ver)

#define BUILD_NUM 2

Contents of the output build pane: (batch – rebuild all)


Build number : 2, 7/16/2003 4:07:35 PM

------ Rebuild All started: Project: MfcApp, Configuration:
       Release Win32 ------

Deleting intermediate files and output files for project 'MfcApp',
         configuration 'Release|Win32'.
Compiling...
stdafx.cpp
Compiling...
MfcAppDlg.cpp
MfcApp.cpp
Generating Code...
Compiling resources...
Linking...
LINK : warning LNK4089: all references to 'OLEAUT32.dll'
                        discarded by /OPT:REF

Build log was saved at "file://d:\MfcApp\Release\BuildLog.htm"
MfcApp - 0 error(s), 1 warning(s)


------ Rebuild All started: Project: MfcApp,
       Configuration: Debug Win32 ------

Deleting intermediate files and output files for project 'MfcApp',
         configuration 'Debug|Win32'.
Compiling...
stdafx.cpp
Compiling...
MfcAppDlg.cpp
MfcApp.cpp
Generating Code...
Compiling resources...
Linking...

Build log was saved at "file://d:\MfcApp\Debug\BuildLog.htm"
MfcApp - 0 error(s), 0 warning(s)


---------------------- Done ----------------------

    Rebuild All: 2 succeeded, 0 failed, 0 skipped

Contents of the log file : (MfcApp.log.txt)

Build number : 1, 7/16/2003 4:07:35 PM
Build number : 2, 7/16/2003 4:19:07 PM

Conclusions

The process of converting macros from VS 6 to VS.NET was both challenging and rewarding. I was somewhat surprised how nicely the DTE automation object model, VB.NET, and the .NET framework worked together.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read