SconstructMultiple has much redundancy in the Sconscripts and some redundancy in the Sconstruct. Some examples:
- The list of variables to import/export is replicated in all sconscripts and in the sconstruct
- the build sequence is done in all the sconscripts
- the set up of the compile flags is done in all the sconscripts
- etc.
The new refactored sconstruct/sconscript below simplifies adding a new sub-project tremendously:
- cut and paste the sconscript to the new project directory.
add a line env.jDev.Subproject(project) at the bottom of the sconstruct with the sub-project's name
As the project matures, the sconstruct will get more complicated. The Dev class can be extended to handle the new complexity, keeping the sconscripts simple. If the Dev class becomes too large, it can be moved out of the Sconstruct into a separate .py file and/or it can be broken up into smaller classes. The sconscript files on the other hand should remain very simple. In the simple project setup below, the contents don't change at all from sub-project to sub-project.
Here's the new Sconstruct:
1 from SCons.Script.SConscript import SConsEnvironment
2 import glob
3
4 #this is our catch-all Dev class
5 #it keeps track of all the variables and common functions we need
6 class Dev:
7 mymode = ''
8 debugcflags = ''
9 releasecflags = ''
10
11 #---
12 # sets up the sconscript file for a given sub-project
13 def Subproject(self, project):
14 SConscript(env.jDev.SPath(project), exports=['project'])
15
16 #sets up the build for a given project
17 def Buildit(self, localenv, project):
18 buildroot = '../' + env.jDev.mymode
19 builddir = buildroot + '/' + project
20 targetpath = builddir + '/' + project
21
22 #append the user's additional compile flags
23 #assume debugcflags and releasecflags are defined
24 if self.mymode == 'debug':
25 localenv.Append(CCFLAGS=self.debugcflags)
26 else:
27 localenv.Append(CCFLAGS=self.releasecflags)
28
29 #specify the build directory
30 localenv.BuildDir(builddir, ".", duplicate=0)
31
32 srclst = map(lambda x: builddir + '/' + x, glob.glob('*.cpp'))
33 pgm = localenv.Program(targetpath, source=srclst)
34 env.Alias('all', pgm) #note: not localenv
35
36 #---- PRIVATE ----
37
38 #---
39 # return the sconscript path to use
40 def SPath(self, project):
41 return project + '/sconscript'
42
43 env = Environment()
44
45 #put all .sconsign files in one place
46 env.SConsignFile()
47
48 #we can put variables right into the environment, however
49 #we must watch out for name clashes.
50 SConsEnvironment.jDev = Dev()
51
52 #get the mode flag from the command line
53 #default to 'release' if the user didn't specify
54 env.jDev.mymode = ARGUMENTS.get('mode', 'release') #holds current mode
55
56 #check if the user has been naughty: only 'debug' or 'release' allowed
57 if not (env.jDev.mymode in ['debug', 'release']):
58 print "Error: expected 'debug' or 'release', found: " + env.jDev.mymode
59 Exit(1)
60
61 #tell the user what we're doing
62 print '**** Compiling in ' + env.jDev.mymode + ' mode...'
63
64 env.jDev.debugcflags = ['-W1', '-GX', '-EHsc', '-D_DEBUG', '/MDd'] #extra compile flags for debug
65 env.jDev.releasecflags = ['-O2', '-EHsc', '-DNDEBUG', '/MD'] #extra compile flags for release
66
67 #make sure the sconscripts can get to the variables
68 #don't need to export anything but 'env'
69 Export('env')
70
71 #specify all of the sub-projects in the section
72 env.jDev.Subproject('myprogram')
73 env.jDev.Subproject('hisprogram')
74 env.jDev.Subproject('herprogram')
The new Sconstruct is more complicated, but it simplifies all the Sconscripts (they are all the same):
