From d28479fe27e46624a86c4ca87ef8743f23d4d108 Mon Sep 17 00:00:00 2001 From: Karl Linden Date: Sat, 22 Jul 2017 09:54:07 +0200 Subject: [PATCH] Initial commit. --- .gitignore | 1 + __init__.py | 356 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..4b0fc2b --- /dev/null +++ b/__init__.py @@ -0,0 +1,356 @@ +# +# Copyright (C) 2017 Karl Linden +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +import optparse +import sys +from waflib import Configure, Logs, Options + +auto_options = [] +default_define = 'WITH_%s' +default_style = 'plain' + +class AutoOption: + """ + This class represents an auto option that can be used in conjunction + with the waf build system. By default it adds options --foo and + --no-foo respectively to turn on or off foo respectively. + Furthermore it incorporats logic and checks that are required for + these features. + + An option can have an arbitrary number of dependencies that must be + present for the option to be enabled. An option can be enabled or + disabled by default. Here is how the logic works: + 1. If the option is explicitly disabled, through --no-foo, then no + checks are made and the option is disabled. + 2. If the option is explicitly enabled, through --foo, then check + for all required dependencies, and if some of them are not + found, then print a fatal error telling the user there were + dependencies missing. + 3. Otherwise, if the option is enabled by default, then check for + all dependencies. If all dependencies are found the option is + enabled. Otherwise it is disabled. + 4. Lastly, if no option was given and the option is disabled by + default, then no checks are performed and the option is + disabled. + + To add a dependency to an option use the check, check_cfg and + find_program methods of this class. The methods are merely small + wrappers around their configuration context counterparts and behave + identically. Note that adding dependencies is done in the options + phase and not in the configure phase, although the checks are + acutally executed during the configure phase. + + Custom check functions can be added using the add_function method. + As with the other checks the check function will be invoked during + the configuration. Refer to the documentation of the add_function + method for details. + + When all checks have been made and the class has made a decision the + result is saved in conf.env['NAME'] where 'NAME' by default is the + uppercase of the name argument to __init__, but it can be changed + with the conf_dest argument to __init__. + + The class will define a preprocessor symbol with the result. The + default name is WITH_NAME, to not collide with the standard define + of check_cfg, but it can be changed using the define argument to + __init__. It can also be changed globally using + set_auto_options_define. + """ + + def __init__(self, opt, name, help, default=True, conf_dest=None, + define=None, style=None): + """ + Class initializing method. + + Arguments: + opt OptionsContext + name name of the option, e.g. alsa + help help text that will be displayed in --help output + conf_dest conf.env variable to define to the result + define the preprocessor symbol to define with the result + style the option style to use; see below for options + """ + + # Dependencies and dependencies that are not found respectively. + # elements of both are on the form (func, k, kw) where type + # is a string equal to "library", "header", "package" or + # "program", func is the function or function name that is used + # for the check and k and kw are the arguments and options to + # give the check function. + self.deps = [] + + # Whether or not the option should be enabled or not. None + # indicates that the checks are not done yet. + self.enable = None + + self.help = help + if default: + help_comment = ' (enabled by default if possible)' + else: + help_comment = ' (disabled by default)' + + self.dest = 'auto_option_' + name + + self.default = default + if conf_dest: + self.conf_dest = conf_dest + else: + self.conf_dest = name.upper() + if not define: + self.define = default_define % name.upper() + else: + self.define = define + + if not style: + style = default_style + self.style = style + + # plain (default): + # --foo | --no-foo + # yesno: + # --foo=yes | --foo=no + # yesno_and_hack: + # --foo[=yes] | --foo=no or --no-foo + # enable: + # --enable-foo | --disble-foo + # wit: + # --with-foo | --without-foo + if style in ['plain', 'yesno', 'yesno_and_hack']: + self.no_option = '--no-' + name + self.yes_option = '--' + name + if style == 'yesno_and_hack': + apply_hack = True + elif style == 'enable': + self.no_option = '--disable-' + name + self.yes_option = '--enable-' + name + elif style == 'with': + self.no_option = '--without-' + name + self.yes_option = '--with-' + name + else: + opt.fatal("invalid style") + + if style in ['yesno', 'yesno_and_hack']: + opt.add_option( + self.yes_option, + dest=self.dest, + action='store', + choices=['auto', 'no', 'yes'], + default='auto', + help=self.help + help_comment, + metavar='no|yes') + else: + opt.add_option( + self.yes_option, + dest=self.dest, + action='store_const', + const='yes', + default='auto', + help=self.help + help_comment) + opt.add_option( + self.no_option, + dest=self.dest, + action='store_const', + const='no', + default='auto') + + funcs = ['check', 'check_cfg', 'find_program'] + for fn in funcs: + def f(*k, fn=fn, **kw): + self.deps.append((fn, k, kw)) + setattr(self, fn, f) + + def add_function(self, func, *k, **kw): + """ + Add a custom function to be invoked as part of the + configuration. During the configuration the function will be + invoked with the configuration context as first argument + followed by the arugments to this method, except for the func + argument. The function must print a "Checking for..." message, + because it is referred to if the check fails and this option is + requested. + + On configuration error the function shall raise + conf.errors.ConfigurationError. + """ + self.deps.append((func, k, kw)) + + def _check(self, conf, required): + """ + This private method runs checks all dependencies. + It checks all dependencies (even if some dependency was not + found) so that the user can install all missing dependencies in + one go, instead of playing the infamous + hit-configure-hit-configure game. + + This function returns True if all dependencies were found and + False otherwise. + """ + all_found = True + + for (f,k,kw) in self.deps: + if hasattr(f, '__call__'): + # This is a function supplied by add_function. + func = f + k = list(k) + k.insert(0, conf) + k = tuple(k) + else: + func = getattr(conf, f) + try: + func(*k, **kw) + except conf.errors.ConfigurationError: + all_found = False + if required: + Logs.error('The above check failed, but the ' + 'checkee is required for %s.' % + self.yes_option) + + return all_found + + def configure(self, conf): + """ + This function configures the option examining the command line + option. It sets self.enable to whether this options should be + enabled or not, that is True or False respectively. If not all + dependencies were found self.enable will be False. + conf.env['NAME'] and a preprocessor symbol will be defined with + the result. + + If the option was desired but one or more dependencies were not + found the an error message will be printed for each missing + dependency. + + This function returns True on success and False on if the option + was requested but cannot be enabled. + """ + argument = getattr(Options.options, self.dest) + if argument == 'no': + self.enable = False + retvalue = True + elif argument == 'yes': + retvalue = self.enable = self._check(conf, True) + else: + self.enable = self.default and self._check(conf, False) + retvalue = True + + conf.env[self.conf_dest] = self.enable + conf.define(self.define, int(self.enable)) + return retvalue + + def summarize(self, conf): + """ + This function displays a result summary with the help text and + the result of the configuration. + """ + if self.enable: + conf.msg(self.help, 'yes', color='GREEN') + else: + conf.msg(self.help, 'no', color='YELLOW') + +def opt(f): + """ + Decorator: attach a new option function to Options.OptionsContext. + + :param f: method to bind + :type f: function + """ + setattr(Options.OptionsContext, f.__name__, f) + +@opt +def add_auto_option(self, *k, **kw): + """ + This function adds an AutoOptions to the options context. It takes + the same arguments as the initializer funtion of the AutoOptions + class. + """ + option = AutoOption(self, *k, **kw) + auto_options.append(option) + return option + +@opt +def set_auto_options_define(self, define): + """ + This function sets the default define name. The default is + "WITH_%s", where %s will be replaced with the name of the option in + uppercase. + """ + global default_define + default_define = define + +@opt +def set_auto_options_style(self, style): + """ + This function sets the default option style, which will be used for + the subsequent options. + """ + global default_style + default_style = style + +@opt +def apply_auto_options_hack(self): + """ + This function applies the hack necessary for the yesno_and_hack + option style. The hack turns --foo into --foo=yes and --no-foo into + --foo=no. + + It must be called before options are parsed, that is before the + configure phase. + """ + for option in auto_options: + # With the hack the yesno options simply extend plain + # options. + if option.style == 'yesno_and_hack': + for i in range(1, len(sys.argv)): + if sys.argv[i] == option.yes_option: + sys.argv[i] = option.yes_option + '=yes' + elif sys.argv[i] == option.no_option: + sys.argv[i] = option.yes_option + '=no' + +@Configure.conf +def summarize_auto_options(self): + """ + This function prints a summary of the configuration of the auto + options. Obviously, it must be called after + conf.load("autooptions"). + """ + for option in auto_options: + option.summarize(self) + +def configure(conf): + """ + This configures all auto options. Call it through + conf.load("autooptions"). + """ + ok = True + for option in auto_options: + if not option.configure(conf): + ok = False + if not ok: + conf.fatal('Some requested options had unsatisfied ' + + 'dependencies.\n' + + 'See the above configuration for details.') -- 2.11.4.GIT