Add an Auto-Incrementing Build-Number to Your Build Process
When building software it's often useful to give each iteration of your build process a unique number. Many IDEs and RAD tools do this for you automatically. If yours doesn't and you're using a make file to build your code you can add an auto-incrementing build number to your project with a few simple changes to your make file.
The mechanism presented here does not need to modify your source code at all, it uses linker symbols to add the build number to your program. Note however that you will probably want to modify your source code to display the build number, but that's not strictly necessary.
Let's start with the following simple make file for building a program:
# Makefile
OBJECTS=bnum.o
a.out: $(OBJECTS)
$(CC) $(LDFLAGS) -o $@ $(OBJECTS)
To add the build number to the make file we set the variable BUILD_NUMBER_FILE to the name of a file that will contain our build number value. Then we add BUILD_NUMBER_FILE to the dependencies for a.out, add BUILD_NUMBER_LDFLAGS to the flags used when linking the program, and finally include the file buildnumber.mak at the end of the make file. The converted make file looks like:
# Makefile
# Name of text file containing build number.
BUILD_NUMBER_FILE=build-number.txt
OBJECTS=bnum.o
a.out: $(OBJECTS) $(BUILD_NUMBER_FILE)
$(CC) $(LDFLAGS) $(BUILD_NUMBER_LDFLAGS) -o $@ $(OBJECTS)
# Include build number rules.
include buildnumber.mak
The included file buildnumber.mak looks like:
# Create an auto-incrementing build number.
BUILD_NUMBER_LDFLAGS = -Xlinker --defsym -Xlinker __BUILD_DATE=$$(date +'%Y%m%d')
BUILD_NUMBER_LDFLAGS += -Xlinker --defsym -Xlinker __BUILD_NUMBER=$$(cat $(BUILD_NUMBER_FILE))
# Build number file. Increment if any object file changes.
$(BUILD_NUMBER_FILE): $(OBJECTS)
@if ! test -f $(BUILD_NUMBER_FILE); then echo 0 > $(BUILD_NUMBER_FILE); fi
@echo $$(($$(cat $(BUILD_NUMBER_FILE)) + 1)) > $(BUILD_NUMBER_FILE)
The linker flags cause the linker to create two symbols: __BUILD_NUMBER and __BUILD_DATE which will be equal to the build number and the build-date respectively. The build-date is set using the standard date command. The build number is simply the value contained in the build number file, which is extracted using the standard cat command.
The make rule for the build number file depends on all the project object files and if any of them changes the build number is incremented by executing the following commands:
if ! test -f build-number.txt; then echo 0 > build-number.txt; fi
echo $(($(cat build-number.txt) + 1)) > build-number.txt
The test program bnum.c merely writes out the build number and build-date:
#include <stdio.h>
extern char __BUILD_DATE;
extern char __BUILD_NUMBER;
main()
{
printf("Build date : %u\n", (unsigned long) &__BUILD_DATE);
printf("Build number: %u\n", (unsigned long) &__BUILD_NUMBER);
}
A sample of iterative builds is shown below:
$ rm bnum.o; make
cc -c -o bnum.o bnum.c
cc -Xlinker --defsym -Xlinker __BUILD_DATE=$(date +'%Y%m%d') \
-Xlinker --defsym -Xlinker __BUILD_NUMBER=$(cat build-number.txt) -o a.out bnum.o
$ ./a.out
Build date : 20080708
Build number: 24
$ rm bnum.o; make
cc -c -o bnum.o bnum.c
cc -Xlinker --defsym -Xlinker __BUILD_DATE=$(date +'%Y%m%d') \
-Xlinker --defsym -Xlinker __BUILD_NUMBER=$(cat build-number.txt) -o a.out bnum.o
$ ./a.out
Build date : 20080708
Build number: 25
One caveat to an auto-incrementing build number is that just because you have two versions of a program with different build numbers it does not mean they are functionally different. If you routinely run make clean; make all just for fun, you'll get a new build number but nothing will have changed.
Mitch Frazier is an Associate Editor for Linux Journal.
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Sponsored by AMD
Built-in forensics, incident response, and security with Red Hat Enterprise Linux 6
Every security policy provides guidance and requirements for ensuring adequate protection of information and data, as well as high-level technical and administrative security requirements for a system in a given environment. Traditionally, providing security for a system focuses on the confidentiality of the information on it. However, protecting the data integrity and system and data availability is just as important. For example, when processing United States intelligence information, there are three attributes that require protection: confidentiality, integrity, and availability.
Learn more about catching the bad guy in this free white paper.
Sponsored by DLT Solutions
| Designing Electronics with Linux | May 22, 2013 |
| Dynamic DNS—an Object Lesson in Problem Solving | May 21, 2013 |
| Using Salt Stack and Vagrant for Drupal Development | May 20, 2013 |
| Making Linux and Android Get Along (It's Not as Hard as It Sounds) | May 16, 2013 |
| Drupal Is a Framework: Why Everyone Needs to Understand This | May 15, 2013 |
| Home, My Backup Data Center | May 13, 2013 |
- Linux Systems Administrator
- Senior Perl Developer
- Technical Support Rep
- New Products
- UX Designer
- Web & UI Developer (JavaScript & j Query)
- Designing Electronics with Linux
- Dynamic DNS—an Object Lesson in Problem Solving
- Using Salt Stack and Vagrant for Drupal Development
- Making Linux and Android Get Along (It's Not as Hard as It Sounds)
- Have you tried Boxen? It's a
9 min 11 sec ago - seo services in india
4 hours 40 min ago - For KDE install kio-mtp
4 hours 41 min ago - Evernote is much more...
6 hours 41 min ago - Reply to comment | Linux Journal
15 hours 26 min ago - Dynamic DNS
16 hours 1 min ago - Reply to comment | Linux Journal
16 hours 59 min ago - Reply to comment | Linux Journal
17 hours 49 min ago - Not free anymore
21 hours 51 min ago - Great
1 day 1 hour ago
Enter to Win an Adafruit Pi Cobbler Breakout Kit for Raspberry Pi

It's Raspberry Pi month at Linux Journal. Each week in May, Adafruit will be giving away a Pi-related prize to a lucky, randomly drawn LJ reader. Winners will be announced weekly.
Fill out the fields below to enter to win this week's prize-- a Pi Cobbler Breakout Kit for Raspberry Pi.
Congratulations to our winners so far:
- 5-8-13, Pi Starter Pack: Jack Davis
- 5-15-13, Pi Model B 512MB RAM: Patrick Dunn
- 5-21-13, Prototyping Pi Plate Kit: Philip Kirby
- Next winner announced on 5-27-13!
Featured Jobs
| Linux Systems Administrator | Houston and Austin, Texas | Host Gator |
| Senior Perl Developer | Austin, Texas | Host Gator |
| Technical Support Rep | Houston and Austin, Texas | Host Gator |
| UX Designer | Austin, Texas | Host Gator |
| Web & UI Developer (JavaScript & j Query) | Austin, Texas | Host Gator |
Free Webinar: Hadoop
How to Build an Optimal Hadoop Cluster to Store and Maintain Unlimited Amounts of Data Using Microservers
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Some of key questions to be discussed are:
- What is the “typical” Hadoop cluster and what should be installed on the different machine types?
- Why should you consider the typical workload patterns when making your hardware decisions?
- Are all microservers created equal for Hadoop deployments?
- How do I plan for expansion if I require more compute, memory, storage or networking?



Comments
I'm working on a large
I'm working on a large project with multiple modules. We would like to have an incrementing build number for each module but the individual modules don't do any linking in the make file so I'm having trouble setting this up. Do you have any advice to be able to use this without linking or to link from the main make file and increment the individual modules build number when they are built?
Perhaps CPP
You could do a similar thing using the -D option of the C compiler, e.g.
and then have an include file something like this that gets included in every file:
That would give each object file a build number and date. You could also add the file name and put all the version numbers into a separate data section so that all of the build info is in one place.
Consider the following structure:
struct build_info_t { char* filename; unsigned long build_date; unsigned long build_number; };Change the include file to:
asm(" .section .build_info, \"d\"\n"); static const struct build_info_t build_info = { __FILE__, __BUILD_DATE, __BUILD_NUMBER }; asm(" .data\n");Now you can modify your link script so that all the ".build_info" sections get put together in one place. Add a symbol at the start of the section so that you can obtain the address of the section. Also add a zero at the end of the section so you have a sentinel. With this you can now get a pointer to an array of all the build_info_t structures and print them out if you wanted to know when things were built:
// Symbol added to front of build_info section in linker script. extern char __build_info; struct build_info_t* binfo = (struct build_info_t*) &__build_info; while ( binfo->filename ) { // Sentinel will end the loop printf("Filename : %s", binfo->filename); printf("Build Date : %s", binfo->build_date); printf("Build Number: %s", binfo->build_number); binfo++; }Note, I haven't actually tested the above so I may have some syntax wrong or may have overlooked something important. For one thing check to make sure that optimization doesn't remove the entire structure since it's static and unused.
Mitch Frazier is an Associate Editor for Linux Journal.
What does the -D option do?
What does the -D option do? I'm using an older gcc (4.1.2) and can't find any documentation with the -D option..
-D Option
The -D option does the same thing as #define. It's documented on the gcc man page. Every C compiler I've ever seen has it, and it's one of the most commonly used options when compiling C code.
Mitch Frazier is an Associate Editor for Linux Journal.
Valid for a shared library
Hello,
Imagine the same solution for a shared library. I tested it and it does not work. It seems that it is unable to get the address of the symbol...
when doing nm mylib.so, I have my symbol __BUILD_LIBVER but no way to get it.
Any idea to solve this issue?
Best Regards,
Pascal
Shared Library
When you say "It seems that it is unable to get the address of the symbol", what do you mean? Does the linking fail or the linking succeeds but the value is wrong?
Have you tried adding a function to your library that returns the value? Does that work?
Mitch Frazier is an Associate Editor for Linux Journal.
Thanks for replying... I have
Thanks for replying... I have headache about that! :)
First I want to say your solution is my prefered one compared to a script updating a header file with #define BUILD_NUM XXXX because changing header file changes the dependencies and then need to compile it again for no reason... In complex makefile, I find it difficult to do it. Anyway, it's another discussion...
First I have to say I tested your solution on a standard binary. It works fine.
But I have in charge of a "big" software using a lots of shared libraries. And I would like to use your solution in every artifacts I build (binaries, shared libraries).
So I did a basic test project aiming at verifying it's ok...
and here's the results:
pascal@titan:~/devel/build_number2$ export LD_LIBRARY_PATH=./ pascal@titan:~/devel/build_number2$ ./testLib Version 7610387 <- this a "random value" (not the right one) pascal@titan:~/devel/build_number2$ nm libVersion.so 000004bc T GetVersion 00001f10 a _DYNAMIC 00001ff4 a _GLOBAL_OFFSET_TABLE_ w _Jv_RegisterClasses 00000013 A __BUILD_LIBVER <-- this is my symbol 00001f00 d __CTOR_END__ 00001efc d __CTOR_LIST__ here's the sources: Makefile (extract) ------------------ all: $(PROGRAM) include buildnumber.mk $(PROGRAM): $(objects) $(LIBRARY) $(CC) -o $@ $(LIBRARY) $(objects) $(LIBRARY): libVersion.c libVersion.h $(BUILD_NUMBER_FILE) $(CC) $(LDFLAGS) $(BUILD_NUMBER_LDFLAGS) -o $@ $< source code ----------- pascal@titan:~/devel/build_number2$ cat libVersion.c #include "libVersion.h" void GetVersion() { printf("Version %u\n", (unsigned long)&__BUILD_LIBVER); } pascal@titan:~/devel/build_number2$ cat libVersion.h #ifndef __LIBVERSION #define __LIBVERSION #include extern char __BUILD_LIBVER; void GetVersion(void); #endifBest Regards
Pascal
Shared Library
You say "random value" in:
Is it random in that it comes out differently each time you run it or is it always the same but wrong? (in which case it's not random)
I suspect the value you're getting is the load address of the shared library plus the value of the symbol. If you take your value "7610387" and convert it to hex you get "742013" which is "742000" plus your version number.
Mitch Frazier is an Associate Editor for Linux Journal.
End of story
You are right.
I created a symbol with a fixed value as a reference and I can retrieve my version from shared libraries. Maybe not the most elegant way but it works!
Thank you so much.
Can you pls elaborate on what
Can you pls elaborate on what you mean by "created a symbol with a fixed value as a reference". I need the value *without* the so load address.
thanks
Fixed Symbol
Use the same method to create a symbol whose value is always the same (zero probably works best), for example:
Now in your code when you want the value of __BUILD_NUMBER, use the value of __LOAD_OFFSET to "remove" the load offset of the shared library, e.g.
unsigned long build_num = (unsigned long) &__BUILD_NUMBER - (unsigned long) &__LOAD_OFFSET;Mitch Frazier is an Associate Editor for Linux Journal.
I personally don't like this
I've done a fair amount of work in trying to verify what source object code came from, and guaranteeing that a build of that same source at another point in time will produce identical output. This is largely of interest to a distribution that wants to make sure their source packages recompile as expected.
This sort of "feature" is quite annoying in doing that. lots of packages do similar things to this. There is even a gnu cpp compiler macro that you can use (if forget what it is).
The problem is that identical source input starts to produce binary output that differs every time a second ticks off the clock.
just my 2 cents.
Avoid a circular reference
This is a nice tip.
However, make will complain about a circular reference.
Instead of:
OBJECTS=bnum.o
You may want to do this:
SOURCES=bnum.c
OBJECTS=$(SOURCES:.c=.o)
Then - (note dependency change)
$(BUILD_NUMBER_FILE): $(SOURCES)
@if ! test -f $(BUILD_NUMBER_FILE); then echo 0 > $(BUILD_NUMBER_FILE); fi
@echo $$(($$(cat $(BUILD_NUMBER_FILE)) + 1)) > $(BUILD_NUMBER_FILE)
This will avoid a circular reference. However, you only get a build number increment for source file changes.
Doesn't seem to complain here
I don't get any complaints from make. Did you actually test this and get a complaint from make?
Seems like BUILD_NUMBER_FILE should depend on the same thing that linking depends on, so that every time you re-link you get a new version number. Not relevant for this example, but in more complex cases, depending on the sources would miss changes in header files.
Mitch Frazier is an Associate Editor for Linux Journal.