I prepared an example that shows how to compile a project like yours with just one SConstruct script (no subsidiary SConscripts) using the SCons VariantDir() function. I decided to do this in a separate answer so that it would be easier to read.
The VariantDir() function isnt documented very well, so the behavior you mention regarding the placement of the compiled object files isnt straight-forward to fix. The "trick" is to refer to all of your source files in the variant directory, not in your actual source directory, as can be seen below.
Here is the structure of the source files in my project:
$ tree .
.
├── SConstruct
├── src1
│ ├── class1.cc
│ └── class1.h
├── src2
│ ├── class2.cc
│ └── class2.h
└── srcMain
└── main.cc
Here is the SConstruct:
env = Environment()
# Set the include paths
env.Append(CPPPATH = ['src1', 'src2'])
# Notice the source files are referred to in the build dir
# If you dont do this, the compiled objects will be in the src dirs
src1Sources = ['build/lib1/class1.cc']
src2Sources = ['build/lib2/class2.cc']
mainSources = ['build/mainApp/main.cc']
env.VariantDir(variant_dir = 'build/lib1', src_dir = 'src1', duplicate = 0)
env.VariantDir(variant_dir = 'build/lib2', src_dir = 'src2', duplicate = 0)
env.VariantDir(variant_dir = 'build/mainApp', src_dir = 'srcMain', duplicate = 0)
lib1 = env.Library(target = 'build/lib1/src1', source = src1Sources)
lib2 = env.Library(target = 'build/lib1/src2', source = src2Sources)
env.Program(target = 'build/mainApp/main', source = [mainSources, lib1, lib2])
Here is the compilation output:
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o build/lib1/class1.o -c -Isrc1 -Isrc2 src1/class1.cc
ar rc build/lib1/libsrc1.a build/lib1/class1.o
ranlib build/lib1/libsrc1.a
g++ -o build/lib2/class2.o -c -Isrc1 -Isrc2 src2/class2.cc
ar rc build/lib1/libsrc2.a build/lib2/class2.o
ranlib build/lib1/libsrc2.a
g++ -o build/mainApp/main.o -c -Isrc1 -Isrc2 srcMain/main.cc
g++ -o build/mainApp/main build/mainApp/main.o build/lib1/libsrc1.a build/lib1/libsrc2.a
scons: done building targets.
And here is the resulting project structure after compiling:
$ tree .
.
├── build
│ ├── lib1
│ │ ├── class1.o
│ │ ├── libsrc1.a
│ │ └── libsrc2.a
│ ├── lib2
│ │ └── class2.o
│ └── mainApp
│ ├── main
│ └── main.o
├── SConstruct
├── src1
│ ├── class1.cc
│ └── class1.h
├── src2
│ ├── class2.cc
│ └── class2.h
└── srcMain
└── main.cc
It should be mentioned that a more straight-forward way to do this is with the SConscript() function, specifying the variant_dir, but if your requirements dont allow you to do so, this example will work. The SCons man page has more info about the VariantDir() function. There you will also find the following:
Note that VariantDir() works most naturally with a subsidiary SConscript file.