diff options
30 files changed, 4208 insertions, 536 deletions
diff --git a/bin/docrun b/bin/docrun new file mode 100644 index 0000000..57ad202 --- /dev/null +++ b/bin/docrun @@ -0,0 +1,16 @@ +#!/bin/sh + +if test $# -eq 0; then + for f in doc/user/*.in; do + sgml=doc/user/`basename $f .in`.sgml + echo $f: + python bin/sconsoutput.py $f + done +else + for a in $*; do + f=doc/user/$a.in + sgml=doc/user/$a.sgml + echo $f: + python bin/sconsoutput.py $f + done +fi @@ -10,7 +10,12 @@ ./SCons/Node/FS.py ./SCons/Node/Python.py ./SCons/Node/__init__.py -./SCons/Options.py +./SCons/Options/__init__.py +./SCons/Options/BoolOption.py +./SCons/Options/EnumOption.py +./SCons/Options/ListOption.py +./SCons/Options/PackageOption.py +./SCons/Options/PathOption.py ./SCons/Platform/__init__.py ./SCons/Platform/aix.py ./SCons/Platform/cygwin.py diff --git a/doc/man/scons.1 b/doc/man/scons.1 index a378539..ff2864c 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -5500,6 +5500,27 @@ opts.Add('CC', 'The C compiler') .EE .TP +.RI AddOptions( list ) +A wrapper script that adds +multiple customizable construction variables +to an Options object. +.I list +is a list of tuple or list objects +that contain the arguments +for an individual call to the +.B Add +method. + +.ES +opt.AddOptions( + ('debug', '', 0), + ('CC', 'The C compiler'), + ('VALIDATE', 'An option for testing validation', + 'notset', validator, None), + ) +.EE + +.TP .RI Update( env ", [" args ]) This updates a construction environment .I env @@ -5562,6 +5583,192 @@ variables. Example: CC = 'my_cc' .EE +To make it more convenient to work with customizable Options, +.B scons +provides a number of functions +that make it easy to set up +various types of Options: + +.TP +.RI BoolOption( key ", " help ", " default ) +Return a tuple of arguments +to set up a Boolean option. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. +The option will interpret the values +.BR y , +.BR yes , +.BR t , +.BR true , +.BR 1 , +.B on +and +.B all +as true, +and the values +.BR n , +.BR no , +.BR f , +.BR false , +.BR 0 , +.B off +and +.B none +as false. + +.TP +.RI EnumOption( key ", " help ", " default ", " allowed_values ", [" map ", " ignorecase ]) +Return a tuple of arguments +to set up an option +whose value may be one +of a specified list of legal enumerated values. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. +The option will only support those +values in the +.I allowed_values +list. +The optional +.I map +argument is a dictionary +that can be used to convert +input values into specific legal values +in the +.I allowed_values +list. +If the value of +.I ignore_case +is +.B 0 +(the default), +then the values are case-sensitive. +If the value of +.I ignore_case +is +.BR 1 , +then values will be matched +case-insensitive. +If the value of +.I ignore_case +is +.BR 1 , +then values will be matched +case-insensitive, +and all input values will be +converted to lower case. + +.TP +.RI ListOption( key ", " help ", " default ", " names ) +Return a tuple of arguments +to set up an option +whose value may be one or more +of a specified list of legal enumerated values. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. +The option will only support the values +.BR all , +.BR none , +or the values in the +.I names +list. +More than one value may be specified, +with all values separated by commas. + +.TP +.RI PackageOption( key ", " help ", " default ) +Return a tuple of arguments +to set up an option +whose value is a path name +of a package that may be +enabled, disabled or +given an explicit path name. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. +The option will support the values +.BR yes , +.BR true , +.BR on , +.BR enable +or +.BR search , +in which case the specified +.I default +will be used, +or the option may be set to an +arbitrary string +(typically the path name to a package +that is being enabled). +The option will also support the values +.BR no , +.BR flase , +.BR off +or +.BR disable +to disable use of the specified option. + +.TP +.RI PathOption( key ", " help ", " default ) +Return a tuple of arguments +to set up an option +whose value is expected to be a path name. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. + +.RE +These functions make it +convenient to create a number +of options with consistent behavior +in a single call to the +.B AddOptions +method: + +.ES +opts.AddOptions( + BoolOption('warnings', 'compilation with -Wall and similiar', 1), + EnumOption('debug', 'debug output and symbols', 'no' + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), # case sensitive + ListOption('shared', + 'libraries to build as shared libraries', + 'all', + names = list_of_libs), + PackageOption('x11', + 'use X11 installed here (yes = search some places)', + 'yes'), + PathOption('qtdir', 'where the root of Qt is installed', qtdir), +) +.EE + .SH EXTENDING SCONS .SS Builder Objects .B scons diff --git a/doc/scons.mod b/doc/scons.mod index 958444d..d9b955f 100644 --- a/doc/scons.mod +++ b/doc/scons.mod @@ -112,9 +112,12 @@ --> +<!ENTITY Add "<function>Add</function>"> +<!ENTITY AddOptions "<function>AddOptions</function>"> <!ENTITY Alias "<function>Alias</function>"> <!ENTITY Aliases "<function>Aliases</function>"> <!ENTITY Append "<function>Append</function>"> +<!ENTITY BoolOption "<function>BoolOption</function>"> <!ENTITY Build "<function>Build</function>"> <!ENTITY CacheDir "<function>CacheDir</function>"> <!ENTITY Clean "<function>Clean</function>"> @@ -124,17 +127,23 @@ <!ENTITY Default "<function>Default</function>"> <!ENTITY DefaultRules "<function>DefaultRules</function>"> <!ENTITY Depends "<function>Depends</function>"> +<!ENTITY EnumOption "<function>EnumOption</function>"> <!ENTITY Environment "<function>Environment</function>"> <!ENTITY Export "<function>Export</function>"> +<!ENTITY GenerateHelpText "<function>GenerateHelpText</function>"> <!ENTITY Help "<function>Help</function>"> <!ENTITY Ignore "<function>Ignore</function>"> <!ENTITY Import "<function>Import</function>"> <!ENTITY Install "<function>Install</function>"> <!ENTITY InstallAs "<function>InstallAs</function>"> <!ENTITY Link "<function>Link</function>"> +<!ENTITY ListOption "<function>ListOption</function>"> <!ENTITY Local "<function>Local</function>"> <!ENTITY Module "<function>Module</function>"> <!ENTITY Objects "<function>Objects</function>"> +<!ENTITY Options "<function>Options</function>"> +<!ENTITY PackageOption "<function>PackageOption</function>"> +<!ENTITY PathOption "<function>PathOption</function>"> <!ENTITY Precious "<function>Precious</function>"> <!ENTITY Prepend "<function>Prepend</function>"> <!ENTITY Replace "<function>Replace</function>"> @@ -160,19 +169,39 @@ <!-- + Global variables. + +--> + +<!ENTITY ARGUMENTS "<varname>ARGUMENTS</varname>"> + + + +<!-- + Construction variables. --> <!ENTITY BUILDERMAP "<varname>BUILDERMAP</varname>"> <!ENTITY BUILDERS "<varname>BUILDERS</varname>"> +<!ENTITY CC "<varname>CC</varname>"> +<!ENTITY CCFLAGS "<varname>CCFLAGS</varname>"> +<!ENTITY COLOR "<varname>COLOR</varname>"> +<!ENTITY COLORS "<varname>COLORS</varname>"> +<!ENTITY CONFIG "<varname>CONFIG</varname>"> +<!ENTITY CPPDEFINES "<varname>CPPDEFINES</varname>"> <!ENTITY LIBDIRPREFIX "<varname>LIBDIRPREFIX</varname>"> <!ENTITY LIBDIRSUFFIX "<varname>LIBDIRSUFFIX</varname>"> <!ENTITY LIBLINKPREFIX "<varname>LIBLINKPREFIX</varname>"> <!ENTITY LIBLINKSUFFIX "<varname>LIBLINKSUFFIX</varname>"> +<!ENTITY LIBPATH "<varname>LIBPATH</varname>"> +<!ENTITY LIBS "<varname>LIBS</varname>"> <!ENTITY LINK "<varname>LINK</varname>"> <!ENTITY LINKCOM "<varname>LINKCOM</varname>"> <!ENTITY LINKFLAGS "<varname>LINKFLAGS</varname>"> +<!ENTITY RELEASE "<varname>RELEASE</varname>"> +<!ENTITY RELEASE_BUILD "<varname>RELEASE_BUILD</varname>"> <!ENTITY SCANNERMAP "<varname>SCANNERMAP</varname>"> <!ENTITY SCANNERS "<varname>SCANNERS</varname>"> <!ENTITY TARFLAGS "<varname>TARFLAGS</varname>"> @@ -186,10 +215,6 @@ --> -<!ENTITY CC "<varname>CC</varname>"> -<!ENTITY CCFLAGS "<varname>CCFLAGS</varname>"> -<!ENTITY LIBPATH "<varname>LIBPATH</varname>"> -<!ENTITY LIBS "<varname>LIBS</varname>"> <!ENTITY PYTHONPATH "<varname>PYTHONPATH</varname>"> <!ENTITY SCONSFLAGS "<varname>SCONSFLAGS</varname>"> @@ -201,7 +226,11 @@ --> +<!ENTITY allowed_values "<varname>allowed_values</varname>"> <!ENTITY build_dir "<varname>build_dir</varname>"> +<!ENTITY map "<varname>map</varname>"> +<!ENTITY ignorecase "<varname>ignorecase</varname>"> +<!ENTITY options "<varname>options</varname>"> <!ENTITY exports "<varname>exports</varname>"> <!ENTITY source "<varname>source</varname>"> <!ENTITY target "<varname>target</varname>"> @@ -210,6 +239,17 @@ <!-- + Values of function and method arguments. + +--> + +<!ENTITY all "<literal>all</literal>"> +<!ENTITY none "<literal>none</literal>"> + + + +<!-- + Builder and Scanner objects. --> @@ -276,6 +316,9 @@ <!ENTITY signature "<literal>signature</literal>"> <!ENTITY buildsignature "<literal>build signature</literal>"> +<!ENTITY true "<literal>true</literal>"> +<!ENTITY false "<literal>false</literal>"> + <!-- File and program names used in examples. @@ -285,6 +328,7 @@ <!ENTITY bar "<application>bar</application>"> <!ENTITY common1_c "<application>common1.c</application>"> <!ENTITY common2_c "<application>common2.c</application>"> +<!ENTITY custom_py "<filename>custom.py</filename>"> <!ENTITY goodbye "<application>goodbye</application>"> <!ENTITY file_dll "<application>file.dll</application>"> <!ENTITY file_lib "<application>file.lib</application>"> diff --git a/doc/user/MANIFEST b/doc/user/MANIFEST index 134a24b..337c8e0 100644 --- a/doc/user/MANIFEST +++ b/doc/user/MANIFEST @@ -5,10 +5,10 @@ builders-built-in.sgml builders-commands.sgml builders-writing.sgml caching.sgml +command-line.sgml cons.pl cons.sgml copyright.sgml -default.sgml depends.sgml environments.sgml errors.sgml diff --git a/doc/user/command-line.in b/doc/user/command-line.in new file mode 100644 index 0000000..09ee8ce --- /dev/null +++ b/doc/user/command-line.in @@ -0,0 +1,1182 @@ +<!-- + + Copyright (c) 2001, 2002, 2003 Steven Knight + + 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> + + &SCons; provides a number of ways that + allow the writer of the &SConscript; files + to give users a great deal of control over how to run the builds. + + </para> + + <section> + <title>Not Having to Specify Command-Line Options Each Time: the &SCONSFLAGS; Environment Variable</title> + + <para> + + Users may find themselves supplying + the same command-line options every time + they run &SCons;. + For example, a user might find that it saves time + to always specify a value of <literal>-j 2</literal> + to run the builds in parallel. + + </para> + + <scons_example name="SCONSFLAGS"> + <file name="SConstruct"> + def b(target, source, env): + pass + def s(target, source, env): + return " ..." + a = Action(b, strfunction = s) + env = Environment(BUILDERS = {'A' : a}) + env.A('foo.out', 'foo.in') + </file> + <file name="foo.in"> + foo.in + </file> + </scons_example> + + <scons_output example="SCONSFLAGS"> + <command>scons</command> + <command>export SCONSFLAGS="-Q"</command> + <command>scons</command> + </scons_output> + + <para> + + </para> + + </section> + + <section> + <title>Controlling the Default Targets</title> + + <para> + + One of the most basic things you can control + is which targets &SCons; will build by default. + As mentioned previously, + &SCons; will normally build every target + in or below the current directory + by default--that is, when you don't + explicitly specify one or more targets + on the command line. + Sometimes, however, you may want + to specify explicitly that only + certain programs, or programs in certain directories, + should be built by default. + You do this with the &Default; function: + + </para> + + <scons_example name="Default1"> + <file name="SConstruct" printme="1"> + env = Environment() + hello = env.Program('hello.c') + env.Program('goodbye.c') + Default(hello) + </file> + <file name="hello.c"> + hello.c + </file> + <file name="goodbye.c"> + goodbye.c + </file> + </scons_example> + + <para> + + This &SConstruct; file knows how to build two programs, + &hello; and &goodbye;, + but only builds the + &hello; program by default: + + </para> + + <scons_output example="Default1"> + <command>scons -Q</command> + <command>scons -Q</command> + <command>scons -Q goodbye</command> + </scons_output> + + <para> + + Note that, even when you use the &Default; + function in your &SConstruct; file, + you can still explicitly specify the current directory + (<literal>.</literal>) on the command line + to tell &SCons; to build + everything in (or below) the current directory: + + </para> + + <scons_output example="Default1"> + <command>scons -Q .</command> + </scons_output> + + <para> + + You can also call the &Default; + function more than once, + in which case each call + adds to the list of targets to be built by default: + + </para> + + <scons_example name="Default2"> + <file name="SConstruct" printme="1"> + env = Environment() + prog1 = env.Program('prog1.c') + Default(prog1) + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog3) + </file> + <file name="prog1.c"> + prog1.c + </file> + <file name="prog2.c"> + prog2.c + </file> + <file name="prog3.c"> + prog3.c + </file> + </scons_example> + + <para> + + Or you can specify more than one target + in a single call to the &Default; function: + + </para> + + <programlisting> + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog1, prog3) + </programlisting> + + <para> + + Either of these last two examples + will build only the + <application>prog1</application> + and + <application>prog3</application> + programs by default: + + </para> + + <scons_output example="Default2"> + <command>scons -Q</command> + <command>scons -Q .</command> + </scons_output> + + <para> + + You can list a directory as + an argument to &Default;: + + </para> + + <scons_example name="Default3"> + <file name="SConstruct" printme="1"> + env = Environment() + env.Program(['prog1/main.c', 'prog1/foo.c']) + env.Program(['prog2/main.c', 'prog2/bar.c']) + Default('prog1') + </file> + <directory name="prog1"></directory> + <directory name="prog2"></directory> + <file name="prog1/main.c"> + int main() { printf("prog1/main.c\n"); } + </file> + <file name="prog1/foo.c"> + int foo() { printf("prog1/foo.c\n"); } + </file> + <file name="prog2/main.c"> + int main() { printf("prog2/main.c\n"); } + </file> + <file name="prog2/bar.c"> + int bar() { printf("prog2/bar.c\n"); } + </file> + </scons_example> + + <para> + + In which case only the target(s) in that + directory will be built by default: + + </para> + + <scons_output example="Default3"> + <command>scons -Q</command> + <command>scons -Q</command> + <command>scons -Q .</command> + </scons_output> + + <para> + + Lastly, if for some reason you don't want + any targets built by default, + you can use the Python <literal>None</literal> + variable: + + </para> + + <scons_example name="Default4"> + <file name="SConstruct" printme="1"> + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + Default(None) + </file> + <file name="prog1.c"> + prog1.c + </file> + <file name="prog2.c"> + prog2.c + </file> + </scons_example> + + <para> + + Which would produce build output like: + + </para> + + <scons_output example="Default4"> + <command>scons -Q</command> + <command>scons -Q .</command> + </scons_output> + + </section> + + <!-- + + <section> + <title>Getting at Command-Line Targets</title> + + <para> + + XXX + + </para> + + </section> + + --> + + <section> + <title>Command-Line <varname>variable</varname>=<varname>value</varname> Build Options</title> + + <para> + + You may want to control various aspects + of your build by allowing the user + to specify <varname>variable</varname>=<varname>value</varname> + values on the command line. + For example, suppose you + want users to be able to + build a debug version of a program + by running &SCons; as follows: + + </para> + + <literallayout> + % scons -Q debug=1 + </literallayout> + + <para> + + &SCons; provides an &ARGUMENTS; dictionary + that stores all of the + <varname>variable</varname>=<varname>value</varname> + assignments from the command line. + This allows you to modify + aspects of your build in response + to specifications on the command line. + (Note that unless you want to require + that users <emphasis>always</emphasis> + specify an option, + you probably want to use + the Python + <literal>ARGUMENTS.get()</literal> function, + which allows you to specify a default value + to be used if there is no specification + on the command line.) + + </para> + + <para> + + The following code sets the &CCFLAGS; construction + variable in response to the <varname>debug</varname> + flag being set in the &ARGUMENTS; dictionary: + + </para> + + <scons_example name="ARGUMENTS"> + <file name="SConstruct" printme="1"> + env = Environment() + debug = ARGUMENTS.get('debug', 0) + if int(debug): + env.Append(CCFLAGS = '-g') + env.Program('prog.c') + </file> + <file name="prog.c"> + prog.c + </file> + </scons_example> + + <para> + + This results in the <varname>-g</varname> + compiler option being used when + <literal>debug=1</literal> + is used on the command line: + + </para> + + <scons_output example="ARGUMENTS"> + <command>scons -Q debug=0</command> + <command>scons -Q debug=0</command> + <command>scons -Q debug=1</command> + <command>scons -Q debug=1</command> + </scons_output> + + <para> + + Notice that &SCons; keeps track of + the last values used to build the object files, + and as a result correctly rebuilds + the object and executable files + only when the value of the <literal>debug</literal> + argument has changed. + + </para> + + </section> + + <section> + <title>Controlling Command-Line Build Options</title> + + <para> + + Being able to use a command-line build option like + <literal>debug=1</literal> is handy, + but it can be a chore to write specific Python code + to recognize each such option + and apply the values to a construction variable. + To help with this, + &SCons; supports a class to + define such build options easily, + and a mechanism to apply the + build options to a construction environment. + This allows you to control how the build options affect + construction environments. + + </para> + + <para> + + For example, suppose that you want users to set + a &RELEASE; construction variable on the + command line whenever the time comes to build + a program for release, + and that the value of this variable + should be added to the command line + with the appropriate <literal>-D</literal> option + (or other command line option) + to pass the value to the C compiler. + Here's how you might do that by setting + the appropriate value in a dictionary for the + &CPPDEFINES; construction variable: + + </para> + + <scons_example name="Options1"> + <file name="SConstruct" printme="1"> + opts = Options() + opts.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(options = opts, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + </file> + <file name="foo.c"> + foo.c + </file> + <file name="bar.c"> + bar.c + </file> + </scons_example> + + <para> + + This &SConstruct; file first creates an + &Options; object + (the <literal>opts = Options()</literal> call), + and then uses the object's &Add; + method to indicate that the &RELEASE; + option can be set on the command line, + and that it's default value will be <literal>0</literal> + (the third argument to the &Add; method). + The second argument is a line of help text; + we'll learn how to use it in the next section. + + </para> + + <para> + + We then pass the created &Options; + object as an &options; keyword argument + to the &Environment; call + used to create the construction environment. + This then allows a user to set the + &RELEASE; build option on the command line + and have the variable show up in + the command line used to build each object from + a C source file: + + </para> + + <scons_output example="Options1"> + <command>scons -Q RELEASE=1</command> + </scons_output> + + </section> + + <section> + <title>Providing Help for Command-Line Build Options</title> + + <para> + + To make command-line build options most useful, + you ideally want to provide + some help text that will describe + the available options + when the user runs <literal>scons -h</literal>. + You could write this text by hand, + but &SCons; provides an easier way. + &Options; objects support a + &GenerateHelpText; method + that will, as its name indicates, + generate text that describes + the various options that + have been added to it. + You then pass the output from this method to + the &Help; function: + + </para> + + <scons_example name="Options_Help"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(options = opts) + Help(opts.GenerateHelpText(env)) + </file> + </scons_example> + + <para> + + &SCons; will now display some useful text + when the <literal>-h</literal> option is used: + + </para> + + <scons_output example="Options_Help"> + <command>scons -Q -h</command> + </scons_output> + + <para> + + Notice that the help output shows the default value, + and the current actual value of the build option. + + </para> + + </section> + + <section> + <title>Reading Build Options From a File</title> + + <para> + + Being able to use a command-line build option like + <literal>debug=1</literal> is handy, + but it can be a chore to write specific Python code + to recognize each such option + and apply the values to a construction variable. + To help with this, + &SCons; supports a class to + define such build options easily + and to read build option values from a file. + This allows you to control how the build options affect + construction environments. + The way you do this is by specifying + a file name when you call &Options;, + like &custom_py; in the following example: + + </para> + + <scons_example name="Options_custom_py_1"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(options = opts, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + Help(opts.GenerateHelpText(env)) + </file> + <file name="foo.c"> + foo.c + </file> + <file name="bar.c"> + bar.c + </file> + <file name="custom.py"> + RELEASE = 1 + </file> + </scons_example> + + <para> + + This then allows us to control the &RELEASE; + variable by setting it in the &custom_py; file: + + </para> + + <scons_example_file example="Options_custom_py_1" name="custom.py"></scons_example_file> + + <para> + + Note that this file is actually executed + like a Python script. + Now when we run &SCons;: + + </para> + + <scons_output example="Options_custom_py_1"> + <command>scons -Q</command> + </scons_output> + + <para> + + And if we change the contents of &custom_py; to: + + </para> + + <scons_example name="Options_custom_py_2"> + <file name="SConstruct"> + opts = Options('custom.py') + opts.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(options = opts, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + Help(opts.GenerateHelpText(env)) + </file> + <file name="foo.c"> + foo.c + </file> + <file name="bar.c"> + bar.c + </file> + <file name="custom.py" printme="1"> + RELEASE = 0 + </file> + </scons_example> + + <para> + + The object files are rebuilt appropriately + with the new option: + + </para> + + <scons_output example="Options_custom_py_2"> + <command>scons -Q</command> + </scons_output> + + </section> + + <section> + <title>Canned Build Options</title> + + <para> + + &SCons; provides a number of functions + that provide ready-made behaviors + for various types of command-line build options. + + </para> + + <section> + <title>True/False Values: the &BoolOption; Build Option</title> + + <para> + + It's often handy to be able to specify an + option that controls a simple Boolean variable + with a &true; or &false; value. + It would be even more handy to accomodate + users who have different preferences for how to represent + &true; or &false; values. + The &BoolOption; function + makes it easy to accomodate a variety of + common values that represent + &true; or &false;. + + </para> + + <para> + + The &BoolOption; function takes three arguments: + the name of the build option, + the default value of the build option, + and the help string for the option. + It then returns appropriate information for + passing to the &Add; method of an &Options; object, like so: + + </para> + + <scons_example name="BoolOption"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add(BoolOption('RELEASE', 0, 'Set to build for release')) + env = Environment(options = opts, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program('foo.c') + </file> + <file name="foo.c"> + foo.c + </file> + </scons_example> + + <para> + + With this build option, + the &RELEASE; variable can now be enabled by + setting it to the value <literal>yes</literal> + or <literal>t</literal>: + + </para> + + <scons_output example="BoolOption"> + <command>scons -Q RELEASE=yes foo.o</command> + </scons_output> + + <scons_output example="BoolOption"> + <command>scons -Q RELEASE=t foo.o</command> + </scons_output> + + <para> + + Other values that equate to &true; include + <literal>y</literal>, + <literal>1</literal>, + <literal>on</literal> + and + <literal>all</literal>. + + </para> + + <para> + + Conversely, &RELEASE; may now be given a &false; + value by setting it to + <literal>no</literal> + or + <literal>f</literal>: + + </para> + + <scons_output example="BoolOption"> + <command>scons -Q RELEASE=no foo.o</command> + </scons_output> + + <scons_output example="BoolOption"> + <command>scons -Q RELEASE=f foo.o</command> + </scons_output> + + <para> + + Other values that equate to &true; include + <literal>n</literal>, + <literal>0</literal>, + <literal>off</literal> + and + <literal>none</literal>. + + </para> + + <para> + + Lastly, if a user tries to specify + any other value, + &SCons; supplies an appropriate error message: + + </para> + + <scons_output example="BoolOption"> + <command>scons -Q RELEASE=bad_value foo.o</command> + </scons_output> + + </section> + + <section> + <title>Single Value From a List: the &EnumOption; Build Option</title> + + <para> + + Suppose that we want a user to be able to + set a &COLOR; option + that selects a background color to be + displayed by an application, + but that we want to restrict the + choices to a specific set of allowed colors. + This can be set up quite easily + using the &EnumOption;, + which takes a list of &allowed_values + in addition to the variable name, + default value, + and help text arguments: + + </para> + + <scons_example name="EnumOption"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add(EnumOption('COLOR', 'red', 'Set background color', + allowed_values=('red', 'green', 'blue'))) + env = Environment(options = opts, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + </file> + <file name="foo.c"> + foo.c + </file> + </scons_example> + + <para> + + The user can now explicity set the &COLOR; build option + to any of the specified allowed values: + + </para> + + <scons_output example="EnumOption"> + <command>scons -Q COLOR=red foo.o</command> + <command>scons -Q COLOR=blue foo.o</command> + <command>scons -Q COLOR=green foo.o</command> + </scons_output> + + <para> + + But, almost more importantly, + an attempt to set &COLOR; + to a value that's not in the list + generates an error message: + + </para> + + <scons_output example="EnumOption"> + <command>scons -Q COLOR=magenta foo.o</command> + </scons_output> + + <para> + + The &EnumOption; function also supports a way + to map alternate names to allowed values. + Suppose, for example, + that we want to allow the user + to use the word <literal>navy</literal> as a synonym for + <literal>blue</literal>. + We do this by adding a ↦ dictionary + that will map its key values + to the desired legal value: + + </para> + + <scons_example name="EnumOption_map"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add(EnumOption('COLOR', 'red', 'Set background color', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'})) + env = Environment(options = opts, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + </file> + <file name="foo.c"> + foo.c + </file> + </scons_example> + + <para> + + As desired, the user can then use + <literal>navy</literal> on the command line, + and &SCons; will translate it into <literal>blue</literal> + when it comes time to use the &COLOR; + option to build a target: + + </para> + + <scons_output example="EnumOption_map"> + <command>scons -Q COLOR=navy foo.o</command> + </scons_output> + + <para> + + By default, when using the &EnumOption; function, + arguments that differ + from the legal values + only in case + are treated as illegal values: + + </para> + + <scons_output example="EnumOption"> + <command>scons -Q COLOR=Red foo.o</command> + <command>scons -Q COLOR=BLUE foo.o</command> + <command>scons -Q COLOR=nAvY foo.o</command> + </scons_output> + + <para> + + The &EnumOption; function can take an additional + &ignorecase; keyword argument that, + when set to <literal>1</literal>, + tells &SCons; to allow case differences + when the values are specified: + + </para> + + <scons_example name="EnumOption_ic1"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add(EnumOption('COLOR', 'red', 'Set background color', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'}, + ignorecase=1)) + env = Environment(options = opts, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + </file> + <file name="foo.c"> + foo.c + </file> + </scons_example> + + <para> + + Which yields the output: + + </para> + + <scons_output example="EnumOption_ic1"> + <command>scons -Q COLOR=Red foo.o</command> + <command>scons -Q COLOR=BLUE foo.o</command> + <command>scons -Q COLOR=nAvY foo.o</command> + <command>scons -Q COLOR=green foo.o</command> + </scons_output> + + <para> + + Notice that an &ignorecase; value of <literal>1</literal> + preserves the case-spelling that the user supplied. + If you want &SCons; to translate the names + into lower-case, + regardless of the case used by the user, + specify an &ignorecase; value of <literal>2</literal>: + + </para> + + <scons_example name="EnumOption_ic2"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add(EnumOption('COLOR', 'red', 'Set background color', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'}, + ignorecase=2)) + env = Environment(options = opts, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + </file> + <file name="foo.c"> + foo.c + </file> + </scons_example> + + <para> + + Now &SCons; will use values of + <literal>red</literal>, + <literal>green</literal> or + <literal>blue</literal> + regardless of how the user spells + those values on the command line: + + </para> + + <scons_output example="EnumOption_ic2"> + <command>scons -Q COLOR=Red foo.o</command> + <command>scons -Q COLOR=nAvY foo.o</command> + <command>scons -Q COLOR=GREEN foo.o</command> + </scons_output> + + </section> + + <section> + <title>Multiple Values From a List: the &ListOption; Build Option</title> + + <para> + + Another way in which you might want to allow users + to control build option is to + specify a list of one or more legal values. + &SCons; supports this through the &ListOption; function. + If, for example, we want a user to be able to set a + &COLORS; option to one or more of the legal list of values: + + </para> + + <scons_example name="ListOption"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add(ListOption('COLORS', 0, 'List of colors', + ['red', 'green', 'blue'])) + env = Environment(options = opts, + CPPDEFINES={'COLORS' : '"${COLORS}"'}) + env.Program('foo.c') + </file> + <file name="foo.c"> + foo.c + </file> + </scons_example> + + <para> + + A user can now specify a comma-separated list + of legal values, + which will get translated into a space-separated + list for passing to the any build commands: + + </para> + + <scons_output example="ListOption"> + <command>scons -Q COLORS=red,blue foo.o</command> + <command>scons -Q COLORS=blue,green,red foo.o</command> + </scons_output> + + <para> + + In addition, the &ListOption; function + allows the user to specify explicit keywords of + &all; or &none; + to select all of the legal values, + or none of them, respectively: + + </para> + + <scons_output example="ListOption"> + <command>scons -Q COLORS=all foo.o</command> + <command>scons -Q COLORS=none foo.o</command> + </scons_output> + + <para> + + And, of course, an illegal value + still generates an error message: + + </para> + + <scons_output example="ListOption"> + <command>scons -Q COLORS=magenta foo.o</command> + </scons_output> + + </section> + + <section> + <title>Path Names: the &PathOption; Build Option</title> + + <para> + + &SCons; supports a &PathOption; function + to make it easy to create a build option + to control an expected path name. + If, for example, you need to + define a variable in the preprocessor + that control the location of a + configuration file: + + </para> + + <scons_example name="PathOption"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add(PathOption('CONFIG', '__ROOT__/etc/my_config', 'Path to configuration file')) + env = Environment(options = opts, + CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'}) + env.Program('foo.c') + </file> + <file name="foo.c"> + foo.c + </file> + <file name="__ROOT__/etc/my_config"> + /opt/location + </file> + <file name="__ROOT__/usr/local/etc/other_config"> + /opt/location + </file> + </scons_example> + + <para> + + This then allows the user to + override the &CONFIG; build option + on the command line as necessary: + + </para> + + <scons_output example="PathOption"> + <command>scons -Q foo.o</command> + <command>scons -Q CONFIG=__ROOT__/usr/local/etc/other_config foo.o</command> + </scons_output> + + </section> + + <section> + <title>Enabled/Disabled Path Names: the &PackageOption; Build Option</title> + + <para> + + Sometimes you want to give users + even more control over a path name variable, + allowing them to explicitly enable or + disable the path name + by using <literal>yes</literal> or <literal>no</literal> keywords, + in addition to allow them + to supply an explicit path name. + &SCons; supports the &PackageOption; + function to support this: + + </para> + + <scons_example name="PackageOption"> + <file name="SConstruct" printme="1"> + opts = Options('custom.py') + opts.Add(PackageOption('PACKAGE', '__ROOT__/opt/location', 'Location package')) + env = Environment(options = opts, + CPPDEFINES={'PACKAGE' : '"$PACKAGE"'}) + env.Program('foo.c') + </file> + <file name="foo.c"> + foo.c + </file> + <file name="__ROOT__/opt/location"> + /opt/location + </file> + <file name="__ROOT__/usr/local/location"> + /opt/location + </file> + </scons_example> + + <para> + + When the &SConscript; file uses the &PackageOption; funciton, + user can now still use the default + or supply an overriding path name, + but can now explicitly set the + specified variable to a value + that indicates the package should be enabled + (in which case the default should be used) + or disabled: + + </para> + + <scons_output example="PackageOption"> + <command>scons -Q foo.o</command> + <command>scons -Q PACKAGE=__ROOT__/usr/local/location foo.o</command> + <command>scons -Q PACKAGE=yes foo.o</command> + <command>scons -Q PACKAGE=no foo.o</command> + </scons_output> + + </section> + + </section> + + <section> + <title>Adding Multiple Command-Line Build Options at Once</title> + + <para> + + Lastly, &SCons; provides a way to add + multiple build options to an &Options object at once. + Instead of having to call the &Add; method + multiple times, + you can call the &AddOptions; + method with a list of build options + to be added to the object. + Each build option is specified + as either a tuple of arguments, + just like you'd pass to the &Add; method itself, + or as a call to one of the canned + functions for pre-packaged command-line build options. + in any order: + + </para> + + <scons_example name="AddOptions_1"> + <file name="SConstruct" printme="1"> + opts = Options() + opts.AddOptions( + ('RELEASE', 'Set to 1 to build for release', 0), + ('CONFIG', 'Configuration file', '/etc/my_config'), + BoolOption('warnings', 'compilation with -Wall and similiar', 1), + EnumOption('debug', 'debug output and symbols', 'no', + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), # case sensitive + ListOption('shared', + 'libraries to build as shared libraries', + 'all', + names = list_of_libs), + PackageOption('x11', + 'use X11 installed here (yes = search some places)', + 'yes'), + PathOption('qtdir', 'where the root of Qt is installed', qtdir), + ) + </file> + </scons_example> + + <para> + </para> + + </section> diff --git a/doc/user/command-line.sgml b/doc/user/command-line.sgml new file mode 100644 index 0000000..3377b69 --- /dev/null +++ b/doc/user/command-line.sgml @@ -0,0 +1,1138 @@ +<!-- + + Copyright (c) 2001, 2002, 2003 Steven Knight + + 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> + + &SCons; provides a number of ways that + allow the writer of the &SConscript; files + to give users a great deal of control over how to run the builds. + + </para> + + <section> + <title>Not Having to Specify Command-Line Options Each Time: the &SCONSFLAGS; Environment Variable</title> + + <para> + + Users may find themselves supplying + the same command-line options every time + they run &SCons;. + For example, a user might find that it saves time + to always specify a value of <literal>-j 2</literal> + to run the builds in parallel. + + </para> + + + + <literallayout> + % <userinput>scons</userinput> + scons: Reading SConscript files ... + ... + scons: done reading SConscript files. + scons: Building targets ... + scons: `.' is up to date. + scons: done building targets. + % <userinput>export SCONSFLAGS="-Q"</userinput> + % <userinput>scons</userinput> + scons: Reading SConscript files ... + ... + scons: done reading SConscript files. + scons: Building targets ... + scons: `.' is up to date. + scons: done building targets. + </literallayout> + + <para> + + </para> + + </section> + + <section> + <title>Controlling the Default Targets</title> + + <para> + + One of the most basic things you can control + is which targets &SCons; will build by default. + As mentioned previously, + &SCons; will normally build every target + in or below the current directory + by default--that is, when you don't + explicitly specify one or more targets + on the command line. + Sometimes, however, you may want + to specify explicitly that only + certain programs, or programs in certain directories, + should be built by default. + You do this with the &Default; function: + + </para> + + <programlisting> + env = Environment() + hello = env.Program('hello.c') + env.Program('goodbye.c') + Default(hello) + </programlisting> + + <para> + + This &SConstruct; file knows how to build two programs, + &hello; and &goodbye;, + but only builds the + &hello; program by default: + + </para> + + <literallayout> + % <userinput>scons -Q</userinput> + cc -c -o hello.o hello.c + cc -o hello hello.o + % <userinput>scons -Q</userinput> + scons: `hello' is up to date. + % <userinput>scons -Q goodbye</userinput> + cc -c -o goodbye.o goodbye.c + cc -o goodbye goodbye.o + </literallayout> + + <para> + + Note that, even when you use the &Default; + function in your &SConstruct; file, + you can still explicitly specify the current directory + (<literal>.</literal>) on the command line + to tell &SCons; to build + everything in (or below) the current directory: + + </para> + + <literallayout> + % <userinput>scons -Q .</userinput> + cc -c -o goodbye.o goodbye.c + cc -o goodbye goodbye.o + cc -c -o hello.o hello.c + cc -o hello hello.o + </literallayout> + + <para> + + You can also call the &Default; + function more than once, + in which case each call + adds to the list of targets to be built by default: + + </para> + + <programlisting> + env = Environment() + prog1 = env.Program('prog1.c') + Default(prog1) + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog3) + </programlisting> + + <para> + + Or you can specify more than one target + in a single call to the &Default; function: + + </para> + + <programlisting> + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog1, prog3) + </programlisting> + + <para> + + Either of these last two examples + will build only the + <application>prog1</application> + and + <application>prog3</application> + programs by default: + + </para> + + <literallayout> + % <userinput>scons -Q</userinput> + cc -c -o prog1.o prog1.c + cc -o prog1 prog1.o + cc -c -o prog3.o prog3.c + cc -o prog3 prog3.o + % <userinput>scons -Q .</userinput> + cc -c -o prog2.o prog2.c + cc -o prog2 prog2.o + </literallayout> + + <para> + + You can list a directory as + an argument to &Default;: + + </para> + + <programlisting> + env = Environment() + env.Program(['prog1/main.c', 'prog1/foo.c']) + env.Program(['prog2/main.c', 'prog2/bar.c']) + Default('prog1') + </programlisting> + + <para> + + In which case only the target(s) in that + directory will be built by default: + + </para> + + <literallayout> + % <userinput>scons -Q</userinput> + cc -c -o prog1/foo.o prog1/foo.c + cc -c -o prog1/main.o prog1/main.c + cc -o prog1/main prog1/main.o prog1/foo.o + % <userinput>scons -Q</userinput> + scons: `prog1' is up to date. + % <userinput>scons -Q .</userinput> + cc -c -o prog2/bar.o prog2/bar.c + cc -c -o prog2/main.o prog2/main.c + cc -o prog2/main prog2/main.o prog2/bar.o + </literallayout> + + <para> + + Lastly, if for some reason you don't want + any targets built by default, + you can use the Python <literal>None</literal> + variable: + + </para> + + <programlisting> + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + Default(None) + </programlisting> + + <para> + + Which would produce build output like: + + </para> + + <literallayout> + % <userinput>scons -Q</userinput> + scons: *** No targets specified and no Default() targets found. Stop. + % <userinput>scons -Q .</userinput> + cc -c -o prog1.o prog1.c + cc -o prog1 prog1.o + cc -c -o prog2.o prog2.c + cc -o prog2 prog2.o + </literallayout> + + </section> + + <!-- + + <section> + <title>Getting at Command-Line Targets</title> + + <para> + + XXX + + </para> + + </section> + + --> + + <section> + <title>Command-Line <varname>variable</varname>=<varname>value</varname> Build Options</title> + + <para> + + You may want to control various aspects + of your build by allowing the user + to specify <varname>variable</varname>=<varname>value</varname> + values on the command line. + For example, suppose you + want users to be able to + build a debug version of a program + by running &SCons; as follows: + + </para> + + <literallayout> + % scons -Q debug=1 + </literallayout> + + <para> + + &SCons; provides an &ARGUMENTS; dictionary + that stores all of the + <varname>variable</varname>=<varname>value</varname> + assignments from the command line. + This allows you to modify + aspects of your build in response + to specifications on the command line. + (Note that unless you want to require + that users <emphasis>always</emphasis> + specify an option, + you probably want to use + the Python + <literal>ARGUMENTS.get()</literal> function, + which allows you to specify a default value + to be used if there is no specification + on the command line.) + + </para> + + <para> + + The following code sets the &CCFLAGS; construction + variable in response to the <varname>debug</varname> + flag being set in the &ARGUMENTS; dictionary: + + </para> + + <programlisting> + env = Environment() + debug = ARGUMENTS.get('debug', 0) + if int(debug): + env.Append(CCFLAGS = '-g') + env.Program('prog.c') + </programlisting> + + <para> + + This results in the <varname>-g</varname> + compiler option being used when + <literal>debug=1</literal> + is used on the command line: + + </para> + + <literallayout> + % <userinput>scons -Q debug=0</userinput> + cc -c -o prog.o prog.c + cc -o prog prog.o + % <userinput>scons -Q debug=0</userinput> + scons: `.' is up to date. + % <userinput>scons -Q debug=1</userinput> + cc -g -c -o prog.o prog.c + cc -o prog prog.o + % <userinput>scons -Q debug=1</userinput> + scons: `.' is up to date. + </literallayout> + + <para> + + Notice that &SCons; keeps track of + the last values used to build the object files, + and as a result correctly rebuilds + the object and executable files + only when the value of the <literal>debug</literal> + argument has changed. + + </para> + + </section> + + <section> + <title>Controlling Command-Line Build Options</title> + + <para> + + Being able to use a command-line build option like + <literal>debug=1</literal> is handy, + but it can be a chore to write specific Python code + to recognize each such option + and apply the values to a construction variable. + To help with this, + &SCons; supports a class to + define such build options easily, + and a mechanism to apply the + build options to a construction environment. + This allows you to control how the build options affect + construction environments. + + </para> + + <para> + + For example, suppose that you want users to set + a &RELEASE; construction variable on the + command line whenever the time comes to build + a program for release, + and that the value of this variable + should be added to the command line + with the appropriate <literal>-D</literal> option + (or other command line option) + to pass the value to the C compiler. + Here's how you might do that by setting + the appropriate value in a dictionary for the + &CPPDEFINES; construction variable: + + </para> + + <programlisting> + opts = Options() + opts.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(options = opts, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + </programlisting> + + <para> + + This &SConstruct; file first creates an + &Options; object + (the <literal>opts = Options()</literal> call), + and then uses the object's &Add; + method to indicate that the &RELEASE; + option can be set on the command line, + and that it's default value will be <literal>0</literal> + (the third argument to the &Add; method). + The second argument is a line of help text; + we'll learn how to use it in the next section. + + </para> + + <para> + + We then pass the created &Options; + object as an &options; keyword argument + to the &Environment; call + used to create the construction environment. + This then allows a user to set the + &RELEASE; build option on the command line + and have the variable show up in + the command line used to build each object from + a C source file: + + </para> + + <literallayout> + % <userinput>scons -Q RELEASE=1</userinput> + cc -DRELEASE_BUILD=1 -c -o bar.o bar.c + cc -DRELEASE_BUILD=1 -c -o foo.o foo.c + cc -o foo foo.o bar.o + </literallayout> + + </section> + + <section> + <title>Providing Help for Command-Line Build Options</title> + + <para> + + To make command-line build options most useful, + you ideally want to provide + some help text that will describe + the available options + when the user runs <literal>scons -h</literal>. + You could write this text by hand, + but &SCons; provides an easier way. + &Options; objects support a + &GenerateHelpText; method + that will, as its name indicates, + generate text that describes + the various options that + have been added to it. + You then pass the output from this method to + the &Help; function: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(options = opts) + Help(opts.GenerateHelpText(env)) + </programlisting> + + <para> + + &SCons; will now display some useful text + when the <literal>-h</literal> option is used: + + </para> + + <literallayout> + % <userinput>scons -Q -h</userinput> + + RELEASE: Set to 1 to build for release + default: 0 + actual: 0 + + Use scons -H for help about command-line options. + </literallayout> + + <para> + + Notice that the help output shows the default value, + and the current actual value of the build option. + + </para> + + </section> + + <section> + <title>Reading Build Options From a File</title> + + <para> + + Being able to use a command-line build option like + <literal>debug=1</literal> is handy, + but it can be a chore to write specific Python code + to recognize each such option + and apply the values to a construction variable. + To help with this, + &SCons; supports a class to + define such build options easily + and to read build option values from a file. + This allows you to control how the build options affect + construction environments. + The way you do this is by specifying + a file name when you call &Options;, + like &custom_py; in the following example: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(options = opts, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + Help(opts.GenerateHelpText(env)) + </programlisting> + + <para> + + This then allows us to control the &RELEASE; + variable by setting it in the &custom_py; file: + + </para> + + <programlisting> + RELEASE = 1 + </programlisting> + + <para> + + Note that this file is actually executed + like a Python script. + Now when we run &SCons;: + + </para> + + <literallayout> + % <userinput>scons -Q</userinput> + cc -DRELEASE_BUILD=1 -c -o bar.o bar.c + cc -DRELEASE_BUILD=1 -c -o foo.o foo.c + cc -o foo foo.o bar.o + </literallayout> + + <para> + + And if we change the contents of &custom_py; to: + + </para> + + <programlisting> + RELEASE = 0 + </programlisting> + + <para> + + The object files are rebuilt appropriately + with the new option: + + </para> + + <literallayout> + % <userinput>scons -Q</userinput> + cc -DRELEASE_BUILD=0 -c -o bar.o bar.c + cc -DRELEASE_BUILD=0 -c -o foo.o foo.c + cc -o foo foo.o bar.o + </literallayout> + + </section> + + <section> + <title>Canned Build Options</title> + + <para> + + &SCons; provides a number of functions + that provide ready-made behaviors + for various types of command-line build options. + + </para> + + <section> + <title>True/False Values: the &BoolOption; Build Option</title> + + <para> + + It's often handy to be able to specify an + option that controls a simple Boolean variable + with a &true; or &false; value. + It would be even more handy to accomodate + users who have different preferences for how to represent + &true; or &false; values. + The &BoolOption; function + makes it easy to accomodate a variety of + common values that represent + &true; or &false;. + + </para> + + <para> + + The &BoolOption; function takes three arguments: + the name of the build option, + the default value of the build option, + and the help string for the option. + It then returns appropriate information for + passing to the &Add; method of an &Options; object, like so: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add(BoolOption('RELEASE', 0, 'Set to build for release')) + env = Environment(options = opts, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program('foo.c') + </programlisting> + + <para> + + With this build option, + the &RELEASE; variable can now be enabled by + setting it to the value <literal>yes</literal> + or <literal>t</literal>: + + </para> + + <literallayout> + % <userinput>scons -Q RELEASE=yes foo.o</userinput> + cc -DRELEASE_BUILD=1 -c -o foo.o foo.c + </literallayout> + + <literallayout> + % <userinput>scons -Q RELEASE=t foo.o</userinput> + cc -DRELEASE_BUILD=1 -c -o foo.o foo.c + </literallayout> + + <para> + + Other values that equate to &true; include + <literal>y</literal>, + <literal>1</literal>, + <literal>on</literal> + and + <literal>all</literal>. + + </para> + + <para> + + Conversely, &RELEASE; may now be given a &false; + value by setting it to + <literal>no</literal> + or + <literal>f</literal>: + + </para> + + <literallayout> + % <userinput>scons -Q RELEASE=no foo.o</userinput> + cc -DRELEASE_BUILD=0 -c -o foo.o foo.c + </literallayout> + + <literallayout> + % <userinput>scons -Q RELEASE=f foo.o</userinput> + cc -DRELEASE_BUILD=0 -c -o foo.o foo.c + </literallayout> + + <para> + + Other values that equate to &true; include + <literal>n</literal>, + <literal>0</literal>, + <literal>off</literal> + and + <literal>none</literal>. + + </para> + + <para> + + Lastly, if a user tries to specify + any other value, + &SCons; supplies an appropriate error message: + + </para> + + <literallayout> + % <userinput>scons -Q RELEASE=bad_value foo.o</userinput> + + scons: *** Error converting option: RELEASE + Invalid value for boolean option: bad_value + File "SConstruct", line 4, in ? + </literallayout> + + </section> + + <section> + <title>Single Value From a List: the &EnumOption; Build Option</title> + + <para> + + Suppose that we want a user to be able to + set a &COLOR; option + that selects a background color to be + displayed by an application, + but that we want to restrict the + choices to a specific set of allowed colors. + This can be set up quite easily + using the &EnumOption;, + which takes a list of &allowed_values + in addition to the variable name, + default value, + and help text arguments: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add(EnumOption('COLOR', 'red', 'Set background color', + allowed_values=('red', 'green', 'blue'))) + env = Environment(options = opts, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + </programlisting> + + <para> + + The user can now explicity set the &COLOR; build option + to any of the specified allowed values: + + </para> + + <literallayout> + % <userinput>scons -Q COLOR=red foo.o</userinput> + cc -DCOLOR="red" -c -o foo.o foo.c + % <userinput>scons -Q COLOR=blue foo.o</userinput> + cc -DCOLOR="blue" -c -o foo.o foo.c + % <userinput>scons -Q COLOR=green foo.o</userinput> + cc -DCOLOR="green" -c -o foo.o foo.c + </literallayout> + + <para> + + But, almost more importantly, + an attempt to set &COLOR; + to a value that's not in the list + generates an error message: + + </para> + + <literallayout> + % <userinput>scons -Q COLOR=magenta foo.o</userinput> + + scons: *** Invalid value for option COLOR: magenta + File "SConstruct", line 5, in ? + </literallayout> + + <para> + + The &EnumOption; function also supports a way + to map alternate names to allowed values. + Suppose, for example, + that we want to allow the user + to use the word <literal>navy</literal> as a synonym for + <literal>blue</literal>. + We do this by adding a ↦ dictionary + that will map its key values + to the desired legal value: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add(EnumOption('COLOR', 'red', 'Set background color', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'})) + env = Environment(options = opts, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + </programlisting> + + <para> + + As desired, the user can then use + <literal>navy</literal> on the command line, + and &SCons; will translate it into <literal>blue</literal> + when it comes time to use the &COLOR; + option to build a target: + + </para> + + <literallayout> + % <userinput>scons -Q COLOR=navy foo.o</userinput> + cc -DCOLOR="blue" -c -o foo.o foo.c + </literallayout> + + <para> + + By default, when using the &EnumOption; function, + arguments that differ + from the legal values + only in case + are treated as illegal values: + + </para> + + <literallayout> + % <userinput>scons -Q COLOR=Red foo.o</userinput> + + scons: *** Invalid value for option COLOR: Red + File "SConstruct", line 5, in ? + % <userinput>scons -Q COLOR=BLUE foo.o</userinput> + + scons: *** Invalid value for option COLOR: BLUE + File "SConstruct", line 5, in ? + % <userinput>scons -Q COLOR=nAvY foo.o</userinput> + + scons: *** Invalid value for option COLOR: nAvY + File "SConstruct", line 5, in ? + </literallayout> + + <para> + + The &EnumOption; function can take an additional + &ignorecase; keyword argument that, + when set to <literal>1</literal>, + tells &SCons; to allow case differences + when the values are specified: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add(EnumOption('COLOR', 'red', 'Set background color', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'}, + ignorecase=1)) + env = Environment(options = opts, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + </programlisting> + + <para> + + Which yields the output: + + </para> + + <literallayout> + % <userinput>scons -Q COLOR=Red foo.o</userinput> + cc -DCOLOR="Red" -c -o foo.o foo.c + % <userinput>scons -Q COLOR=BLUE foo.o</userinput> + cc -DCOLOR="BLUE" -c -o foo.o foo.c + % <userinput>scons -Q COLOR=nAvY foo.o</userinput> + cc -DCOLOR="blue" -c -o foo.o foo.c + % <userinput>scons -Q COLOR=green foo.o</userinput> + cc -DCOLOR="green" -c -o foo.o foo.c + </literallayout> + + <para> + + Notice that an &ignorecase; value of <literal>1</literal> + preserves the case-spelling that the user supplied. + If you want &SCons; to translate the names + into lower-case, + regardless of the case used by the user, + specify an &ignorecase; value of <literal>2</literal>: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add(EnumOption('COLOR', 'red', 'Set background color', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'}, + ignorecase=2)) + env = Environment(options = opts, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + </programlisting> + + <para> + + Now &SCons; will use values of + <literal>red</literal>, + <literal>green</literal> or + <literal>blue</literal> + regardless of how the user spells + those values on the command line: + + </para> + + <literallayout> + % <userinput>scons -Q COLOR=Red foo.o</userinput> + cc -DCOLOR="red" -c -o foo.o foo.c + % <userinput>scons -Q COLOR=nAvY foo.o</userinput> + cc -DCOLOR="blue" -c -o foo.o foo.c + % <userinput>scons -Q COLOR=GREEN foo.o</userinput> + cc -DCOLOR="green" -c -o foo.o foo.c + </literallayout> + + </section> + + <section> + <title>Multiple Values From a List: the &ListOption; Build Option</title> + + <para> + + Another way in which you might want to allow users + to control build option is to + specify a list of one or more legal values. + &SCons; supports this through the &ListOption; function. + If, for example, we want a user to be able to set a + &COLORS; option to one or more of the legal list of values: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add(ListOption('COLORS', 0, 'List of colors', + ['red', 'green', 'blue'])) + env = Environment(options = opts, + CPPDEFINES={'COLORS' : '"${COLORS}"'}) + env.Program('foo.c') + </programlisting> + + <para> + + A user can now specify a comma-separated list + of legal values, + which will get translated into a space-separated + list for passing to the any build commands: + + </para> + + <literallayout> + % <userinput>scons -Q COLORS=red,blue foo.o</userinput> + TypeError: sequence item 0: expected string, int found: + % <userinput>scons -Q COLORS=blue,green,red foo.o</userinput> + TypeError: sequence item 0: expected string, int found: + </literallayout> + + <para> + + In addition, the &ListOption; function + allows the user to specify explicit keywords of + &all; or &none; + to select all of the legal values, + or none of them, respectively: + + </para> + + <literallayout> + % <userinput>scons -Q COLORS=all foo.o</userinput> + TypeError: sequence item 0: expected string, int found: + % <userinput>scons -Q COLORS=none foo.o</userinput> + TypeError: sequence item 0: expected string, int found: + </literallayout> + + <para> + + And, of course, an illegal value + still generates an error message: + + </para> + + <literallayout> + % <userinput>scons -Q COLORS=magenta foo.o</userinput> + TypeError: sequence item 0: expected string, int found: + </literallayout> + + </section> + + <section> + <title>Path Names: the &PathOption; Build Option</title> + + <para> + + &SCons; supports a &PathOption; function + to make it easy to create a build option + to control an expected path name. + If, for example, you need to + define a variable in the preprocessor + that control the location of a + configuration file: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add(PathOption('CONFIG', '/etc/my_config', 'Path to configuration file')) + env = Environment(options = opts, + CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'}) + env.Program('foo.c') + </programlisting> + + <para> + + This then allows the user to + override the &CONFIG; build option + on the command line as necessary: + + </para> + + <literallayout> + % <userinput>scons -Q foo.o</userinput> + + scons: *** Path does not exist for option CONFIG: Path to configuration file + File "SConstruct", line 4, in ? + % <userinput>scons -Q CONFIG=/usr/local/etc/other_config foo.o</userinput> + cc -DCONFIG_FILE="/usr/local/etc/other_config" -c -o foo.o foo.c + </literallayout> + + </section> + + <section> + <title>Enabled/Disabled Path Names: the &PackageOption; Build Option</title> + + <para> + + Sometimes you want to give users + even more control over a path name variable, + allowing them to explicitly enable or + disable the path name + by using <literal>yes</literal> or <literal>no</literal> keywords, + in addition to allow them + to supply an explicit path name. + &SCons; supports the &PackageOption; + function to support this: + + </para> + + <programlisting> + opts = Options('custom.py') + opts.Add(PackageOption('PACKAGE', '/opt/location', 'Location package')) + env = Environment(options = opts, + CPPDEFINES={'PACKAGE' : '"$PACKAGE"'}) + env.Program('foo.c') + </programlisting> + + <para> + + When the &SConscript; file uses the &PackageOption; funciton, + user can now still use the default + or supply an overriding path name, + but can now explicitly set the + specified variable to a value + that indicates the package should be enabled + (in which case the default should be used) + or disabled: + + </para> + + <literallayout> + % <userinput>scons -Q foo.o</userinput> + + scons: *** Path does not exist for option PACKAGE: Location package + File "SConstruct", line 4, in ? + % <userinput>scons -Q PACKAGE=/usr/local/location foo.o</userinput> + cc -DPACKAGE="/usr/local/location" -c -o foo.o foo.c + % <userinput>scons -Q PACKAGE=yes foo.o</userinput> + cc -DPACKAGE="1" -c -o foo.o foo.c + % <userinput>scons -Q PACKAGE=no foo.o</userinput> + cc -DPACKAGE="0" -c -o foo.o foo.c + </literallayout> + + </section> + + </section> + + <section> + <title>Adding Multiple Command-Line Build Options at Once</title> + + <para> + + Lastly, &SCons; provides a way to add + multiple build options to an &Options; object at once. + Instead of having to call the &Add; method + multiple times, + you can call the &AddOptions; + method with a list of build options + to be added to the object. + Each build option is specified + as either a tuple of arguments, + just like you'd pass to the &Add; method itself, + or as a call to one of the canned + functions for pre-packaged command-line build options. + in any order: + + </para> + + <programlisting> + opts = Options() + opts.AddOptions( + ('RELEASE', 'Set to 1 to build for release', 0), + ('CONFIG', 'Configuration file', '/etc/my_config'), + BoolOption('warnings', 'compilation with -Wall and similiar', 1), + EnumOption('debug', 'debug output and symbols', 'no', + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), # case sensitive + ListOption('shared', + 'libraries to build as shared libraries', + 'all', + names = list_of_libs), + PackageOption('x11', + 'use X11 installed here (yes = search some places)', + 'yes'), + PathOption('qtdir', 'where the root of Qt is installed', qtdir), + ) + </programlisting> + + <para> + </para> + + </section> diff --git a/doc/user/default.in b/doc/user/default.in deleted file mode 100644 index 2410da6..0000000 --- a/doc/user/default.in +++ /dev/null @@ -1,216 +0,0 @@ -<!-- - - Copyright (c) 2001, 2002, 2003 Steven Knight - - 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. - ---> - -<!-- - -=head1 Default targets - -Until now, we've demonstrated invoking Cons with an explicit target -to build: - - % cons hello - -Normally, Cons does not build anything unless a target is specified, -but specifying '.' (the current directory) will build everything: - - % cons # does not build anything - - % cons . # builds everything under the top-level directory - -Adding the C<Default> method to any F<Construct> or F<Conscript> file will add -the specified targets to a list of default targets. Cons will build -these defaults if there are no targets specified on the command line. -So adding the following line to the top-level F<Construct> file will mimic -Make's typical behavior of building everything by default: - - Default '.'; - -The following would add the F<hello> and F<goodbye> commands (in the -same directory as the F<Construct> or F<Conscript> file) to the default list: - - Default qw( - hello - goodbye - ); - -The C<Default> method may be used more than once to add targets to the -default list. - ---> - - <para> - - As mentioned previously, - &SCons; will build every target - in or below the current directory - by default--that is, when you don't - explicitly specify one or more targets - on the command line. - Sometimes, however, you may want - to specify explicitly that only - certain programs should be built by default. - You do this with the &Default; function: - - </para> - - <scons_example name="ex1"> - <file name="SConstruct" printme="1"> - env = Environment() - hello = env.Program('hello.c') - env.Program('goodbye.c') - Default(hello) - </file> - <file name="hello.c"> - hello.c - </file> - <file name="goodbye.c"> - goodbye.c - </file> - </scons_example> - - <para> - - This &SConstruct; file knows how to build two programs, - &hello; and &goodbye;, - but only builds the - &hello; program by default: - - </para> - - <scons_output example="ex1"> - <command>scons -Q</command> - <command>scons -Q</command> - <command>scons -Q goodbye</command> - </scons_output> - - <para> - - Note that, even when you use the &Default; - function in your &SConstruct; file, - you can still explicitly specify the current directory - (<literal>.</literal>) on the command line - to tell &SCons; to build - everything in (or below) the current directory: - - </para> - - <scons_output example="ex1"> - <command>scons -Q .</command> - </scons_output> - - <para> - - You can also call the &Default; - function more than once, - in which case each call - adds to the list of targets to be built by default: - - </para> - - <scons_example name="ex2"> - <file name="SConstruct" printme="1"> - env = Environment() - prog1 = env.Program('prog1.c') - Default(prog1) - prog2 = env.Program('prog2.c') - prog3 = env.Program('prog3.c') - Default(prog3) - </file> - <file name="prog1.c"> - prog1.c - </file> - <file name="prog2.c"> - prog2.c - </file> - <file name="prog3.c"> - prog3.c - </file> - </scons_example> - - <para> - - Or you can specify more than one target - in a single call to the &Default; function: - - </para> - - <programlisting> - env = Environment() - prog1 = env.Program('prog1.c') - prog2 = env.Program('prog2.c') - prog3 = env.Program('prog3.c') - Default(prog1, prog3) - </programlisting> - - <para> - - Either of these last two examples - will build only the - <application>prog1</application> - and - <application>prog3</application> - programs by default: - - </para> - - <scons_output example="ex2"> - <command>scons -Q</command> - <command>scons -Q .</command> - </scons_output> - - <para> - - Lastly, if for some reason you don't want - any targets built by default, - you can use the Python <literal>None</literal> - variable: - - </para> - - <scons_example name="ex3"> - <file name="SConstruct" printme="1"> - env = Environment() - prog1 = env.Program('prog1.c') - prog2 = env.Program('prog2.c') - Default(None) - </file> - <file name="prog1.c"> - prog1.c - </file> - <file name="prog2.c"> - prog2.c - </file> - </scons_example> - - <para> - - Which would produce build output like: - - </para> - - <scons_output example="ex3"> - <command>scons -Q</command> - <command>scons -Q .</command> - </scons_output> diff --git a/doc/user/default.sgml b/doc/user/default.sgml deleted file mode 100644 index fa21bae..0000000 --- a/doc/user/default.sgml +++ /dev/null @@ -1,209 +0,0 @@ -<!-- - - Copyright (c) 2001, 2002, 2003 Steven Knight - - 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. - ---> - -<!-- - -=head1 Default targets - -Until now, we've demonstrated invoking Cons with an explicit target -to build: - - % cons hello - -Normally, Cons does not build anything unless a target is specified, -but specifying '.' (the current directory) will build everything: - - % cons # does not build anything - - % cons . # builds everything under the top-level directory - -Adding the C<Default> method to any F<Construct> or F<Conscript> file will add -the specified targets to a list of default targets. Cons will build -these defaults if there are no targets specified on the command line. -So adding the following line to the top-level F<Construct> file will mimic -Make's typical behavior of building everything by default: - - Default '.'; - -The following would add the F<hello> and F<goodbye> commands (in the -same directory as the F<Construct> or F<Conscript> file) to the default list: - - Default qw( - hello - goodbye - ); - -The C<Default> method may be used more than once to add targets to the -default list. - ---> - - <para> - - As mentioned previously, - &SCons; will build every target - in or below the current directory - by default--that is, when you don't - explicitly specify one or more targets - on the command line. - Sometimes, however, you may want - to specify explicitly that only - certain programs should be built by default. - You do this with the &Default; function: - - </para> - - <programlisting> - env = Environment() - hello = env.Program('hello.c') - env.Program('goodbye.c') - Default(hello) - </programlisting> - - <para> - - This &SConstruct; file knows how to build two programs, - &hello; and &goodbye;, - but only builds the - &hello; program by default: - - </para> - - <literallayout> - % <userinput>scons -Q</userinput> - cc -c -o hello.o hello.c - cc -o hello hello.o - % <userinput>scons -Q</userinput> - scons: `hello' is up to date. - % <userinput>scons -Q goodbye</userinput> - cc -c -o goodbye.o goodbye.c - cc -o goodbye goodbye.o - </literallayout> - - <para> - - Note that, even when you use the &Default; - function in your &SConstruct; file, - you can still explicitly specify the current directory - (<literal>.</literal>) on the command line - to tell &SCons; to build - everything in (or below) the current directory: - - </para> - - <literallayout> - % <userinput>scons -Q .</userinput> - cc -c -o goodbye.o goodbye.c - cc -o goodbye goodbye.o - cc -c -o hello.o hello.c - cc -o hello hello.o - </literallayout> - - <para> - - You can also call the &Default; - function more than once, - in which case each call - adds to the list of targets to be built by default: - - </para> - - <programlisting> - env = Environment() - prog1 = env.Program('prog1.c') - Default(prog1) - prog2 = env.Program('prog2.c') - prog3 = env.Program('prog3.c') - Default(prog3) - </programlisting> - - <para> - - Or you can specify more than one target - in a single call to the &Default; function: - - </para> - - <programlisting> - env = Environment() - prog1 = env.Program('prog1.c') - prog2 = env.Program('prog2.c') - prog3 = env.Program('prog3.c') - Default(prog1, prog3) - </programlisting> - - <para> - - Either of these last two examples - will build only the - <application>prog1</application> - and - <application>prog3</application> - programs by default: - - </para> - - <literallayout> - % <userinput>scons -Q</userinput> - cc -c -o prog1.o prog1.c - cc -o prog1 prog1.o - cc -c -o prog3.o prog3.c - cc -o prog3 prog3.o - % <userinput>scons -Q .</userinput> - cc -c -o prog2.o prog2.c - cc -o prog2 prog2.o - </literallayout> - - <para> - - Lastly, if for some reason you don't want - any targets built by default, - you can use the Python <literal>None</literal> - variable: - - </para> - - <programlisting> - env = Environment() - prog1 = env.Program('prog1.c') - prog2 = env.Program('prog2.c') - Default(None) - </programlisting> - - <para> - - Which would produce build output like: - - </para> - - <literallayout> - % <userinput>scons -Q</userinput> - scons: *** No targets specified and no Default() targets found. Stop. - % <userinput>scons -Q .</userinput> - cc -c -o prog1.o prog1.c - cc -o prog1 prog1.o - cc -c -o prog2.o prog2.c - cc -o prog2 prog2.o - </literallayout> diff --git a/doc/user/main.in b/doc/user/main.in index e0ae71d..4aa4d9c 100644 --- a/doc/user/main.in +++ b/doc/user/main.in @@ -39,9 +39,9 @@ <!ENTITY builders-commands SYSTEM "builders-commands.sgml"> <!ENTITY builders-writing SYSTEM "builders-writing.sgml"> <!ENTITY caching SYSTEM "caching.sgml"> + <!ENTITY command-line SYSTEM "command-line.sgml"> <!ENTITY cons SYSTEM "cons.sgml"> <!ENTITY copyright SYSTEM "copyright.sgml"> - <!ENTITY default SYSTEM "default.sgml"> <!ENTITY depends SYSTEM "depends.sgml"> <!ENTITY environments SYSTEM "environments.sgml"> <!ENTITY errors SYSTEM "errors.sgml"> @@ -115,9 +115,9 @@ &environments; </chapter> - <chapter id="chap-default"> - <title>Default Targets</title> - &default; + <chapter id="chap-command-line"> + <title>Controlling a Build From the Command Line</title> + &command-line; </chapter> <chapter id="chap-help"> diff --git a/doc/user/main.sgml b/doc/user/main.sgml index e0ae71d..4aa4d9c 100644 --- a/doc/user/main.sgml +++ b/doc/user/main.sgml @@ -39,9 +39,9 @@ <!ENTITY builders-commands SYSTEM "builders-commands.sgml"> <!ENTITY builders-writing SYSTEM "builders-writing.sgml"> <!ENTITY caching SYSTEM "caching.sgml"> + <!ENTITY command-line SYSTEM "command-line.sgml"> <!ENTITY cons SYSTEM "cons.sgml"> <!ENTITY copyright SYSTEM "copyright.sgml"> - <!ENTITY default SYSTEM "default.sgml"> <!ENTITY depends SYSTEM "depends.sgml"> <!ENTITY environments SYSTEM "environments.sgml"> <!ENTITY errors SYSTEM "errors.sgml"> @@ -115,9 +115,9 @@ &environments; </chapter> - <chapter id="chap-default"> - <title>Default Targets</title> - &default; + <chapter id="chap-command-line"> + <title>Controlling a Build From the Command Line</title> + &command-line; </chapter> <chapter id="chap-help"> diff --git a/doc/user/run.in b/doc/user/run.in index d8e19f8..0d3e7f0 100644 --- a/doc/user/run.in +++ b/doc/user/run.in @@ -373,44 +373,3 @@ do an unconstrained build before committing the integration). </para> </section> - - <section> - <title>The &SCONSFLAGS; Environment Variable</title> - - <para> - - Users may find themselves supplying - the same command-line options every time - they run &SCons;. - For example, a user might find that it saves time - to always specify a value of <literal>-j 2</literal> - to run the builds in parallel. - - </para> - - <scons_example name="SCONSFLAGS"> - <file name="SConstruct"> - def b(target, source, env): - pass - def s(target, source, env): - return " ..." - a = Action(b, strfunction = s) - env = Environment(BUILDERS = {'A' : a}) - env.A('foo.out', 'foo.in') - </file> - <file name="foo.in"> - foo.in - </file> - </scons_example> - - <scons_output example="SCONSFLAGS"> - <command>scons</command> - <command>export SCONSFLAGS="-Q"</command> - <command>scons</command> - </scons_output> - - <para> - - </para> - - </section> diff --git a/doc/user/run.sgml b/doc/user/run.sgml index 9cdb07c..0d3e7f0 100644 --- a/doc/user/run.sgml +++ b/doc/user/run.sgml @@ -373,43 +373,3 @@ do an unconstrained build before committing the integration). </para> </section> - - <section> - <title>The &SCONSFLAGS; Environment Variable</title> - - <para> - - Users may find themselves supplying - the same command-line options every time - they run &SCons;. - For example, a user might find that it saves time - to always specify a value of <literal>-j 2</literal> - to run the builds in parallel. - - </para> - - - - <literallayout> - % <userinput>scons</userinput> - scons: Reading SConscript files ... - ... - scons: done reading SConscript files. - scons: Building targets ... - scons: `.' is up to date. - scons: done building targets. - % <userinput>export SCONSFLAGS="-Q"</userinput> - % <userinput>scons</userinput> - scons: Reading SConscript files ... - ... - scons: done reading SConscript files. - scons: Building targets ... - scons: `.' is up to date. - scons: done building targets. - </literallayout> - - <para> - - </para> - - </section> diff --git a/src/CHANGES.txt b/src/CHANGES.txt index c81e870..2905339 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -10,6 +10,12 @@ RELEASE X.XX - XXX, XX XXX XXXX XX:XX:XX -XXXX + From Hartmut Goebel: + + - Add several new types of canned functions to help create options: + BoolOption(), EnumOption(), ListOption(), PackageOption(), + PathOption(). + From Steven Knight: - Fix use of CPPDEFINES with C++ source files. diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 24e3d76..c5fe7a3 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -16,7 +16,12 @@ SCons/Optik/__init__.py SCons/Optik/errors.py SCons/Optik/option.py SCons/Optik/option_parser.py -SCons/Options.py +SCons/Options/__init__.py +SCons/Options/BoolOption.py +SCons/Options/EnumOption.py +SCons/Options/ListOption.py +SCons/Options/PackageOption.py +SCons/Options/PathOption.py SCons/Platform/__init__.py SCons/Platform/aix.py SCons/Platform/cygwin.py diff --git a/src/engine/SCons/Options/BoolOption.py b/src/engine/SCons/Options/BoolOption.py new file mode 100644 index 0000000..f38bf02 --- /dev/null +++ b/src/engine/SCons/Options/BoolOption.py @@ -0,0 +1,88 @@ +"""engine.SCons.Options.BoolOption + +This file defines the option type for SCons implementing true/false values. + +Usage example: + + opts = Options() + opts.Add(BoolOption('embedded', 'build for an embedded system', 0)) + ... + if env['embedded'] == 1: + ... +""" + +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +__all__ = ('BoolOption', 'True', 'False') + +import string + +import SCons.Errors + +__true_strings = ('y', 'yes', 'true', 't', '1', 'on' , 'all' ) +__false_strings = ('n', 'no', 'false', 'f', '0', 'off', 'none') + +# we need this since SCons should work version indepentant +True, False = 1, 0 + + +def _text2bool(val): + """ + Converts strings to True/False depending on the 'truth' expressed by + the string. If the string can't be converted, the original value + will be returned. + + See '__true_strings' and '__false_strings' for values considered + 'true' or 'false respectivly. + + This is usable as 'converter' for SCons' Options. + """ + lval = string.lower(val) + if lval in __true_strings: return True + if lval in __false_strings: return False + raise ValueError("Invalid value for boolean option: %s" % val) + + +def _validator(key, val, env): + """ + Validates the given value to be either '0' or '1'. + + This is usable as 'validator' for SCons' Options. + """ + if not env[key] in (True, False): + raise SCons.Errors.UserError( + 'Invalid value for boolean option %s: %s' % (key, env[key])) + + +def BoolOption(key, help, default): + """ + The input parameters describe a boolen option, thus they are + returned with the correct converter and validator appended. The + 'help' text will by appended by '(yes|no) to show the valid + valued. The result is usable for input to opts.Add(). + """ + return (key, '%s (yes|no)' % help, default, + _validator, _text2bool) diff --git a/src/engine/SCons/Options/BoolOptionTests.py b/src/engine/SCons/Options/BoolOptionTests.py new file mode 100644 index 0000000..845f251 --- /dev/null +++ b/src/engine/SCons/Options/BoolOptionTests.py @@ -0,0 +1,121 @@ +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys +import unittest + +import SCons.Errors +import SCons.Options + +class BoolOptionTestCase(unittest.TestCase): + def test_BoolOption(self): + """Test BoolOption creation""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.BoolOption('test', 'test option help', 0)) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help (yes|no)', o.help + assert o.default == 0, o.default + assert not o.validator is None, o.validator + assert not o.converter is None, o.converter + + def test_converter(self): + """Test the BoolOption converter""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.BoolOption('test', 'test option help', 0)) + + o = opts.options[0] + + true_values = [ + 'y', 'Y', + 'yes', 'YES', + 't', 'T', + 'true', 'TRUE', + 'on', 'ON', + 'all', 'ALL', + '1', + ] + false_values = [ + 'n', 'N', + 'no', 'NO', + 'f', 'F', + 'false', 'FALSE', + 'off', 'OFF', + 'none', 'NONE', + '0', + ] + + for t in true_values: + x = o.converter(t) + assert x, "converter returned false for '%s'" % t + + for f in false_values: + x = o.converter(f) + assert not x, "converter returned true for '%s'" % f + + caught = None + try: + o.converter('x') + except ValueError: + caught = 1 + assert caught, "did not catch expected ValueError" + + def test_validator(self): + """Test the BoolOption validator""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.BoolOption('test', 'test option help', 0)) + + o = opts.options[0] + + env = { + 'T' : SCons.Options.True, + 'F' : SCons.Options.False, + 'N' : 'xyzzy', + } + + o.validator('T', 0, env) + + o.validator('F', 0, env) + + caught = None + try: + o.validator('N', 0, env) + except SCons.Errors.UserError: + caught = 1 + assert caught, "did not catch expected UserError for N" + + caught = None + try: + o.validator('NOSUCHKEY', 0, env) + except KeyError: + caught = 1 + assert caught, "did not catch expected KeyError for NOSUCHKEY" + + +if __name__ == "__main__": + suite = unittest.makeSuite(BoolOptionTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) diff --git a/src/engine/SCons/Options/EnumOption.py b/src/engine/SCons/Options/EnumOption.py new file mode 100644 index 0000000..d4e2ac1 --- /dev/null +++ b/src/engine/SCons/Options/EnumOption.py @@ -0,0 +1,101 @@ +"""engine.SCons.Options.EnumOption + +This file defines the option type for SCons allowing only specified +input-values. + +Usage example: + + opts = Options() + opts.Add(EnumOption('debug', 'debug output and symbols', 'no', + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=2)) + ... + if env['debug'] == 'full': + ... +""" + +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +__all__ = ('EnumOption',) + +import string + +import SCons.Errors + +def _validator(key, val, env, vals): + if not val in vals: + raise SCons.Errors.UserError( + 'Invalid value for option %s: %s' % (key, val)) + + +def EnumOption(key, help, default, allowed_values, map={}, ignorecase=0): + """ + The input parameters describe a option with only certain values + allowed. They are returned with an appropriate converter and + validator appended. The result is usable for input to + Options.Add(). + + 'key' and 'default' are the values to be passed on to Options.Add(). + + 'help' will be appended by the allowed values automatically + + 'allowed_values' is a list of strings, which are allowed as values + for this option. + + The 'map'-dictionary may be used for converting the input value + into canonical values (eg. for aliases). + + 'ignorecase' defines the behaviour of the validator: + + If ignorecase == 0, the validator/converter are case-sensitive. + If ignorecase == 1, the validator/converter are case-insensitive. + If ignorecase == 2, the validator/converter is case-insensitive and + the converted value will always be lower-case. + + The 'validator' tests whether the value is in the list of allowed + values. The 'converter' converts input values according to the + given 'map'-dictionary (unmapped input values are returned + unchanged). + """ + help = '%s (%s)' % (help, string.join(allowed_values, '|')) + # define validator + if ignorecase >= 1: + validator = lambda key, val, env, vals=allowed_values: \ + _validator(key, string.lower(val), env, vals) + else: + validator = lambda key, val, env, vals=allowed_values: \ + _validator(key, val, env, vals) + # define converter + if ignorecase == 2: + converter = lambda val, map=map: \ + string.lower(map.get(string.lower(val), val)) + elif ignorecase == 1: + converter = lambda val, map=map: \ + map.get(string.lower(val), val) + else: + converter = lambda val, map=map: \ + map.get(val, val) + return (key, help, default, validator, converter) diff --git a/src/engine/SCons/Options/EnumOptionTests.py b/src/engine/SCons/Options/EnumOptionTests.py new file mode 100644 index 0000000..20ae6c3 --- /dev/null +++ b/src/engine/SCons/Options/EnumOptionTests.py @@ -0,0 +1,198 @@ +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys +import unittest + +import SCons.Errors +import SCons.Options + +class EnumOptionTestCase(unittest.TestCase): + def test_EnumOption(self): + """Test EnumOption creation""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.EnumOption('test', 'test option help', 0, + ['one', 'two', 'three'], + {})) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help (one|two|three)', o.help + assert o.default == 0, o.default + assert not o.validator is None, o.validator + assert not o.converter is None, o.converter + + def test_converter(self): + """Test the EnumOption converter""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.EnumOption('test', 'test option help', 0, + ['one', 'two', 'three'])) + + o = opts.options[0] + + for a in ['one', 'two', 'three', 'no_match']: + x = o.converter(a) + assert x == a, x + + opts = SCons.Options.Options() + opts.Add(SCons.Options.EnumOption('test', 'test option help', 0, + ['one', 'two', 'three'], + {'1' : 'one', + '2' : 'two', + '3' : 'three'})) + + o = opts.options[0] + + x = o.converter('one') + assert x == 'one', x + x = o.converter('1') + assert x == 'one', x + + x = o.converter('two') + assert x == 'two', x + x = o.converter('2') + assert x == 'two', x + + x = o.converter('three') + assert x == 'three', x + x = o.converter('3') + assert x == 'three', x + + opts = SCons.Options.Options() + opts.Add(SCons.Options.EnumOption('test0', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=0)) + opts.Add(SCons.Options.EnumOption('test1', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=1)) + opts.Add(SCons.Options.EnumOption('test2', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=2)) + + o0 = opts.options[0] + o1 = opts.options[1] + o2 = opts.options[2] + + table = { + 'one' : ['one', 'one', 'one'], + 'One' : ['One', 'One', 'one'], + 'ONE' : ['ONE', 'ONE', 'one'], + 'two' : ['two', 'two', 'two'], + 'twO' : ['twO', 'twO', 'two'], + 'TWO' : ['TWO', 'TWO', 'two'], + 'three' : ['three', 'three', 'three'], + 'thRee' : ['thRee', 'thRee', 'three'], + 'THREE' : ['THREE', 'THREE', 'three'], + 'a' : ['one', 'one', 'one'], + 'A' : ['A', 'one', 'one'], + 'b' : ['two', 'two', 'two'], + 'B' : ['B', 'two', 'two'], + 'c' : ['three', 'three', 'three'], + 'C' : ['C', 'three', 'three'], + } + + for k, l in table.items(): + x = o0.converter(k) + assert x == l[0], "o0 got %s, expected %s" % (x, l[0]) + x = o1.converter(k) + assert x == l[1], "o1 got %s, expected %s" % (x, l[1]) + x = o2.converter(k) + assert x == l[2], "o2 got %s, expected %s" % (x, l[2]) + + def test_validator(self): + """Test the EnumOption validator""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.EnumOption('test0', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=0)) + opts.Add(SCons.Options.EnumOption('test1', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=1)) + opts.Add(SCons.Options.EnumOption('test2', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=2)) + + o0 = opts.options[0] + o1 = opts.options[1] + o2 = opts.options[2] + + def valid(o, v): + o.validator('X', v, {}) + + def invalid(o, v): + caught = None + try: + o.validator('X', v, {}) + except SCons.Errors.UserError: + caught = 1 + assert caught, "did not catch expected UserError for o = %s, v = %s" % (o.key, v) + + table = { + 'one' : [ valid, valid, valid], + 'One' : [invalid, valid, valid], + 'ONE' : [invalid, valid, valid], + 'two' : [ valid, valid, valid], + 'twO' : [invalid, valid, valid], + 'TWO' : [invalid, valid, valid], + 'three' : [ valid, valid, valid], + 'thRee' : [invalid, valid, valid], + 'THREE' : [invalid, valid, valid], + 'a' : [invalid, invalid, invalid], + 'A' : [invalid, invalid, invalid], + 'b' : [invalid, invalid, invalid], + 'B' : [invalid, invalid, invalid], + 'c' : [invalid, invalid, invalid], + 'C' : [invalid, invalid, invalid], + 'no_v' : [invalid, invalid, invalid], + } + + for v, l in table.items(): + l[0](o0, v) + l[1](o1, v) + l[2](o2, v) + + +if __name__ == "__main__": + suite = unittest.makeSuite(EnumOptionTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) diff --git a/src/engine/SCons/Options/ListOption.py b/src/engine/SCons/Options/ListOption.py new file mode 100644 index 0000000..20b7e99 --- /dev/null +++ b/src/engine/SCons/Options/ListOption.py @@ -0,0 +1,131 @@ +"""engine.SCons.Options.ListOption + +This file defines the option type for SCons implementing 'lists'. + +A 'list' option may either be 'all', 'none' or a list of names +separated by comma. After the option has been processed, the option +value holds either the named list elements, all list elemens or no +list elements at all. + +Usage example: + + list_of_libs = Split('x11 gl qt ical') + + opts = Options() + opts.Add(ListOption('shared', + 'libraries to build as shared libraries', + 'all', + elems = list_of_libs)) + ... + for lib in list_of_libs: + if lib in env['shared']: + env.SharedObject(...) + else: + env.Object(...) +""" + +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +# Know Bug: This should behave like a Set-Type, but does not really, +# since elements can occur twice. + +__all__ = ('ListOption',) + +import string +import UserList + + +class _ListOption(UserList.UserList): + def __init__(self, allowedElems, initlist): + UserList.UserList.__init__(self, filter(None, initlist)) + self.allowedElems = allowedElems[:] + self.allowedElems.sort() + + def __cmp__(self, other): + raise NotImplementedError + def __eq__(self, other): + raise NotImplementedError + def __ge__(self, other): + raise NotImplementedError + def __gt__(self, other): + raise NotImplementedError + def __le__(self, other): + raise NotImplementedError + def __lt__(self, other): + raise NotImplementedError + def __str__(self): + if len(self) == 0: + return 'none' + self.data.sort() + if self.data == self.allowedElems: + return 'all' + else: + return string.join(self, ',') + #def __repr__(self): + # todo: implement this + +def _converter(val, allowedElems): + """ + """ + if val == 'none': + val = [] + elif val == 'all': + val = allowedElems + else: + val = filter(None, string.split(val, ',')) + notAllowed = [] + for v in val: + if not v in allowedElems: + notAllowed.append(v) + if notAllowed: + raise ValueError("Invalid value(s) for option: %s" % + string.join(notAllowed, ',')) + return _ListOption(allowedElems, val) + + +## def _validator(key, val, env): +## """ +## """ +## # todo: write validater for pgk list +## return 1 + + +def ListOption(key, help, default, names): + """ + The input parameters describe a 'package list' option, thus they + are returned with the correct converter and validater appended. The + result is usable for input to opts.Add() . + + A 'package list' option may either be 'all', 'none' or a list of + package names (separated by space). + """ + names_str = 'allowed names: %s' % string.join(names, ' ') + help = string.join( + (help, '(all|none|comma-separated list of names)', names_str), + '\n ') + return (key, help, default, + None, #_validator, + lambda val, elems=names: _converter(val, elems)) diff --git a/src/engine/SCons/Options/ListOptionTests.py b/src/engine/SCons/Options/ListOptionTests.py new file mode 100644 index 0000000..2175720 --- /dev/null +++ b/src/engine/SCons/Options/ListOptionTests.py @@ -0,0 +1,101 @@ +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys +import unittest + +import SCons.Errors +import SCons.Options + +class ListOptionTestCase(unittest.TestCase): + def test_ListOption(self): + """Test ListOption creation""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.ListOption('test', 'test option help', 'all', + ['one', 'two', 'three'])) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help\n (all|none|comma-separated list of names)\n allowed names: one two three', repr(o.help) + assert o.default == 'all', o.default + assert o.validator is None, o.validator + assert not o.converter is None, o.converter + + def test_converter(self): + """Test the ListOption converter""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.ListOption('test', 'test option help', 'all', + ['one', 'two', 'three'])) + + o = opts.options[0] + + x = o.converter('all') + assert str(x) == 'all', x + + x = o.converter('none') + assert str(x) == 'none', x + + x = o.converter('one') + assert str(x) == 'one', x + + x = o.converter('two') + assert str(x) == 'two', x + + x = o.converter('three') + assert str(x) == 'three', x + + x = o.converter('one,two') + assert str(x) == 'one,two', x + x = o.converter('two,one') + assert str(x) == 'one,two', x + + x = o.converter('one,three') + assert str(x) == 'one,three', x + x = o.converter('three,one') + assert str(x) == 'one,three', x + + x = o.converter('two,three') + assert str(x) == 'three,two', x + x = o.converter('three,two') + assert str(x) == 'three,two', x + + x = o.converter('one,two,three') + assert str(x) == 'all', x + + x = o.converter('three,two,one') + assert str(x) == 'all', x + + caught = None + try: + x = o.converter('no_match') + except ValueError: + caught = 1 + assert caught, "did not catch expected ValueError" + + +if __name__ == "__main__": + suite = unittest.makeSuite(ListOptionTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) diff --git a/src/engine/SCons/OptionsTests.py b/src/engine/SCons/Options/OptionsTests.py index 87fbc9a..ab931e2 100644 --- a/src/engine/SCons/OptionsTests.py +++ b/src/engine/SCons/Options/OptionsTests.py @@ -60,6 +60,7 @@ def checkSave(file, expected): assert expected == ldict, "%s\n...not equal to...\n%s" % (expected, ldict) class OptionsTestCase(unittest.TestCase): + def test_Add(self): """Test adding to an Options object""" opts = SCons.Options.Options() @@ -118,6 +119,30 @@ class OptionsTestCase(unittest.TestCase): o = opts.options[2] o.validator(o.key, o.converter(o.default), {}) + def test_AddOptions(self): + """Test adding a list of options to an Options object""" + opts = SCons.Options.Options() + + opts.AddOptions(('VAR2',), + ('ANSWER2', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12)) + + o = opts.options[0] + assert o.key == 'VAR2', o.key + assert o.help == '', o.help + assert o.default == None, o.default + assert o.validator == None, o.validator + assert o.converter == None, o.converter + + o = opts.options[1] + assert o.key == 'ANSWER2', o.key + assert o.help == 'THE answer to THE question', o.help + assert o.default == "42", o.default + o.validator(o.key, o.converter(o.default), {}) + def test_Update(self): """Test updating an Environment""" diff --git a/src/engine/SCons/Options/PackageOption.py b/src/engine/SCons/Options/PackageOption.py new file mode 100644 index 0000000..5940974 --- /dev/null +++ b/src/engine/SCons/Options/PackageOption.py @@ -0,0 +1,106 @@ +"""engine.SCons.Options + +This file defines the option type for SCons implementing 'package +activation'. + +To be used whenever a 'package' may be enabled/disabled and the +package path may be specified. + +Usage example: + + Examples: + x11=no (disables X11 support) + x11=yes (will search for the package installation dir) + x11=/usr/local/X11 (will check this path for existance) + + To replace autoconf's --with-xxx=yyy + + opts = Options() + opts.Add(PackageOption('x11', + 'use X11 installed here (yes = search some places', + 'yes')) + ... + if env['x11'] == True: + dir = ... search X11 in some standard places ... + env['x11'] = dir + if env['x11']: + ... build with x11 ... +""" + +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +__all__ = ('PackageOption', 'True', 'False') + +import string + +from BoolOption import True, False +import SCons.Errors + +__enable_strings = ('yes', 'true', 'on', 'enable', 'search') +__disable_strings = ('no', 'false', 'off', 'disable') + +def _converter(val): + """ + """ + lval = string.lower(val) + if lval in __enable_strings: return True + if lval in __disable_strings: return False + #raise ValueError("Invalid value for boolean option: %s" % val) + return val + + +def _validator(key, val, env, searchfunc): + # NB: searchfunc is currenty undocumented and unsupported + """ + """ + # todo: write validator, check for path + import os + if env[key] == False: + pass + elif env[key] == True: + if searchfunc: + env[key] = searchfunc(key, val) + elif not os.path.exists(val): + raise SCons.Errors.UserError( + 'Path does not exist for option %s: %s' % (key, val)) + + +def PackageOption(key, help, default, searchfunc=None): + # NB: searchfunc is currenty undocumented and unsupported + """ + The input parameters describe a 'package list' option, thus they + are returned with the correct converter and validator appended. The + result is usable for input to opts.Add() . + + A 'package list' option may either be 'all', 'none' or a list of + package names (seperated by space). + """ + help = string.join( + (help, '( yes | no | /path/to/%s )' % key), + '\n ') + return (key, help, default, + lambda k, v, e, f=searchfunc: _validator(k,v,e,f), + _converter) diff --git a/src/engine/SCons/Options/PackageOptionTests.py b/src/engine/SCons/Options/PackageOptionTests.py new file mode 100644 index 0000000..d9fbe7b --- /dev/null +++ b/src/engine/SCons/Options/PackageOptionTests.py @@ -0,0 +1,109 @@ +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys +import unittest + +import SCons.Errors +import SCons.Options + +import TestCmd + +class PackageOptionTestCase(unittest.TestCase): + def test_PackageOption(self): + """Test PackageOption creation""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.PackageOption('test', 'test option help', '/default/path')) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help\n ( yes | no | /path/to/test )', repr(o.help) + assert o.default == '/default/path', o.default + assert not o.validator is None, o.validator + assert not o.converter is None, o.converter + + def test_converter(self): + """Test the PackageOption converter""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.PackageOption('test', 'test option help', '/default/path')) + + o = opts.options[0] + + true_values = [ + 'yes', 'YES', + 'true', 'TRUE', + 'on', 'ON', + 'enable', 'ENABLE', + 'search', 'SEARCH', + ] + false_values = [ + 'no', 'NO', + 'false', 'FALSE', + 'off', 'OFF', + 'disable', 'DISABLE', + ] + + for t in true_values: + x = o.converter(t) + assert x, "converter returned false for '%s'" % t + + for f in false_values: + x = o.converter(f) + assert not x, "converter returned true for '%s'" % f + + x = o.converter('/explicit/path') + assert x == '/explicit/path', x + + def test_validator(self): + """Test the PackageOption validator""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.PackageOption('test', 'test option help', '/default/path')) + + test = TestCmd.TestCmd(workdir='') + test.write('exists', 'exists\n') + + o = opts.options[0] + + env = {'F':0, 'T':1, 'X':'x'} + + exists = test.workpath('exists') + does_not_exist = test.workpath('does_not_exist') + + o.validator('F', '/path', env) + o.validator('T', '/path', env) + o.validator('X', exists, env) + + caught = None + try: + o.validator('X', does_not_exist, env) + except SCons.Errors.UserError: + caught = 1 + assert caught, "did not catch expected UserError" + + +if __name__ == "__main__": + suite = unittest.makeSuite(PackageOptionTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) diff --git a/src/engine/SCons/Options/PathOption.py b/src/engine/SCons/Options/PathOption.py new file mode 100644 index 0000000..cea4750 --- /dev/null +++ b/src/engine/SCons/Options/PathOption.py @@ -0,0 +1,85 @@ +"""engine.SCons.Options.PathOption + +This file defines an option type for SCons implementing 'package +activation'. + +To be used whenever a 'package' may be enabled/disabled and the +package path may be specified. + +Usage example: + + Examples: + x11=no (disables X11 support) + x11=yes (will search for the package installation dir) + x11=/usr/local/X11 (will check this path for existance) + + To replace autoconf's --with-xxx=yyy + + opts = Options() + + opts = Options() + opts.Add(PathOption('qtdir', + 'where the root of Qt is installed', + qtdir)) + opts.Add(PathOption('qt_includes', + 'where the Qt includes are installed', + '$qtdir/includes')) + opts.Add(PathOption('qt_libraries', + 'where the Qt library is installed', + '$qtdir/lib')) + +""" + +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +__all__ = ('PathOption',) + +import os + +import SCons.Errors + +def _validator(key, val, env): + """ + """ + # todo: write validator, check for path + if not os.path.exists(val): + raise SCons.Errors.UserError( + 'Path does not exist for option %s: %s' % (key, val)) + + +def PathOption(key, help, default): + # NB: searchfunc is currenty undocumented and unsupported + """ + The input parameters describe a 'path list' option, thus they + are returned with the correct converter and validator appended. The + result is usable for input to opts.Add() . + + A 'package list' option may either be 'all', 'none' or a list of + package names (seperated by space). + """ + return (key, '%s ( /path/to/%s )' % (help, key), default, + _validator, None) + diff --git a/src/engine/SCons/Options/PathOptionTests.py b/src/engine/SCons/Options/PathOptionTests.py new file mode 100644 index 0000000..358f8e7 --- /dev/null +++ b/src/engine/SCons/Options/PathOptionTests.py @@ -0,0 +1,70 @@ +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys +import unittest + +import SCons.Errors +import SCons.Options + +import TestCmd + +class PathOptionTestCase(unittest.TestCase): + def test_PathOption(self): + """Test PathOption creation""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.PathOption('test', 'test option help', '/default/path')) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help ( /path/to/test )', repr(o.help) + assert o.default == '/default/path', o.default + assert not o.validator is None, o.validator + assert o.converter is None, o.converter + + def test_validator(self): + """Test the PathOption validator""" + opts = SCons.Options.Options() + opts.Add(SCons.Options.PathOption('test', 'test option help', '/default/path')) + + test = TestCmd.TestCmd(workdir='') + test.write('exists', 'exists\n') + + o = opts.options[0] + + o.validator('X', test.workpath('exists'), {}) + + caught = None + try: + o.validator('X', test.workpath('does_not_exist'), {}) + except SCons.Errors.UserError: + caught = 1 + assert caught, "did not catch expected UserError" + + +if __name__ == "__main__": + suite = unittest.makeSuite(PathOptionTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) diff --git a/src/engine/SCons/Options.py b/src/engine/SCons/Options/__init__.py index f4d2bcc..6236102 100644 --- a/src/engine/SCons/Options.py +++ b/src/engine/SCons/Options/__init__.py @@ -1,7 +1,7 @@ """engine.SCons.Options -This file defines the Options class that is used to add user-friendly customizable -variables to a scons build. +This file defines the Options class that is used to add user-friendly +customizable variables to an SCons build. """ # @@ -35,6 +35,12 @@ import SCons.Errors import SCons.Util import SCons.Warnings +from BoolOption import BoolOption, True, False # okay +from EnumOption import EnumOption # okay +from ListOption import ListOption # naja +from PackageOption import PackageOption # naja +from PathOption import PathOption # okay + class Options: """ @@ -56,12 +62,25 @@ class Options: elif files: self.files = files + def _do_add(self, key, help="", default=None, validator=None, converter=None): + class Option: + pass + + option = Option() + option.key = key + option.help = help + option.default = default + option.validator = validator + option.converter = converter + + self.options.append(option) + def Add(self, key, help="", default=None, validator=None, converter=None, **kw): """ Add an option. - key - the name of the variable + key - the name of the variable, or a list or tuple of arguments help - optional help text for the options default - optional default value validator - optional function that is called to validate the option's value @@ -70,8 +89,13 @@ class Options: putting it in the environment. """ - if not SCons.Util.is_valid_construction_var(key): - raise SCons.Errors.UserError, "Illegal Options.Add() key `%s'" % key + if SCons.Util.is_List(key) or type(key) == type(()): + apply(self._do_add, key) + return + + if not SCons.Util.is_String(key) or \ + not SCons.Util.is_valid_construction_var(key): + raise SCons.Errors.UserError, "Illegal Options.Add() key `%s'" % str(key) if kw.has_key('validater'): SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, @@ -80,17 +104,27 @@ class Options: if validator is None: validator = kw['validater'] - class Option: - pass + self._do_add(key, help, default, validator, converter) - option = Option() - option.key = key - option.help = help - option.default = default - option.validator = validator - option.converter = converter - self.options.append(option) + def AddOptions(self, *optlist): + """ + Add a list of options. + + Each list element is a tuple/list of arguments to be passed on + to the underlying method for adding options. + + Example: + opt.AddOptions( + ('debug', '', 0), + ('CC', 'The C compiler'), + ('VALIDATE', 'An option for testing validation', 'notset', + validator, None), + ) + """ + for o in optlist: + apply(self._do_add, o) + def Update(self, env, args=None): """ diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index 924ddf3..fff1869 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -627,17 +627,22 @@ def BuildDefaultGlobals(): globals = {} globals['Action'] = SCons.Action.Action globals['ARGUMENTS'] = arguments + globals['BoolOption'] = SCons.Options.BoolOption globals['Builder'] = SCons.Builder.Builder globals['Configure'] = SCons.SConf.SConf + globals['EnumOption'] = SCons.Options.EnumOption globals['Environment'] = SCons.Environment.Environment + globals['ListOption'] = SCons.Options.ListOption globals['Options'] = Options + globals['PackageOption'] = SCons.Options.PackageOption + globals['PathOption'] = SCons.Options.PathOption globals['Platform'] = SCons.Platform.Platform globals['Return'] = Return globals['Scanner'] = SCons.Scanner.Base globals['Tool'] = SCons.Tool.Tool globals['WhereIs'] = SCons.Util.WhereIs - # Functions we're in the process of converting to Environment methods. + # Functions we might still convert to Environment methods. globals['CScan'] = SCons.Defaults.CScan globals['DefaultEnvironment'] = SCons.Defaults.DefaultEnvironment diff --git a/src/setup.py b/src/setup.py index be38a84..be3171d 100644 --- a/src/setup.py +++ b/src/setup.py @@ -198,6 +198,7 @@ arguments = { 'packages' : ["SCons", "SCons.Node", "SCons.Optik", + "SCons.Options", "SCons.Platform", "SCons.Scanner", "SCons.Script", diff --git a/test/OptionsTypes.py b/test/OptionsTypes.py new file mode 100644 index 0000000..43cbfbe --- /dev/null +++ b/test/OptionsTypes.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python +# +# __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. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import TestSCons, SCons.Errors +import string, os + +test = TestSCons.TestSCons() + +def check(expect): + result = string.split(test.stdout(), '\n') + assert result[1:len(expect)+1] == expect, (result[1:len(expect)+1], expect) + +#### test BoolOption ##### + +test.write('SConstruct', """ +from SCons.Options import BoolOption + +opts = Options(args=ARGUMENTS) +opts.AddOptions( + BoolOption('warnings', 'compilation with -Wall and similiar', 1), + BoolOption('profile', 'create profiling informations', 0), + ) + +env = Environment(options=opts) +Help(opts.GenerateHelpText(env)) + +print env['warnings'] +print env['profile'] + +Default(env.Alias('dummy', None)) +""") + +test.run() +check(['1', '0']) + +test.run(arguments='warnings=0 profile=no profile=true') +check(['0', '1']) + +test.run(arguments='warnings=irgendwas', + stderr = """ +scons: *** Error converting option: warnings +Invalid value for boolean option: irgendwas +File "SConstruct", line 10, in ? +""", status=2) + + +#### test EnumOption #### + +test.write('SConstruct', """ +from SCons.Options import EnumOption + +list_of_libs = Split('x11 gl qt ical') + +opts = Options(args=ARGUMENTS) +opts.AddOptions( + EnumOption('debug', 'debug output and symbols', 'no', + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), # case sensitive + EnumOption('guilib', 'gui lib to use', 'gtk', + allowed_values=('motif', 'gtk', 'kde'), + map={}, ignorecase=1), # case insensitive + EnumOption('some', 'some option', 'xaver', + allowed_values=('xaver', 'eins'), + map={}, ignorecase=2), # make lowercase + ) + +env = Environment(options=opts) +Help(opts.GenerateHelpText(env)) + +print env['debug'] +print env['guilib'] +print env['some'] + +Default(env.Alias('dummy', None)) +""") + + +test.run(); check(['no', 'gtk', 'xaver']) +test.run(arguments='debug=yes guilib=Motif some=xAVER') +check(['yes', 'Motif', 'xaver']) +test.run(arguments='debug=full guilib=KdE some=EiNs') +check(['full', 'KdE', 'eins']) + +test.run(arguments='debug=FULL', + stderr = """ +scons: *** Invalid value for option debug: FULL +File "SConstruct", line 19, in ? +""", status=2) + +test.run(arguments='guilib=IrGeNdwas', + stderr = """ +scons: *** Invalid value for option guilib: irgendwas +File "SConstruct", line 19, in ? +""", status=2) + +test.run(arguments='some=IrGeNdwas', + stderr = """ +scons: *** Invalid value for option some: irgendwas +File "SConstruct", line 19, in ? +""", status=2) + + + +#### test ListOption #### + +test.write('SConstruct', """ +from SCons.Options import ListOption + +list_of_libs = Split('x11 gl qt ical') + +opts = Options(args=ARGUMENTS) +opts.AddOptions( + ListOption('shared', + 'libraries to build as shared libraries', + 'all', + names = list_of_libs), + ) + +env = Environment(options=opts) +Help(opts.GenerateHelpText(env)) + +print env['shared'] +if 'ical' in env['shared']: print '1' +else: print '0' +for x in env['shared']: + print x, +print +print env.subst('$shared') +Default(env.Alias('dummy', None)) +""") + +test.run() +check(['all', '1', 'gl ical qt x11', 'gl ical qt x11']) +test.run(arguments='shared=none') +check(['none', '0', '', '']) +test.run(arguments='shared=') +check(['none', '0', '', '']) +test.run(arguments='shared=x11,ical') +check(['ical,x11', '1', 'ical x11', 'ical x11']) +test.run(arguments='shared=x11,,ical,,') +check(['ical,x11', '1', 'ical x11', 'ical x11']) + + +test.run(arguments='shared=foo', + stderr = """ +scons: *** Error converting option: shared +Invalid value(s) for option: foo +File "SConstruct", line 14, in ? +""", status=2) + +# be paranoid in testing some more combinations + +test.run(arguments='shared=foo,ical', + stderr = """ +scons: *** Error converting option: shared +Invalid value(s) for option: foo +File "SConstruct", line 14, in ? +""", status=2) + +test.run(arguments='shared=ical,foo', + stderr = """ +scons: *** Error converting option: shared +Invalid value(s) for option: foo +File "SConstruct", line 14, in ? +""", status=2) + +test.run(arguments='shared=ical,foo,x11', + stderr = """ +scons: *** Error converting option: shared +Invalid value(s) for option: foo +File "SConstruct", line 14, in ? +""", status=2) + +test.run(arguments='shared=foo,x11,,,bar', + stderr = """ +scons: *** Error converting option: shared +Invalid value(s) for option: foo,bar +File "SConstruct", line 14, in ? +""", status=2) + + +#### test PackageOption #### + +test.write('SConstruct', """ +from SCons.Options import PackageOption + +opts = Options(args=ARGUMENTS) +opts.AddOptions( + PackageOption('x11', + 'use X11 installed here (yes = search some places', + 'yes'), + ) + +env = Environment(options=opts) +Help(opts.GenerateHelpText(env)) + +print env['x11'] +Default(env.Alias('dummy', None)) +""") + +test.run() +check(['1']) +test.run(arguments='x11=no'); check(['0']) +test.run(arguments='"x11=%s"' % test.workpath()); check([test.workpath()]) + +test.run(arguments='x11=0', + stderr = """ +scons: *** Path does not exist for option x11: 0 +File "SConstruct", line 11, in ? +""", status=2) + +test.run(arguments='x11=/non/existing/path/', + stderr = """ +scons: *** Path does not exist for option x11: /non/existing/path/ +File "SConstruct", line 11, in ? +""", status=2) + + + +#### test PathOption #### + +test.subdir('lib', 'qt', ['qt', 'lib'], 'nolib' ) +workpath = test.workpath() +libpath = os.path.join(workpath, 'lib') + +test.write('SConstruct', """ +from SCons.Options import PathOption + +qtdir = '%s' + +opts = Options(args=ARGUMENTS) +opts.AddOptions( + PathOption('qtdir', 'where the root of Qt is installed', qtdir), + PathOption('qt_libraries', 'where the Qt library is installed', '%s'), + ) + +env = Environment(options=opts) +Help(opts.GenerateHelpText(env)) + +print env['qtdir'] +print env['qt_libraries'] +print env.subst('$qt_libraries') + +Default(env.Alias('dummy', None)) +""" % (workpath, os.path.join('$qtdir', 'lib') )) + +qtpath = workpath +libpath = os.path.join(qtpath, 'lib') +test.run() +check([qtpath, os.path.join('$qtdir', 'lib'), libpath]) + +qtpath = os.path.join(workpath, 'qt') +libpath = os.path.join(qtpath, 'lib') +test.run(arguments='"qtdir=%s"' % qtpath) +check([qtpath, os.path.join('$qtdir', 'lib'), libpath]) + +qtpath = workpath +libpath = os.path.join(qtpath, 'nolib') +test.run(arguments='"qt_libraries=%s"' % libpath) +check([qtpath, libpath, libpath]) + +qtpath = os.path.join(workpath, 'qt') +libpath = os.path.join(workpath, 'nolib') +test.run(arguments='"qtdir=%s" "qt_libraries=%s"' % (qtpath, libpath)) +check([qtpath, libpath, libpath]) + +qtpath = os.path.join(workpath, 'non', 'existing', 'path') +test.run(arguments='"qtdir=%s"' % qtpath, + stderr = """ +scons: *** Path does not exist for option qtdir: %s +File "SConstruct", line 12, in ? +""" % qtpath, status=2) + +test.run(arguments='"qt_libraries=%s"' % qtpath, + stderr = """ +scons: *** Path does not exist for option qt_libraries: %s +File "SConstruct", line 12, in ? +""" % qtpath, status=2) + + +### test help messages #### + +workpath = test.workpath() +qtpath = os.path.join(workpath, 'qt') +libpath = os.path.join(qtpath, 'lib') +libdirvar = os.path.join('$qtdir', 'lib') + +test.write('SConstruct', """ +from SCons.Options import BoolOption, EnumOption, ListOption, \ + PackageOption, PathOption + +list_of_libs = Split('x11 gl qt ical') +qtdir = '%(qtdir)s' + +opts = Options(args=ARGUMENTS) +opts.AddOptions( + BoolOption('warnings', 'compilation with -Wall and similiar', 1), + BoolOption('profile', 'create profiling informations', 0), + EnumOption('debug', 'debug output and symbols', 'no', + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), # case sensitive + EnumOption('guilib', 'gui lib to use', 'gtk', + allowed_values=('motif', 'gtk', 'kde'), + map={}, ignorecase=1), # case insensitive + EnumOption('some', 'some option', 'xaver', + allowed_values=('xaver', 'eins'), + map={}, ignorecase=2), # make lowercase + ListOption('shared', + 'libraries to build as shared libraries', + 'all', + names = list_of_libs), + PackageOption('x11', + 'use X11 installed here (yes = search some places)', + 'yes'), PathOption('qtdir', 'where the root of Qt is installed', qtdir), + PathOption('qt_libraries', + 'where the Qt library is installed', + '%(libdirvar)s'), + ) + +env = Environment(options=opts) +Help(opts.GenerateHelpText(env)) + +print env['warnings'] +print env['profile'] + +Default(env.Alias('dummy', None)) +""" % {'qtdir': qtpath, 'libdirvar': libdirvar, 'libdir': libpath}) + + +test.run(arguments='-h', + stdout = """scons: Reading SConscript files ... +scons: done reading SConscript files. + +warnings: compilation with -Wall and similiar (yes|no) + default: 1 + actual: 1 + +profile: create profiling informations (yes|no) + default: 0 + actual: 0 + +debug: debug output and symbols (yes|no|full) + default: no + actual: no + +guilib: gui lib to use (motif|gtk|kde) + default: gtk + actual: gtk + +some: some option (xaver|eins) + default: xaver + actual: xaver + +shared: libraries to build as shared libraries + (all|none|comma-separated list of names) + allowed names: x11 gl qt ical + default: all + actual: x11 gl qt ical + +x11: use X11 installed here (yes = search some places) + ( yes | no | /path/to/x11 ) + default: yes + actual: 1 + +qtdir: where the root of Qt is installed ( /path/to/qtdir ) + default: %(qtdir)s + actual: %(qtdir)s + +qt_libraries: where the Qt library is installed ( /path/to/qt_libraries ) + default: $qtdir/lib + actual: %(libdir)s + +Use scons -H for help about command-line options. +""" % {'qtdir': qtpath, 'libdirvar': libdirvar, 'libdir': libpath}) + + +test.pass_test() |