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.



Click here for a larger image.

  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. Click here for a larger image.

  7. 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.
  8. Add the two helper methods.
  9. 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.



Comments

  • You can do this in more easy way

    Posted by RIANON on 05/29/2008 07:27am

    I use VS2008, and I hacve tryed to use your code and foun I think more easy way:
    
        ' ------------------------------------
        ' 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
    
        '    'Find our project
        '    Dim prjcts As Projects
        '    Dim prj As Project
        '    Dim i As Integer
        '    Dim j As Integer
        '    Dim s As String
    
        '    prjcts = DTE.Solution.Projects
    
    
        '    For i = 1 To prjcts.Count
        '        prj = prjcts.Item(i)
        '        If prj.UniqueName = Project Then
        '            'Start setting of project properties we need
        '            For j = 1 To prj.Properties.Count
        '                Try
        '                    s = "Index: " & j & " Name: " & prj.Properties.Item(j).Name & " Value: " & prj.Properties.Item(j).Value.ToString()
        '                    Logger.WriteToOutputBuildPane(s)
        '                Catch ex As Exception
        '                    s = "Index: " & j & " Name: " & prj.Properties.Item(j).Name & " Err: " & ex.Message
        '                    Logger.WriteToOutputBuildPane(s)
        '                End Try
    
        '            Next j
        '        End If
    
        '        'Set assembly version
        '        prj.Properties.Item(37).Value = "100.100.123.334"
        '    Next i
    
        'End Sub
    
    
    COPY COMMENTED CODE and uncomment it, you can easy set any properties, I whanted to add new properties but I do not know how to do this(((
    
    Thank you...
    
    I was need only changing file version - it is simple)))

    Reply
  • not good with multiproject solutions

    Posted by Legacy on 07/23/2003 12:00am

    Originally posted by: Valerio

    I've just tested it with my solution which contains over 10 projects: when I am building one project, whole solution build number and log is changing.

    May be better to read/update not

    > log_filename = DTE.Solution.FullName

    but something applicable for project?

    I've just fixed it to the:

    log_filename = Path.GetDirectoryName(DTE.Solution.FullName) + "\" + Project

    and pass additional Project parameter to the WriteToLogFile:

    Sub WriteToLogFile(ByVal Project As String, ByVal msg_text As String)

    and now it's more applicable, what's you think?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • CentreCorp is a fully integrated and diversified property management and real estate service company, specializing in the "shopping center" segment, and is one of the premier retail service providers in North America. Company executives travel a great deal, carrying a number of traveling laptops with critical current business data, and no easy way to back up to the network outside the office. Read this case study to learn how CentreCorp implemented a suite of business continuity services that included …

Most Popular Programming Stories

More for Developers

RSS Feeds