summaryrefslogtreecommitdiffstats
path: root/doc/user/builders-writing.xml
diff options
context:
space:
mode:
Diffstat (limited to 'doc/user/builders-writing.xml')
-rw-r--r--doc/user/builders-writing.xml690
1 files changed, 690 insertions, 0 deletions
diff --git a/doc/user/builders-writing.xml b/doc/user/builders-writing.xml
new file mode 100644
index 0000000..50f6556
--- /dev/null
+++ b/doc/user/builders-writing.xml
@@ -0,0 +1,690 @@
+<!--
+
+ __COPYRIGHT__
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-->
+
+<!--
+
+=head2 Adding new methods
+
+For slightly more demanding changes, you may wish to add new methods to the
+C<cons> package. Here's an example of a very simple extension,
+C<InstallScript>, which installs a tcl script in a requested location, but
+edits the script first to reflect a platform-dependent path that needs to be
+installed in the script:
+
+ # cons::InstallScript - Create a platform dependent version of a shell
+ # script by replacing string ``#!your-path-here'' with platform specific
+ # path $BIN_DIR.
+
+ sub cons::InstallScript {
+ my ($env, $dst, $src) = @_;
+ Command $env $dst, $src, qq(
+ sed s+your-path-here+$BIN_DIR+ %< > %>
+ chmod oug+x %>
+ );
+ }
+
+Notice that this method is defined directly in the C<cons> package (by
+prefixing the name with C<cons::>). A change made in this manner will be
+globally visible to all environments, and could be called as in the
+following example:
+
+ InstallScript $env "$BIN/foo", "foo.tcl";
+
+For a small improvement in generality, the C<BINDIR> variable could be
+passed in as an argument or taken from the construction environment-,-as
+C<%BINDIR>.
+
+
+=head2 Overriding methods
+
+Instead of adding the method to the C<cons> name space, you could define a
+new package which inherits existing methods from the C<cons> package and
+overrides or adds others. This can be done using Perl's inheritance
+mechanisms.
+
+The following example defines a new package C<cons::switch> which
+overrides the standard C<Library> method. The overridden method builds
+linked library modules, rather than library archives. A new
+constructor is provided. Environments created with this constructor
+will have the new library method; others won't.
+
+ package cons::switch;
+ BEGIN {@ISA = 'cons'}
+
+ sub new {
+ shift;
+ bless new cons(@_);
+ }
+
+ sub Library {
+ my($env) = shift;
+ my($lib) = shift;
+ my(@objs) = Objects $env @_;
+ Command $env $lib, @objs, q(
+ %LD -r %LDFLAGS %< -o %>
+ );
+ }
+
+This functionality could be invoked as in the following example:
+
+ $env = new cons::switch(@overrides);
+ ...
+ Library $env 'lib.o', 'foo.c', 'bar.c';
+
+-->
+
+ <para>
+
+ 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.)
+
+ </para>
+
+ <section>
+ <title>Writing Builders That Execute External Commands</title>
+
+ <para>
+
+ 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
+ <literal>foobuild</literal>,
+ creating that &Builder; might look like:
+
+ </para>
+
+ <programlisting>
+ bld = Builder(action = 'foobuild < $SOURCE > $TARGET')
+ </programlisting>
+
+ <para>
+
+ All the above line does is create a free-standing
+ &Builder; object.
+ The next section will show us how to actually use it.
+
+ </para>
+
+ </section>
+
+ <section>
+ <title>Attaching a Builder to a &ConsEnv;</title>
+
+ <para>
+
+ 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
+ <function>Foo</function>,
+ our &SConstruct; file might look like:
+
+ </para>
+
+
+
+ <programlisting>
+ bld = Builder(action = 'foobuild < $SOURCE > $TARGET')
+ env = Environment(BUILDERS = {'Foo' : bld})
+ </programlisting>
+
+ <para>
+
+ With the &Builder; so attached to our &consenv;
+ we can now actually call it like so:
+
+ </para>
+
+ <programlisting>
+ env.Foo('file.foo', 'file.input')
+ </programlisting>
+
+ <para>
+
+ Then when we run &SCons; it looks like:
+
+ </para>
+
+ <screen>
+ % <userinput>scons -Q</userinput>
+ foobuild &lt; file.input &gt; file.foo
+ </screen>
+
+ <para>
+
+ 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:
+
+ </para>
+
+ <!--
+ The ToolSurrogate stuff that's used to capture output initializes
+ SCons.Defaults.ConstructionEnvironment with its own list of TOOLS.
+ In this next example, we want to show the user that when they
+ set the BUILDERS explicitly, the call to env.Program() generates
+ an AttributeError. This won't happen with all of the default
+ ToolSurrogates in the default construction environment. To make the
+ AttributeError show up, we have to overwite the default construction
+ environment's TOOLS variable so Program() builder doesn't show up.
+
+ We do this by executing a slightly different SConstruct file than the
+ one we print in the guide, with two extra statements at the front
+ that overwrite the TOOLS variable as described. Note that we have
+ to jam those statements on to the first line to keep the line number
+ in the generated error consistent with what the user will see in the
+ User's Guide.
+ -->
+ <programlisting>
+ bld = Builder(action = 'foobuild &lt; $SOURCE &gt; $TARGET')
+ env = Environment(BUILDERS = {'Foo' : bld})
+ env.Foo('file.foo', 'file.input')
+ env.Program('hello.c')
+ </programlisting>
+
+ <screen>
+ % <userinput>scons -Q</userinput>
+ AttributeError: SConsEnvironment instance has no attribute 'Program':
+ File "/home/my/project/SConstruct", line 4:
+ env.Program('hello.c')
+ </screen>
+
+ <para>
+
+ To be able 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:
+
+ </para>
+
+
+
+ <programlisting>
+ env = Environment()
+ bld = Builder(action = 'foobuild < $SOURCE > $TARGET')
+ env.Append(BUILDERS = {'Foo' : bld})
+ env.Foo('file.foo', 'file.input')
+ env.Program('hello.c')
+ </programlisting>
+
+ <para>
+
+ Or you can explicitly set the appropriately-named
+ key in the &cv-BUILDERS; dictionary:
+
+ </para>
+
+ <programlisting>
+ env = Environment()
+ bld = Builder(action = 'foobuild < $SOURCE > $TARGET')
+ env['BUILDERS']['Foo'] = bld
+ env.Foo('file.foo', 'file.input')
+ env.Program('hello.c')
+ </programlisting>
+
+ <para>
+
+ Either way, the same &consenv;
+ can then use both the newly-defined
+ <function>Foo</function> &Builder;
+ and the default &b-link-Program; &Builder;:
+
+ </para>
+
+ <screen>
+ % <userinput>scons -Q</userinput>
+ foobuild &lt; file.input &gt; file.foo
+ cc -o hello.o -c hello.c
+ cc -o hello hello.o
+ </screen>
+
+ </section>
+
+ <section>
+ <title>Letting &SCons; Handle The File Suffixes</title>
+
+ <para>
+
+ 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 <literal>Foo</literal>
+ &Builder; to build the <literal>file.foo</literal>
+ target file from the <literal>file.input</literal> source file,
+ you can give the <literal>.foo</literal>
+ and <literal>.input</literal> suffixes to the &Builder;,
+ making for more compact and readable calls to
+ the <literal>Foo</literal> &Builder;:
+
+ </para>
+
+
+
+ <programlisting>
+ bld = Builder(action = 'foobuild < $SOURCE > $TARGET',
+ suffix = '.foo',
+ src_suffix = '.input')
+ env = Environment(BUILDERS = {'Foo' : bld})
+ env.Foo('file1')
+ env.Foo('file2')
+ </programlisting>
+
+ <screen>
+ % <userinput>scons -Q</userinput>
+ foobuild &lt; file1.input &gt; file1.foo
+ foobuild &lt; file2.input &gt; file2.foo
+ </screen>
+
+ <para>
+
+ You can also supply a <literal>prefix</literal> keyword argument
+ if it's appropriate to have &SCons; append a prefix
+ to the beginning of target file names.
+
+ </para>
+
+ </section>
+
+ <section>
+ <title>Builders That Execute Python Functions</title>
+
+ <para>
+
+ 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:
+
+ </para>
+
+ <programlisting>
+ def build_function(target, source, env):
+ # Code to build "target" from "source"
+ return None
+ </programlisting>
+
+ <para>
+
+ The arguments of a &buildfunc; are:
+
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>target</term>
+
+ <listitem>
+ <para>
+
+ 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.
+
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>source</term>
+
+ <listitem>
+ <para>
+
+ 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.
+
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>env</term>
+
+ <listitem>
+ <para>
+
+ 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.
+
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ <para>
+
+ The builder function must
+ return a <literal>0</literal> or <literal>None</literal> 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,
+
+ </para>
+
+ <para>
+
+ 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
+ <literal>action</literal>
+ argument:
+
+ </para>
+
+ <programlisting>
+ 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')
+ </programlisting>
+
+ <para>
+
+ 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:
+
+ </para>
+
+ <screen>
+ % <userinput>scons -Q</userinput>
+ build_function(["file.foo"], ["file.input"])
+ </screen>
+
+ </section>
+
+ <section>
+ <title>Builders That Create Actions Using a &Generator;</title>
+
+ <para>
+
+ &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:
+
+ </para>
+
+ <programlisting>
+ def generate_actions(source, target, env, for_signature):
+ return 'foobuild < %s > %s' % (target[0], source[0])
+ </programlisting>
+
+ <para>
+
+ The arguments of a &generator; are:
+
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>source</term>
+
+ <listitem>
+ <para>
+
+ 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.
+
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ <varlistentry>
+ <term>target</term>
+
+ <listitem>
+ <para>
+
+ 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.
+
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ <varlistentry>
+ <term>env</term>
+
+ <listitem>
+ <para>
+
+ 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.
+
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ <varlistentry>
+ <term>for_signature</term>
+
+ <listitem>
+ <para>
+
+ A flag that specifies whether the
+ generator is being called to contribute to a build signature,
+ as opposed to actually executing the command.
+
+ <!-- XXX NEED MORE HERE, describe generators use in signatures -->
+
+ </para>
+ </listitem>
+
+ </varlistentry>
+
+ </variablelist>
+
+ <para>
+
+ 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).
+
+ </para>
+
+ <para>
+
+ Once you've defined a &generator;,
+ you create a &Builder; to use it
+ by specifying the generator keyword argument
+ instead of <literal>action</literal>.
+
+ </para>
+
+
+
+ <programlisting>
+ 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')
+ </programlisting>
+
+ <screen>
+ % <userinput>scons -Q</userinput>
+ foobuild &lt; file.input &gt; file.foo
+ </screen>
+
+ <para>
+
+ Note that it's illegal to specify both an
+ <literal>action</literal>
+ and a
+ <literal>generator</literal>
+ for a &Builder;.
+
+ </para>
+
+ </section>
+
+ <section>
+ <title>Builders That Modify the Target or Source Lists Using an &Emitter;</title>
+
+ <para>
+
+ &SCons; supports the ability for a Builder to modify the
+ lists of target(s) from the specified source(s).
+
+ </para>
+
+
+
+ <programlisting>
+ 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')
+ </programlisting>
+
+ <screen>
+ % <userinput>scons -Q</userinput>
+ foobuild file.foo new_target - file.input new_source
+ </screen>
+
+ <programlisting>
+ bld = Builder(action = 'my_command',
+ suffix = '.foo',
+ src_suffix = '.input',
+ emitter = 'MY_EMITTER')
+ def modify1(target, source, env):
+ return target, source
+ def modify2(target, source, env):
+ return target, source
+ env1 = Environment(BUILDERS = {'Foo' : bld},
+ MY_EMITTER = modify1)
+ env2 = Environment(BUILDERS = {'Foo' : bld},
+ MY_EMITTER = modify2)
+ env1.Foo('file1')
+ env2.Foo('file2')
+ </programlisting>
+
+ </section>
+
+ <!--
+
+ <section>
+ <title>Builders That Use Other Builders</title>
+
+ <para>
+
+ XXX para
+
+ </para>
+
+ <scons_example name="ex8">
+ <file name="SConstruct" printme="1">
+ env = Environment()
+ #env.SourceCode('.', env.BitKeeper('my_command'))
+ env.Program('hello.c')
+ </file>
+ <file name="hello.c">
+ hello.c
+ </file>
+ </scons_example>
+
+ <scons_output example="ex8">
+ <scons_output_command>scons -Q</scons_output_command>
+ </scons_output>
+
+ </section>
+
+ -->