SCons: Forcing SCons to duplicate files - scons

I have a header-only library consisting of a folder hierarchy and a bunch of .hpp files I'd like to install. My problem is, that scons does not copy the folder into the build folder.
Here is what my directory layout looks like:
root
SConstruct
subdir
SConscript
the_lib
subdir_a
header_a.hpp
subdir_b
header_b.hpp
build
(...)
Here is what I do in subdir/SConscript:
all_headers = []
for root, dirnames, filenames in os.walk('.'):
for filename in fnmatch.filter(filenames, '*.hpp'):
fn = os.path.join(root, filename)
all_headers.append((fn, root))
for f, d in all_headers:
install.AddHeader( f, d )
I do this to get the filenames along with their relative paths and then, I use the installer I found in the scons wiki the other day.
Observation: all_headers remains empty because the the_lib folder does not get copied. I tired subdir_env.Dir('the_lib'), but did not change a thing.
After running the script, I have the_lib/SConscript in my build folder, but nothing else. Of course I can understand that my filesystem walk does nothing in that case.
Can anyone help me?
UPDATE
The only way out I found was to run a find -name "*.hpp" and paste the result into my SConscript. Works like a charm now, but since the library is an external one (and maybe some files are added or removed), I thought of a more generic solution without the need to know all headers by name.

The first thing I thought of was to use the SCons Install() builder, but that is to install actual SCons targets in different locations, and since these header files are not targets, that wont work.
So, in this case, you can use what is called the SCons Copy Factory.

if build is a VariantDir then you don't need to copy the file yourself, scons will do it if the header is used in any Builder.
If you want to a list of the files you can use env.Glob('*/*.hpp') (but wildcards won't traverse directories, so you need to know the depth)

Related

How do I get Scons to remove unused source files from a variant directory

I have a generated file from all of my sources, and the source generator uses a glob. Since I use variant directories to build, all (and only) the source that is in this build should placed in the variant directory so I pointed the glob at that dir to scrape from. When I remove a file from the build, this fails as the .c file I removed is still in the variant dir from a previous build. Clean does not remove sources copied into variants, so even clean builds don't work, I have to remove all the directory (or at least *.c) to get a good build again. Is there a way I can either: get scons to remove the extra sources, get a list of the no-longer needed sources so I can remove them pre-generation, or .....
I can not easily change the generator to take a list of files, tho that is the obvious 'best' solution.
SConstruct
VariantDir('build','src')
Command('generated.h', ['src1.c','src2.c'], 'python scrape_sources $TARGET.dir')
scrape_sources
import glob
import sys
f = open('generated')
f.write(','.join(glob.glob(sys.args[1]+'*.c'))
f.close()
When I remove src2.c from the list, the source file is still in build from the previous run and thus scanned. I want a way to nicely remove the sources no longer in the build.

Building relative to src/ directory with SCons

I have an app with the following (I would have thought quite common) directory hierarchy:
/src
subdir1/ # Subdirs with more source files.
more.c
SConscript
foo.c # Source files.
foo.h
SConscript
/other # Other top-level directories with no source code.
/stuff # However, there are other assets I may want to build.
README # Other top-level files.
SConstruct
The problem is that when I run scons from the top-level directory, it calls gcc from that directory without cding into src, like this:
gcc -o src/foo.o src/foo.c
This is problematic for several reasons:
Within my program, I #include files giving the path relative to the src directory. For example, more.c could include foo.h with #include "foo.h". This fails because GCC is run from the parent directory. I don't want to change my includes to #include "src/foo.h".
I use the __FILE__ special macro for things like logging. When built from the top-level directory, GCC puts "src/" at the front of all filenames, since that was the path it was given to compile. This may seem picky, but I don't want that, because I think of my source tree as being relative to the src directory.
(Edit: I should add that obviously #1 can be fixed by adding -Isrc as a flag to GCC, but this seems like more hacks around the main issue.)
How can I make SCons cd into the src directory before calling gcc?
I don't want to get rid of the src directory and move everything up, because there are lots of other (non-code) files at the top level.
I don't want SCons to cd into every subdirectory. It should just cd into src and then build all files in the hierarchy from there.
I could solve this by moving SConscript inside the src directory and running it from there, perhaps using a Makefile at the top level. But this seems quite hacky, and I also do want to use SCons to build (non-code) assets in other directories than src.
I've read that you can make a custom Builder and make it change directories. However, I do not want to write a whole new Builder for C/C++. Is there a way to modify the behaviour of an existing builder without writing one from scratch? Also, on one forum, someone said that changing directories from within a Builder would break parallel builds since it would change the directory that other tasks are building from. Is this true?
This behavior is just how SCons works, and its not possible to avoid/change. I've been looking for some supporting documentation in its favor, and havent found any. Its just something Ive become used to.
As you mention, the include paths are simple to fix. The harder part is the __FILE__ macro. I hadnt ever noticed this until you mentioned it. Unfortunately, I think the only way around this is to strip the path in the logger, which is a rather ugly fix.
You can add src to your env['CPPPATH'] which will fix the include paths.
env.Append(CPPPATH=[Dir('src')])
However, this doesn't solve the problem with __FILE__.

SCons Output in Build directory

I'm trying to modify my SCons files so that they put the generated files into a build directory. Initially I though VariantDir could be an option but judging from all I read and the examples it does not do what I want.
Is there any easy way to force SCons to put the output in a certain directory without having to rewrite all the sources and scripts?
After struggling with VariantDir for a while (it wasn't doing anything at all), I ended up using variant_dir parameter in the top level SConscript call, which causes all downstream build outputs end up in a parallel 'build' tree:
SConscript(['subdirs/SConscript'], variant_dir='build', duplicate=0)
My build structure is a hierarchy of SConscripts in subdirs/sub-subdirs, etc. With this call the outputs end up in build/sub-subdirs at the same level as they would in the source.
This eats up one level, though (subdirs), and using "../build" does not help. The solution is to have a SConscript file at the same level as SConstruct and call SConscript(['SConscript'], variant_dir='build', duplicate=0)
See also Force Scons output (exe, obj, lib & dll) to specific build directory - it has a similar answer
Using VariantDir with duplicate=0 should work.
Facing similar frustration, I added a site_scons that added replacement builders (e.g. "Exe" instead of "Program") and specified an emitter for that builder that replaced the path portion with the build directory. This requires the use of the alternate builder throughout your SConscripts though.
Alternatively you could try to subclass Environment and rewrite the main targets to use target rewrites. Then you specify your Environment as the default (modifying Scons.Script.DefaultEnvironment or something like that). This approach kept the SConscripts static but got very messy and requires more maintenance over time as scons internals change.
You might use Install or InstallAs on target output. It works for me.
lib = env.SharedLibrary(target = "some_target", source = sources);
env.InstallAs( target = "folder/output_name.ext", source = lib );

Multiple locations within a folder hierarchy to run SCons from

So far, I've only seen examples of running SCons in the same folder as the single SConstruct file resides. Let's say my project structure is like:
src/*.(cpp|h)
tools/mytool/*.(cpp|h)
What I'd like is to be able to run 'scons' at the root and also inside tools/mytool. The latter compiles only mytool. Is this possible with SCons?
I assume it involves creating another SConstruct file. I've made another one: tools/mytool/SConstruct
I made it contain only:
SConscript('../../SConstruct')
and I was thinking of doing Import('env mytoolTarget') and calling Default(mytoolTarget), but running it with just the above runs in the current directory instead of from the root, so the include paths are broken.
What's the correct way to do this?
You can use the -u option to do this. From any subdirectory, scons -u will search upwards in the directory tree for an SConstruct file.

How to force Scons output (exe, obj, lib & dll) to specific build directory?

I've been trying to get scons to output exe, obj, lib and dll files to a specific build directory.
My file structure looks like this:
/projectdir
/build
/bin
/obj
/source
/subdirs
/..
SConstruct
Basically, what I get now is my source directory is getting polluted with obj files. I'd rather have it all in one place.
The SConstruct file looks like this:
env.VariantDir('build', 'source', duplicate = 0)
env.Program('Hierarchy', source = ['source/sconstest.cpp', 'source/utils/IntUtil.cpp'])
The easiest way I've found is to use 2 files, a SConstruct file and a separate SConscript.
In the SConstruct you simply call the other file and specify the directory for the build output:
# content SConstruct
SConscript('main.scons', variant_dir='build', duplicate=0)
Then in 'main.scons' you do the meat of your build. You can forget about variant directories in this file.
# content of main.scons
env = Environment()
env.Program('Hierarchy',
source = ['source/sconstest.cpp', 'source/utils/IntUtil.cpp'])
It's not that tough to get VariantDir working using only one SConstruct file (for a small project), but it's very confusing as the configuration is different for the one-file and two-file use case.
Only SConstruct:
env = Environment()
env.VariantDir('build', 'src', duplicate=0)
files = Glob('build\*.c')
env.Program("build\program", files)
Notice how the source files are located in .\src but .\build is specified as the location. The output has to be also "prefixed" with .\build otherwise the compiled program will reside in the directory of the SConstruct file.
When you execute the script SCons will compile the *.c files from .\src and put the resulting objects to .\build.
No wonder they renamed BuildDir to VariantDir to try to avoid the confusion (without much success).
The VariantDir (also described in the user guide) tells scons to put generated files in a separate directory. In older versions of scons this function was named BuildDir.
You may also want to read up on avoiding duplicating the source directory (described both in the user guide and on the wiki).
I was using a two-file method like richq's answer, but although the final build products (libs, programs) were going into the right variant directory, the object files were still going to the source directory.
The solution turned out to be to glob the source files by relative path instead of absolute. I have no idea why.
My second scons file originally looked like this. Note globbing by absolute path - when I first wrote this I didn't realize paths would automatically be relative to the scons file.
import os, inspect
env = Environment()
packageDir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
src = Glob(os.path.join(packageDir, "src/*/*.c*"), strings=True, source=True)
env.Program('Foo', source = src)
And that resulted in *.obj ending up under src/ and the program under my variant dir. When I changed it to the following, the object files also went to the variant dir:
env = Environment()
src = Glob("src/*/*.c*", strings=True, source=True)
env.Program('Foo', source = src)
Using absolute paths is probably a noob mistake - I'm relatively new to both scons and Python - but I thought I'd share it in case anyone else has the same frustrating problem.

Resources