mirror of
https://github.com/Relintai/scons_gd.git
synced 2025-02-06 16:25:59 +01:00
1129 lines
30 KiB
XML
1129 lines
30 KiB
XML
|
<?xml version='1.0'?>
|
||
|
<!DOCTYPE sconsdoc [
|
||
|
<!ENTITY % scons SYSTEM "../scons.mod">
|
||
|
%scons;
|
||
|
|
||
|
<!ENTITY % builders-mod SYSTEM "../generated/builders.mod">
|
||
|
%builders-mod;
|
||
|
<!ENTITY % functions-mod SYSTEM "../generated/functions.mod">
|
||
|
%functions-mod;
|
||
|
<!ENTITY % tools-mod SYSTEM "../generated/tools.mod">
|
||
|
%tools-mod;
|
||
|
<!ENTITY % variables-mod SYSTEM "../generated/variables.mod">
|
||
|
%variables-mod;
|
||
|
|
||
|
]>
|
||
|
|
||
|
<chapter id="chap-builders-writing"
|
||
|
xmlns="http://www.scons.org/dbxsd/v1.0"
|
||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||
|
xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd">
|
||
|
<title>Extending &SCons;: Writing Your Own Builders</title>
|
||
|
|
||
|
<!--
|
||
|
|
||
|
MIT License
|
||
|
|
||
|
Copyright The SCons Foundation
|
||
|
|
||
|
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.
|
||
|
|
||
|
-->
|
||
|
|
||
|
<para>
|
||
|
|
||
|
Although &SCons; provides many useful methods
|
||
|
for building common software products
|
||
|
(programs, libraries, documents, etc.),
|
||
|
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 using 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 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-link-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>
|
||
|
|
||
|
<scons_example name="builderswriting_ex1">
|
||
|
<file name="SConstruct">
|
||
|
import os
|
||
|
bld=Builder(action = 'foobuild < $SOURCE > $TARGET')
|
||
|
env = Environment(BUILDERS={'Foo': bld})
|
||
|
env.AppendENVPath('PATH', os.getcwd())
|
||
|
env.Foo('file.foo', 'file.input')
|
||
|
</file>
|
||
|
<file name="file.input">
|
||
|
file.input
|
||
|
</file>
|
||
|
<file name="foobuild" chmod="0o755">
|
||
|
cat
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<sconstruct>
|
||
|
bld = Builder(action='foobuild < $SOURCE > $TARGET')
|
||
|
env = Environment(BUILDERS={'Foo': bld})
|
||
|
</sconstruct>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
With the &Builder; attached to our &consenv;
|
||
|
with the name <function>Foo</function>,
|
||
|
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>
|
||
|
|
||
|
<scons_output example="builderswriting_ex1" suffix="1">
|
||
|
<scons_output_command>scons -Q</scons_output_command>
|
||
|
</scons_output>
|
||
|
|
||
|
<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.
|
||
|
-->
|
||
|
<scons_example name="builderswriting_ex2">
|
||
|
<file name="SConstruct">
|
||
|
import SCons.Defaults
|
||
|
|
||
|
SCons.Defaults.ConstructionEnvironment['TOOLS'] = {}
|
||
|
bld = Builder(action='foobuild < $SOURCE > $TARGET')
|
||
|
env = Environment(BUILDERS={'Foo': bld})
|
||
|
env.Foo('file.foo', 'file.input')
|
||
|
env.Program('hello.c')
|
||
|
</file>
|
||
|
<file name="SConstruct.printme" printme="1">
|
||
|
bld = Builder(action='foobuild < $SOURCE > $TARGET')
|
||
|
env = Environment(BUILDERS={'Foo': bld})
|
||
|
env.Foo('file.foo', 'file.input')
|
||
|
env.Program('hello.c')
|
||
|
</file>
|
||
|
<file name="file.input">
|
||
|
file.input
|
||
|
</file>
|
||
|
<file name="hello.c">
|
||
|
hello.c
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<scons_output example="builderswriting_ex2" suffix="1">
|
||
|
<scons_output_command>scons -Q</scons_output_command>
|
||
|
</scons_output>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
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-link-BUILDERS; variable
|
||
|
using the &Append; function:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<scons_example name="builderswriting_ex3">
|
||
|
<file name="SConstruct">
|
||
|
import os
|
||
|
env = Environment()
|
||
|
env.AppendENVPath('PATH', os.getcwd())
|
||
|
bld = Builder(action='foobuild < $SOURCE > $TARGET')
|
||
|
env.Append(BUILDERS={'Foo': bld})
|
||
|
env.Foo('file.foo', 'file.input')
|
||
|
env.Program('hello.c')
|
||
|
</file>
|
||
|
<file name="file.input">
|
||
|
file.input
|
||
|
</file>
|
||
|
<file name="hello.c">
|
||
|
hello.c
|
||
|
</file>
|
||
|
<file name="foobuild" chmod="0o755">
|
||
|
cat
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<sconstruct>
|
||
|
env = Environment()
|
||
|
bld = Builder(action='foobuild < $SOURCE > $TARGET')
|
||
|
env.Append(BUILDERS={'Foo': bld})
|
||
|
env.Foo('file.foo', 'file.input')
|
||
|
env.Program('hello.c')
|
||
|
</sconstruct>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
Or you can explicitly set the appropriately-named
|
||
|
key in the &cv-BUILDERS; dictionary:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<sconstruct>
|
||
|
env = Environment()
|
||
|
bld = Builder(action='foobuild < $SOURCE > $TARGET')
|
||
|
env['BUILDERS']['Foo'] = bld
|
||
|
env.Foo('file.foo', 'file.input')
|
||
|
env.Program('hello.c')
|
||
|
</sconstruct>
|
||
|
|
||
|
<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>
|
||
|
|
||
|
<scons_output example="builderswriting_ex3" suffix="1">
|
||
|
<scons_output_command>scons -Q</scons_output_command>
|
||
|
</scons_output>
|
||
|
|
||
|
</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 <filename>file.foo</filename>
|
||
|
target file from the <filename>file.input</filename> 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>
|
||
|
|
||
|
<scons_example name="builderswriting_ex4">
|
||
|
<file name="SConstruct">
|
||
|
import os
|
||
|
bld = Builder(
|
||
|
action='foobuild < $SOURCE > $TARGET',
|
||
|
suffix='.foo',
|
||
|
src_suffix='.input',
|
||
|
)
|
||
|
env = Environment(BUILDERS={'Foo': bld})
|
||
|
env.AppendENVPath('PATH', os.getcwd())
|
||
|
env.Foo('file1')
|
||
|
env.Foo('file2')
|
||
|
</file>
|
||
|
<file name="file1.input">
|
||
|
file1.input
|
||
|
</file>
|
||
|
<file name="file2.input">
|
||
|
file2.input
|
||
|
</file>
|
||
|
<file name="foobuild" chmod="0o755">
|
||
|
cat
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<sconstruct>
|
||
|
bld = Builder(
|
||
|
action='foobuild < $SOURCE > $TARGET',
|
||
|
suffix='.foo',
|
||
|
src_suffix='.input',
|
||
|
)
|
||
|
env = Environment(BUILDERS={'Foo': bld})
|
||
|
env.Foo('file1')
|
||
|
env.Foo('file2')
|
||
|
</sconstruct>
|
||
|
|
||
|
<scons_output example="builderswriting_ex4" suffix="1">
|
||
|
<scons_output_command>scons -Q</scons_output_command>
|
||
|
</scons_output>
|
||
|
|
||
|
<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><parameter>target</parameter></term>
|
||
|
|
||
|
<listitem>
|
||
|
<para>
|
||
|
|
||
|
A list of Node objects representing
|
||
|
the target or targets to be
|
||
|
built by this function.
|
||
|
The file names of these target(s)
|
||
|
may be extracted using the &Python; &str; function.
|
||
|
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</varlistentry>
|
||
|
|
||
|
<varlistentry>
|
||
|
<term><parameter>source</parameter></term>
|
||
|
|
||
|
<listitem>
|
||
|
<para>
|
||
|
|
||
|
A list of Node objects representing
|
||
|
the sources to be
|
||
|
used by this 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><parameter>env</parameter></term>
|
||
|
|
||
|
<listitem>
|
||
|
<para>
|
||
|
|
||
|
The &consenv; used for building the target(s).
|
||
|
The function may use any of the
|
||
|
environment's &consvars;
|
||
|
in any way to affect how it builds the targets.
|
||
|
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</varlistentry>
|
||
|
|
||
|
</variablelist>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
The function will be constructed as a SCons FunctionAction and
|
||
|
must return a <literal>0</literal> or <literal>None</literal>
|
||
|
value if the target(s) are built successfully. The function may
|
||
|
raise an exception or return any non-zero value to indicate that
|
||
|
the build is unsuccessful.
|
||
|
|
||
|
For more information on Actions see the Action Objects section of
|
||
|
the man page.
|
||
|
|
||
|
</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>
|
||
|
|
||
|
<scons_example name="builderswriting_ex5">
|
||
|
<file name="SConstruct" printme="1">
|
||
|
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')
|
||
|
</file>
|
||
|
<file name="file.input">
|
||
|
file.input
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<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>
|
||
|
|
||
|
<scons_output example="builderswriting_ex5" suffix="1">
|
||
|
<scons_output_command>scons -Q</scons_output_command>
|
||
|
</scons_output>
|
||
|
|
||
|
</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 <firstterm>&Generator;</firstterm>.
|
||
|
(Note: this is not the same thing as a &Python; generator function
|
||
|
described in <ulink url="https://www.python.org/dev/peps/pep-0255/">PEP 255</ulink>)
|
||
|
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><parameter>source</parameter></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><parameter>target</parameter></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><parameter>env</parameter></term>
|
||
|
|
||
|
<listitem>
|
||
|
<para>
|
||
|
|
||
|
The &consenv; used for building the target(s).
|
||
|
The &generator; may use any of the
|
||
|
environment's &consvars;
|
||
|
in any way to determine what command
|
||
|
or other action to return.
|
||
|
|
||
|
</para>
|
||
|
</listitem>
|
||
|
|
||
|
</varlistentry>
|
||
|
|
||
|
<varlistentry>
|
||
|
<term><parameter>for_signature</parameter></term>
|
||
|
|
||
|
<listitem>
|
||
|
<para>
|
||
|
|
||
|
A flag that specifies whether the
|
||
|
&generator; is being called to contribute to a &buildsig;,
|
||
|
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 <parameter>generator</parameter> keyword argument
|
||
|
instead of <parameter>action</parameter>.
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<scons_example name="builderswriting_ex6">
|
||
|
<file name="SConstruct">
|
||
|
import os
|
||
|
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.AppendENVPath('PATH', os.getcwd())
|
||
|
env.Foo('file')
|
||
|
</file>
|
||
|
<file name="file.input">
|
||
|
file.input
|
||
|
</file>
|
||
|
<file name="foobuild" chmod="0o755">
|
||
|
cat
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<sconstruct>
|
||
|
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')
|
||
|
</sconstruct>
|
||
|
|
||
|
<scons_output example="builderswriting_ex6" suffix="1">
|
||
|
<scons_output_command>scons -Q</scons_output_command>
|
||
|
</scons_output>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
Note that it's illegal to specify both an
|
||
|
<parameter>action</parameter>
|
||
|
and a
|
||
|
<parameter>generator</parameter>
|
||
|
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).
|
||
|
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 &consenv;.
|
||
|
The emitter function should return the modified
|
||
|
lists of targets that should be built
|
||
|
and sources from which the targets will be built.
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
For example, suppose you want to define a Builder
|
||
|
that always calls a <command>foobuild</command> program,
|
||
|
and you want to automatically add
|
||
|
a new target file named
|
||
|
<filename>new_target</filename>
|
||
|
and a new source file named
|
||
|
<filename>new_source</filename>
|
||
|
whenever it's called.
|
||
|
The &SConstruct; file might look like this:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<scons_example name="builderswriting_ex7">
|
||
|
<file name="SConstruct">
|
||
|
import os
|
||
|
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.AppendENVPath('PATH', os.getcwd())
|
||
|
env.Foo('file')
|
||
|
</file>
|
||
|
<file name="file.input">
|
||
|
file.input
|
||
|
</file>
|
||
|
<file name="new_source">
|
||
|
new_source
|
||
|
</file>
|
||
|
<file name="foobuild" chmod="0o755">
|
||
|
cat
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<sconstruct>
|
||
|
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')
|
||
|
</sconstruct>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
And would yield the following output:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<scons_output example="builderswriting_ex7" suffix="1">
|
||
|
<scons_output_command>scons -Q</scons_output_command>
|
||
|
</scons_output>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
One very flexible thing that you can do is
|
||
|
use a &consvar; to specify
|
||
|
different emitter functions for different &consenvs;.
|
||
|
To do this, specify a string
|
||
|
containing a &consvar;
|
||
|
expansion as the emitter when you call
|
||
|
the &f-link-Builder; function,
|
||
|
and set that &consvar; to
|
||
|
the desired emitter function
|
||
|
in different &consenvs;:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<scons_example name="builderswriting_MY_EMITTER">
|
||
|
|
||
|
<file name="SConstruct" printme="1">
|
||
|
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')
|
||
|
</file>
|
||
|
<file name="file1.input">
|
||
|
file1.input
|
||
|
</file>
|
||
|
<file name="file2.input">
|
||
|
file2.input
|
||
|
</file>
|
||
|
<file name="modify1.in">
|
||
|
modify1.input
|
||
|
</file>
|
||
|
<file name="modify2.in">
|
||
|
modify2.input
|
||
|
</file>
|
||
|
<file name="my_command" chmod="0o755">
|
||
|
cat
|
||
|
</file>
|
||
|
|
||
|
</scons_example>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
In this example, the <filename>modify1.in</filename>
|
||
|
and <filename>modify2.in</filename> files
|
||
|
get added to the source lists
|
||
|
of the different commands:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<scons_output example="builderswriting_MY_EMITTER" suffix="1">
|
||
|
<scons_output_command>scons -Q</scons_output_command>
|
||
|
</scons_output>
|
||
|
|
||
|
</section>
|
||
|
|
||
|
<section>
|
||
|
<title>Modifying a Builder by adding an Emitter</title>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
Defining an emitter to work with a custom Builder
|
||
|
is a powerful concept, but sometimes all you really want
|
||
|
is to be able to use an existing builder but change its
|
||
|
concept of what targets are created.
|
||
|
In this case,
|
||
|
trying to recreate the logic of an existing Builder to
|
||
|
supply a special emitter can be a lot of work.
|
||
|
The typical case for this is when you want to use a compiler flag
|
||
|
that causes additional files to be generated.
|
||
|
For example the GNU linker accepts an option
|
||
|
<option>-Map</option> which outputs a link map
|
||
|
to the file specified by the option's argument.
|
||
|
If this option is just supplied to the build,
|
||
|
&SCons; will not consider the link map file a tracked target,
|
||
|
which has various undesirable efffects.
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
To help with this, &SCons; provides &consvars; which correspond
|
||
|
to a few standard builders:
|
||
|
&cv-link-PROGEMITTER; for &b-link-Program;;
|
||
|
&cv-link-LIBEMITTER; for &b-link-Library;;
|
||
|
&cv-link-SHLIBEMITTER; for &b-link-SharedLibrary; and
|
||
|
&cv-link-LDMODULEEMITTER; for &b-link-LoadableModule;;.
|
||
|
Adding an emitter to one of these will cause it to be
|
||
|
invoked in addition to any existing emitter for the
|
||
|
corresponding builder.
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
This example adds map creation as a linker flag,
|
||
|
and modifies the standard &b-link-Program;
|
||
|
emitter to know that map generation is a side-effect:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<scons_example name="builders_adding_emitter">
|
||
|
|
||
|
<file name="SConstruct" printme="1">
|
||
|
env = Environment()
|
||
|
map_filename = "${TARGET.name}.map"
|
||
|
|
||
|
def map_emitter(target, source, env):
|
||
|
target.append(map_filename)
|
||
|
return target, source
|
||
|
|
||
|
env.Append(LINKFLAGS="-Wl,-Map={},--cref".format(map_filename))
|
||
|
env.Append(PROGEMITTER=map_emitter)
|
||
|
env.Program('hello.c')
|
||
|
</file>
|
||
|
<file name="hello.c">
|
||
|
#include <stdio.h>
|
||
|
|
||
|
int
|
||
|
main()
|
||
|
{
|
||
|
printf("Hello, world!\n");
|
||
|
}
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
If you run this example, adding an option to tell &SCons; to
|
||
|
dump some information about the dependencies it knows,
|
||
|
it shows the map file option in use, and that &SCons; indeed
|
||
|
knows about the map file,
|
||
|
it's not just a silent side effect of the compiler:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<scons_output example="builders_adding_emitter" suffix="1">
|
||
|
<scons_output_command>scons -Q --tree=prune</scons_output_command>
|
||
|
</scons_output>
|
||
|
|
||
|
</section>
|
||
|
|
||
|
<!-- Placeholder for describing other keyword args to Builder
|
||
|
|
||
|
<section>
|
||
|
<title>target_factor=, source_factory=</title>
|
||
|
|
||
|
</section>
|
||
|
|
||
|
<section>
|
||
|
<title>target_scanner=, source_scanner=</title>
|
||
|
|
||
|
</section>
|
||
|
|
||
|
<section>
|
||
|
<title>multi=</title>
|
||
|
|
||
|
</section>
|
||
|
|
||
|
<section>
|
||
|
<title>single_source=</title>
|
||
|
|
||
|
</section>
|
||
|
|
||
|
<section>
|
||
|
<title>src_builder=</title>
|
||
|
|
||
|
</section>
|
||
|
|
||
|
<section>
|
||
|
<title>ensure_suffix=</title>
|
||
|
|
||
|
</section>
|
||
|
|
||
|
-->
|
||
|
|
||
|
<section>
|
||
|
<title>Where To Put Your Custom Builders and Tools</title>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
The <filename>site_scons</filename> directories give you a place to
|
||
|
put &Python; modules and packages that you can import into your
|
||
|
&SConscript; files (at the top level)
|
||
|
add-on tools that can integrate into &SCons;
|
||
|
(in a <filename>site_tools</filename> subdirectory),
|
||
|
and a <filename>site_scons/site_init.py</filename> file that
|
||
|
gets read before any &SConstruct; or &SConscript; file,
|
||
|
allowing you to change &SCons;'s default behavior.
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
Each system type (Windows, Mac, Linux, etc.) searches a canonical
|
||
|
set of directories for <filename>site_scons</filename>;
|
||
|
see the man page for details.
|
||
|
The top-level SConstruct's <filename>site_scons</filename> dir
|
||
|
(that is, the one in the project) is always searched last,
|
||
|
and its dir is placed first in the tool path so it overrides all
|
||
|
others.
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
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, a
|
||
|
<filename>site_scons</filename> 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; module or package containing two functions,
|
||
|
<function>exists()</function> and <function>generate()</function>.
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
A single-function Tool can just be included in your
|
||
|
<filename>site_scons/site_init.py</filename> file where it will be
|
||
|
parsed and made available for use. For instance, you could have a
|
||
|
<filename>site_scons/site_init.py</filename> file like this:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<scons_example name="builderswriting_site1">
|
||
|
<file name="site_scons/site_init.py" printme="1">
|
||
|
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
|
||
|
</file>
|
||
|
<file name="SConstruct">
|
||
|
env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====")
|
||
|
env.AddHeader('tgt', 'src')
|
||
|
</file>
|
||
|
<file name="src">
|
||
|
hi there
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
and a &SConstruct; like this:
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<sconstruct>
|
||
|
# Use TOOL_ADD_HEADER from site_scons/site_init.py
|
||
|
env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====")
|
||
|
env.AddHeader('tgt', 'src')
|
||
|
</sconstruct>
|
||
|
|
||
|
<para>
|
||
|
|
||
|
The <function>TOOL_ADD_HEADER</function> tool method will be
|
||
|
called to add the <function>AddHeader</function> tool to the
|
||
|
environment.
|
||
|
|
||
|
</para>
|
||
|
|
||
|
<!--
|
||
|
<scons_output example="builderswriting_site1" os="posix" suffix="1">
|
||
|
<scons_output_command>scons -Q</scons_output_command>
|
||
|
</scons_output>
|
||
|
-->
|
||
|
|
||
|
<para>
|
||
|
A more full-fledged tool with
|
||
|
<function>exists()</function> and <function>generate()</function>
|
||
|
methods can be installed either as a module in the file
|
||
|
<filename>site_scons/site_tools/toolname.py</filename> or as a
|
||
|
package in the
|
||
|
directory <filename>site_scons/site_tools/toolname</filename>. In
|
||
|
the case of using a package, the <function>exists()</function>
|
||
|
and <function>generate()</function> are in the
|
||
|
file <filename>site_scons/site_tools/toolname/__init__.py</filename>.
|
||
|
(In all the above case <filename>toolname</filename> is replaced
|
||
|
by the name of the tool.)
|
||
|
Since <filename>site_scons/site_tools</filename> 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, <filename>site_scons</filename> gives you the hook you need.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Many people have a collection of utility &Python; functions they'd like
|
||
|
to include in their &SConscript; files: just put them in
|
||
|
<filename>site_scons/my_utils.py</filename>
|
||
|
or any valid &Python; module name of your
|
||
|
choice. For instance you can do something like this in
|
||
|
<filename>site_scons/my_utils.py</filename> to add
|
||
|
<function>build_id</function> and <function>MakeWorkDir</function>
|
||
|
functions:
|
||
|
</para>
|
||
|
|
||
|
<scons_example name="builderswriting_site2">
|
||
|
<file name="site_scons/my_utils.py" printme="1">
|
||
|
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))
|
||
|
</file>
|
||
|
<file name="SConscript">
|
||
|
import my_utils
|
||
|
|
||
|
MakeWorkDir('/tmp/work')
|
||
|
print("build_id=" + my_utils.build_id())
|
||
|
</file>
|
||
|
</scons_example>
|
||
|
|
||
|
<para>
|
||
|
And then in your &SConscript; or any sub-&SConscript; anywhere in
|
||
|
your build, you can import <filename>my_utils</filename> and use it:
|
||
|
</para>
|
||
|
|
||
|
<sconstruct>
|
||
|
import my_utils
|
||
|
print("build_id=" + my_utils.build_id())
|
||
|
my_utils.MakeWorkDir('/tmp/work')
|
||
|
</sconstruct>
|
||
|
|
||
|
<para>
|
||
|
You can put this collection in its own module in a
|
||
|
<filename>site_scons</filename> and import it as in the example,
|
||
|
or you can include it in
|
||
|
<filename>site_scons/site_init.py</filename>,
|
||
|
which is automatically imported (unless you disable site directories).
|
||
|
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
|
||
|
</para>
|
||
|
<sconstruct>
|
||
|
from SCons.Script import *
|
||
|
</sconstruct>
|
||
|
|
||
|
<para>
|
||
|
This is true of modules in <filename>site_scons</filename> such as
|
||
|
<filename>site_scons/site_init.py</filename> as well.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
You can use any of the user- or machine-wide site directories such as
|
||
|
<filename>~/.scons/site_scons</filename> instead of
|
||
|
<filename>./site_scons</filename>, or use the
|
||
|
<option>--site-dir</option> option to point to your own directory.
|
||
|
<filename>site_init.py</filename> and
|
||
|
<filename>site_tools</filename> will be located under that directory.
|
||
|
To avoid using a <filename>site_scons</filename> directory at all,
|
||
|
even if it exists, use the <option>--no-site-dir</option>
|
||
|
option.
|
||
|
</para>
|
||
|
|
||
|
</section>
|
||
|
|
||
|
</chapter>
|