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:

   1   env=Environment()
   2   env.Program('a', ['a.c', 'b.c', 'c.c', env.Object('id', 'id.c')])
   3   env.Command('id.c', '/release/id.dat',
   4               '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:

   1   env.Command('id.c', [],
   2               'updateReleaseId -o id.c $USER $BUILDDIR $SOURCE')

--Stephen.Ng


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

   1    prog = env.Program('a', ['a.c', 'b.c', 'c.c'], CCFLAGS='id.o')
   2    env.AddPreAction(prog,
   3          ['updateReleaseId -o id.c $USER $BUILDDIR /shared/id.dat',
   4           '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.

   1 import os
   2 import sys
   3 import SCons.Script
   4 
   5 #so that we see the 'reading' output from scons
   6 print "\n"
   7 
   8 
   9 all_args=sys.argv[1:]
  10 parser=SCons.Script.OptParser()
  11 options,targets = parser.parse_args(all_args)
  12 
  13 
  14 # this function returns a string containing the
  15 # text of the build_id.c file.
  16 #
  17 # essentially, it creates a static character string with the
  18 # build time, date, user, and machine.
  19 #
  20 def get_build_id_file_text():
  21         import socket
  22         import getpass
  23         import time
  24 
  25         build_time = time.strftime("%Y_%m_%d__%H_%M_%S", time.gmtime())
  26         build_username = getpass.getuser()
  27         #if windows (don't know how to ask that yet.. then
  28         #username = win32api.GetUserName()
  29         build_hostname = socket.gethostname()
  30         build_id_statements = '//this file is automatically generated \n'
  31         build_id_statements+= 'static char* build_id="'
  32         build_id_statements+= 'build_id' + '|' + build_time + '|' + build_username + '@' + build_hostname
  33         build_id_statements+= '";\n'
  34         return build_id_statements
  35 
  36 # we use a separate build_dir
  37 # but for here, we'll call that '.'
  38 build_dir="."
  39 
  40 
  41 Clean('','build_id.c')
  42 Clean('','build_id.o')
  43 
  44 if('build_id' in targets) :
  45 
  46         try : os.mkdir(build_dir)
  47         except :  print("build_dir already exists")
  48 
  49         build_id_file= file( build_dir + "/build_id.c", 'w' )
  50         build_id_file.write( get_build_id_file_text() )
  51         build_id_file.close( )
  52 
  53         # this is for a static build..
  54         build_id=AlwaysBuild(      Object(build_dir + '/build_id.o',build_dir + '/build_id.c'))
  55 
  56         # for a shared build you may want to make it a 'SharedObject', as in the commented out line below.
  57         #build_id=AlwaysBuild(SharedObject(build_dir + '/build_id.os',build_dir + '/build_id.c'))
  58 
  59         Alias('build_id',build_id)
  60 else :
  61         # the following line invokes scons to build the build_id.
  62         os.system('scons build_id')
  63 
  64         env=Environment()
  65         env['build_dir']=build_dir
  66 
  67         # add the build_id.o flag to the LINKFLAGS.
  68         env['LINKFLAGS'] +=  [ build_dir + '/build_id.o']
  69 
  70         # and now we'd put our 'regular' scons statements.
  71         # note that you'd want to use env.COMMAND(f00)
  72         # as opposed to COMMAND(foo)
  73         # so that the linkflags are used.
  74 
  75         env.Program('foo','foo.c')
  76         # if you do 'strings foo' on the executable created, you should see the build_id text.
  77 
  78 
  79         #... etc ...
  80 
  81 #so that we see the 'done reading' output from scons
  82 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:

   1 SOURCES = [
   2   'Bar.cpp',
   3   'Foo.cpp',
   4   'Version.cpp',
   5 ]
   6 
   7 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:

   1 from SCons.Node import NodeList
   2 def MakeVersionDeps(env, target, prefix='Version.'):
   3   # TODO add error handling/reporting
   4   # isinstance(target,NodeList) is needed for scons > 0.96
   5   if type(target) == type([]) or isinstance(target,NodeList):
   6     target = target[0]
   7   version = [child for child in target.children() if str(child).startswith(prefix)]
   8   others = [child for child in target.children() if not str(child).startswith(prefix)]
   9   if version:
  10     env.Depends(version, others)
  11 
  12 MakeVersionDeps(env, static_lib)

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

---

Here's another solution:

   1 def get_build_id():
   2      return "my_unique_build_id_string"
   3 
   4 def generate_build_id(env, target, source):
   5      out = open(target[0].path, "w")
   6      out.write(source[0].get_contents())
   7      out.close()
   8 
   9 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.

BuildNumberProcessing (last edited 2014-07-08 15:19:29 by GaryOberbrunner)