scons check for available file without caching result - scons

I'm trying to use scons functions to detect if a specific header file is available. Given a file main.cpp with a #include "fns.h". I've created this:
env = Environment(CXXFLAGS='-I.')
conf = Configure(env)
if not conf.CheckCXXHeader('fns.h'):
print 'please get fns.h'
Exit(1)
env.Program('main.cpp')
The first time I run it, it actually checks for the header, but after that it always uses the cached result
$ scons
scons: Reading SConscript files ...
Checking for C++ header file fns.h... no
please get fns.h
$ touch fns.h
$ scons
scons: Reading SConscript files ...
Checking for C++ header file fns.h... yes
scons: done reading SConscript files.
scons: Building targets ...
g++ -o main.o -c -I. main.cpp
g++ -o main main.o
scons: done building targets.
$ rm fns.h # delete it
$ scons # should check again here, but doesn't
scons: Reading SConscript files ...
Checking for C++ header file fns.h... (cached) yes
scons: done reading SConscript files.
scons: Building targets ...
g++ -o main.o -c -I. main.cpp
main.cpp:1:17: fatal error: fns.h: No such file or directory
#include "fns.h"

Please check the SCons manual (man scons), the command-line option you're looking for is "--config=force". You can also make it the default behavior for your build by setting the config option in an SConscript file:
SetOption('config', 'force')

Related

Override an SCons builder

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.

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