Click here to Skip to main content
15,892,059 members
Articles / Programming Languages / C++
Article

RCStamp - add build counts and more to your .rc files

Rate me:
Please Sign up or sign in to vote.
4.87/5 (22 votes)
23 Apr 2002CPOL3 min read 144.3K   2.6K   45   33
RCStamp is a flexible command line tool to modify the FILEVERSION entries in a .rc Resource script (source included)

Introduction

File Version numbers in Windows consist of four positions with 16 bits each, e.g. 6.0.2600.0 is the version of my iexplore.exe. Typically you use the first two positions as "major.minor" version, based on the features of the module, the third position for an incremental build count, and the fourth for special builds. (You already knew all this, didn't you?)

RCStamp grew out of the necessity to manage a rather complex versioning scheme on a dozen of binaries. It can modify the FILEVERSION entry and the "FileVersion" string entry in a VERSIONINFO resource. It uses a format string that can affect the four positions of the version number individually. A typical call looks like this:

rcstamp myproject.rc *.*.+.*

This would increment the third position, and leave all others unmodified. You get a simple automatic build counter when you integrate this as custom build step into your Visual Studio project.

A more complex call:

rcstamp myproject.rc *.+.0.+1000

would increment the second position (minor version), set the third position to 0, and increment the fourth by 1000.

Command line options:

rcstamp file <format> [<options>...]
rcstamp @file [<format>] [<options>...]

Format options:

*leave position as-is
+increment position by one
nset the position to the number n (decimal)
+nincrement position by the number n (decimal)
-decrement position by one (if you ever need it)
-ndecrement position by the number n (still decimal, and you have been warned)

Additonal command line options:

-ndon't modify the "FileVersion" string value
-rRESIDmodify only the version resource with the resource id RESID (Default: modify all resources)
-vVerbose (echo the modified values)
-lProcess a list file instead of a resource (see below)

List Files

For batch processing, you can specify a text file containing a list of file names, like this:
rcstamp @files.txt *.*.+.*
files.txt could look like this:
c:\sources\hamlet\hamlet.rc
c:\sources\ophelia\ophelia.rc=*.*.0.+
c:\sources\laertes\laertes.rc

You can add a format for an individual file (separated by a '=') to override the format specified at the command line.

-l - the Self-Awareness switch

The -l option allows to modify a list file like the one above, instead of a resource script. This is far from useful, since all special formattings will be rendered as 0, and I suspect a bug there. But it's a perfect example of the useless features you spend late hours with.

Return Values

Since it's intended for batching, there are some errorlevel's to evaluate:
ERRORLEVEL 3 : invalidarguments
ERRORLEVEL 1 : an error occured when processing at least
      one file (error message written to stderr)
ERRORLEVEL 0 : everything's fine

Source

The sources are pretty straightforward, it doesn't rely on any library (besides the VC runtime). main() clobbers the command line, and then processes either the specified file, or opens the file list and processes each of the files. ParseArg() will analyze one command line parameter at a time, and store it's findings in global variables. ProcessFile() will process a single file according to the options set. CalcNewVersion() calculates the new file version from a string containing the old one and a format specifier.

The files are read with std::ifstream line-by-line, The modified text is collected in a std::string, and then the entire file is overwritten.

Issues

This is a one-nighter (a half-nighter, even) - I didn't test everything (e.g. the '-' format specification, and the -l option), there are some potential buffer overruns and when you run out of memory it will look ugly. The file parsing is homegrown, it doesn't really "understand" the .rc file; it just looks for certain tokens, using of strdup and strtok. it is fairly stable with the .rc files produced by Visual Studio, but it might fail in exotic cases (I didn't even dive into the .rc documentation so I don't know what is allowed).

Oh, and you might already have guessed - you can't decrement the first position, since the leading '-' is interpreted as option. Since this tool is in daily use (I use it now for the daily builds at work, and for preparing releases) I for sure will fix the bugs that appear in the features I need. Beyond that, it's up to you - at least, up to your persuasion skills.

Enjoy.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Klippel
Germany Germany
Peter is tired of being called "Mr. Chen", even so certain individuals insist on it. No, he's not chinese.

Peter has seen lots of boxes you youngsters wouldn't even accept as calculators. He is proud of having visited the insides of a 16 Bit Machine.

In his spare time he ponders new ways of turning groceries into biohazards, or tries to coax South American officials to add some stamps to his passport.

Beyond these trivialities Peter works for Klippel[^], a small german company that wants to make mankind happier by selling them novel loudspeaker measurement equipment.


Where are you from?[^]



Please, if you are using one of my articles for anything, just leave me a comment. Seeing that this stuff is actually useful to someone is what keeps me posting and updating them.
Should you happen to not like it, tell me, too

Comments and Discussions

 
QuestionOutdated Pin
Serjinator24-Feb-16 1:47
Serjinator24-Feb-16 1:47 
Questionremove all in rc file after using RCstamp comand. Pin
Mạnh Lê22-Dec-15 21:02
Mạnh Lê22-Dec-15 21:02 
SuggestionIntegrating into VS2008 Pin
softwaremonkey5-Mar-13 6:41
softwaremonkey5-Mar-13 6:41 
GeneralUnicode rc files Pin
ThomT21-Dec-06 0:27
ThomT21-Dec-06 0:27 
GeneralProblems with ',' and '.'? What about ProductVersion? Here comes an improved version... Pin
knaster bax28-Dec-05 6:49
knaster bax28-Dec-05 6:49 
Truly a nice and lean tool, Peterchen! But there is probably a little non-compliance of standards (actually a very tiny one):

The four numbers after the FILEVERSION statement are separated by commas, because internally they are regarded as separate parameters and are transformed into 32/16 bit integers. The "FileVersion" string, on the other hand is an entity. Such entities are usually separated by decimal points, not commas. Further in the .rc file generated by VS there are no blanks. Take the improved version of RCStamp posted below, if you like an .rc file like this:


FILEVERSION 1,0,9,0
...
VALUE "FileVersion", "1.0.9.0"


It also has another little improvement: It increments not only the FileVersion, but the ProductVersion, too.

And here it comes…




// RCStamp.cpp : Defines the entry point for the console application.

// Minor changes (comma / decimal point) by PI 12/2005

#include "stdafx.h"

#include <iostream>
#include <fstream>
#include <strstream>
#include <string>

using namespace std;

char const * strUsage =
" RCSTAMP command line:\r\n"
"\r\n"
" rcstamp file <format> [options...]\r\n"
" rcstamp @file [<format>] [options...]\r\n"
"\r\n"
" file : resource script to modify\r\n"
" @file : file containing a list of file (see below)\r\n"
"\r\n"
" format: specifies version number changes, e.g. *.*.33.+\r\n"
" * : don't modify position\r\n"
" + : increment position by one\r\n"
" number : set position to this value\r\n"
" \r\n"
" options: \r\n"
" -n : don't update the FileVersion string \r\n"
" (default: set to match the FILEVERSION value)\r\n"
" \r\n"
" -rRESNAME: update only version resource with resource id RESNAME\r\n"
" (default: update all Version resources)\r\n"
"\r\n"
" -l the specified file(s) are file list files\r\n"
"\r\n"
" -v Verbose\r\n"
" \r\n"
" file list files:\r\n"
"\r\n"
" can specify one file on each line, e.g.\r\n"
" d:\\sources\\project1\\project1.rc\r\n"
" d:\\sources\\project2\\project2.rc=*.*.*.+\r\n"
"\r\n"
" a format in the list file overrides the format from the command line\r\n"
" using the -l option, list files itself can be modified\r\n"
"\r\n";

// command line options:
//
// rcstamp file <format> [options...]
// rcstamp @file [<format>] [options...]
//
// file : resource script to modify
// @file : file containing a list of file (see below)
//
//
// format: specifies verison numbr changes, e.g. *.*.33.+
// * : don't modify
// + : increment by one
// number : set to this value
// P : take from product version
//
// options:
// -n : don't update the FileVersion string
// (by default, set to match the FILEVERSION value)
//
// -rRESNAME: replace version resource with resource id RESNAME
// (default: replace
//
// -l the specified file (or files in the file list) are file list files
//
// -v Verbose
//
// file list files:
//
// can specify one file on each line, e.g.
// d:\sources\project1\project1.rc
// d:\sources\project2\project2.rc=*.*.*.+
//
// specifying a format overrides the format specified on the command line
// using the -l option, list files itself can be modified
//
//


// Command Line Information

char const * scriptFile = NULL; // single resource script (.rc), or name of list file
bool scriptFileList = false; // script file is a file list file
char const * format = NULL; // format specifier
char const * resName = NULL;

bool processListFile = false; // the file(s) specified are list files, not resource files
bool noReplaceString = false; // don't replace fileversion string
bool verbose = false;


bool ParseArg(char const * arg)
{
if (*arg!='-' && *arg!='/') {
if (scriptFile == NULL) {
scriptFile = arg;
if (*scriptFile=='@') {
scriptFileList = true;
++scriptFile;
}
}
else if (format == NULL) {
format = arg;
}
else {
cerr << "Unexpected argument\"" << arg << "\"\r\n";
return false;
}
return true;
}

++arg;
char c = tolower(*arg);

if (c=='n') noReplaceString = true;
else if (c=='r') resName = arg+1;
else if (c=='l') processListFile = true;
else if (c=='v') verbose = true;
else {
cerr << "Unknown option\"" << arg << "\"\r\n";
return false;
}
return true;
}


bool CalcNewVersion(char const * oldVersion, char const * fmtstr, char * version)
{
if (!fmtstr) fmtstr = format;

char const * fmt[4];
char * fmtDup = strdup(fmtstr);
fmt[0] = strtok(fmtDup, " .,");
fmt[1] = strtok(NULL, " .,");
fmt[2] = strtok(NULL, " .,");
fmt[3] = strtok(NULL, " .,");

if (fmt[3] == 0)
{
cerr << "Invalid Format\r\n";
return false;
}

char * outHead = version;
char * verDup = strdup(oldVersion);
char * verStr = strtok(verDup, " ,.");

*version = 0;

for(int i=0; i<4; ++i)
{
int oldVersion = atoi(verStr);
int newVersion = oldVersion;

char c = fmt[i][0];

if (strcmp(fmt[i], "*")==0)
newVersion = oldVersion;
else if (isdigit(c))
newVersion = atoi(fmt[i]);
else if (c=='+' || c=='-')
{
if (isdigit(fmt[i][1]))
newVersion = oldVersion + atoi(fmt[i]);
else
newVersion = oldVersion + ((c=='+') ? 1 : -1);
}

itoa(newVersion, outHead, 10);
outHead += strlen(outHead);

if (i != 3) {
strcpy(outHead, ",");
outHead += 1;
verStr = strtok(NULL, ",");
}
}
free(fmtDup);
free(verDup);
return true;
}


bool ProcessFile(char const * fileName, char const * fmt = NULL)
{
const int MAXLINELEN = 2048;

ifstream is(fileName);
if (is.fail()) {
cerr << "cannot open " << fileName << "\r\n";
return false;
}

string result;

char line[MAXLINELEN];
char fileversion[64] = { 0 }; // "final" version string
char productversion[64] = { 0 }; // "final" version string

bool inReplaceBlock = false;

while (!is.eof()) {
is.getline(line, MAXLINELEN);
if (is.bad()) {
cerr << "Error reading " << fileName << "\r\n";
return false;
}

if (processListFile)
{
char * pos = strchr(line, '=');
if (pos) {
CalcNewVersion(pos+1, fmt, pos+1); // in-place replace
}
}
else
{
char * dupl = strdup(line);
char * word1 = strtok(dupl, " \t");
char * word2 = strtok(NULL, " \t,"); // allow comma for [VALUE "FileVersion",] entry

if (word1 && word2 && strcmpi(word2, "VERSIONINFO") == 0 && strnicmp(word1, "//",2)!=0)
{
if (resName==NULL || strcmpi(resName, word1)==0)
inReplaceBlock = true;
else
inReplaceBlock = false;
}

if (inReplaceBlock) {
if ( word1 && (strcmpi(word1, "FILEVERSION") == 0 || strcmpi(word1, "PRODUCTVERSION") == 0) )
{
//int offset;
if (strcmpi(word1, "FILEVERSION") == 0)
{
int offset = strlen("FILEVERSION") + word1 - dupl + 1;
CalcNewVersion(line+offset, fmt, fileversion);
strcpy(line+offset, fileversion);
}
else
{
int offset = strlen("PRODUCTVERSION") + word1 - dupl + 1;
CalcNewVersion(line+offset, fmt, productversion);
strcpy(line+offset, productversion);
}


if (verbose) cout << line << "\r\n";
}

if (!noReplaceString && word1 && word2 && strcmpi(word1, "VALUE")==0)
{
if( strcmpi(word2, "\"FileVersion\"")==0 )
{
if (!*fileversion)
{
cerr << "Error: VALUE \"FileVersion\" encountered before FILEVERSION\r\n";
}
else
{
for (unsigned int j=0; j
GeneralRe: Problems with ',' and '.'? What about ProductVersion? Here comes an improved version... Pin
Sir Athos22-Sep-06 6:57
Sir Athos22-Sep-06 6:57 
GeneralThanks! Pin
hairyfellow27-Jul-05 11:21
hairyfellow27-Jul-05 11:21 
GeneralDaily Builds - Build Manager Pin
Anonymous27-Jun-05 4:54
Anonymous27-Jun-05 4:54 
GeneralFollowup Tool Pin
peterchen19-May-05 5:39
peterchen19-May-05 5:39 
GeneralNaming output files according to version/build numbers Pin
Beau Skinner5-May-05 11:17
Beau Skinner5-May-05 11:17 
GeneralRe: Naming output files according to version/build numbers Pin
peterchen5-May-05 11:34
peterchen5-May-05 11:34 
GeneralRe: Naming output files according to version/build numbers Pin
Beau Skinner5-May-05 12:20
Beau Skinner5-May-05 12:20 
GeneralRe: Naming output files according to version/build numbers Pin
peterchen5-May-05 12:30
peterchen5-May-05 12:30 
GeneralRe: Naming output files according to version/build numbers Pin
Beau Skinner6-May-05 23:22
Beau Skinner6-May-05 23:22 
GeneralRe: Naming output files according to version/build numbers Pin
peterchen7-May-05 1:13
peterchen7-May-05 1:13 
GeneralRe: Naming output files according to version/build numbers Pin
Beau Skinner7-May-05 12:58
Beau Skinner7-May-05 12:58 
GeneralRe: Naming output files according to version/build numbers Pin
Beau Skinner6-May-05 23:04
Beau Skinner6-May-05 23:04 
GeneralFormat differences... Pin
rbid26-Nov-04 23:50
rbid26-Nov-04 23:50 
GeneralRe: Format differences... Pin
peterchen27-Nov-04 22:55
peterchen27-Nov-04 22:55 
GeneralRCStamp appends empty lines at the end of the target file Pin
Thomas Haase17-Mar-04 23:20
Thomas Haase17-Mar-04 23:20 
Generalfix Pin
peterchen16-Apr-04 14:05
peterchen16-Apr-04 14:05 
GeneralImprovement Pin
Anonymous16-Aug-02 23:14
Anonymous16-Aug-02 23:14 
GeneralRe: Improvement Pin
Ddub8-Mar-03 15:25
Ddub8-Mar-03 15:25 
GeneralRe: Improvement Pin
Suhn11-Feb-06 9:40
Suhn11-Feb-06 9:40 
GeneralRe: Improvement Pin
advance51218-Jun-09 3:21
advance51218-Jun-09 3:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.