-
-
Notifications
You must be signed in to change notification settings - Fork 319
BuildNumberProcessing
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").
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.