Override an SCons builder - scons

I want to do some post-processing on all .o files (e.g. those generated by the Object or StaticObject Builders), no matter what Builder is used by the user.
I'm trying to "override" or "hook" the base environment's builders like so, but I can't come up with anything that works:
old = env['BUILDERS']['StaticObject']
env['BUILDERS']['StaticObject'] = something_that_calls(old)
Is there a prescribed way to hook or override something like the StaticObject builder?
I've seen the question about a Nested SCons Builder, but it doesn't tell me how to replace an existing builder, only supplementing its behavior.

I don't know if there is a blessed way to replace a Builder, but I think you're on the right track. The following (admittedly trivial) example works for me:
def wrap_builder(bld):
return Builder(action = [bld.action, 'echo $TARGET'],
suffix = bld.suffix,
src_suffix = bld.src_suffix)
obj_bld = env['BUILDERS']['Object']
env['BUILDERS']['Object'] = wrap_builder(obj_bld)
env.Program('test', ['test.c'])
with output:
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o test.o -c -std=c99 test.c
echo test.o
test.o
gcc -o test test.o
scons: done building targets.
As you can see, the additional (echo) action is performed after building the object file.

Related

How to modify a list of filenames in scons?

I use to use this simple Makefile to compile all .cc files in the current directory
SRCS:=$(wildcard *.cc)
OBJS:=$(SRCS:.cc=)
CXX := clang++
CXXFLAGS := -std=c++11 -g
all: $(OBJS)
I'm trying to translate this into an SConstruct file. I can use scons' Glob built-in to get the .cc file list but I don't know how to remove their suffix (like the OBJS := $(SRCS:.cc=) do). Of course I can write Python code to do the modification but does scons has built-in support for this kind of modification?
UPDATE:
My original SConstruct file (literally list all the .cc files)
env = Environment(CXX="clang++", CXXFLAGS=['-std=c++11', '-g'])
env.Program("1.1.cc")
env.Program("1.2.cc")
env.Program("1.3.cc")
env.Program("1.4.cc")
One version that works
import glob
env = Environment(CXX="clang++", CXXFLAGS=['-std=c++11', '-g'])
sources = glob.glob("./*.cc")
for s in sources:
env.Program(s)
Based on your response to my question in the comments above, it appears that you want SCons to automatically create a binary or object file based on the source file.
This can be done in SCons as follows:
env = Environment()
# This will build example.o
env.Object('example.cc')
# This will build main
env.Program('main.cc')
Here is the output from this build:
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o example.o -c example.cc
g++ -o main.o -c main.cc
g++ -o main main.o
scons: done building targets.
If you need to build a binary with more than one source file, then you will need to specify the binary name, as follows:
Program(target = 'myBinary', source = ['main.cc', 'example.cc'])
This will create main.o and example.o, but cant know the name of the binary/program, so you have to specify it.
As for your example with glob, Scons has a built-in Glob() function, so you could do the following:
env = Environment(CXX="clang++", CXXFLAGS=['-std=c++11', '-g'])
sources = Glob("./*.cc")
for s in sources:
env.Program(s)
The SCons Glob() function is not recursive, so if you need to recursively list files, then you'll need to do it differently.

scons: changing compilation flags for a single source file

I have a fairly complex scons system with several subdirectories, with many libraries and executables.
Currently, every SConscript gets its own cloned environment, so I can easily change CFLAGS (or any other wariable) on a per-SConscript basis, but I'd like to change it per-target, and even per-object-file within a target.
I created a simple example SConscript and SConstruct to explain the problem, as follows.
SConstruct:
env = Environment()
env['CFLAGS'] = '-O2'
env.SConscript('SConscript', 'env')
SConscript:
Import('env')
env=env.Clone()
env.Program('foo', ['foo.c', 'bar.c'])
If I run scons, both foo.c and bar.c compile with -O2 flags. I could easily change flags SConscript-wide by just adding env['CFLAGS'] = '...' within the SConscript, but let's say that I want to compile foo.c with -O2, but bar.c with full debugging, -O0 -g. How do I do that (in the simplest possible way)?
The example uses gcc, but I'd like something that can be used with any compiler.
This happens frequently with performance-sensitive projects where compiling everything without optimization would result in unacceptable performance, but there is a need to debug one single file (or a subset of them).
The simplest one-liner answer is probably just to replace your Program line with this:
env.Program('foo', ['foo.c', env.Object('bar.c', CFLAGS='-g')])
because Program can take Object nodes as well as source files, and you can override any construction variable(s) in any builder (here, we override CFLAGS in the Object builder call). If you want to break out the Object into its own line for clarity:
debug_objs = env.Object('bar.c', CFLAGS='-g')
env.Program('foo', ['foo.c', debug_objs])
and of course taking that to the limit you get a system like Avatar33 showed above.
I suppose this is a bit harder in scons than it would be in make where you could just clean the required target and rebuilt with debug flags. Which would then just rebuild a specific object.
The solution to your particular project depends on it's size and how much effort the developer is prepared to put in.
So here's a rough solution where you specify source files on the command line that you want to be compiled with debug and no optimization, the rest will be compiled with -O2.
In your SConsctruct one additional line to get source files that we want to compile with debug from a command line option:
env = Environment()
env['CFLAGS'] = '-O2'
AddOption('--debug-targets', dest='debug-targets', type='string')
env.SConscript('SConscript', 'env')
And now in the SConscript file:
Import('env')
env=env.Clone()
debug_env = env.Clone()
debug_env['CFLAGS'] = '-g -O0'
normal_src = ['foo.c', 'bar.c']
debug_src = []
#Add src specified via the command line to the debug build
if GetOption('debug-targets'):
for x in GetOption('debug-targets').split(','):
if x in normal_src:
normal_src.remove(x)
debug_src.append(x)
normal_obj = env.Object(normal_src)
debug_obj = debug_env.Object(debug_src)
all_obj = normal_obj + debug_obj
env.Program('foo', all_obj)
Running our scons with out our debug-targets flag:
scons -Q
gcc -o bar.o -c -O2 bar.c
gcc -o foo.o -c -O2 foo.c
gcc -o foo foo.o bar.o
But now we want to compile bar.c with debug info:
scons -Q --debug-targets=bar.c
gcc -o bar.o -c -g -O0 bar.c
gcc -o foo foo.o bar.o
So that adds a bit of complexity to your build system, but if you don't need to specify debug targets from the command line like that, then the developer can obviously just cut and past sources from the normal_src list to debug_src.
There's probably many ways to improve and fine tune this for your specific environment

Scons: Generating version file only if target has changed

I have a requirement to generate version.cc file from SCons Script. This file should be generated only if any of the source file for a target has changed.
Suppose the SCons script has the following statements
#python function which generates version.cc in the same folder where libtest.a is generated. This will always generate a differnt version.cc because the version string contained inside that will have timestamp
GenerateVersionCode()
#target which uses version.cc
libtest = env.Library('test', ['a.cc', 'b.cc', 'version.cc'])
First time when I run the above code everything is fine. But when I run the same script again, the target 'test' will be rebuilt because of new version.cc which got generated.
My requirement is we should not generate new version of version.cc file if the file is already present and there are no changes in any of the sources (namely a.cc and b.cc in this example)
if not version_file_present:
GenerateVersionCode()
else
if no_changes_in_source:
GenerateVersionCode()
#target which uses version.cc which could be newly generated one or previous one
libtest = env.Library('test', ['a.cc', 'b.cc', 'version.cc'])
One related question on this site suggested something as follows
env.Command(target="version.c", source="version-in.c",
action=PythonFunctionToUpdateContents)
env.Program("foo", ["foo.c", "version.c"])
W.r.to the above suggestion I would want to know the contents of function PythonFunctionToUpdateContents which checks the change in source files since previous build.
As I understand it, you only want to generate version.cc if any of the source files change, and you only want to build the library if version.cc changes or if any of the library source files change. That is, consider version.cc as one of the source files for the library.
If this is the case, you could consider 2 sets of dependencies, both of which would be controlled by the SCons dependency checking.
Its not real clear what the version.cc generation consists of, but lets assume that the python function GenerateVersionCode() will do exactly that: generate version.cc, but wont have any dependency checking related logic.
Here is the SConscript code:
def GenerateVersionCode(env, target, source):
# fill in generation code here
# The version.cc checking
env.Command(target='version.cc',
source=['a.cc', 'b.cc'],
action=GenerateVersionCode)
# The library
env.Library(target='test', source=['version.cc', 'a.cc', 'b.cc'])
It shouldnt be necessary, but this could be taken one step further by explicitly setting a dependency from the Library target to the version.cc target with the SCons Depends() function.
Here is the output I get when I build, and instead of using the GenerateVersionCode() function, I use a simple shell script versionGen.sh, thus changing the call to Command() to this:
env.Command(target='version.cc',
source=['a.cc', 'b.cc'],
action='./versionGen.sh')
Here is the first build:
> scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o a.o -c a.cc
g++ -o b.o -c b.cc
./versionGen.sh
g++ -o version.o -c version.cc
ar rc libtest.a version.o a.o b.o
ranlib libtest.a
scons: done building targets.
Then, without having changed anything, I build again, and it does nothing:
> scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
scons: `.' is up to date.
scons: done building targets.
Then, I modify a.cc, and build again, and it generates a new version of version.cc:
> vi a.cc
> scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o a.o -c a.cc
./versionGen.sh
g++ -o version.o -c version.cc
ar rc libtest.a version.o a.o b.o
ranlib libtest.a
scons: done building targets.

scons generating incorrect include path directive in build

I've got a scons build using a simple, common directory setup:
project/
SConstruct
src/
file.cpp
SConscript
include/
namespace/
header.h
In file.cpp, I include header.h via #include "namespace/header.h" so what I want to do is simply add the include directory to the include path list. From the source (and SConscript) point of view, that path is "../include" but the build command always has an invalid path for the include in it. I've tried the following in the SConscript:
env.Append(CPPPATH = ["#include"])
env.Append(CPPPATH = [Dir("include")])
env.Append(CPPPATH = [os.getcwd() + os.sep + ".." + os.sep + "include"])
env.Append(CPPPATH = ["../include"])
env.Append(CPPPATH = ["#../include"])
none of which seem to work. The first four give "-Iinclude" while the last puts the include at the directory level above project! Here's the full SConscript
env = Environment()
import os
target_name = "device"
source_files = Split("""
file.cpp
""")
env.Append(CPPPATH = ["#include", os.environ.get("SYSTEMC_PATH"),
os.environ.get("SYSTEMC_TLM_PATH"), os.environ.get("BOOST_PATH")])
object_list = env.SharedObject(source = source_files)
targetobj = env.SharedLibrary(target = target_name, source = object_list )
Default(targetobj)
And the SConstruct is just:
import sys
SConscript('src/SConscript', variant_dir='Release-'+sys.platform, duplicate=0, exports={'MODE':'release'})
SConscript('src/SConscript', variant_dir='Debug-'+sys.platform, duplicate=0, exports={'MODE':'debug'})
I'm running scons from the directory where the SConstruct is located (i.e. the top level directory).
I've done some looking and according to the scons doco, the # is supposed to tell scons to generate the path from the current directory of the SConscript (which is the src directory) - I'm assuming this is instead of the SConstruct directory??? Further, I can't see any questions out there about this particular problem (on this site or via Google in general), usually I'm just hitting people asking for scons scripts for exactly the setup I've got already (which is to add "include" to the CPPPATH).
Any thoughts on where this is going awry?
'#' is relative to the top-level SConstruct, as per the SCons manual http://scons.org/doc/HTML/scons-user/x3240.html
The scripts you provide above build successfully when I recreate the tree you specify. Here's the working output:
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o Release-cygwin/file.os -c -Iinclude src/file.cpp
g++ -o Release-cygwin/device.dll -shared Release-cygwin/file.os
g++ -o Debug-cygwin/file.os -c -Iinclude src/file.cpp
g++ -o Debug-cygwin/device.dll -shared Debug-cygwin/file.os
scons: done building targets.

scons Dependencies for wrapper methods

I use a wrapper method to combine static libraries as shown below.
def MergeLibs(env, tgt, src_list)
....
return lib
and used as,
lib = env.MergeLibs(tgt, src_lists)
env.Depends(lib, <path_to_lib1>)
...
env.Depends(lib, <path_to_libn>)
But MergeLibs() method is being executed in scons parse phase itself.
How can I use dependencies here.
Thanks
Well I'm not too sure on the details of the MergeLib step but it seems like you would want something else to depend on your merge lib step.. like your final program?
import SCons
env = Environment()
def merge_libs(self, target, source, env):
print "hi"
return env.StaticLibrary(target, source)
env.Append(BUILDERS = {'MergeLibs' : merge_libs})
lib = env.MergeLibs('mrglibs', ['some_file.cpp', 'some_file2.cpp'], env)
prog = env.Program('test.cpp')
env.Depends(prog, lib)
This gives me the output:
scons: Reading SConscript files ...
hi
scons: done reading SConscript files.
scons: Building targets ...
g++ -o some_file.o -c some_file.cpp
g++ -o some_file2.o -c some_file2.cpp
ar rc libmrglibs.a some_file.o some_file2.o
ranlib libmrglibs.a
g++ -o test.o -c test.cpp
g++ -o test test.o
scons: done building targets.
So its definitely still read during the parse phase (I think it has to be) but it should get you what you want.

Resources