BuildNumberProcessing

garyo edited this page Dec 13, 2014 · 1 revision

The accepted way to handle an 'id.c' or 'version.c' file which includes a build number or build date/time in the executable but doesn't rebuild anything when that date/time or number changes is to use Requires(). See the SCons user guide for a fully worked example: http://www.scons.org/doc/HTML/scons-user/ch06s08.html (if in the future that link breaks, just go to the user guide and look for "Order-Only Dependencies: the Requires Function").

The rest of this page is for historical interest only.


This came from an email from Oleg.Krivosheev on the mailing list:

We have small file with static string inside which identifies build id.This build id is automatically generated during link stage.

Here is id.c

 volatile char ReleaseId[] = "Build 543, made by USER, on Oct/20/2003, 11:45:23"

The old makefile was this:

 a.exe: a.o b.o c.o
       updateReleaseId -o id.c $(USER) $(BUILDDIR) /shared/id.dat
       cc -o id.o id.c
       ld -o a.exe a.o b.o c.o id.o -lA -lB -lC

Note: updateReleaseId generates id.c with a new build number.

This solution came from AnthonyRoach:

#!python 
  env=Environment()
  env.Program('a', ['a.c', 'b.c', 'c.c', env.Object('id', 'id.c')])
  env.Command('id.c', '/release/id.dat',
              'updateReleaseId -o id.c $USER $BUILDDIR $SOURCE')

assuming USER and BUILDDIR are environment variables.


It took me a few minutes to realize this, but you can use an empty list as the source here.

So if id.c is generated entirely from the SConscript, you can say:

#!python 
  env.Command('id.c', [],
              'updateReleaseId -o id.c $USER $BUILDDIR $SOURCE')

--Stephen.Ng


Or if you want to completely emulate the way the makefile did it:

#!python 
   prog = env.Program('a', ['a.c', 'b.c', 'c.c'], CCFLAGS='id.o')
   env.AddPreAction(prog,
         ['updateReleaseId -o id.c $USER $BUILDDIR /shared/id.dat',
          'cc -c -o id.o id.c'])

The "trick" in the above is to use CCFLAGS to add the id.o target to avoid the dependency.


An alternate approach is presented here by Roberto JP:

The basic idea is to recursively call scons to build the build_id from the SConstruct file, and have the SConstruct file identify when it is called to build the build_id and then not recursively call itself.

An example is given below.

Note that this also creates the build_id using python, so it might work on windows more easily.

#!python 
import os
import sys
import SCons.Script

#so that we see the 'reading' output from scons
print "\n"


all_args=sys.argv[1:]
parser=SCons.Script.OptParser()
options,targets = parser.parse_args(all_args)


# this function returns a string containing the
# text of the build_id.c file.
#
# essentially, it creates a static character string with the
# build time, date, user, and machine.
#
def get_build_id_file_text():
        import socket
        import getpass
        import time

        build_time = time.strftime("%Y_%m_%d__%H_%M_%S", time.gmtime())
        build_username = getpass.getuser()
        #if windows (don't know how to ask that yet.. then
        #username = win32api.GetUserName()
        build_hostname = socket.gethostname()
        build_id_statements = '//this file is automatically generated \n'
        build_id_statements+= 'static char* build_id="'
        build_id_statements+= 'build_id' + '|' + build_time + '|' + build_username + '@' + build_hostname
        build_id_statements+= '";\n'
        return build_id_statements

# we use a separate build_dir
# but for here, we'll call that '.'
build_dir="."


Clean('','build_id.c')
Clean('','build_id.o')

if('build_id' in targets) :

        try : os.mkdir(build_dir)
        except :  print("build_dir already exists")

        build_id_file= file( build_dir + "/build_id.c", 'w' )
        build_id_file.write( get_build_id_file_text() )
        build_id_file.close( )

        # this is for a static build..
        build_id=AlwaysBuild(      Object(build_dir + '/build_id.o',build_dir + '/build_id.c'))

        # for a shared build you may want to make it a 'SharedObject', as in the commented out line below.
        #build_id=AlwaysBuild(SharedObject(build_dir + '/build_id.os',build_dir + '/build_id.c'))

        Alias('build_id',build_id)
else :
        # the following line invokes scons to build the build_id.
        os.system('scons build_id')

        env=Environment()
        env['build_dir']=build_dir

        # add the build_id.o flag to the LINKFLAGS.
        env['LINKFLAGS'] +=  [ build_dir + '/build_id.o']

        # and now we'd put our 'regular' scons statements.
        # note that you'd want to use env.COMMAND(f00)
        # as opposed to COMMAND(foo)
        # so that the linkflags are used.

        env.Program('foo','foo.c')
        # if you do 'strings foo' on the executable created, you should see the build_id text.


        #... etc ...

#so that we see the 'done reading' output from scons
print "\n"

Here's a related problem that I wasn't able to solve with the above techniques. I have a file Version.cpp that serves a similar purpose to id.c in the previous examples:

// Version.cpp

#define BUILD_DATE __TIME__ " " __DATE__
#define BUILD_STRING  "[built " BUILD_DATE "]"

const char *buildString() { return BUILD_STRING; }
const char *buildDate() { return BUILD_DATE; }

In this case, the file contents automatically change whenever it is recompiled, and I want that recompile to be forced whenever the library that contains Version.o is rebuilt. The rule for building the library looks something like this:

#!python 
SOURCES = [
  'Bar.cpp',
  'Foo.cpp',
  'Version.cpp',
]

static_lib = env.StaticLibrary('foobar', SOURCES)

Now dig into the internals a bit to expand the dependecies of Version.o to match those of the shared library:

#!python 
from SCons.Node import NodeList
def MakeVersionDeps(env, target, prefix='Version.'):
  # TODO add error handling/reporting
  # isinstance(target,NodeList) is needed for scons > 0.96
  if type(target) == type([]) or isinstance(target,NodeList):
    target = target[0]
  version = [child for child in target.children() if str(child).startswith(prefix)]
  others = [child for child in target.children() if not str(child).startswith(prefix)]
  if version:
    env.Depends(version, others)

MakeVersionDeps(env, static_lib)

MakeVersionDeps() also works with shared libraries, and probably other target types.


Here's another solution:

#!python 
def get_build_id():
     return "my_unique_build_id_string"

def generate_build_id(env, target, source):
     out = open(target[0].path, "w")
     out.write(source[0].get_contents())
     out.close()

Command("build_id.c", [Value(get_build_id())], generate_build_id)

This causes build_id.c to be regenerated only if the build_id changes, without the need for any external programs or similar to update the build_id.c file. The drawback is that it if the build id contains a string which is difference for each scons run, then the target will always be rebuilt with a new build id.


This page is also relevant for including time stamps and date stamps in builds (so the built code contains the build date or time). I mention this here so people can find this page if that's what they're looking for.

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.