Add an Auto-Incrementing Build-Number to Your Build Process

 in

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)
This make file builds a.out from the file bnum.c (through a make built-in rule).

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 first few lines define the linker flags and the rule defines the mechanism for incrementing the build number.

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 first command checks to see if the build number file exists. If it doesn't a single line of text, a zero is written to it. The second command uses cat to get the line of text and the shell's built-in arithmetic evaluation $((expr)) to increment it and write it back to the build number file.

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);
}
Note that linker symbols are not variables, they have no memory allocated for maintaining a value, rather their address is their value.

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.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

I'm working on a large

lou's picture

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

Mitch Frazier's picture

You could do a similar thing using the -D option of the C compiler, e.g.

   cc -D__BUILD_DATE=$(date +'%Y%m%d') -D__BUILD_NUMBER=$(cat build-number.txt) ...

and then have an include file something like this that gets included in every file:

static const unsigned long BUILD_DATE   = __BUILD_DATE;
static const unsigned long BUILD_NUMBER = __BUILD_NUMBER;

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?

lou's picture

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

Mitch Frazier's picture

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

Pascal's picture

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

Mitch Frazier's picture

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

Pascal's picture

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);

#endif

Best Regards
Pascal

Shared Library

Mitch Frazier's picture

You say "random value" in:

pascal@titan:~/devel/build_number2$ ./testLib
Version 7610387 <- this a "random value"

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

Anonymous's picture

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

Brad Hoskins's picture

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

Mitch Frazier's picture

Use the same method to create a symbol whose value is always the same (zero probably works best), for example:

   ... --defsym -Xlinker __LOAD_OFFSET=0

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

smoser's picture

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

V-12 Bill's picture

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

Mitch Frazier's picture

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.

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState