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 -lCNote: 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.
