From 72c578fd4b0b4a5a43e18594339ac4ff26c376dc Mon Sep 17 00:00:00 2001 From: Luca Falavigna Date: Sat, 2 Jan 2010 20:56:27 +0100 Subject: Imported Upstream version 1.2.0.d20091224 --- doc/user/builders-writing.xml | 953 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 953 insertions(+) create mode 100644 doc/user/builders-writing.xml (limited to 'doc/user/builders-writing.xml') diff --git a/doc/user/builders-writing.xml b/doc/user/builders-writing.xml new file mode 100644 index 0000000..b05cce6 --- /dev/null +++ b/doc/user/builders-writing.xml @@ -0,0 +1,953 @@ + + + + + + + Although &SCons; provides many useful methods + for building common software products: + programs, libraries, documents. + you frequently want to be + able to build some other type of file + not supported directly by &SCons;. + Fortunately, &SCons; makes it very easy + to define your own &Builder; objects + for any custom file types you want to build. + (In fact, the &SCons; interfaces for creating + &Builder; objects are flexible enough and easy enough to use + that all of the the &SCons; built-in &Builder; objects + are created the mechanisms described in this section.) + + + +
+ Writing Builders That Execute External Commands + + + + The simplest &Builder; to create is + one that executes an external command. + For example, if we want to build + an output file by running the contents + of the input file through a command named + foobuild, + creating that &Builder; might look like: + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + + + + + All the above line does is create a free-standing + &Builder; object. + The next section will show us how to actually use it. + + + +
+ +
+ Attaching a Builder to a &ConsEnv; + + + + A &Builder; object isn't useful + until it's attached to a &consenv; + so that we can call it to arrange + for files to be built. + This is done through the &cv-link-BUILDERS; + &consvar; in an environment. + The &cv-BUILDERS; variable is a Python dictionary + that maps the names by which you want to call + various &Builder; objects to the objects themselves. + For example, if we want to call the + &Builder; we just defined by the name + Foo, + our &SConstruct; file might look like: + + + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env = Environment(BUILDERS = {'Foo' : bld}) + + + + + With the &Builder; attached to our &consenv; + with the name Foo, + we can now actually call it like so: + + + + + env.Foo('file.foo', 'file.input') + + + + + Then when we run &SCons; it looks like: + + + + + % scons -Q + foobuild < file.input > file.foo + + + + + Note, however, that the default &cv-BUILDERS; + variable in a &consenv; + comes with a default set of &Builder; objects + already defined: + &b-link-Program;, &b-link-Library;, etc. + And when we explicitly set the &cv-BUILDERS; variable + when we create the &consenv;, + the default &Builder;s are no longer part of + the environment: + + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + + % scons -Q + AttributeError: SConsEnvironment instance has no attribute 'Program': + File "/home/my/project/SConstruct", line 4: + env.Program('hello.c') + + + + + To be able to use both our own defined &Builder; objects + and the default &Builder; objects in the same &consenv;, + you can either add to the &cv-BUILDERS; variable + using the &Append; function: + + + + + + + env = Environment() + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env.Append(BUILDERS = {'Foo' : bld}) + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + + + Or you can explicitly set the appropriately-named + key in the &cv-BUILDERS; dictionary: + + + + + env = Environment() + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env['BUILDERS']['Foo'] = bld + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + + + Either way, the same &consenv; + can then use both the newly-defined + Foo &Builder; + and the default &b-link-Program; &Builder;: + + + + + % scons -Q + foobuild < file.input > file.foo + cc -o hello.o -c hello.c + cc -o hello hello.o + + +
+ +
+ Letting &SCons; Handle The File Suffixes + + + + By supplying additional information + when you create a &Builder;, + you can let &SCons; add appropriate file + suffixes to the target and/or the source file. + For example, rather than having to specify + explicitly that you want the Foo + &Builder; to build the file.foo + target file from the file.input source file, + you can give the .foo + and .input suffixes to the &Builder;, + making for more compact and readable calls to + the Foo &Builder;: + + + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET', + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file1') + env.Foo('file2') + + + + % scons -Q + foobuild < file1.input > file1.foo + foobuild < file2.input > file2.foo + + + + + You can also supply a prefix keyword argument + if it's appropriate to have &SCons; append a prefix + to the beginning of target file names. + + + +
+ +
+ Builders That Execute Python Functions + + + + In &SCons;, you don't have to call an external command + to build a file. + You can, instead, define a Python function + that a &Builder; object can invoke + to build your target file (or files). + Such a &buildfunc; definition looks like: + + + + + def build_function(target, source, env): + # Code to build "target" from "source" + return None + + + + + The arguments of a &buildfunc; are: + + + + + + + target + + + + + A list of Node objects representing + the target or targets to be + built by this builder function. + The file names of these target(s) + may be extracted using the Python &str; function. + + + + + + + source + + + + + A list of Node objects representing + the sources to be + used by this builder function to build the targets. + The file names of these source(s) + may be extracted using the Python &str; function. + + + + + + + env + + + + + The &consenv; used for building the target(s). + The builder function may use any of the + environment's construction variables + in any way to affect how it builds the targets. + + + + + + + + + + The builder function must + return a 0 or None value + if the target(s) are built successfully. + The builder function + may raise an exception + or return any non-zero value + to indicate that the build is unsuccessful, + + + + + + Once you've defined the Python function + that will build your target file, + defining a &Builder; object for it is as + simple as specifying the name of the function, + instead of an external command, + as the &Builder;'s + action + argument: + + + + + def build_function(target, source, env): + # Code to build "target" from "source" + return None + bld = Builder(action = build_function, + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file') + + + + + And notice that the output changes slightly, + reflecting the fact that a Python function, + not an external command, + is now called to build the target file: + + + + + % scons -Q + build_function(["file.foo"], ["file.input"]) + + +
+ +
+ Builders That Create Actions Using a &Generator; + + + + &SCons; Builder objects can create an action "on the fly" + by using a function called a &generator;. + This provides a great deal of flexibility to + construct just the right list of commands + to build your target. + A &generator; looks like: + + + + + def generate_actions(source, target, env, for_signature): + return 'foobuild < %s > %s' % (target[0], source[0]) + + + + + The arguments of a &generator; are: + + + + + + + source + + + + + A list of Node objects representing + the sources to be built + by the command or other action + generated by this function. + The file names of these source(s) + may be extracted using the Python &str; function. + + + + + + + + target + + + + + A list of Node objects representing + the target or targets to be built + by the command or other action + generated by this function. + The file names of these target(s) + may be extracted using the Python &str; function. + + + + + + + + env + + + + + The &consenv; used for building the target(s). + The generator may use any of the + environment's construction variables + in any way to determine what command + or other action to return. + + + + + + + + for_signature + + + + + A flag that specifies whether the + generator is being called to contribute to a build signature, + as opposed to actually executing the command. + + + + + + + + + + + + + The &generator; must return a + command string or other action that will be used to + build the specified target(s) from the specified source(s). + + + + + + Once you've defined a &generator;, + you create a &Builder; to use it + by specifying the generator keyword argument + instead of action. + + + + + + + def generate_actions(source, target, env, for_signature): + return 'foobuild < %s > %s' % (source[0], target[0]) + bld = Builder(generator = generate_actions, + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file') + + + + % scons -Q + foobuild < file.input > file.foo + + + + + Note that it's illegal to specify both an + action + and a + generator + for a &Builder;. + + + +
+ +
+ Builders That Modify the Target or Source Lists Using an &Emitter; + + + + &SCons; supports the ability for a Builder to modify the + lists of target(s) from the specified source(s). + You do this by defining an &emitter; function + that takes as its arguments + the list of the targets passed to the builder, + the list of the sources passed to the builder, + and the construction environment. + The emitter function should return the modified + lists of targets that should be built + and sources from which the targets will be built. + + + + + + For example, suppose you want to define a Builder + that always calls a foobuild program, + and you want to automatically add + a new target file named + new_target + and a new source file named + new_source + whenever it's called. + The &SConstruct; file might look like this: + + + + + + + def modify_targets(target, source, env): + target.append('new_target') + source.append('new_source') + return target, source + bld = Builder(action = 'foobuild $TARGETS - $SOURCES', + suffix = '.foo', + src_suffix = '.input', + emitter = modify_targets) + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file') + + + + + And would yield the following output: + + + + + % scons -Q + foobuild file.foo new_target - file.input new_source + + + + + One very flexible thing that you can do is + use a construction variable to specify + different emitter functions for different + construction variable. + To do this, specify a string + containing a construction variable + expansion as the emitter when you call + the &Builder; function, + and set that construction variable to + the desired emitter function + in different construction environments: + + + + + bld = Builder(action = 'my_command $SOURCES > $TARGET', + suffix = '.foo', + src_suffix = '.input', + emitter = '$MY_EMITTER') + def modify1(target, source, env): + return target, source + ['modify1.in'] + def modify2(target, source, env): + return target, source + ['modify2.in'] + env1 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify1) + env2 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify2) + env1.Foo('file1') + env2.Foo('file2') + import os + env1['ENV']['PATH'] = env2['ENV']['PATH'] + os.pathsep + os.getcwd() + env2['ENV']['PATH'] = env2['ENV']['PATH'] + os.pathsep + os.getcwd() + + + + + + bld = Builder(action = 'my_command $SOURCES > $TARGET', + suffix = '.foo', + src_suffix = '.input', + emitter = '$MY_EMITTER') + def modify1(target, source, env): + return target, source + ['modify1.in'] + def modify2(target, source, env): + return target, source + ['modify2.in'] + env1 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify1) + env2 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify2) + env1.Foo('file1') + env2.Foo('file2') + + + + + + In this example, the modify1.in + and modify2.in files + get added to the source lists + of the different commands: + + + + + % scons -Q + my_command file1.input modify1.in > file1.foo + my_command file2.input modify2.in > file2.foo + + +
+ + + +
+ Where To Put Your Custom Builders and Tools + + + + The site_scons directory gives you a place to + put Python modules you can import into your SConscripts + (site_scons), add-on tools that can integrate into &SCons; + (site_scons/site_tools), and a site_scons/site_init.py file that + gets read before any &SConstruct; or &SConscript;, allowing you to + change &SCons;'s default behavior. + + + + + + If you get a tool from somewhere (the &SCons; wiki or a third party, + for instance) and you'd like to use it in your project, the + site_scons dir is the simplest place to put it. + Tools come in two flavors; either a Python function that operates on + an &Environment; or a Python file containing two functions, exists() + and generate(). + + + + + + A single-function Tool can just be included in your + site_scons/site_init.py file where it will be + parsed and made available for use. For instance, you could have a + site_scons/site_init.py file like this: + + + + + def TOOL_ADD_HEADER(env): + """A Tool to add a header from $HEADER to the source file""" + add_header = Builder(action=['echo "$HEADER" > $TARGET', + 'cat $SOURCE >> $TARGET']) + env.Append(BUILDERS = {'AddHeader' : add_header}) + env['HEADER'] = '' # set default value + + + + + and a &SConstruct; like this: + + + + + # Use TOOL_ADD_HEADER from site_scons/site_init.py + env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====") + env.AddHeader('tgt', 'src') + + + + + The TOOL_ADD_HEADER tool method will be + called to add the AddHeader tool to the + environment. + + + + + + + Similarly, a more full-fledged tool with + exists() and generate() + methods can be installed in + site_scons/site_tools/toolname.py. Since + site_scons/site_tools is automatically added + to the head of the tool search path, any tool found there will be + available to all environments. Furthermore, a tool found there + will override a built-in tool of the same name, so if you need to + change the behavior of a built-in tool, site_scons gives you the + hook you need. + + + + Many people have a library of utility Python functions they'd like + to include in &SConscript;s; just put that module in + site_scons/my_utils.py or any valid Python module name of your + choice. For instance you can do something like this in + site_scons/my_utils.py to add build_id and MakeWorkDir functions: + + + + from SCons.Script import * # for Execute and Mkdir + def build_id(): + """Return a build ID (stub version)""" + return "100" + def MakeWorkDir(workdir): + """Create the specified dir immediately""" + Execute(Mkdir(workdir)) + + + + + And then in your &SConscript; or any sub-&SConscript; anywhere in + your build, you can import my_utils and use it: + + + + + import my_utils + print "build_id=" + my_utils.build_id() + my_utils.MakeWorkDir('/tmp/work') + + + + Note that although you can put this library in + site_scons/site_init.py, + it is no better there than site_scons/my_utils.py + since you still have to import that module into your &SConscript;. + Also note that in order to refer to objects in the SCons namespace + such as &Environment; or &Mkdir; or &Execute; in any file other + than a &SConstruct; or &SConscript; you always need to do + + + from SCons.Script import * + + + + This is true in modules in site_scons such as + site_scons/site_init.py as well. + + + + + If you have a machine-wide site dir you'd like to use instead of + ./site_scons, use the + --site-dir option to point to your dir. + site_init.py and + site_tools will be located under that dir. + To avoid using a site_scons dir at all, even + if it exists, use the --no-site-dir option. + + + +
+ + + -- cgit v1.2.3