Introduction
This article describes one way - and the only way I have found thus far - of avoiding unnecessary re-compiles when using the versioning scheme proposed in another CodeProject article when using the Microsoft Visual C++ IDE.
While this particular solution was designed to work specifically with Microsoft Visual Studio - and by the way, it also works with the Express Edition - a similar arrangement ought to be workable on most other IDEs which provide hooks to execute user commands at pre-build time.
I am using it primarily with MSVC 2008 C++ Express, but the 2005 version also provides the facility to specify pre-build actions, though I haven't checked out earlier editions myself regarding this issue.
As well, since I neither use the VerCopy routine presented in the earlier CodeProject article referred to, nor the VerHeader utility and the header file it generates or updates, my code here does not show it. If you want to use VerCopy, keep that command in your post-build step. It does nothing to upset the file dates and times, and thus causes no problems.
As mentioned, I do not use the Verheader utility, since for my needs, it introduces more problems than it solves - at least under Windows. More on this later.
My scheme does use the RCStamp utility, also used and referred to in the earlier CodeProject article, and for any information regarding it, please consult the original article.
Background
After I found the above CodeProject article some time ago, I quickly realized that, while it did what I wanted, it caused a re-compile far too often, even if no source changes had been made.
For me, the re-compiles happened too often, and what was a lot more annoying, they happened at unexpected times, particularly when I get distracted for any reason and came back to my project - eager to continue a debug session of some new code.
Every time we start debugging, MSVC checks to be sure the code is up-to-date, which is a good thing ordinarily. Of course, it never is under the earlier circumstances, since at least one file, the .rc resource file, was modified as the last step of the previous compile, and so it forces a new compile, making you wait - again. And, if you also use the header file, the problems just multiply because more files are 'out of date'.
This was especially aggravating on some of the slower machines which I sometimes need(ed) to use for one reason or another.
Besides all of the above, of course, unnecessary re-compiles sort of defeat a good part of the very purpose of having build numbers - a sort of unique indicator of changes in code - because it bumps the number needlessly when nothing has changed.
After trying various ways to get around the problem - with no real success - I finally decided to try and get it sorted out properly - or understand why I had to put up with the problem.
The first thing I came to realize, is that with the basic setup as proposed in the earlier article, there is no way to avoid the problem. A successful compile invokes the post-build step, it in turn modifies the .rc file. Next time, the IDE is asked to see if anything has changed, it detects this change in the .rc file, and re-compiles, or at the very least, re-links the application, causing another update of the .rc file ... and around the loop we go again.
Creating the header file, which needs to be included somewhere and thereby makes the file which includes it out-of-date, just worsens the problem. In addition, of course, the build number included from the header and the build number in the .rc file in the project are always out by one. This, in many cases, makes little difference since the build numbers from the header agree with those of the linked-in resources. But, it is something you would need to keep in mind, were you to inspect the source of the resource file in the project.
In my early tries to avoid the 'problem', I used touch to update the file times of various files, and at times, I was successful in avoiding a recompile, but then the version information available from the header information gets out of step with that from the .rc file.
Even naming the file with the odd extension .info does not fool the IDE. It still seems to detect a dependency, and does the 'proper thing'. While MS provides ways to exclude files by changing the SYSINCL.DAT file - at least for some versions - using this feature does not prevent the real problem either.
Of course, one way to avoid some of the problems would be to avoid the extra header file altogether and use some of the available functions - such as GetFileVersionInfo()
- to extract and then parse the relevant data.
This would still leave the problem of the needless re-compiles, and parsing the data in the application, rather than in an external application, would, of course, also add the necessary parsing code to every application, though that likely would, in general, not present much of a problem. The extra code would, in most cases, be negligible compared to the rest and the effort required to avoid re-compiling.
The fix
First off, dispense with the extra header and reduce your problem by much more than half.
Then, since the real problem is the unavoidable re-compile of the .rc file, why not take care of that as soon as we have changed it? We know which file we are changing, and the resource compiler has a relatively uncomplicated command line, so we simply, after we call RCStamp, also immediately call the resource compiler.
If we do this in the pre-build step - which only gets invoked if there are other changes which make a recompile necessary - then the link step will leave the dependencies such that there are no outstanding changes which would require a re-compile.
Unfortunately, we can't depend on the IDE to detect the change we made to the .rc file in the pre-build step, because by the time this step is invoked, all dependency scanning has been done and there is no way I know of to restart it.
In some ways, it makes little difference whether we we do all this work in the pre- or post-build step, except that doing it in the pre-build step keeps the build numbers in the resource file and those linked into the executable the same.
Doing the work in the post build step would leave the build number in the .rc file on disk always one higher than the value in the linked-in resource.
Of course, to now use the version information, one needs to call the function GetFileVersionInfo()
and extract the version numbers.
So, how do we do it?
In the pre-build step, add the following:
Description:
Updating Version ....
Command line:
RCStamp -v $(ProjectName)r.rc *.*.+.*
rc.exe /foDebug/$(ProjectName)r.res .\$(ProjectName)r.rc
To get to the proper dialog, press Alt-F7 to open the "Project properties" dialog, then in the left hand pane, expand the "Build Event" item, click on the right field of the "Command Line" item, and paste the appropriate data into the field.
To edit the data, click on the "..." button which will appear at the right hand side of the field.
If your .rc file includes other files - they often do - then, you'll have to add the appropriate "/I include/path" item to the resource compiler line. Remember, the contents of this line are entirely your responsibility. While you can use macros defined by the IDE, it will use the lines as written by you. It won't search any other paths aside from those you specify on the command line.
And, what if you want or need the header file?
If you really do want to keep using the header under Windows, then at the very least, you will have to do (a fair bit) more work.
Extracting the information into the header is easy - use VerHeader. The real problem will then be to also to compile (all of the) the source file(s) which include(s) this header at the same time. Hence, I'd recommend that you minimize the number - and also the size - of the files which include the header. Perhaps, a header which only defines a set of global variables, not too many other includes, and no other code to make the command line as straightforward as possible, and then compile as quick and painless as possible.
For myself, the extra effort is just not worth the time it will take to implement and test it as long as I am working only in a Windows environment.
Points of Interest
If you use this sort of build numbering scheme when you build different versions of the same application, such as ANSI and Unicode under Windows, then, as long as you are using a common code base, you have to choose whether to stick with a 'build number' per configuration - and an associated .rc file - or a single build number representative of the code base.
There are arguments and trade-offs on either side of the issue, and you have to adjust your procedures accordingly, and update the build number only for the chosen base configuration.
If you choose to use a single build number and a single .rc file - and want all of the various versions to have the same build number - then, you will need to add the update code to only one configuration and then rebuild all others after updating the first one, but at least, there will be no unnecessary re-compiles, and any change in the build number does indeed represent some change in the source.
What about cross-platform build numbers?
If you need to go cross-platform, then, since *nixes don't use the same sort of .rc files - but Windows does, which means you're stuck with the .rc file - you may have to go with the header file, but hide it from the MSVC IDE. That way, the MSVC IDE won't recompile because the header changed, and for non-Windows platforms, you will have the appropriate header file.
After that, set things up so that the build number is incremented only at the desired point and all versions are built after that point.
In that case, you will, of course, also not need the code in the Linux version which is used to extract and parse the version numbers from the .rc file under Windows.
Just when should we increment the build number?
An issue which, I am sure, is highly controversial, is just when to increment the build number. My scheme currently increments it whenever my 'main release' version - which happens to be the Unicode version - is compiled. In this way, all the many debug compiles don't crank up the build number unnecessarily. But, this way does have some implications which I am still finding out about.
Right now, I'm happy enough to no longer have to wait on those extra compiles, and to have build numbers with a bit more meaning than they used to.
As it stands at present, the build numbers will increment (unnecessarily) even if no code changes are made, if
- the build environment changes - i.e., some change in the project configuration
- a build (of the 'main' version) does not complete successfully for any reason - user abort, compiler or linker problems ...
History
Nothing exciting yet.