From 159b7df0c6893524b9ab1f96f4c9eb00c970a9b3 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Tue, 4 Nov 2003 18:03:06 +0000 Subject: Add new Options flavors. (Hartmut Goebel) --- bin/docrun | 16 + bin/files | 7 +- doc/man/scons.1 | 207 +++++ doc/scons.mod | 52 +- doc/user/MANIFEST | 2 +- doc/user/command-line.in | 1182 ++++++++++++++++++++++++ doc/user/command-line.sgml | 1138 +++++++++++++++++++++++ doc/user/default.in | 216 ----- doc/user/default.sgml | 209 ----- doc/user/main.in | 8 +- doc/user/main.sgml | 8 +- doc/user/run.in | 41 - doc/user/run.sgml | 40 - src/CHANGES.txt | 6 + src/engine/MANIFEST.in | 7 +- src/engine/SCons/Options.py | 206 ----- src/engine/SCons/Options/BoolOption.py | 88 ++ src/engine/SCons/Options/BoolOptionTests.py | 121 +++ src/engine/SCons/Options/EnumOption.py | 101 ++ src/engine/SCons/Options/EnumOptionTests.py | 198 ++++ src/engine/SCons/Options/ListOption.py | 131 +++ src/engine/SCons/Options/ListOptionTests.py | 101 ++ src/engine/SCons/Options/OptionsTests.py | 448 +++++++++ src/engine/SCons/Options/PackageOption.py | 106 +++ src/engine/SCons/Options/PackageOptionTests.py | 109 +++ src/engine/SCons/Options/PathOption.py | 85 ++ src/engine/SCons/Options/PathOptionTests.py | 70 ++ src/engine/SCons/Options/__init__.py | 240 +++++ src/engine/SCons/OptionsTests.py | 423 --------- src/engine/SCons/Script/SConscript.py | 7 +- src/setup.py | 1 + test/OptionsTypes.py | 400 ++++++++ 32 files changed, 4823 insertions(+), 1151 deletions(-) create mode 100644 bin/docrun create mode 100644 doc/user/command-line.in create mode 100644 doc/user/command-line.sgml delete mode 100644 doc/user/default.in delete mode 100644 doc/user/default.sgml delete mode 100644 src/engine/SCons/Options.py create mode 100644 src/engine/SCons/Options/BoolOption.py create mode 100644 src/engine/SCons/Options/BoolOptionTests.py create mode 100644 src/engine/SCons/Options/EnumOption.py create mode 100644 src/engine/SCons/Options/EnumOptionTests.py create mode 100644 src/engine/SCons/Options/ListOption.py create mode 100644 src/engine/SCons/Options/ListOptionTests.py create mode 100644 src/engine/SCons/Options/OptionsTests.py create mode 100644 src/engine/SCons/Options/PackageOption.py create mode 100644 src/engine/SCons/Options/PackageOptionTests.py create mode 100644 src/engine/SCons/Options/PathOption.py create mode 100644 src/engine/SCons/Options/PathOptionTests.py create mode 100644 src/engine/SCons/Options/__init__.py delete mode 100644 src/engine/SCons/OptionsTests.py create mode 100644 test/OptionsTypes.py 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 diff --git a/bin/files b/bin/files index 574e17c..cc8bf10 100644 --- a/bin/files +++ b/bin/files @@ -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 @@ --> +Add"> +AddOptions"> Alias"> Aliases"> Append"> +BoolOption"> Build"> CacheDir"> Clean"> @@ -124,17 +127,23 @@ Default"> DefaultRules"> Depends"> +EnumOption"> Environment"> Export"> +GenerateHelpText"> Help"> Ignore"> Import"> Install"> InstallAs"> Link"> +ListOption"> Local"> Module"> Objects"> +Options"> +PackageOption"> +PathOption"> Precious"> Prepend"> Replace"> @@ -160,19 +169,39 @@ + +ARGUMENTS"> + + + + BUILDERMAP"> BUILDERS"> +CC"> +CCFLAGS"> +COLOR"> +COLORS"> +CONFIG"> +CPPDEFINES"> LIBDIRPREFIX"> LIBDIRSUFFIX"> LIBLINKPREFIX"> LIBLINKSUFFIX"> +LIBPATH"> +LIBS"> LINK"> LINKCOM"> LINKFLAGS"> +RELEASE"> +RELEASE_BUILD"> SCANNERMAP"> SCANNERS"> TARFLAGS"> @@ -186,10 +215,6 @@ --> -CC"> -CCFLAGS"> -LIBPATH"> -LIBS"> PYTHONPATH"> SCONSFLAGS"> @@ -201,7 +226,11 @@ --> +allowed_values"> build_dir"> +map"> +ignorecase"> +options"> exports"> source"> target"> @@ -210,6 +239,17 @@ + +all"> +none"> + + + + @@ -276,6 +316,9 @@ signature"> build signature"> +true"> +false"> + + + + + &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. + + + +
+ Not Having to Specify Command-Line Options Each Time: the &SCONSFLAGS; Environment Variable + + + + 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 -j 2 + to run the builds in parallel. + + + + + + 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') + + + foo.in + + + + + scons + export SCONSFLAGS="-Q" + scons + + + + + + +
+ +
+ Controlling the Default Targets + + + + 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: + + + + + + env = Environment() + hello = env.Program('hello.c') + env.Program('goodbye.c') + Default(hello) + + + hello.c + + + goodbye.c + + + + + + This &SConstruct; file knows how to build two programs, + &hello; and &goodbye;, + but only builds the + &hello; program by default: + + + + + scons -Q + scons -Q + scons -Q goodbye + + + + + Note that, even when you use the &Default; + function in your &SConstruct; file, + you can still explicitly specify the current directory + (.) on the command line + to tell &SCons; to build + everything in (or below) the current directory: + + + + + scons -Q . + + + + + 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: + + + + + + env = Environment() + prog1 = env.Program('prog1.c') + Default(prog1) + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog3) + + + prog1.c + + + prog2.c + + + prog3.c + + + + + + Or you can specify more than one target + in a single call to the &Default; function: + + + + + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog1, prog3) + + + + + Either of these last two examples + will build only the + prog1 + and + prog3 + programs by default: + + + + + scons -Q + scons -Q . + + + + + You can list a directory as + an argument to &Default;: + + + + + + env = Environment() + env.Program(['prog1/main.c', 'prog1/foo.c']) + env.Program(['prog2/main.c', 'prog2/bar.c']) + Default('prog1') + + + + + int main() { printf("prog1/main.c\n"); } + + + int foo() { printf("prog1/foo.c\n"); } + + + int main() { printf("prog2/main.c\n"); } + + + int bar() { printf("prog2/bar.c\n"); } + + + + + + In which case only the target(s) in that + directory will be built by default: + + + + + scons -Q + scons -Q + scons -Q . + + + + + Lastly, if for some reason you don't want + any targets built by default, + you can use the Python None + variable: + + + + + + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + Default(None) + + + prog1.c + + + prog2.c + + + + + + Which would produce build output like: + + + + + scons -Q + scons -Q . + + +
+ + + +
+ Command-Line <varname>variable</varname>=<varname>value</varname> Build Options + + + + You may want to control various aspects + of your build by allowing the user + to specify variable=value + 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: + + + + + % scons -Q debug=1 + + + + + &SCons; provides an &ARGUMENTS; dictionary + that stores all of the + variable=value + 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 always + specify an option, + you probably want to use + the Python + ARGUMENTS.get() function, + which allows you to specify a default value + to be used if there is no specification + on the command line.) + + + + + + The following code sets the &CCFLAGS; construction + variable in response to the debug + flag being set in the &ARGUMENTS; dictionary: + + + + + + env = Environment() + debug = ARGUMENTS.get('debug', 0) + if int(debug): + env.Append(CCFLAGS = '-g') + env.Program('prog.c') + + + prog.c + + + + + + This results in the -g + compiler option being used when + debug=1 + is used on the command line: + + + + + scons -Q debug=0 + scons -Q debug=0 + scons -Q debug=1 + scons -Q debug=1 + + + + + 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 debug + argument has changed. + + + +
+ +
+ Controlling Command-Line Build Options + + + + Being able to use a command-line build option like + debug=1 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. + + + + + + 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 -D 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: + + + + + + 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']) + + + foo.c + + + bar.c + + + + + + This &SConstruct; file first creates an + &Options; object + (the opts = Options() 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 0 + (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. + + + + + + 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: + + + + + scons -Q RELEASE=1 + + +
+ +
+ Providing Help for Command-Line Build Options + + + + 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 scons -h. + 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: + + + + + + opts = Options('custom.py') + opts.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(options = opts) + Help(opts.GenerateHelpText(env)) + + + + + + &SCons; will now display some useful text + when the -h option is used: + + + + + scons -Q -h + + + + + Notice that the help output shows the default value, + and the current actual value of the build option. + + + +
+ +
+ Reading Build Options From a File + + + + Being able to use a command-line build option like + debug=1 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: + + + + + + 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)) + + + foo.c + + + bar.c + + + RELEASE = 1 + + + + + + This then allows us to control the &RELEASE; + variable by setting it in the &custom_py; file: + + + + + + + + Note that this file is actually executed + like a Python script. + Now when we run &SCons;: + + + + + scons -Q + + + + + And if we change the contents of &custom_py; to: + + + + + + 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)) + + + foo.c + + + bar.c + + + RELEASE = 0 + + + + + + The object files are rebuilt appropriately + with the new option: + + + + + scons -Q + + +
+ +
+ Canned Build Options + + + + &SCons; provides a number of functions + that provide ready-made behaviors + for various types of command-line build options. + + + +
+ True/False Values: the &BoolOption; Build Option + + + + 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;. + + + + + + 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: + + + + + + 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') + + + foo.c + + + + + + With this build option, + the &RELEASE; variable can now be enabled by + setting it to the value yes + or t: + + + + + scons -Q RELEASE=yes foo.o + + + + scons -Q RELEASE=t foo.o + + + + + Other values that equate to &true; include + y, + 1, + on + and + all. + + + + + + Conversely, &RELEASE; may now be given a &false; + value by setting it to + no + or + f: + + + + + scons -Q RELEASE=no foo.o + + + + scons -Q RELEASE=f foo.o + + + + + Other values that equate to &true; include + n, + 0, + off + and + none. + + + + + + Lastly, if a user tries to specify + any other value, + &SCons; supplies an appropriate error message: + + + + + scons -Q RELEASE=bad_value foo.o + + +
+ +
+ Single Value From a List: the &EnumOption; Build Option + + + + 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: + + + + + + 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') + + + foo.c + + + + + + The user can now explicity set the &COLOR; build option + to any of the specified allowed values: + + + + + scons -Q COLOR=red foo.o + scons -Q COLOR=blue foo.o + scons -Q COLOR=green foo.o + + + + + But, almost more importantly, + an attempt to set &COLOR; + to a value that's not in the list + generates an error message: + + + + + scons -Q COLOR=magenta foo.o + + + + + 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 navy as a synonym for + blue. + We do this by adding a ↦ dictionary + that will map its key values + to the desired legal value: + + + + + + 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') + + + foo.c + + + + + + As desired, the user can then use + navy on the command line, + and &SCons; will translate it into blue + when it comes time to use the &COLOR; + option to build a target: + + + + + scons -Q COLOR=navy foo.o + + + + + By default, when using the &EnumOption; function, + arguments that differ + from the legal values + only in case + are treated as illegal values: + + + + + scons -Q COLOR=Red foo.o + scons -Q COLOR=BLUE foo.o + scons -Q COLOR=nAvY foo.o + + + + + The &EnumOption; function can take an additional + &ignorecase; keyword argument that, + when set to 1, + tells &SCons; to allow case differences + when the values are specified: + + + + + + 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') + + + foo.c + + + + + + Which yields the output: + + + + + scons -Q COLOR=Red foo.o + scons -Q COLOR=BLUE foo.o + scons -Q COLOR=nAvY foo.o + scons -Q COLOR=green foo.o + + + + + Notice that an &ignorecase; value of 1 + 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 2: + + + + + + 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') + + + foo.c + + + + + + Now &SCons; will use values of + red, + green or + blue + regardless of how the user spells + those values on the command line: + + + + + scons -Q COLOR=Red foo.o + scons -Q COLOR=nAvY foo.o + scons -Q COLOR=GREEN foo.o + + +
+ +
+ Multiple Values From a List: the &ListOption; Build Option + + + + 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: + + + + + + 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') + + + foo.c + + + + + + 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: + + + + + scons -Q COLORS=red,blue foo.o + scons -Q COLORS=blue,green,red foo.o + + + + + 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: + + + + + scons -Q COLORS=all foo.o + scons -Q COLORS=none foo.o + + + + + And, of course, an illegal value + still generates an error message: + + + + + scons -Q COLORS=magenta foo.o + + +
+ +
+ Path Names: the &PathOption; Build Option + + + + &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: + + + + + + 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') + + + foo.c + + + /opt/location + + + /opt/location + + + + + + This then allows the user to + override the &CONFIG; build option + on the command line as necessary: + + + + + scons -Q foo.o + scons -Q CONFIG=__ROOT__/usr/local/etc/other_config foo.o + + +
+ +
+ Enabled/Disabled Path Names: the &PackageOption; Build Option + + + + 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 yes or no keywords, + in addition to allow them + to supply an explicit path name. + &SCons; supports the &PackageOption; + function to support this: + + + + + + opts = Options('custom.py') + opts.Add(PackageOption('PACKAGE', '__ROOT__/opt/location', 'Location package')) + env = Environment(options = opts, + CPPDEFINES={'PACKAGE' : '"$PACKAGE"'}) + env.Program('foo.c') + + + foo.c + + + /opt/location + + + /opt/location + + + + + + 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: + + + + + scons -Q foo.o + scons -Q PACKAGE=__ROOT__/usr/local/location foo.o + scons -Q PACKAGE=yes foo.o + scons -Q PACKAGE=no foo.o + + +
+ +
+ +
+ Adding Multiple Command-Line Build Options at Once + + + + 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: + + + + + + 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), + ) + + + + + + +
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 @@ + + + + + &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. + + + +
+ Not Having to Specify Command-Line Options Each Time: the &SCONSFLAGS; Environment Variable + + + + 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 -j 2 + to run the builds in parallel. + + + + + + + % scons + scons: Reading SConscript files ... + ... + scons: done reading SConscript files. + scons: Building targets ... + scons: `.' is up to date. + scons: done building targets. + % export SCONSFLAGS="-Q" + % scons + scons: Reading SConscript files ... + ... + scons: done reading SConscript files. + scons: Building targets ... + scons: `.' is up to date. + scons: done building targets. + + + + + + +
+ +
+ Controlling the Default Targets + + + + 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: + + + + + env = Environment() + hello = env.Program('hello.c') + env.Program('goodbye.c') + Default(hello) + + + + + This &SConstruct; file knows how to build two programs, + &hello; and &goodbye;, + but only builds the + &hello; program by default: + + + + + % scons -Q + cc -c -o hello.o hello.c + cc -o hello hello.o + % scons -Q + scons: `hello' is up to date. + % scons -Q goodbye + cc -c -o goodbye.o goodbye.c + cc -o goodbye goodbye.o + + + + + Note that, even when you use the &Default; + function in your &SConstruct; file, + you can still explicitly specify the current directory + (.) on the command line + to tell &SCons; to build + everything in (or below) the current directory: + + + + + % scons -Q . + cc -c -o goodbye.o goodbye.c + cc -o goodbye goodbye.o + cc -c -o hello.o hello.c + cc -o hello hello.o + + + + + 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: + + + + + env = Environment() + prog1 = env.Program('prog1.c') + Default(prog1) + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog3) + + + + + Or you can specify more than one target + in a single call to the &Default; function: + + + + + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog1, prog3) + + + + + Either of these last two examples + will build only the + prog1 + and + prog3 + programs by default: + + + + + % scons -Q + cc -c -o prog1.o prog1.c + cc -o prog1 prog1.o + cc -c -o prog3.o prog3.c + cc -o prog3 prog3.o + % scons -Q . + cc -c -o prog2.o prog2.c + cc -o prog2 prog2.o + + + + + You can list a directory as + an argument to &Default;: + + + + + env = Environment() + env.Program(['prog1/main.c', 'prog1/foo.c']) + env.Program(['prog2/main.c', 'prog2/bar.c']) + Default('prog1') + + + + + In which case only the target(s) in that + directory will be built by default: + + + + + % scons -Q + 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 + % scons -Q + scons: `prog1' is up to date. + % scons -Q . + 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 + + + + + Lastly, if for some reason you don't want + any targets built by default, + you can use the Python None + variable: + + + + + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + Default(None) + + + + + Which would produce build output like: + + + + + % scons -Q + scons: *** No targets specified and no Default() targets found. Stop. + % scons -Q . + cc -c -o prog1.o prog1.c + cc -o prog1 prog1.o + cc -c -o prog2.o prog2.c + cc -o prog2 prog2.o + + +
+ + + +
+ Command-Line <varname>variable</varname>=<varname>value</varname> Build Options + + + + You may want to control various aspects + of your build by allowing the user + to specify variable=value + 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: + + + + + % scons -Q debug=1 + + + + + &SCons; provides an &ARGUMENTS; dictionary + that stores all of the + variable=value + 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 always + specify an option, + you probably want to use + the Python + ARGUMENTS.get() function, + which allows you to specify a default value + to be used if there is no specification + on the command line.) + + + + + + The following code sets the &CCFLAGS; construction + variable in response to the debug + flag being set in the &ARGUMENTS; dictionary: + + + + + env = Environment() + debug = ARGUMENTS.get('debug', 0) + if int(debug): + env.Append(CCFLAGS = '-g') + env.Program('prog.c') + + + + + This results in the -g + compiler option being used when + debug=1 + is used on the command line: + + + + + % scons -Q debug=0 + cc -c -o prog.o prog.c + cc -o prog prog.o + % scons -Q debug=0 + scons: `.' is up to date. + % scons -Q debug=1 + cc -g -c -o prog.o prog.c + cc -o prog prog.o + % scons -Q debug=1 + scons: `.' is up to date. + + + + + 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 debug + argument has changed. + + + +
+ +
+ Controlling Command-Line Build Options + + + + Being able to use a command-line build option like + debug=1 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. + + + + + + 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 -D 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: + + + + + 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']) + + + + + This &SConstruct; file first creates an + &Options; object + (the opts = Options() 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 0 + (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. + + + + + + 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: + + + + + % scons -Q RELEASE=1 + 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 + + +
+ +
+ Providing Help for Command-Line Build Options + + + + 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 scons -h. + 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: + + + + + opts = Options('custom.py') + opts.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(options = opts) + Help(opts.GenerateHelpText(env)) + + + + + &SCons; will now display some useful text + when the -h option is used: + + + + + % scons -Q -h + + RELEASE: Set to 1 to build for release + default: 0 + actual: 0 + + Use scons -H for help about command-line options. + + + + + Notice that the help output shows the default value, + and the current actual value of the build option. + + + +
+ +
+ Reading Build Options From a File + + + + Being able to use a command-line build option like + debug=1 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: + + + + + 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)) + + + + + This then allows us to control the &RELEASE; + variable by setting it in the &custom_py; file: + + + + + RELEASE = 1 + + + + + Note that this file is actually executed + like a Python script. + Now when we run &SCons;: + + + + + % scons -Q + 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 + + + + + And if we change the contents of &custom_py; to: + + + + + RELEASE = 0 + + + + + The object files are rebuilt appropriately + with the new option: + + + + + % scons -Q + 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 + + +
+ +
+ Canned Build Options + + + + &SCons; provides a number of functions + that provide ready-made behaviors + for various types of command-line build options. + + + +
+ True/False Values: the &BoolOption; Build Option + + + + 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;. + + + + + + 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: + + + + + 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') + + + + + With this build option, + the &RELEASE; variable can now be enabled by + setting it to the value yes + or t: + + + + + % scons -Q RELEASE=yes foo.o + cc -DRELEASE_BUILD=1 -c -o foo.o foo.c + + + + % scons -Q RELEASE=t foo.o + cc -DRELEASE_BUILD=1 -c -o foo.o foo.c + + + + + Other values that equate to &true; include + y, + 1, + on + and + all. + + + + + + Conversely, &RELEASE; may now be given a &false; + value by setting it to + no + or + f: + + + + + % scons -Q RELEASE=no foo.o + cc -DRELEASE_BUILD=0 -c -o foo.o foo.c + + + + % scons -Q RELEASE=f foo.o + cc -DRELEASE_BUILD=0 -c -o foo.o foo.c + + + + + Other values that equate to &true; include + n, + 0, + off + and + none. + + + + + + Lastly, if a user tries to specify + any other value, + &SCons; supplies an appropriate error message: + + + + + % scons -Q RELEASE=bad_value foo.o + + scons: *** Error converting option: RELEASE + Invalid value for boolean option: bad_value + File "SConstruct", line 4, in ? + + +
+ +
+ Single Value From a List: the &EnumOption; Build Option + + + + 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: + + + + + 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') + + + + + The user can now explicity set the &COLOR; build option + to any of the specified allowed values: + + + + + % scons -Q COLOR=red foo.o + cc -DCOLOR="red" -c -o foo.o foo.c + % scons -Q COLOR=blue foo.o + cc -DCOLOR="blue" -c -o foo.o foo.c + % scons -Q COLOR=green foo.o + cc -DCOLOR="green" -c -o foo.o foo.c + + + + + But, almost more importantly, + an attempt to set &COLOR; + to a value that's not in the list + generates an error message: + + + + + % scons -Q COLOR=magenta foo.o + + scons: *** Invalid value for option COLOR: magenta + File "SConstruct", line 5, in ? + + + + + 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 navy as a synonym for + blue. + We do this by adding a ↦ dictionary + that will map its key values + to the desired legal value: + + + + + 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') + + + + + As desired, the user can then use + navy on the command line, + and &SCons; will translate it into blue + when it comes time to use the &COLOR; + option to build a target: + + + + + % scons -Q COLOR=navy foo.o + cc -DCOLOR="blue" -c -o foo.o foo.c + + + + + By default, when using the &EnumOption; function, + arguments that differ + from the legal values + only in case + are treated as illegal values: + + + + + % scons -Q COLOR=Red foo.o + + scons: *** Invalid value for option COLOR: Red + File "SConstruct", line 5, in ? + % scons -Q COLOR=BLUE foo.o + + scons: *** Invalid value for option COLOR: BLUE + File "SConstruct", line 5, in ? + % scons -Q COLOR=nAvY foo.o + + scons: *** Invalid value for option COLOR: nAvY + File "SConstruct", line 5, in ? + + + + + The &EnumOption; function can take an additional + &ignorecase; keyword argument that, + when set to 1, + tells &SCons; to allow case differences + when the values are specified: + + + + + 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') + + + + + Which yields the output: + + + + + % scons -Q COLOR=Red foo.o + cc -DCOLOR="Red" -c -o foo.o foo.c + % scons -Q COLOR=BLUE foo.o + cc -DCOLOR="BLUE" -c -o foo.o foo.c + % scons -Q COLOR=nAvY foo.o + cc -DCOLOR="blue" -c -o foo.o foo.c + % scons -Q COLOR=green foo.o + cc -DCOLOR="green" -c -o foo.o foo.c + + + + + Notice that an &ignorecase; value of 1 + 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 2: + + + + + 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') + + + + + Now &SCons; will use values of + red, + green or + blue + regardless of how the user spells + those values on the command line: + + + + + % scons -Q COLOR=Red foo.o + cc -DCOLOR="red" -c -o foo.o foo.c + % scons -Q COLOR=nAvY foo.o + cc -DCOLOR="blue" -c -o foo.o foo.c + % scons -Q COLOR=GREEN foo.o + cc -DCOLOR="green" -c -o foo.o foo.c + + +
+ +
+ Multiple Values From a List: the &ListOption; Build Option + + + + 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: + + + + + 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') + + + + + 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: + + + + + % scons -Q COLORS=red,blue foo.o + TypeError: sequence item 0: expected string, int found: + % scons -Q COLORS=blue,green,red foo.o + TypeError: sequence item 0: expected string, int found: + + + + + 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: + + + + + % scons -Q COLORS=all foo.o + TypeError: sequence item 0: expected string, int found: + % scons -Q COLORS=none foo.o + TypeError: sequence item 0: expected string, int found: + + + + + And, of course, an illegal value + still generates an error message: + + + + + % scons -Q COLORS=magenta foo.o + TypeError: sequence item 0: expected string, int found: + + +
+ +
+ Path Names: the &PathOption; Build Option + + + + &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: + + + + + 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') + + + + + This then allows the user to + override the &CONFIG; build option + on the command line as necessary: + + + + + % scons -Q foo.o + + scons: *** Path does not exist for option CONFIG: Path to configuration file + File "SConstruct", line 4, in ? + % scons -Q CONFIG=/usr/local/etc/other_config foo.o + cc -DCONFIG_FILE="/usr/local/etc/other_config" -c -o foo.o foo.c + + +
+ +
+ Enabled/Disabled Path Names: the &PackageOption; Build Option + + + + 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 yes or no keywords, + in addition to allow them + to supply an explicit path name. + &SCons; supports the &PackageOption; + function to support this: + + + + + opts = Options('custom.py') + opts.Add(PackageOption('PACKAGE', '/opt/location', 'Location package')) + env = Environment(options = opts, + CPPDEFINES={'PACKAGE' : '"$PACKAGE"'}) + env.Program('foo.c') + + + + + 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: + + + + + % scons -Q foo.o + + scons: *** Path does not exist for option PACKAGE: Location package + File "SConstruct", line 4, in ? + % scons -Q PACKAGE=/usr/local/location foo.o + cc -DPACKAGE="/usr/local/location" -c -o foo.o foo.c + % scons -Q PACKAGE=yes foo.o + cc -DPACKAGE="1" -c -o foo.o foo.c + % scons -Q PACKAGE=no foo.o + cc -DPACKAGE="0" -c -o foo.o foo.c + + +
+ +
+ +
+ Adding Multiple Command-Line Build Options at Once + + + + 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: + + + + + 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), + ) + + + + + +
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 @@ - - - - - - - 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: - - - - - - env = Environment() - hello = env.Program('hello.c') - env.Program('goodbye.c') - Default(hello) - - - hello.c - - - goodbye.c - - - - - - This &SConstruct; file knows how to build two programs, - &hello; and &goodbye;, - but only builds the - &hello; program by default: - - - - - scons -Q - scons -Q - scons -Q goodbye - - - - - Note that, even when you use the &Default; - function in your &SConstruct; file, - you can still explicitly specify the current directory - (.) on the command line - to tell &SCons; to build - everything in (or below) the current directory: - - - - - scons -Q . - - - - - 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: - - - - - - env = Environment() - prog1 = env.Program('prog1.c') - Default(prog1) - prog2 = env.Program('prog2.c') - prog3 = env.Program('prog3.c') - Default(prog3) - - - prog1.c - - - prog2.c - - - prog3.c - - - - - - Or you can specify more than one target - in a single call to the &Default; function: - - - - - env = Environment() - prog1 = env.Program('prog1.c') - prog2 = env.Program('prog2.c') - prog3 = env.Program('prog3.c') - Default(prog1, prog3) - - - - - Either of these last two examples - will build only the - prog1 - and - prog3 - programs by default: - - - - - scons -Q - scons -Q . - - - - - Lastly, if for some reason you don't want - any targets built by default, - you can use the Python None - variable: - - - - - - env = Environment() - prog1 = env.Program('prog1.c') - prog2 = env.Program('prog2.c') - Default(None) - - - prog1.c - - - prog2.c - - - - - - Which would produce build output like: - - - - - scons -Q - scons -Q . - 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 @@ - - - - - - - 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: - - - - - env = Environment() - hello = env.Program('hello.c') - env.Program('goodbye.c') - Default(hello) - - - - - This &SConstruct; file knows how to build two programs, - &hello; and &goodbye;, - but only builds the - &hello; program by default: - - - - - % scons -Q - cc -c -o hello.o hello.c - cc -o hello hello.o - % scons -Q - scons: `hello' is up to date. - % scons -Q goodbye - cc -c -o goodbye.o goodbye.c - cc -o goodbye goodbye.o - - - - - Note that, even when you use the &Default; - function in your &SConstruct; file, - you can still explicitly specify the current directory - (.) on the command line - to tell &SCons; to build - everything in (or below) the current directory: - - - - - % scons -Q . - cc -c -o goodbye.o goodbye.c - cc -o goodbye goodbye.o - cc -c -o hello.o hello.c - cc -o hello hello.o - - - - - 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: - - - - - env = Environment() - prog1 = env.Program('prog1.c') - Default(prog1) - prog2 = env.Program('prog2.c') - prog3 = env.Program('prog3.c') - Default(prog3) - - - - - Or you can specify more than one target - in a single call to the &Default; function: - - - - - env = Environment() - prog1 = env.Program('prog1.c') - prog2 = env.Program('prog2.c') - prog3 = env.Program('prog3.c') - Default(prog1, prog3) - - - - - Either of these last two examples - will build only the - prog1 - and - prog3 - programs by default: - - - - - % scons -Q - cc -c -o prog1.o prog1.c - cc -o prog1 prog1.o - cc -c -o prog3.o prog3.c - cc -o prog3 prog3.o - % scons -Q . - cc -c -o prog2.o prog2.c - cc -o prog2 prog2.o - - - - - Lastly, if for some reason you don't want - any targets built by default, - you can use the Python None - variable: - - - - - env = Environment() - prog1 = env.Program('prog1.c') - prog2 = env.Program('prog2.c') - Default(None) - - - - - Which would produce build output like: - - - - - % scons -Q - scons: *** No targets specified and no Default() targets found. Stop. - % scons -Q . - cc -c -o prog1.o prog1.c - cc -o prog1 prog1.o - cc -c -o prog2.o prog2.c - cc -o prog2 prog2.o - 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 @@ + - @@ -115,9 +115,9 @@ &environments; - - Default Targets - &default; + + Controlling a Build From the Command Line + &command-line; 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 @@ + - @@ -115,9 +115,9 @@ &environments; - - Default Targets - &default; + + Controlling a Build From the Command Line + &command-line; 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). - -
- The &SCONSFLAGS; Environment Variable - - - - 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 -j 2 - to run the builds in parallel. - - - - - - 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') - - - foo.in - - - - - scons - export SCONSFLAGS="-Q" - scons - - - - - - -
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). - -
- The &SCONSFLAGS; Environment Variable - - - - 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 -j 2 - to run the builds in parallel. - - - - - - - % scons - scons: Reading SConscript files ... - ... - scons: done reading SConscript files. - scons: Building targets ... - scons: `.' is up to date. - scons: done building targets. - % export SCONSFLAGS="-Q" - % scons - scons: Reading SConscript files ... - ... - scons: done reading SConscript files. - scons: Building targets ... - scons: `.' is up to date. - scons: done building targets. - - - - - - -
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.py b/src/engine/SCons/Options.py deleted file mode 100644 index f4d2bcc..0000000 --- a/src/engine/SCons/Options.py +++ /dev/null @@ -1,206 +0,0 @@ -"""engine.SCons.Options - -This file defines the Options class that is used to add user-friendly customizable -variables to a scons build. -""" - -# -# __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 os.path - -import SCons.Errors -import SCons.Util -import SCons.Warnings - - -class Options: - """ - Holds all the options, updates the environment with the variables, - and renders the help text. - """ - def __init__(self, files=None, args={}): - """ - files - [optional] List of option configuration files to load - (backward compatibility) If a single string is passed it is - automatically placed in a file list - """ - - self.options = [] - self.args = args - self.files = None - if SCons.Util.is_String(files): - self.files = [ files ] - elif files: - self.files = files - - - def Add(self, key, help="", default=None, validator=None, converter=None, **kw): - """ - Add an option. - - key - the name of the variable - help - optional help text for the options - default - optional default value - validator - optional function that is called to validate the option's value - Called with (key, value, environment) - converter - optional function that is called to convert the option's value before - 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 kw.has_key('validater'): - SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, - "The 'validater' keyword of the Options.Add() method is deprecated\n" +\ - "and should be changed to 'validator'.") - if validator is None: - validator = kw['validater'] - - 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 Update(self, env, args=None): - """ - Update an environment with the option variables. - - env - the environment to update. - """ - - values = {} - - # first set the defaults: - for option in self.options: - if not option.default is None: - values[option.key] = option.default - - # next set the value specified in the options file - if self.files: - for filename in self.files: - if os.path.exists(filename): - execfile(filename, values) - - # finally set the values specified on the command line - if args is None: - args = self.args - values.update(args) - - # put the variables in the environment: - # (don't copy over variables that are not declared - # as options) - for option in self.options: - try: - env[option.key] = values[option.key] - except KeyError: - pass - - # Call the convert functions: - for option in self.options: - if option.converter and values.has_key(option.key): - value = env.subst('${%s}'%option.key) - try: - env[option.key] = option.converter(value) - except ValueError, x: - raise SCons.Errors.UserError, 'Error converting option: %s\n%s'%(option.key, x) - - - # Finally validate the values: - for option in self.options: - if option.validator: - option.validator(option.key, env.subst('${%s}'%option.key), env) - - def Save(self, filename, env): - """ - Saves all the options in the given file. This file can - then be used to load the options next run. This can be used - to create an option cache file. - - filename - Name of the file to save into - env - the environment get the option values from - """ - - # Create the file and write out the header - try: - fh = open(filename, 'w') - - try: - # Make an assignment in the file for each option within the environment - # that was assigned a value other than the default. - for option in self.options: - try: - value = env[option.key] - try: - eval(repr(value)) - except KeyboardInterrupt: - raise - except: - # Convert stuff that has a repr() that - # cannot be evaluated into a string - value = SCons.Util.to_String(value) - if env.subst('${%s}' % option.key) != \ - env.subst(SCons.Util.to_String(option.default)): - fh.write('%s = %s\n' % (option.key, repr(value))) - except KeyError: - pass - finally: - fh.close() - - except IOError, x: - raise SCons.Errors.UserError, 'Error writing options to file: %s\n%s' % (filename, x) - - def GenerateHelpText(self, env, sort=None): - """ - Generate the help text for the options. - - env - an environment that is used to get the current values - of the options. - """ - - help_text = "" - - if sort: - options = self.options[:] - options.sort(lambda x,y,func=sort: func(x.key,y.key)) - else: - options = self.options - - for option in options: - help_text = help_text + '\n%s: %s\n default: %s\n'%(option.key, option.help, option.default) - if env.has_key(option.key): - help_text = help_text + ' actual: %s\n'%env.subst('${%s}'%option.key) - else: - help_text = help_text + ' actual: None\n' - - return help_text 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/Options/OptionsTests.py b/src/engine/SCons/Options/OptionsTests.py new file mode 100644 index 0000000..ab931e2 --- /dev/null +++ b/src/engine/SCons/Options/OptionsTests.py @@ -0,0 +1,448 @@ +# +# __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 string +import sys +import unittest +import TestSCons + +import SCons.Options +import SCons.Util +import SCons.Warnings + + +class Environment: + def __init__(self): + self.dict = {} + def subst(self, x): + return SCons.Util.scons_subst(x, self) + def __setitem__(self, key, value): + self.dict[key] = value + def __getitem__(self, key): + return self.dict[key] + def has_key(self, key): + return self.dict.has_key(key) + def Dictionary(self): + return self.dict + + +def check(key, value, env): + assert int(value) == 6 * 9, "key %s = %s" % (key, repr(value)) + +# Check saved option file by executing and comparing against +# the expected dictionary +def checkSave(file, expected): + gdict = {} + ldict = {} + execfile(file, gdict, ldict) + 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() + + opts.Add('VAR') + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + o = opts.options[0] + assert o.key == 'VAR' + assert o.help == '' + assert o.default == None + assert o.validator == None + assert o.converter == None + + o = opts.options[1] + assert o.key == 'ANSWER' + assert o.help == 'THE answer to THE question' + assert o.default == "42" + o.validator(o.key, o.converter(o.default), {}) + + def test_it(var, opts=opts): + exc_caught = None + try: + opts.Add(var) + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch UserError for '%s'" % var + test_it('foo/bar') + test_it('foo-bar') + test_it('foo.bar') + + save = {} + save['warn'] = SCons.Warnings.warn + save['DeprecatedWarning'] = SCons.Warnings.DeprecatedWarning + def warn(type, message, save=save): + save['type'] = type + save['message'] = message + SCons.Warnings.warn = warn + SCons.Warnings.DeprecatedWarning = "xyzzy" + + try: + opts.Add('MISSPELLED', + 'test using the old validater keyword', + "42", + validater=check, + converter=lambda x: int(x) + 12) + finally: + SCons.Warnings.DeprecatedWarning = save['DeprecatedWarning'] + SCons.Warnings.warn = save['warn'] + assert save['type'] == "xyzzy", save['type'] + assert string.find(save['message'], "keyword of the Options.Add() method", save['message'] != -1), save['message'] + 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""" + + # Test that a default value is validated correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + opts = SCons.Options.Options(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env) + assert env['ANSWER'] == 54 + + env = Environment() + opts.Update(env, {}) + assert env['ANSWER'] == 54 + + # Test that a bad value from the file is used and + # validation fails correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=54') + opts = SCons.Options.Options(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + env = Environment() + exc_caught = None + try: + opts.Update(env) + except AssertionError: + exc_caught = 1 + assert exc_caught, "did not catch expected assertion" + + env = Environment() + exc_caught = None + try: + opts.Update(env, {}) + except AssertionError: + exc_caught = 1 + assert exc_caught, "did not catch expected assertion" + + # Test that a good value from the file is used and validated. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=42') + opts = SCons.Options.Options(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "10", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env) + assert env['ANSWER'] == 54 + + env = Environment() + opts.Update(env, {}) + assert env['ANSWER'] == 54 + + # Test that a bad value from an args dictionary passed to + # Update() is used and validation fails correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=10') + opts = SCons.Options.Options(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "12", + check, + lambda x: int(x) + 12) + + env = Environment() + exc_caught = None + try: + opts.Update(env, {'ANSWER':'54'}) + except AssertionError: + exc_caught = 1 + assert exc_caught, "did not catch expected assertion" + + # Test that a good value from an args dictionary + # passed to Update() is used and validated. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=10') + opts = SCons.Options.Options(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "12", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env, {'ANSWER':'42'}) + assert env['ANSWER'] == 54 + + # Test against a former bug. If we supply a converter, + # but no default, the value should *not* appear in the + # Environment if no value is specified in the options file + # or args. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + opts = SCons.Options.Options(file) + + opts.Add('ANSWER', + help='THE answer to THE question', + converter=str) + + env = Environment() + opts.Update(env, {}) + assert not env.has_key('ANSWER') + + def test_args(self): + """Test updating an Environment with arguments overridden""" + + # Test that a bad (command-line) argument is used + # and the validation fails correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=42') + opts = SCons.Options.Options(file, {'ANSWER':54}) + + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + env = Environment() + exc_caught = None + try: + opts.Update(env) + except AssertionError: + exc_caught = 1 + assert exc_caught, "did not catch expected assertion" + + # Test that a good (command-line) argument is used and validated. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=54') + opts = SCons.Options.Options(file, {'ANSWER':42}) + + opts.Add('ANSWER', + 'THE answer to THE question', + "54", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env) + assert env['ANSWER'] == 54 + + # Test that a (command-line) argument is overridden by a dictionary + # supplied to Update() and the dictionary value is validated correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=54') + opts = SCons.Options.Options(file, {'ANSWER':54}) + + opts.Add('ANSWER', + 'THE answer to THE question', + "54", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env, {'ANSWER':42}) + assert env['ANSWER'] == 54 + + def test_Save(self): + """Testing saving Options""" + + test = TestSCons.TestSCons() + cache_file = test.workpath('cached.options') + opts = SCons.Options.Options() + + # test saving out empty file + opts.Add('OPT_VAL', + 'An option to test', + 21, + None, + None) + opts.Add('OPT_VAL_2', + default='foo') + opts.Add('OPT_VAL_3', + default=1) + + env = Environment() + opts.Update(env, {'OPT_VAL_3' : 2}) + assert env['OPT_VAL'] == 21 + assert env['OPT_VAL_2'] == 'foo' + assert env['OPT_VAL_3'] == 2 + env['OPT_VAL_2'] = 'bar' + opts.Save(cache_file, env) + checkSave(cache_file, { 'OPT_VAL_2' : 'bar', + 'OPT_VAL_3' : 2 }) + + # Test against some old bugs + class Foo: + def __init__(self, x): + self.x = x + def __str__(self): + return self.x + + test = TestSCons.TestSCons() + cache_file = test.workpath('cached.options') + opts = SCons.Options.Options() + + opts.Add('THIS_USED_TO_BREAK', + 'An option to test', + "Default") + + opts.Add('THIS_ALSO_BROKE', + 'An option to test', + "Default2") + + opts.Add('THIS_SHOULD_WORK', + 'An option to test', + Foo('bar')) + + env = Environment() + opts.Update(env, { 'THIS_USED_TO_BREAK' : "Single'Quotes'In'String", + 'THIS_ALSO_BROKE' : "\\Escape\nSequences\t", + 'THIS_SHOULD_WORK' : Foo('baz') }) + opts.Save(cache_file, env) + checkSave(cache_file, { 'THIS_USED_TO_BREAK' : "Single'Quotes'In'String", + 'THIS_ALSO_BROKE' : "\\Escape\nSequences\t", + 'THIS_SHOULD_WORK' : 'baz' }) + + def test_GenerateHelpText(self): + opts = SCons.Options.Options() + + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + opts.Add('B', + 'b - alpha test', + "42", + check, + lambda x: int(x) + 12) + + opts.Add('A', + 'a - alpha test', + "42", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env, {}) + + expect = """ +ANSWER: THE answer to THE question + default: 42 + actual: 54 + +B: b - alpha test + default: 42 + actual: 54 + +A: a - alpha test + default: 42 + actual: 54 +""" + + text = opts.GenerateHelpText(env) + assert text == expect, text + + expectAlpha = """ +A: a - alpha test + default: 42 + actual: 54 + +ANSWER: THE answer to THE question + default: 42 + actual: 54 + +B: b - alpha test + default: 42 + actual: 54 +""" + text = opts.GenerateHelpText(env, sort=cmp) + assert text == expectAlpha, text + +if __name__ == "__main__": + suite = unittest.makeSuite(OptionsTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) 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/__init__.py b/src/engine/SCons/Options/__init__.py new file mode 100644 index 0000000..6236102 --- /dev/null +++ b/src/engine/SCons/Options/__init__.py @@ -0,0 +1,240 @@ +"""engine.SCons.Options + +This file defines the Options class that is used to add user-friendly +customizable variables to an SCons build. +""" + +# +# __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 os.path + +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: + """ + Holds all the options, updates the environment with the variables, + and renders the help text. + """ + def __init__(self, files=None, args={}): + """ + files - [optional] List of option configuration files to load + (backward compatibility) If a single string is passed it is + automatically placed in a file list + """ + + self.options = [] + self.args = args + self.files = None + if SCons.Util.is_String(files): + self.files = [ files ] + 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, 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 + Called with (key, value, environment) + converter - optional function that is called to convert the option's value before + putting it in the environment. + """ + + 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, + "The 'validater' keyword of the Options.Add() method is deprecated\n" +\ + "and should be changed to 'validator'.") + if validator is None: + validator = kw['validater'] + + self._do_add(key, help, default, validator, converter) + + + 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): + """ + Update an environment with the option variables. + + env - the environment to update. + """ + + values = {} + + # first set the defaults: + for option in self.options: + if not option.default is None: + values[option.key] = option.default + + # next set the value specified in the options file + if self.files: + for filename in self.files: + if os.path.exists(filename): + execfile(filename, values) + + # finally set the values specified on the command line + if args is None: + args = self.args + values.update(args) + + # put the variables in the environment: + # (don't copy over variables that are not declared + # as options) + for option in self.options: + try: + env[option.key] = values[option.key] + except KeyError: + pass + + # Call the convert functions: + for option in self.options: + if option.converter and values.has_key(option.key): + value = env.subst('${%s}'%option.key) + try: + env[option.key] = option.converter(value) + except ValueError, x: + raise SCons.Errors.UserError, 'Error converting option: %s\n%s'%(option.key, x) + + + # Finally validate the values: + for option in self.options: + if option.validator: + option.validator(option.key, env.subst('${%s}'%option.key), env) + + def Save(self, filename, env): + """ + Saves all the options in the given file. This file can + then be used to load the options next run. This can be used + to create an option cache file. + + filename - Name of the file to save into + env - the environment get the option values from + """ + + # Create the file and write out the header + try: + fh = open(filename, 'w') + + try: + # Make an assignment in the file for each option within the environment + # that was assigned a value other than the default. + for option in self.options: + try: + value = env[option.key] + try: + eval(repr(value)) + except KeyboardInterrupt: + raise + except: + # Convert stuff that has a repr() that + # cannot be evaluated into a string + value = SCons.Util.to_String(value) + if env.subst('${%s}' % option.key) != \ + env.subst(SCons.Util.to_String(option.default)): + fh.write('%s = %s\n' % (option.key, repr(value))) + except KeyError: + pass + finally: + fh.close() + + except IOError, x: + raise SCons.Errors.UserError, 'Error writing options to file: %s\n%s' % (filename, x) + + def GenerateHelpText(self, env, sort=None): + """ + Generate the help text for the options. + + env - an environment that is used to get the current values + of the options. + """ + + help_text = "" + + if sort: + options = self.options[:] + options.sort(lambda x,y,func=sort: func(x.key,y.key)) + else: + options = self.options + + for option in options: + help_text = help_text + '\n%s: %s\n default: %s\n'%(option.key, option.help, option.default) + if env.has_key(option.key): + help_text = help_text + ' actual: %s\n'%env.subst('${%s}'%option.key) + else: + help_text = help_text + ' actual: None\n' + + return help_text diff --git a/src/engine/SCons/OptionsTests.py b/src/engine/SCons/OptionsTests.py deleted file mode 100644 index 87fbc9a..0000000 --- a/src/engine/SCons/OptionsTests.py +++ /dev/null @@ -1,423 +0,0 @@ -# -# __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 string -import sys -import unittest -import TestSCons - -import SCons.Options -import SCons.Util -import SCons.Warnings - - -class Environment: - def __init__(self): - self.dict = {} - def subst(self, x): - return SCons.Util.scons_subst(x, self) - def __setitem__(self, key, value): - self.dict[key] = value - def __getitem__(self, key): - return self.dict[key] - def has_key(self, key): - return self.dict.has_key(key) - def Dictionary(self): - return self.dict - - -def check(key, value, env): - assert int(value) == 6 * 9, "key %s = %s" % (key, repr(value)) - -# Check saved option file by executing and comparing against -# the expected dictionary -def checkSave(file, expected): - gdict = {} - ldict = {} - execfile(file, gdict, ldict) - 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() - - opts.Add('VAR') - opts.Add('ANSWER', - 'THE answer to THE question', - "42", - check, - lambda x: int(x) + 12) - - o = opts.options[0] - assert o.key == 'VAR' - assert o.help == '' - assert o.default == None - assert o.validator == None - assert o.converter == None - - o = opts.options[1] - assert o.key == 'ANSWER' - assert o.help == 'THE answer to THE question' - assert o.default == "42" - o.validator(o.key, o.converter(o.default), {}) - - def test_it(var, opts=opts): - exc_caught = None - try: - opts.Add(var) - except SCons.Errors.UserError: - exc_caught = 1 - assert exc_caught, "did not catch UserError for '%s'" % var - test_it('foo/bar') - test_it('foo-bar') - test_it('foo.bar') - - save = {} - save['warn'] = SCons.Warnings.warn - save['DeprecatedWarning'] = SCons.Warnings.DeprecatedWarning - def warn(type, message, save=save): - save['type'] = type - save['message'] = message - SCons.Warnings.warn = warn - SCons.Warnings.DeprecatedWarning = "xyzzy" - - try: - opts.Add('MISSPELLED', - 'test using the old validater keyword', - "42", - validater=check, - converter=lambda x: int(x) + 12) - finally: - SCons.Warnings.DeprecatedWarning = save['DeprecatedWarning'] - SCons.Warnings.warn = save['warn'] - assert save['type'] == "xyzzy", save['type'] - assert string.find(save['message'], "keyword of the Options.Add() method", save['message'] != -1), save['message'] - o = opts.options[2] - o.validator(o.key, o.converter(o.default), {}) - - def test_Update(self): - """Test updating an Environment""" - - # Test that a default value is validated correctly. - test = TestSCons.TestSCons() - file = test.workpath('custom.py') - opts = SCons.Options.Options(file) - - opts.Add('ANSWER', - 'THE answer to THE question', - "42", - check, - lambda x: int(x) + 12) - - env = Environment() - opts.Update(env) - assert env['ANSWER'] == 54 - - env = Environment() - opts.Update(env, {}) - assert env['ANSWER'] == 54 - - # Test that a bad value from the file is used and - # validation fails correctly. - test = TestSCons.TestSCons() - file = test.workpath('custom.py') - test.write('custom.py', 'ANSWER=54') - opts = SCons.Options.Options(file) - - opts.Add('ANSWER', - 'THE answer to THE question', - "42", - check, - lambda x: int(x) + 12) - - env = Environment() - exc_caught = None - try: - opts.Update(env) - except AssertionError: - exc_caught = 1 - assert exc_caught, "did not catch expected assertion" - - env = Environment() - exc_caught = None - try: - opts.Update(env, {}) - except AssertionError: - exc_caught = 1 - assert exc_caught, "did not catch expected assertion" - - # Test that a good value from the file is used and validated. - test = TestSCons.TestSCons() - file = test.workpath('custom.py') - test.write('custom.py', 'ANSWER=42') - opts = SCons.Options.Options(file) - - opts.Add('ANSWER', - 'THE answer to THE question', - "10", - check, - lambda x: int(x) + 12) - - env = Environment() - opts.Update(env) - assert env['ANSWER'] == 54 - - env = Environment() - opts.Update(env, {}) - assert env['ANSWER'] == 54 - - # Test that a bad value from an args dictionary passed to - # Update() is used and validation fails correctly. - test = TestSCons.TestSCons() - file = test.workpath('custom.py') - test.write('custom.py', 'ANSWER=10') - opts = SCons.Options.Options(file) - - opts.Add('ANSWER', - 'THE answer to THE question', - "12", - check, - lambda x: int(x) + 12) - - env = Environment() - exc_caught = None - try: - opts.Update(env, {'ANSWER':'54'}) - except AssertionError: - exc_caught = 1 - assert exc_caught, "did not catch expected assertion" - - # Test that a good value from an args dictionary - # passed to Update() is used and validated. - test = TestSCons.TestSCons() - file = test.workpath('custom.py') - test.write('custom.py', 'ANSWER=10') - opts = SCons.Options.Options(file) - - opts.Add('ANSWER', - 'THE answer to THE question', - "12", - check, - lambda x: int(x) + 12) - - env = Environment() - opts.Update(env, {'ANSWER':'42'}) - assert env['ANSWER'] == 54 - - # Test against a former bug. If we supply a converter, - # but no default, the value should *not* appear in the - # Environment if no value is specified in the options file - # or args. - test = TestSCons.TestSCons() - file = test.workpath('custom.py') - opts = SCons.Options.Options(file) - - opts.Add('ANSWER', - help='THE answer to THE question', - converter=str) - - env = Environment() - opts.Update(env, {}) - assert not env.has_key('ANSWER') - - def test_args(self): - """Test updating an Environment with arguments overridden""" - - # Test that a bad (command-line) argument is used - # and the validation fails correctly. - test = TestSCons.TestSCons() - file = test.workpath('custom.py') - test.write('custom.py', 'ANSWER=42') - opts = SCons.Options.Options(file, {'ANSWER':54}) - - opts.Add('ANSWER', - 'THE answer to THE question', - "42", - check, - lambda x: int(x) + 12) - - env = Environment() - exc_caught = None - try: - opts.Update(env) - except AssertionError: - exc_caught = 1 - assert exc_caught, "did not catch expected assertion" - - # Test that a good (command-line) argument is used and validated. - test = TestSCons.TestSCons() - file = test.workpath('custom.py') - test.write('custom.py', 'ANSWER=54') - opts = SCons.Options.Options(file, {'ANSWER':42}) - - opts.Add('ANSWER', - 'THE answer to THE question', - "54", - check, - lambda x: int(x) + 12) - - env = Environment() - opts.Update(env) - assert env['ANSWER'] == 54 - - # Test that a (command-line) argument is overridden by a dictionary - # supplied to Update() and the dictionary value is validated correctly. - test = TestSCons.TestSCons() - file = test.workpath('custom.py') - test.write('custom.py', 'ANSWER=54') - opts = SCons.Options.Options(file, {'ANSWER':54}) - - opts.Add('ANSWER', - 'THE answer to THE question', - "54", - check, - lambda x: int(x) + 12) - - env = Environment() - opts.Update(env, {'ANSWER':42}) - assert env['ANSWER'] == 54 - - def test_Save(self): - """Testing saving Options""" - - test = TestSCons.TestSCons() - cache_file = test.workpath('cached.options') - opts = SCons.Options.Options() - - # test saving out empty file - opts.Add('OPT_VAL', - 'An option to test', - 21, - None, - None) - opts.Add('OPT_VAL_2', - default='foo') - opts.Add('OPT_VAL_3', - default=1) - - env = Environment() - opts.Update(env, {'OPT_VAL_3' : 2}) - assert env['OPT_VAL'] == 21 - assert env['OPT_VAL_2'] == 'foo' - assert env['OPT_VAL_3'] == 2 - env['OPT_VAL_2'] = 'bar' - opts.Save(cache_file, env) - checkSave(cache_file, { 'OPT_VAL_2' : 'bar', - 'OPT_VAL_3' : 2 }) - - # Test against some old bugs - class Foo: - def __init__(self, x): - self.x = x - def __str__(self): - return self.x - - test = TestSCons.TestSCons() - cache_file = test.workpath('cached.options') - opts = SCons.Options.Options() - - opts.Add('THIS_USED_TO_BREAK', - 'An option to test', - "Default") - - opts.Add('THIS_ALSO_BROKE', - 'An option to test', - "Default2") - - opts.Add('THIS_SHOULD_WORK', - 'An option to test', - Foo('bar')) - - env = Environment() - opts.Update(env, { 'THIS_USED_TO_BREAK' : "Single'Quotes'In'String", - 'THIS_ALSO_BROKE' : "\\Escape\nSequences\t", - 'THIS_SHOULD_WORK' : Foo('baz') }) - opts.Save(cache_file, env) - checkSave(cache_file, { 'THIS_USED_TO_BREAK' : "Single'Quotes'In'String", - 'THIS_ALSO_BROKE' : "\\Escape\nSequences\t", - 'THIS_SHOULD_WORK' : 'baz' }) - - def test_GenerateHelpText(self): - opts = SCons.Options.Options() - - opts.Add('ANSWER', - 'THE answer to THE question', - "42", - check, - lambda x: int(x) + 12) - - opts.Add('B', - 'b - alpha test', - "42", - check, - lambda x: int(x) + 12) - - opts.Add('A', - 'a - alpha test', - "42", - check, - lambda x: int(x) + 12) - - env = Environment() - opts.Update(env, {}) - - expect = """ -ANSWER: THE answer to THE question - default: 42 - actual: 54 - -B: b - alpha test - default: 42 - actual: 54 - -A: a - alpha test - default: 42 - actual: 54 -""" - - text = opts.GenerateHelpText(env) - assert text == expect, text - - expectAlpha = """ -A: a - alpha test - default: 42 - actual: 54 - -ANSWER: THE answer to THE question - default: 42 - actual: 54 - -B: b - alpha test - default: 42 - actual: 54 -""" - text = opts.GenerateHelpText(env, sort=cmp) - assert text == expectAlpha, text - -if __name__ == "__main__": - suite = unittest.makeSuite(OptionsTestCase, 'test_') - if not unittest.TextTestRunner().run(suite).wasSuccessful(): - sys.exit(1) 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() -- cgit v0.12