Saturday 15 March 2014

Auto Increment build numbers in Visual Studio 2012


I have seen most of the posts on auto incrementing build numbers recommending some external plugins. However, working for a corporate environment its hard to rely on any external add-on or plugins which are not signed and authorized. It took me some time to search around to get an auto incrementing build numbers and eventually couldn't find a decent post. This post will help in writing your own build increment tools. Although it looks sluggish in implementation ( as I tweaked some other tutorial), I would prefer doing this rather than relying on external add-on.

Alright, lets get started. I believe that it works on most of the visual studio versions. Just as formal post introduction, I have used Visual Studio Ultimate 2012 and python 2.7 for this exercise.



Step:1 Add a version resource.

1.1. Open solution explorer and right click on Resource Files.
1.2. Select Add ->Resource->Version and click New.
1.3. This will create a new VS_VERSION_INFO file, specific to project.
1.4. Incase, you want to access it later, expand the Resource Files folder, double click on <project-name.rc> file. Expand Version folder. It will contain VS_VERSION_INFO file. Doble click to open it.


Step:2 Updating Resource File

Now that we have created a version resource. It now time to edit the file to our requirements.

2.1.Locate Resource Files  section in the <Project-Name>. Expand the folder to find the <project-name.rc> file. Right click on the <project-name.rc> file and open with C++ source code Editor.
2.2. Identify the following code in the file to locate version information. (~line 47)

/////////////////////////////////////////////////////////////////////////////
//
// Version
//

VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
   BLOCK "StringFileInfo"
   BEGIN
       BLOCK "040904b0"
       BEGIN
           VALUE "CompanyName", "TODO: <Company name>"
           VALUE "FileDescription", "TODO: <File description>"
           VALUE "FileVersion", "1.0.0.1"
           VALUE "InternalName", "project-name.exe"
           VALUE "LegalCopyright", "Copyright (C) 2014"
           VALUE "OriginalFilename", "project-name.exe"
           VALUE "ProductName", "TODO: <Product name>"
           VALUE "ProductVersion", "1.0.0.1"
       END
   END
   BLOCK "VarFileInfo"
   BEGIN
       VALUE "Translation", 0x409, 1200
   END
END
Comment out this part of the code using multi line comments and paste the following code instead.

/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION     VER_FILE_VERSION
PRODUCTVERSION VER_PRODUCT_VERSION
FILEFLAGSMASK   0x3fL
FILEFLAGS       VER_FILEFLAGS
FILEOS          VER_FILEOS
FILETYPE        VER_FILETYPE
FILESUBTYPE     0x0L
BEGIN
   BLOCK "StringFileInfo"
   BEGIN
       BLOCK "040904b0"
       BEGIN
           VALUE "FileDescription",  VER_FILE_DESCRIPTION_STR "\0"
           VALUE "FileVersion",   VER_FILE_VERSION_STR "\0"
           VALUE "InternalName", VER_INTERNAL_NAME_STR "\0"
           VALUE "LegalCopyright",   VER_COPYRIGHT_STR "\0"
           VALUE "OriginalFilename", VER_ORIGINAL_FILENAME_STR "\0"
           VALUE "ProductName",   VER_PRODUCTNAME_STR
           VALUE "ProductVersion",   VER_PRODUCT_VERSION_STR "\0"
       END
   END
   BLOCK "VarFileInfo"
   BEGIN
       VALUE "Translation", 0x409, 1200
   END
END

This piece will use a version.h header file to fetch the information dynamically. The next section deals with constructing appropriate version.h file.

Step 3: Adding a Version Header

Next we create a file named version.h to provide a more convenient location to set the various version information. This is especially useful if you are sharing version information across multiple projects in a single solution.

3.1 How to create a version.h file?

To create  version.h Header file, right click on the Header Files Folder in the solution explorer.
select Add-> New Item -> Header File (.h)
Give the name as version.h and click add. This will put the version.h file in the Header files folder.

Now that we have version.h file. Open the file and paste the following code there.

#define STRINGIZE2(s) #s
#define STRINGIZE(s) STRINGIZE2(s)
#define VERSION_MAJOR            1
#define VERSION_MINOR            0
#define VERSION_REVISION         0
#define VERSION_BUILD            0
#define VER_FILE_DESCRIPTION_STR "Description"
#define VER_FILE_VERSION         VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
#define VER_FILE_VERSION_STR     STRINGIZE(VERSION_MAJOR)     \
                                   "." STRINGIZE(VERSION_MINOR) \
                                   "." STRINGIZE(VERSION_REVISION) \
                                   "." STRINGIZE(VERSION_BUILD) \
#define VER_PRODUCTNAME_STR      "c_version_binary"
#define VER_PRODUCT_VERSION      VER_FILE_VERSION
#define VER_PRODUCT_VERSION_STR VER_FILE_VERSION_STR
#define VER_ORIGINAL_FILENAME_STR   VER_PRODUCTNAME_STR ".exe"
#define VER_INTERNAL_NAME_STR    VER_ORIGINAL_FILENAME_STR
#define VER_COPYRIGHT_STR        "Copyright (C) 2011"
#ifdef _DEBUG
 #define VER_VER_DEBUG          VS_FF_DEBUG
#else
 #define VER_VER_DEBUG          0
#endif
#define VER_FILEOS               VOS_NT_WINDOWS32
#define VER_FILEFLAGS            VER_VER_DEBUG
#define VER_FILETYPE             VFT_APP

For now, the version information starts with 1.0.0.0 (manor, minor,build and revision). One can feel free to change it according to their requirements.

Step 4: Add Includes

The final step is to add the necessary include line to the <project-name>.rc file for the version.h file. ( see step 2 and point 1, on how to edit the <project-name>.rc file)







// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#include "version.h"
#define APSTUDIO_READONLY_SYMBOLS


Now that we are done with arranging the necessary files, just try to build the project. You have to see the version information used in the version.h file, instead of information used in the VS_VERSION_INFO file ( for instance, see in the details section of build exe properties). After this, its just writing a script to control the version.h file for auto increment of build numbers.

Note:

Error 1 error RC1004: unexpected end of file found
C:\<project-name>\version.h 32

Incase , if you see this error. Add a new line at the end of the version.h file.

Step 5: Auto Incrementing the Build Numbers.
Follow the step 3.1 to create a header file Build_Increment.h in the Header Files folder section. Open the Build_increment.h Header file and paste the following code and close it. (This file can be later used to print the SVN numbers).

#ifndef _SVN_VERSION_H_
#define _SVN_VERSION_H_
#define SVN_LOCAL_MODIFICATIONS $WCMODS?1:0$  // 1 if there are modifications to the local working copy, 0 otherwise
#define SVN_REVISION            $WCREV$       // Highest committed revision number in the working copy
#define SVN_TIME_NOW            $WCNOW$       // Current system date &amp; time
#define BUILD_INCREMENT         0
#endif

This is used as header include in the version.h file. A python (2.7) script is used to increment the build number every time in the Build_increment.h header file.

Now that we have the Build_Increment.h File. place this in the header section of the version.h file.

#include "Build_Increment.h"

#define STRINGIZE2(s) #s
#define STRINGIZE(s) STRINGIZE2(s)

After adding the header file, modify the version major, minor, build and revision defines as per your taste.

#define VERSION_MAJOR               2
#define VERSION_MINOR               5
#define VERSION_REVISION            0
#define VERSION_BUILD               BUILD_INCREMENT

Now that, I want VERSION_BUILD to increment every time, I used the BUILD_INCREMENT macro from the Build_Increment.h file. If someone wants VERSION_REVISION to be incremented every time, place the BUILD_INCREMENT macro instead of 0.

Now, go to your visual studio project folder. Create a new file called BuildIncrementer.py and paste the following code there and save it.

import string

# Open a file
fo = open("Build_Increment.h", "r+")
print "Name of the file: ", fo.name
line = fo.readlines()
for idx, item in enumerate(line):
  if 'BUILD_INCREMENT' in item:
      tmp_str =  item.split();
      tmp_str[2] = str(int(tmp_str[2])+1)+"\n";
      item = ' '.join(tmp_str);
      #print item;
      line[idx] = item;    
fo.seek(0)

for idx, item in enumerate(line):
   fo.write(item)
fo.truncate()

# Close opend file
fo.close()
This script will increment the build number every time it runs. so, we will call this script as a pre build event.

Step 6: Configure pre-Build event.

To configure this script in the pre build event. Goto Debug>Project properties>Build Events >pre Build Events> Command Line.

In the command line, enter the following command

python $(ProjectDir)\Build_Incrementer.py

and apply it.

Now, try to Build the project. See the BUILD_INCREMENT macro in the Build_Increment.h file. It should keep incrementing for every build.

To verify that, Right click and select properties of the Build Exe. Navigate to Details tab. See information about the version. One can pull this build information into their code using this link.

Incase, you want to have SVN version instead of auto Increment, Follow this tutorial after Step 4.

References

Versioning a Native C/C++ Binary with Visual Studio | Zach Burlingame

4 comments:

  1. line 4, in
    fo = open("Build_Increment.h", "r+")
    IOError: [Errno 13] Permission denied: 'Build_Increment.h'

    ReplyDelete
  2. For those who want a native script to run that doesn't require python, check below as I have created a VBScript that runs without the need for extra utilities.

    Make sure you add this line to your pre-build event:
    CScript "$(ProjectDir)\BuildIncrementer.vbs"

    (change the filename.vbs above to whatever you called your file, instead of the .py extension)

    CODE:
    '//////////////////// AUTO BUILD INCREMENT SCRIPT \\\\\\\\\\\\\\\\\\\\
    Const ForReading = 1
    OrigBuildFile = "Build_Increment.h"
    TmpBuildFile = "Build_Increment.tmp"

    Set objFSO = CreateObject("Scripting.FileSystemObject")
    Set objTextFile = objFSO.OpenTextFile _
    (OrigBuildFile, ForReading)
    Set objTmpFile = objFSO.CreateTextFile(TmpBuildFile, True)

    strAllLines = objTextFile.ReadAll
    objTextFile.Close

    arrLines = split(strAllLines, vbCrLf)
    For i = 0 to Ubound(arrLines)
    if InStr(arrLines(i), "BUILD_INCREMENT") then
    foundAtIndex = i

    'Wscript.Echo "Service: " & arrLines(i)

    ' convert tabs to spaces first, just in case we used TAB to align the values in the .h file
    arrLines(i) = Replace(arrLines(i), vbTab, " ")

    ' make sure we only get single WhiteSpaces between words/letters, .Split() doesn;t do well if it encounters multiple continous white spaces in VBScript.
    Do While (InStr(arrLines(i), " "))
    ' if true, the string still contains double spaces,
    ' replace with single space
    arrLines(i) = Replace(arrLines(i), " ", " ")
    Loop

    arrLineTokens = split(arrLines(i))
    build = CInt(arrLineTokens(2))
    build = build + 1
    arrLineTokens(2) = CStr(build)

    'Wscript.Echo "Build Inremented to: " & arrLineTokens(2)

    objTmpFile.Write(arrLineTokens(0) & " ")
    objTmpFile.Write(arrLineTokens(1) & vbTab & vbTab & vbTab)
    objTmpFile.WriteLine(arrLineTokens(2))

    else
    objTmpFile.WriteLine(arrLines(i))
    end if

    Next

    objTmpFile.Close
    ObjFSO.DeleteFile OrigBuildFile
    ObjFso.MoveFile TmpBuildFile, OrigBuildFile
    '///////////////////////////// END OF CODE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    ReplyDelete
  3. And thank you Ramu for the great article, used this approach in one of my client's projects obviously and I like it.

    Great job.

    ReplyDelete
  4. I've been using this approach now for a few weeks and I've a couple of comments:

    1. There's a small typo in the code by Fadi that gives an infinite loop. The line arrLines(i) = Replace(arrLines(i), " ", " ") should have TWO spaces in the search and replace by ONE space.

    2. This overall approach doesn't play nice with VS itself. VS updates/edits/cleans the *.rc file whenever it likes and removes whatever it doesn't like. As in your variables! It's not too bad though since I have track of BUILD_INCREMENT but it's not fully auto-magical unfortunately.

    ReplyDelete