|
- #
- # 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, Utils
-
- # A list of AutoOptions. It is local to each module, so all modules that
- # use AutoOptions need to run both opt.load and conf.load. In contrast
- # to the define and style options this does not need to and cannot be
- # declared in the OptionsContext, because it is needed both for the
- # options and the configure phase.
- auto_options = []
-
- 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
- actually 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__, with hyphens replaced by
- underscores. This default 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=None, 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
- """
-
- # The dependencies to check for. The elements are on the form
- # (func, k, kw) where 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 function.
- self.deps = []
-
- # Whether or not the option should be enabled. None indicates
- # that the checks have not been performed yet.
- self.enable = None
-
- self.help = help
- if help:
- if default:
- help_comment = ' (enabled by default if possible)'
- else:
- help_comment = ' (disabled by default)'
- option_help = help + help_comment
- no_option_help = None
- else:
- option_help = no_option_help = optparse.SUPPRESS_HELP
-
- self.dest = 'auto_option_' + name
-
- self.default = default
-
- safe_name = Utils.quote_define_name(name)
- self.conf_dest = conf_dest or safe_name
-
- default_define = opt.get_auto_options_define()
- self.define = define or default_define % safe_name
-
- if not style:
- style = opt.get_auto_options_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
- # with:
- # --with-foo | --without-foo
- if style in ['plain', 'yesno', 'yesno_and_hack']:
- self.no_option = '--no-' + name
- self.yes_option = '--' + name
- 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=option_help,
- metavar='no|yes')
- else:
- opt.add_option(
- self.yes_option,
- dest=self.dest,
- action='store_const',
- const='yes',
- default='auto',
- help=option_help)
- opt.add_option(
- self.no_option,
- dest=self.dest,
- action='store_const',
- const='no',
- default='auto',
- help=no_option_help)
-
- def check(self, *k, **kw):
- self.deps.append(('check', k, kw))
- def check_cfg(self, *k, **kw):
- self.deps.append(('check_cfg', k, kw))
- def find_program(self, *k, **kw):
- self.deps.append(('find_program', k, kw))
-
- 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 arguments 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 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.
- """
- # If the option has already been configured once, do not
- # configure it again.
- if self.enable != None:
- return True
-
- 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.help:
- if self.enable:
- conf.msg(self.help, 'yes', color='GREEN')
- else:
- conf.msg(self.help, 'no', color='YELLOW')
-
- def options(opt):
- """
- This function declares necessary variables in the option context.
- The reason for saving variables in the option context is to allow
- autooptions to be loaded from modules (which will receive a new
- instance of this module, clearing any global variables) with a
- uniform style and default in the entire project.
-
- Call this function through opt.load('autooptions').
- """
- if not hasattr(opt, 'auto_options_style'):
- opt.auto_options_style = 'plain'
- if not hasattr(opt, 'auto_options_define'):
- opt.auto_options_define = 'WITH_%s'
-
- 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 AutoOption to the options context. It takes
- the same arguments as the initializer function of the AutoOptions
- class.
- """
- option = AutoOption(self, *k, **kw)
- auto_options.append(option)
- return option
-
- @opt
- def get_auto_options_define(self):
- """
- This function gets the default define name. This default can be
- changed through set_auto_optoins_define.
- """
- return self.auto_options_define
-
- @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.
- """
- self.auto_options_define = define
-
- @opt
- def get_auto_options_style(self):
- """
- This function gets the default option style, which will be used for
- the subsequent options.
- """
- return self.auto_options_style
-
- @opt
- def set_auto_options_style(self, style):
- """
- This function sets the default option style, which will be used for
- the subsequent options.
- """
- self.auto_options_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.')
|