Initial commit.
[waf-autooptions.git] / __init__.py
blob4b0fc2bb0e0aaed8c58b51e743b389346ea9e815
2 # Copyright (C) 2017 Karl Linden
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
8 # 1. Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the
14 # distribution.
16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 import optparse
30 import sys
31 from waflib import Configure, Logs, Options
33 auto_options = []
34 default_define = 'WITH_%s'
35 default_style = 'plain'
37 class AutoOption:
38 """
39 This class represents an auto option that can be used in conjunction
40 with the waf build system. By default it adds options --foo and
41 --no-foo respectively to turn on or off foo respectively.
42 Furthermore it incorporats logic and checks that are required for
43 these features.
45 An option can have an arbitrary number of dependencies that must be
46 present for the option to be enabled. An option can be enabled or
47 disabled by default. Here is how the logic works:
48 1. If the option is explicitly disabled, through --no-foo, then no
49 checks are made and the option is disabled.
50 2. If the option is explicitly enabled, through --foo, then check
51 for all required dependencies, and if some of them are not
52 found, then print a fatal error telling the user there were
53 dependencies missing.
54 3. Otherwise, if the option is enabled by default, then check for
55 all dependencies. If all dependencies are found the option is
56 enabled. Otherwise it is disabled.
57 4. Lastly, if no option was given and the option is disabled by
58 default, then no checks are performed and the option is
59 disabled.
61 To add a dependency to an option use the check, check_cfg and
62 find_program methods of this class. The methods are merely small
63 wrappers around their configuration context counterparts and behave
64 identically. Note that adding dependencies is done in the options
65 phase and not in the configure phase, although the checks are
66 acutally executed during the configure phase.
68 Custom check functions can be added using the add_function method.
69 As with the other checks the check function will be invoked during
70 the configuration. Refer to the documentation of the add_function
71 method for details.
73 When all checks have been made and the class has made a decision the
74 result is saved in conf.env['NAME'] where 'NAME' by default is the
75 uppercase of the name argument to __init__, but it can be changed
76 with the conf_dest argument to __init__.
78 The class will define a preprocessor symbol with the result. The
79 default name is WITH_NAME, to not collide with the standard define
80 of check_cfg, but it can be changed using the define argument to
81 __init__. It can also be changed globally using
82 set_auto_options_define.
83 """
85 def __init__(self, opt, name, help, default=True, conf_dest=None,
86 define=None, style=None):
87 """
88 Class initializing method.
90 Arguments:
91 opt OptionsContext
92 name name of the option, e.g. alsa
93 help help text that will be displayed in --help output
94 conf_dest conf.env variable to define to the result
95 define the preprocessor symbol to define with the result
96 style the option style to use; see below for options
97 """
99 # Dependencies and dependencies that are not found respectively.
100 # elements of both are on the form (func, k, kw) where type
101 # is a string equal to "library", "header", "package" or
102 # "program", func is the function or function name that is used
103 # for the check and k and kw are the arguments and options to
104 # give the check function.
105 self.deps = []
107 # Whether or not the option should be enabled or not. None
108 # indicates that the checks are not done yet.
109 self.enable = None
111 self.help = help
112 if default:
113 help_comment = ' (enabled by default if possible)'
114 else:
115 help_comment = ' (disabled by default)'
117 self.dest = 'auto_option_' + name
119 self.default = default
120 if conf_dest:
121 self.conf_dest = conf_dest
122 else:
123 self.conf_dest = name.upper()
124 if not define:
125 self.define = default_define % name.upper()
126 else:
127 self.define = define
129 if not style:
130 style = default_style
131 self.style = style
133 # plain (default):
134 # --foo | --no-foo
135 # yesno:
136 # --foo=yes | --foo=no
137 # yesno_and_hack:
138 # --foo[=yes] | --foo=no or --no-foo
139 # enable:
140 # --enable-foo | --disble-foo
141 # wit:
142 # --with-foo | --without-foo
143 if style in ['plain', 'yesno', 'yesno_and_hack']:
144 self.no_option = '--no-' + name
145 self.yes_option = '--' + name
146 if style == 'yesno_and_hack':
147 apply_hack = True
148 elif style == 'enable':
149 self.no_option = '--disable-' + name
150 self.yes_option = '--enable-' + name
151 elif style == 'with':
152 self.no_option = '--without-' + name
153 self.yes_option = '--with-' + name
154 else:
155 opt.fatal("invalid style")
157 if style in ['yesno', 'yesno_and_hack']:
158 opt.add_option(
159 self.yes_option,
160 dest=self.dest,
161 action='store',
162 choices=['auto', 'no', 'yes'],
163 default='auto',
164 help=self.help + help_comment,
165 metavar='no|yes')
166 else:
167 opt.add_option(
168 self.yes_option,
169 dest=self.dest,
170 action='store_const',
171 const='yes',
172 default='auto',
173 help=self.help + help_comment)
174 opt.add_option(
175 self.no_option,
176 dest=self.dest,
177 action='store_const',
178 const='no',
179 default='auto')
181 funcs = ['check', 'check_cfg', 'find_program']
182 for fn in funcs:
183 def f(*k, fn=fn, **kw):
184 self.deps.append((fn, k, kw))
185 setattr(self, fn, f)
187 def add_function(self, func, *k, **kw):
189 Add a custom function to be invoked as part of the
190 configuration. During the configuration the function will be
191 invoked with the configuration context as first argument
192 followed by the arugments to this method, except for the func
193 argument. The function must print a "Checking for..." message,
194 because it is referred to if the check fails and this option is
195 requested.
197 On configuration error the function shall raise
198 conf.errors.ConfigurationError.
200 self.deps.append((func, k, kw))
202 def _check(self, conf, required):
204 This private method runs checks all dependencies.
205 It checks all dependencies (even if some dependency was not
206 found) so that the user can install all missing dependencies in
207 one go, instead of playing the infamous
208 hit-configure-hit-configure game.
210 This function returns True if all dependencies were found and
211 False otherwise.
213 all_found = True
215 for (f,k,kw) in self.deps:
216 if hasattr(f, '__call__'):
217 # This is a function supplied by add_function.
218 func = f
219 k = list(k)
220 k.insert(0, conf)
221 k = tuple(k)
222 else:
223 func = getattr(conf, f)
224 try:
225 func(*k, **kw)
226 except conf.errors.ConfigurationError:
227 all_found = False
228 if required:
229 Logs.error('The above check failed, but the '
230 'checkee is required for %s.' %
231 self.yes_option)
233 return all_found
235 def configure(self, conf):
237 This function configures the option examining the command line
238 option. It sets self.enable to whether this options should be
239 enabled or not, that is True or False respectively. If not all
240 dependencies were found self.enable will be False.
241 conf.env['NAME'] and a preprocessor symbol will be defined with
242 the result.
244 If the option was desired but one or more dependencies were not
245 found the an error message will be printed for each missing
246 dependency.
248 This function returns True on success and False on if the option
249 was requested but cannot be enabled.
251 argument = getattr(Options.options, self.dest)
252 if argument == 'no':
253 self.enable = False
254 retvalue = True
255 elif argument == 'yes':
256 retvalue = self.enable = self._check(conf, True)
257 else:
258 self.enable = self.default and self._check(conf, False)
259 retvalue = True
261 conf.env[self.conf_dest] = self.enable
262 conf.define(self.define, int(self.enable))
263 return retvalue
265 def summarize(self, conf):
267 This function displays a result summary with the help text and
268 the result of the configuration.
270 if self.enable:
271 conf.msg(self.help, 'yes', color='GREEN')
272 else:
273 conf.msg(self.help, 'no', color='YELLOW')
275 def opt(f):
277 Decorator: attach a new option function to Options.OptionsContext.
279 :param f: method to bind
280 :type f: function
282 setattr(Options.OptionsContext, f.__name__, f)
284 @opt
285 def add_auto_option(self, *k, **kw):
287 This function adds an AutoOptions to the options context. It takes
288 the same arguments as the initializer funtion of the AutoOptions
289 class.
291 option = AutoOption(self, *k, **kw)
292 auto_options.append(option)
293 return option
295 @opt
296 def set_auto_options_define(self, define):
298 This function sets the default define name. The default is
299 "WITH_%s", where %s will be replaced with the name of the option in
300 uppercase.
302 global default_define
303 default_define = define
305 @opt
306 def set_auto_options_style(self, style):
308 This function sets the default option style, which will be used for
309 the subsequent options.
311 global default_style
312 default_style = style
314 @opt
315 def apply_auto_options_hack(self):
317 This function applies the hack necessary for the yesno_and_hack
318 option style. The hack turns --foo into --foo=yes and --no-foo into
319 --foo=no.
321 It must be called before options are parsed, that is before the
322 configure phase.
324 for option in auto_options:
325 # With the hack the yesno options simply extend plain
326 # options.
327 if option.style == 'yesno_and_hack':
328 for i in range(1, len(sys.argv)):
329 if sys.argv[i] == option.yes_option:
330 sys.argv[i] = option.yes_option + '=yes'
331 elif sys.argv[i] == option.no_option:
332 sys.argv[i] = option.yes_option + '=no'
334 @Configure.conf
335 def summarize_auto_options(self):
337 This function prints a summary of the configuration of the auto
338 options. Obviously, it must be called after
339 conf.load("autooptions").
341 for option in auto_options:
342 option.summarize(self)
344 def configure(conf):
346 This configures all auto options. Call it through
347 conf.load("autooptions").
349 ok = True
350 for option in auto_options:
351 if not option.configure(conf):
352 ok = False
353 if not ok:
354 conf.fatal('Some requested options had unsatisfied ' +
355 'dependencies.\n' +
356 'See the above configuration for details.')