Please note:The SCons wiki is now restored from the attack in March 2013. All old passwords have been invalidated. Please reset your password if you have an account. If you note missing pages, please report them to webmaster@scons.org. Also, new account creation is currently disabled due to an ongoing spam flood (2013/08/27).

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 2008-03-12 02:46:58 by localhost)