buildtools/wafsamba: use top for waf 2.0
[samba.git] / buildtools / wafsamba / samba_utils.py
1 # a waf tool to add autoconf-like macros to the configure section
2 # and for SAMBA_ macros for building libraries, binaries etc
3
4 import os, sys, re, fnmatch, shlex, inspect
5 from optparse import SUPPRESS_HELP
6 from waflib import Build, Options, Utils, Task, Logs, Configure, Errors, Context
7 from waflib.TaskGen import feature, before, after
8 from waflib.Configure import ConfigurationContext
9 from waflib.Logs import debug
10 from waflib import ConfigSet
11
12 # TODO: make this a --option
13 LIB_PATH="shared"
14
15
16 # sigh, python octal constants are a mess
17 MODE_644 = int('644', 8)
18 MODE_755 = int('755', 8)
19
20 def conf(f):
21     # override in order to propagate the argument "mandatory"
22     def fun(*k, **kw):
23         mandatory = True
24         if 'mandatory' in kw:
25             mandatory = kw['mandatory']
26             del kw['mandatory']
27
28         try:
29             return f(*k, **kw)
30         except Errors.ConfigurationError:
31             if mandatory:
32                 raise
33
34     fun.__name__ = f.__name__
35     if 'mandatory' in inspect.getsource(f):
36         fun = f
37
38     setattr(Configure.ConfigurationContext, f.__name__, fun)
39     setattr(Build.BuildContext, f.__name__, fun)
40     return f
41 Configure.conf = conf
42 Configure.conftest = conf
43
44 @conf
45 def SET_TARGET_TYPE(ctx, target, value):
46     '''set the target type of a target'''
47     cache = LOCAL_CACHE(ctx, 'TARGET_TYPE')
48     if target in cache and cache[target] != 'EMPTY':
49         Logs.error("ERROR: Target '%s' in directory %s re-defined as %s - was %s" % (target, ctx.path.abspath(), value, cache[target]))
50         sys.exit(1)
51     LOCAL_CACHE_SET(ctx, 'TARGET_TYPE', target, value)
52     debug("task_gen: Target '%s' created of type '%s' in %s" % (target, value, ctx.path.abspath()))
53     return True
54
55
56 def GET_TARGET_TYPE(ctx, target):
57     '''get target type from cache'''
58     cache = LOCAL_CACHE(ctx, 'TARGET_TYPE')
59     if not target in cache:
60         return None
61     return cache[target]
62
63
64 def ADD_LD_LIBRARY_PATH(path):
65     '''add something to LD_LIBRARY_PATH'''
66     if 'LD_LIBRARY_PATH' in os.environ:
67         oldpath = os.environ['LD_LIBRARY_PATH']
68     else:
69         oldpath = ''
70     newpath = oldpath.split(':')
71     if not path in newpath:
72         newpath.append(path)
73         os.environ['LD_LIBRARY_PATH'] = ':'.join(newpath)
74
75
76 def needs_private_lib(bld, target):
77     '''return True if a target links to a private library'''
78     for lib in getattr(target, "final_libs", []):
79         t = bld.get_tgen_by_name(lib)
80         if t and getattr(t, 'private_library', False):
81             return True
82     return False
83
84
85 def install_rpath(target):
86     '''the rpath value for installation'''
87     bld = target.bld
88     bld.env['RPATH'] = []
89     ret = set()
90     if bld.env.RPATH_ON_INSTALL:
91         ret.add(bld.EXPAND_VARIABLES(bld.env.LIBDIR))
92     if bld.env.RPATH_ON_INSTALL_PRIVATE and needs_private_lib(bld, target):
93         ret.add(bld.EXPAND_VARIABLES(bld.env.PRIVATELIBDIR))
94     return list(ret)
95
96
97 def build_rpath(bld):
98     '''the rpath value for build'''
99     rpaths = [os.path.normpath('%s/%s' % (bld.env.BUILD_DIRECTORY, d)) for d in ("shared", "shared/private")]
100     bld.env['RPATH'] = []
101     if bld.env.RPATH_ON_BUILD:
102         return rpaths
103     for rpath in rpaths:
104         ADD_LD_LIBRARY_PATH(rpath)
105     return []
106
107
108 @conf
109 def LOCAL_CACHE(ctx, name):
110     '''return a named build cache dictionary, used to store
111        state inside other functions'''
112     if name in ctx.env:
113         return ctx.env[name]
114     ctx.env[name] = {}
115     return ctx.env[name]
116
117
118 @conf
119 def LOCAL_CACHE_SET(ctx, cachename, key, value):
120     '''set a value in a local cache'''
121     cache = LOCAL_CACHE(ctx, cachename)
122     cache[key] = value
123
124
125 @conf
126 def ASSERT(ctx, expression, msg):
127     '''a build assert call'''
128     if not expression:
129         raise Errors.WafError("ERROR: %s\n" % msg)
130 Build.BuildContext.ASSERT = ASSERT
131
132
133 def SUBDIR(bld, subdir, list):
134     '''create a list of files by pre-pending each with a subdir name'''
135     ret = ''
136     for l in TO_LIST(list):
137         ret = ret + os.path.normpath(os.path.join(subdir, l)) + ' '
138     return ret
139 Build.BuildContext.SUBDIR = SUBDIR
140
141
142 def dict_concat(d1, d2):
143     '''concatenate two dictionaries d1 += d2'''
144     for t in d2:
145         if t not in d1:
146             d1[t] = d2[t]
147
148 def ADD_COMMAND(opt, name, function):
149     '''add a new top level command to waf'''
150     Context.g_module.__dict__[name] = function
151     opt.name = function
152 Options.OptionsContext.ADD_COMMAND = ADD_COMMAND
153
154
155 @feature('c', 'cc', 'cshlib', 'cprogram')
156 @before('apply_core','exec_rule')
157 def process_depends_on(self):
158     '''The new depends_on attribute for build rules
159        allow us to specify a dependency on output from
160        a source generation rule'''
161     if getattr(self , 'depends_on', None):
162         lst = self.to_list(self.depends_on)
163         for x in lst:
164             y = self.bld.get_tgen_by_name(x)
165             self.bld.ASSERT(y is not None, "Failed to find dependency %s of %s" % (x, self.name))
166             y.post()
167             if getattr(y, 'more_includes', None):
168                   self.includes += " " + y.more_includes
169
170
171 os_path_relpath = getattr(os.path, 'relpath', None)
172 if os_path_relpath is None:
173     # Python < 2.6 does not have os.path.relpath, provide a replacement
174     # (imported from Python2.6.5~rc2)
175     def os_path_relpath(path, start):
176         """Return a relative version of a path"""
177         start_list = os.path.abspath(start).split("/")
178         path_list = os.path.abspath(path).split("/")
179
180         # Work out how much of the filepath is shared by start and path.
181         i = len(os.path.commonprefix([start_list, path_list]))
182
183         rel_list = ['..'] * (len(start_list)-i) + path_list[i:]
184         if not rel_list:
185             return start
186         return os.path.join(*rel_list)
187
188
189 def unique_list(seq):
190     '''return a uniquified list in the same order as the existing list'''
191     seen = {}
192     result = []
193     for item in seq:
194         if item in seen: continue
195         seen[item] = True
196         result.append(item)
197     return result
198
199
200 def TO_LIST(str, delimiter=None):
201     '''Split a list, preserving quoted strings and existing lists'''
202     if str is None:
203         return []
204     if isinstance(str, list):
205         # we need to return a new independent list...
206         return list(str)
207     if len(str) == 0:
208         return []
209     lst = str.split(delimiter)
210     # the string may have had quotes in it, now we
211     # check if we did have quotes, and use the slower shlex
212     # if we need to
213     for e in lst:
214         if e[0] == '"':
215             return shlex.split(str)
216     return lst
217
218
219 def subst_vars_error(string, env):
220     '''substitute vars, throw an error if a variable is not defined'''
221     lst = re.split('(\$\{\w+\})', string)
222     out = []
223     for v in lst:
224         if re.match('\$\{\w+\}', v):
225             vname = v[2:-1]
226             if not vname in env:
227                 raise KeyError("Failed to find variable %s in %s in env %s <%s>" % (vname, string, env.__class__, str(env)))
228             v = env[vname]
229             if isinstance(v, list):
230                 v = ' '.join(v)
231         out.append(v)
232     return ''.join(out)
233
234
235 @conf
236 def SUBST_ENV_VAR(ctx, varname):
237     '''Substitute an environment variable for any embedded variables'''
238     return subst_vars_error(ctx.env[varname], ctx.env)
239 Build.BuildContext.SUBST_ENV_VAR = SUBST_ENV_VAR
240
241
242 def ENFORCE_GROUP_ORDERING(bld):
243     '''enforce group ordering for the project. This
244        makes the group ordering apply only when you specify
245        a target with --target'''
246     if Options.options.compile_targets:
247         @feature('*')
248         @before('exec_rule', 'apply_core', 'collect')
249         def force_previous_groups(self):
250             if getattr(self.bld, 'enforced_group_ordering', False):
251                 return
252             self.bld.enforced_group_ordering = True
253
254             def group_name(g):
255                 tm = self.bld.task_manager
256                 return [x for x in tm.groups_names if id(tm.groups_names[x]) == id(g)][0]
257
258             my_id = id(self)
259             bld = self.bld
260             stop = None
261             for g in bld.task_manager.groups:
262                 for t in g.tasks_gen:
263                     if id(t) == my_id:
264                         stop = id(g)
265                         debug('group: Forcing up to group %s for target %s',
266                               group_name(g), self.name or self.target)
267                         break
268                 if stop is not None:
269                     break
270             if stop is None:
271                 return
272
273             for i in xrange(len(bld.task_manager.groups)):
274                 g = bld.task_manager.groups[i]
275                 bld.task_manager.current_group = i
276                 if id(g) == stop:
277                     break
278                 debug('group: Forcing group %s', group_name(g))
279                 for t in g.tasks_gen:
280                     if not getattr(t, 'forced_groups', False):
281                         debug('group: Posting %s', t.name or t.target)
282                         t.forced_groups = True
283                         t.post()
284 Build.BuildContext.ENFORCE_GROUP_ORDERING = ENFORCE_GROUP_ORDERING
285
286
287 def recursive_dirlist(dir, relbase, pattern=None):
288     '''recursive directory list'''
289     ret = []
290     for f in os.listdir(dir):
291         f2 = dir + '/' + f
292         if os.path.isdir(f2):
293             ret.extend(recursive_dirlist(f2, relbase))
294         else:
295             if pattern and not fnmatch.fnmatch(f, pattern):
296                 continue
297             ret.append(os_path_relpath(f2, relbase))
298     return ret
299
300
301 def mkdir_p(dir):
302     '''like mkdir -p'''
303     if not dir:
304         return
305     if dir.endswith("/"):
306         mkdir_p(dir[:-1])
307         return
308     if os.path.isdir(dir):
309         return
310     mkdir_p(os.path.dirname(dir))
311     os.mkdir(dir)
312
313
314 def SUBST_VARS_RECURSIVE(string, env):
315     '''recursively expand variables'''
316     if string is None:
317         return string
318     limit=100
319     while (string.find('${') != -1 and limit > 0):
320         string = subst_vars_error(string, env)
321         limit -= 1
322     return string
323
324
325 @conf
326 def EXPAND_VARIABLES(ctx, varstr, vars=None):
327     '''expand variables from a user supplied dictionary
328
329     This is most useful when you pass vars=locals() to expand
330     all your local variables in strings
331     '''
332
333     if isinstance(varstr, list):
334         ret = []
335         for s in varstr:
336             ret.append(EXPAND_VARIABLES(ctx, s, vars=vars))
337         return ret
338
339     if not isinstance(varstr, str):
340         return varstr
341
342     env = ConfigSet.ConfigSet()
343     ret = varstr
344     # substitute on user supplied dict if avaiilable
345     if vars is not None:
346         for v in vars.keys():
347             env[v] = vars[v]
348         ret = SUBST_VARS_RECURSIVE(ret, env)
349
350     # if anything left, subst on the environment as well
351     if ret.find('${') != -1:
352         ret = SUBST_VARS_RECURSIVE(ret, ctx.env)
353     # make sure there is nothing left. Also check for the common
354     # typo of $( instead of ${
355     if ret.find('${') != -1 or ret.find('$(') != -1:
356         Logs.error('Failed to substitute all variables in varstr=%s' % ret)
357         sys.exit(1)
358     return ret
359 Build.BuildContext.EXPAND_VARIABLES = EXPAND_VARIABLES
360
361
362 def RUN_COMMAND(cmd,
363                 env=None,
364                 shell=False):
365     '''run a external command, return exit code or signal'''
366     if env:
367         cmd = SUBST_VARS_RECURSIVE(cmd, env)
368
369     status = os.system(cmd)
370     if os.WIFEXITED(status):
371         return os.WEXITSTATUS(status)
372     if os.WIFSIGNALED(status):
373         return - os.WTERMSIG(status)
374     Logs.error("Unknown exit reason %d for command: %s" (status, cmd))
375     return -1
376
377
378 def RUN_PYTHON_TESTS(testfiles, pythonpath=None, extra_env=None):
379     env = LOAD_ENVIRONMENT()
380     if pythonpath is None:
381         pythonpath = os.path.join(Context.g_module.out, 'python')
382     result = 0
383     for interp in env.python_interpreters:
384         if not isinstance(interp, str):
385             interp = ' '.join(interp)
386         for testfile in testfiles:
387             cmd = "PYTHONPATH=%s %s %s" % (pythonpath, interp, testfile)
388             if extra_env:
389                 for key, value in extra_env.items():
390                     cmd = "%s=%s %s" % (key, value, cmd)
391             print('Running Python test with %s: %s' % (interp, testfile))
392             ret = RUN_COMMAND(cmd)
393             if ret:
394                 print('Python test failed: %s' % cmd)
395                 result = ret
396     return result
397
398
399 # make sure we have md5. some systems don't have it
400 try:
401     from hashlib import md5
402     # Even if hashlib.md5 exists, it may be unusable.
403     # Try to use MD5 function. In FIPS mode this will cause an exception
404     # and we'll get to the replacement code
405     foo = md5('abcd')
406 except:
407     try:
408         import md5
409         # repeat the same check here, mere success of import is not enough.
410         # Try to use MD5 function. In FIPS mode this will cause an exception
411         foo = md5.md5('abcd')
412     except:
413         Context.SIG_NIL = hash('abcd')
414         class replace_md5(object):
415             def __init__(self):
416                 self.val = None
417             def update(self, val):
418                 self.val = hash((self.val, val))
419             def digest(self):
420                 return str(self.val)
421             def hexdigest(self):
422                 return self.digest().encode('hex')
423         def replace_h_file(filename):
424             f = open(filename, 'rb')
425             m = replace_md5()
426             while (filename):
427                 filename = f.read(100000)
428                 m.update(filename)
429             f.close()
430             return m.digest()
431         Utils.md5 = replace_md5
432         Task.md5 = replace_md5
433         Utils.h_file = replace_h_file
434
435
436 def LOAD_ENVIRONMENT():
437     '''load the configuration environment, allowing access to env vars
438        from new commands'''
439     env = ConfigSet.ConfigSet()
440     try:
441         p = os.path.join(Context.g_module.out, 'c4che/default_cache.py')
442         env.load(p)
443     except (OSError, IOError):
444         pass
445     return env
446
447
448 def IS_NEWER(bld, file1, file2):
449     '''return True if file1 is newer than file2'''
450     curdir = bld.path.abspath()
451     t1 = os.stat(os.path.join(curdir, file1)).st_mtime
452     t2 = os.stat(os.path.join(curdir, file2)).st_mtime
453     return t1 > t2
454 Build.BuildContext.IS_NEWER = IS_NEWER
455
456
457 @conf
458 def RECURSE(ctx, directory):
459     '''recurse into a directory, relative to the curdir or top level'''
460     try:
461         visited_dirs = ctx.visited_dirs
462     except AttributeError:
463         visited_dirs = ctx.visited_dirs = set()
464     d = os.path.join(ctx.path.abspath(), directory)
465     if os.path.exists(d):
466         abspath = os.path.abspath(d)
467     else:
468         abspath = os.path.abspath(os.path.join(Context.g_module.top, directory))
469     ctxclass = ctx.__class__.__name__
470     key = ctxclass + ':' + abspath
471     if key in visited_dirs:
472         # already done it
473         return
474     visited_dirs.add(key)
475     relpath = os_path_relpath(abspath, ctx.path.abspath())
476     if ctxclass in ['tmp', 'OptionsContext', 'ConfigurationContext', 'BuildContext']:
477         return ctx.recurse(relpath)
478     if 'waflib.extras.compat15' in sys.modules:
479         return ctx.recurse(relpath)
480     Logs.error('Unknown RECURSE context class: {}'.format(ctxclass))
481     raise
482 Options.OptionsContext.RECURSE = RECURSE
483 Build.BuildContext.RECURSE = RECURSE
484
485
486 def CHECK_MAKEFLAGS(bld):
487     '''check for MAKEFLAGS environment variable in case we are being
488     called from a Makefile try to honor a few make command line flags'''
489     if not 'WAF_MAKE' in os.environ:
490         return
491     makeflags = os.environ.get('MAKEFLAGS')
492     if makeflags is None:
493         return
494     jobs_set = False
495     # we need to use shlex.split to cope with the escaping of spaces
496     # in makeflags
497     for opt in shlex.split(makeflags):
498         # options can come either as -x or as x
499         if opt[0:2] == 'V=':
500             Options.options.verbose = Logs.verbose = int(opt[2:])
501             if Logs.verbose > 0:
502                 Logs.zones = ['runner']
503             if Logs.verbose > 2:
504                 Logs.zones = ['*']
505         elif opt[0].isupper() and opt.find('=') != -1:
506             # this allows us to set waf options on the make command line
507             # for example, if you do "make FOO=blah", then we set the
508             # option 'FOO' in Options.options, to blah. If you look in wafsamba/wscript
509             # you will see that the command line accessible options have their dest=
510             # set to uppercase, to allow for passing of options from make in this way
511             # this is also how "make test TESTS=testpattern" works, and
512             # "make VERBOSE=1" as well as things like "make SYMBOLCHECK=1"
513             loc = opt.find('=')
514             setattr(Options.options, opt[0:loc], opt[loc+1:])
515         elif opt[0] != '-':
516             for v in opt:
517                 if v == 'j':
518                     jobs_set = True
519                 elif v == 'k':
520                     Options.options.keep = True
521         elif opt == '-j':
522             jobs_set = True
523         elif opt == '-k':
524             Options.options.keep = True
525     if not jobs_set:
526         # default to one job
527         Options.options.jobs = 1
528
529 Build.BuildContext.CHECK_MAKEFLAGS = CHECK_MAKEFLAGS
530
531 option_groups = {}
532
533 def option_group(opt, name):
534     '''find or create an option group'''
535     global option_groups
536     if name in option_groups:
537         return option_groups[name]
538     gr = opt.add_option_group(name)
539     option_groups[name] = gr
540     return gr
541 Options.OptionsContext.option_group = option_group
542
543
544 def save_file(filename, contents, create_dir=False):
545     '''save data to a file'''
546     if create_dir:
547         mkdir_p(os.path.dirname(filename))
548     try:
549         f = open(filename, 'w')
550         f.write(contents)
551         f.close()
552     except:
553         return False
554     return True
555
556
557 def load_file(filename):
558     '''return contents of a file'''
559     try:
560         f = open(filename, 'r')
561         r = f.read()
562         f.close()
563     except:
564         return None
565     return r
566
567
568 def reconfigure(ctx):
569     '''rerun configure if necessary'''
570     if not os.path.exists(".lock-wscript"):
571         raise Errors.WafError('configure has not been run')
572     import samba_wildcard
573     bld = samba_wildcard.fake_build_environment()
574     Configure.autoconfig = True
575     Scripting.check_configured(bld)
576
577
578 def map_shlib_extension(ctx, name, python=False):
579     '''map a filename with a shared library extension of .so to the real shlib name'''
580     if name is None:
581         return None
582     if name[-1:].isdigit():
583         # some libraries have specified versions in the wscript rule
584         return name
585     (root1, ext1) = os.path.splitext(name)
586     if python:
587         return ctx.env.pyext_PATTERN % root1
588     else:
589         (root2, ext2) = os.path.splitext(ctx.env.cshlib_PATTERN)
590     return root1+ext2
591 Build.BuildContext.map_shlib_extension = map_shlib_extension
592
593 def apply_pattern(filename, pattern):
594     '''apply a filename pattern to a filename that may have a directory component'''
595     dirname = os.path.dirname(filename)
596     if not dirname:
597         return pattern % filename
598     basename = os.path.basename(filename)
599     return os.path.join(dirname, pattern % basename)
600
601 def make_libname(ctx, name, nolibprefix=False, version=None, python=False):
602     """make a library filename
603          Options:
604               nolibprefix: don't include the lib prefix
605               version    : add a version number
606               python     : if we should use python module name conventions"""
607
608     if python:
609         libname = apply_pattern(name, ctx.env.pyext_PATTERN)
610     else:
611         libname = apply_pattern(name, ctx.env.cshlib_PATTERN)
612     if nolibprefix and libname[0:3] == 'lib':
613         libname = libname[3:]
614     if version:
615         if version[0] == '.':
616             version = version[1:]
617         (root, ext) = os.path.splitext(libname)
618         if ext == ".dylib":
619             # special case - version goes before the prefix
620             libname = "%s.%s%s" % (root, version, ext)
621         else:
622             libname = "%s%s.%s" % (root, ext, version)
623     return libname
624 Build.BuildContext.make_libname = make_libname
625
626
627 def get_tgt_list(bld):
628     '''return a list of build objects for samba'''
629
630     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
631
632     # build a list of task generators we are interested in
633     tgt_list = []
634     for tgt in targets:
635         type = targets[tgt]
636         if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
637             continue
638         t = bld.get_tgen_by_name(tgt)
639         if t is None:
640             Logs.error("Target %s of type %s has no task generator" % (tgt, type))
641             sys.exit(1)
642         tgt_list.append(t)
643     return tgt_list
644
645 from waflib.Context import WSCRIPT_FILE
646 def PROCESS_SEPARATE_RULE(self, rule):
647     ''' cause waf to process additional script based on `rule'.
648         You should have file named wscript_<stage>_rule in the current directory
649         where stage is either 'configure' or 'build'
650     '''
651     stage = ''
652     if isinstance(self, Configure.ConfigurationContext):
653         stage = 'configure'
654     elif isinstance(self, Build.BuildContext):
655         stage = 'build'
656     file_path = os.path.join(self.path.abspath(), WSCRIPT_FILE+'_'+stage+'_'+rule)
657     node = self.root.find_node(file_path)
658     if node:
659         try:
660             cache = self.recurse_cache
661         except AttributeError:
662             cache = self.recurse_cache = {}
663         if node not in cache:
664             cache[node] = True
665             self.pre_recurse(node)
666             try:
667                 function_code = node.read('rU', None)
668                 exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
669             finally:
670                 self.post_recurse(node)
671
672 Build.BuildContext.PROCESS_SEPARATE_RULE = PROCESS_SEPARATE_RULE
673 ConfigurationContext.PROCESS_SEPARATE_RULE = PROCESS_SEPARATE_RULE
674
675 def AD_DC_BUILD_IS_ENABLED(self):
676     if self.CONFIG_SET('AD_DC_BUILD_IS_ENABLED'):
677         return True
678     return False
679
680 Build.BuildContext.AD_DC_BUILD_IS_ENABLED = AD_DC_BUILD_IS_ENABLED
681
682 @feature('cprogram', 'cshlib', 'cstaticlib')
683 @after('apply_lib_vars')
684 @before('apply_obj_vars')
685 def samba_before_apply_obj_vars(self):
686     """before apply_obj_vars for uselib, this removes the standard paths"""
687
688     def is_standard_libpath(env, path):
689         for _path in env.STANDARD_LIBPATH:
690             if _path == os.path.normpath(path):
691                 return True
692         return False
693
694     v = self.env
695
696     for i in v['RPATH']:
697         if is_standard_libpath(v, i):
698             v['RPATH'].remove(i)
699
700     for i in v['LIBPATH']:
701         if is_standard_libpath(v, i):
702             v['LIBPATH'].remove(i)
703
704 def samba_add_onoff_option(opt, option, help=(), dest=None, default=True,
705                            with_name="with", without_name="without"):
706     if default is None:
707         default_str = "auto"
708     elif default is True:
709         default_str = "yes"
710     elif default is False:
711         default_str = "no"
712     else:
713         default_str = str(default)
714
715     if help == ():
716         help = ("Build with %s support (default=%s)" % (option, default_str))
717     if dest is None:
718         dest = "with_%s" % option.replace('-', '_')
719
720     with_val = "--%s-%s" % (with_name, option)
721     without_val = "--%s-%s" % (without_name, option)
722
723     opt.add_option(with_val, help=help, action="store_true", dest=dest,
724                    default=default)
725     opt.add_option(without_val, help=SUPPRESS_HELP, action="store_false",
726                    dest=dest)
727 Options.OptionsContext.samba_add_onoff_option = samba_add_onoff_option