SCons
SCons Documentation
SCons is not included with the CD install of Linux Mint 12. It can be installed using aptitude:
While GNU Make uses its own special syntax, SCons uses Python which is fully featured and general purpose.$ sudo aptitude update $ sudo aptitude install scons
SConstruct files
The Scons equivalent of a GNU 'makefile' is a file named SConstruct, Sconstruct, or sconstruct. They are written like a Python script (though lines aren't necessarily executed in order like a normal script; they are 'declarative' rather than 'imperative'):
You can see that g++ is invoked automatically when we provide two C++ source files to the builder method Program(...) and creates a target executable of the name provided. If you were to run SCons on a Windows system, the proper compiler will be used instead and output a Windows .exe file.$ vim Sconstruct $ cat Sconstruct # simple myproject Sconstruct file Program(target = 'runme', source = Split('mymain.cc mycode.cc') ) $ scons scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... g++ -o mycode.o -c mycode.cc g++ -o mymain.o -c mymain.cc g++ -o runme mymain.o mycode.o scons: done building targets.
Split(...) is an SCons function that makes a list from the string argument (a cleaner alternative to the Python list syntax). Note that the hash-bang (#!/bin/bash) at the top of the file will result in text editors like vim or gedit to apply a more complete color scheme.
To clean up, provide the command line option '-c':
SCons will know what files need to be removed based on the structure of the Sconstruct file.$ scons -c scons: Reading SConscript files ... scons: done reading SConscript files. scons: Cleaning targets ... Removed mycode.o Removed mymain.o Removed runme scons: done cleaning targets.
Environments
Like the variables used in makefiles, we can define mutable Python objects to pass as arguments within the SConstruct script:
The function Glob( ... ) takes care of creating a list similar to the wildcard function of GNU Make.$ cat Sconstruct #!/bin/bash # myproject Sconstruct file # set up environment env = Environment( CXXFLAGS = '-Wall' ) # define the source files srcfiles = Glob('*.cc') # define build method env.Program(target = 'runme', source = srcfiles)
Be aware there are three kinds of environments in SCons. In the SConstruct file above, env is a 'construction environment'. It stores a number of 'construction variables' that may be used in building targets from sources (eg CXXFLAGS above).
A list of construction variables is available in appendix A of the user guide. The construction variable 'ENV' is another type of 'environment' in SCons: the 'execution environment'. This holds PATH which is a list of paths where SCons will look for executables (eg /usr/local/bin:/opt/bin:/bin:/usr/bin).
The last type of environment not yet mentioned is the 'external environment' which is the user's own environment at the time SCons runs. This is the os.environ dictionary so you will need to import os in any SConstruct file in order to to access it.
Seperate directories with a single SConstruct file
Below is an example of an Sconstruct file that will create object files and an executable in a build subdirectory (build/) from source files in the src/ subdirectory:
Glob() works just fine when we provide the src directory as part of the wildcard.$ cat Sconstruct #!/bin/bash # myproject Sconstruct file # set up environment env = Environment( CXXFLAGS = '-Wall' ) output = 'runme' builddir = 'build/' srcdir = 'src/' # define the source files srcfiles = Glob( srcdir + '*.cc' ) # create list of object files objfiles = [] for item in srcfiles: objfiles.append( builddir + item.name.rsplit( ".", 1 )[ 0 ] + '.o' ) # define object build methods for src, obj in zip( srcfiles, objfiles ): env.StaticObject( target = obj, source = src ) # define program build method env.Program( target = builddir + output, source = objfiles ) $ scons scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... g++ -o build/mycode.o -c -Wall src/mycode.cc g++ -o build/mymain.o -c -Wall src/mymain.cc g++ -o build/runme build/mycode.o build/mymain.o scons: done building targets.
The list of object files to be created in the build/ directory is constructed manually by using the Python function rsplit to replace the file extensions with '.o'. Glob() creates a list of SCons 'File' objects which store the filename string (no directory) in the .name attribute. Accordingly, we specify the StaticObject() builder method for each object/source file pair in the next loop. Finally, we specify the Program() builder method.
This is essentially the same as the Makefile we ended up with in the previous post. But having to specify build objects manually by extension makes this script non-portable to Windows based systems. SCons provides a portable way to handle this situation but it will require additional scripts.
Hierarchical & Variant Builds
To separate the source code from the build directory requires the creation of subsidiary Scons scripts called 'SConscript' files (or Sconscript or sconscript; though you can actually specify another name when calling the SConscript( ... ) function used below).
In the main project directory will be your master or 'root' SConstruct file. In this file we specify what other subsidiary SConscript files exist, thereby creating a 'hierarchy' of scripts. These SConscript files could also be intermediate to other SConscript files.
Before looking at the content of the scripts, here is an example usage of SCons from the command line:
$ ls build NOTES README Sconstruct src $ ls src mycode.cc mycode.hh mymain.cc mysup.hh Sconscript $ ls build debug release $ ls build/debug/ build/release/ build/debug/: build/release/: $ scons scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... g++ -o build/debug/mycode.o -c -Wall -DDEBUG src/mycode.cc g++ -o build/debug/mymain.o -c -Wall -DDEBUG src/mymain.cc g++ -o build/debug/runme_d build/debug/mycode.o build/debug/mymain.o g++ -o build/release/mycode.o -c -Wall -DNDEBUG src/mycode.cc g++ -o build/release/mymain.o -c -Wall -DNDEBUG src/mymain.cc g++ -o build/release/runme build/release/mycode.o build/release/mymain.o scons: done building targets. $ ls build/debug/ build/release/ build/debug/: mycode.o mymain.o runme_d build/release/: mycode.o mymain.o runme $ ./build/debug/runme_d runme_d: src/mymain.cc:8: int main(): Assertion `0' failed. Aborted $ ./build/release/runme Hello World mycode foo(): -11 $ scons -c scons: Reading SConscript files ... scons: done reading SConscript files. scons: Cleaning targets ... Removed build/debug/mycode.o Removed build/debug/mymain.o Removed build/debug/runme_d Removed build/release/mycode.o Removed build/release/mymain.o Removed build/release/runme scons: done cleaning targets. $ scons build/debug scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... g++ -o build/debug/mycode.o -c -Wall -DDEBUG src/mycode.cc g++ -o build/debug/mymain.o -c -Wall -DDEBUG src/mymain.cc g++ -o build/debug/runme_d build/debug/mycode.o build/debug/mymain.o scons: done building targets. $ ls build/debug/ build/release/ build/debug/: mycode.o mymain.o runme_d build/release/:
You can see that the directory structure is the same as in the last example. There is a new file called 'Sconscript' now in the source directory.
Upon executing the 'scons' command, both variants are built (build/debug and build/release) with appropriate command line options and you can see that the object files and build targets are in the proper directories.
Cleaning with the '-c' option removes files from both variant builds. You can also build or clean just one of the variants by providing that variant directory as at the command line as shown in the example.
Now the scripts:
The Sconstruct file specifies how to build two variant builds: 'build/debug' and 'build/release'. For both, we use the SCons function 'Export( ... )' to export variables to the subsidiary Sconstruct files for each build. You can see in the Sconscript file the corresponding call to 'Import( ... )'.$ cat Sconstruct #!/usr/bin/python # myproject Sconstruct file # debug setup cxxflags = '-Wall -DDEBUG' output = 'runme_d' Export( 'cxxflags', 'output' ) VariantDir( 'build/debug', 'src', duplicate=0 ) SConscript( 'build/debug/Sconscript' ) # release setup cxxflags = '-Wall -DNDEBUG' output = 'runme' Export( 'cxxflags', 'output' ) VariantDir( 'build/release', 'src', duplicate=0 ) SConscript( 'build/release/Sconscript' ) $ cat src/Sconscript #!/usr/bin/python # debug Sconscript file Import( 'cxxflags', 'output' ) env = Environment( CXXFLAGS = cxxflags ) env.Program( target = output, source = Glob('*.cc') )
Next, the VariantDir( ... ) function is used to specify the details of each build variant. The first argument is the output directory, the second argument is the source directory, and the third argument is required to prevent SCons from copying the source files (and SConscript file) to the output directory. (Having duplicated source code in the same directory in the output may be required for some compilers or other situations but it is not necessary in this case).
Finally the SConscript( ... ) function finalizes the execution of this particular build. Both Export() and VariantDir() are equivalent ways of passing various variables to the SConscript function. (See the SCons manual entry for the SConscript() function).
SCons notes
The above example is fairly simple and does not include any libraries. The following notes may aid in learning how to use SCons.
-'Execute("command")' in an SCons script will execute the provided 'command' in the local shell.
Part 5: GDB & Valgrind
No comments:
Post a Comment