Options.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Scott Newton, 2005 (scottn)
  4. # Thomas Nagy, 2006-2018 (ita)
  5. """
  6. Support for waf command-line options
  7. Provides default and command-line options, as well the command
  8. that reads the ``options`` wscript function.
  9. """
  10. import os, tempfile, argparse, sys, re
  11. from waflib import Logs, Utils, Context, Errors
  12. class OptionValues:
  13. def __str__(self):
  14. return str(self.__dict__)
  15. options = OptionValues()
  16. """
  17. A global dictionary representing user-provided command-line options::
  18. $ waf --foo=bar
  19. """
  20. commands = []
  21. """
  22. List of commands to execute extracted from the command-line. This list
  23. is consumed during the execution by :py:func:`waflib.Scripting.run_commands`.
  24. """
  25. lockfile = os.environ.get('WAFLOCK', '.lock-waf_%s_build' % sys.platform)
  26. """
  27. Name of the lock file that marks a project as configured
  28. """
  29. class ArgParser(argparse.ArgumentParser):
  30. """
  31. Command-line options parser.
  32. """
  33. def __init__(self, ctx):
  34. argparse.ArgumentParser.__init__(self, add_help=False, conflict_handler='resolve')
  35. self.ctx = ctx
  36. def format_help(self):
  37. self.usage = self.get_usage()
  38. return super(ArgParser, self).format_help()
  39. def format_usage(self):
  40. return self.format_help()
  41. def _get_formatter(self):
  42. """Initialize the argument parser to the adequate terminal width"""
  43. return self.formatter_class(prog=self.prog, width=Logs.get_term_cols())
  44. def get_option(self, name):
  45. if name in self._option_string_actions:
  46. return self._option_string_actions[name]
  47. def remove_option(self, name):
  48. if name in self._option_string_actions:
  49. action = self._option_string_actions[name]
  50. self._remove_action(action)
  51. action.option_strings.remove(name)
  52. self._option_string_actions.pop(name, None)
  53. for group in self._action_groups:
  54. try:
  55. group._group_actions.remove(action)
  56. except ValueError:
  57. pass
  58. def get_usage(self):
  59. """
  60. Builds the message to print on ``waf --help``
  61. :rtype: string
  62. """
  63. cmds_str = {}
  64. for cls in Context.classes:
  65. if not cls.cmd or cls.cmd == 'options' or cls.cmd.startswith( '_' ):
  66. continue
  67. s = cls.__doc__ or ''
  68. cmds_str[cls.cmd] = s
  69. if Context.g_module:
  70. for (k, v) in Context.g_module.__dict__.items():
  71. if k in ('options', 'init', 'shutdown'):
  72. continue
  73. if type(v) is type(Context.create_context):
  74. if v.__doc__ and len(v.__doc__.splitlines()) < 3 and not k.startswith('_'):
  75. cmds_str[k] = v.__doc__
  76. just = 0
  77. for k in cmds_str:
  78. just = max(just, len(k))
  79. lst = [' %s: %s' % (k.ljust(just), v) for (k, v) in cmds_str.items()]
  80. lst.sort()
  81. ret = '\n'.join(lst)
  82. return '''%s [commands] [options]
  83. Main commands (example: ./%s build -j4)
  84. %s
  85. ''' % (Context.WAFNAME, Context.WAFNAME, ret)
  86. class OptionsContext(Context.Context):
  87. """
  88. Collects custom options from wscript files and parses the command line.
  89. Sets the global :py:const:`waflib.Options.commands` and :py:const:`waflib.Options.options` values.
  90. """
  91. cmd = 'options'
  92. fun = 'options'
  93. def __init__(self, **kw):
  94. super(OptionsContext, self).__init__(**kw)
  95. self.parser = ArgParser(self)
  96. """Instance of :py:class:`waflib.Options.ArgParser`"""
  97. self.option_groups = {}
  98. jobs = self.jobs()
  99. p = self.add_option
  100. color = os.environ.get('NOCOLOR', '') and 'no' or 'auto'
  101. if os.environ.get('CLICOLOR', '') == '0':
  102. color = 'no'
  103. elif os.environ.get('CLICOLOR_FORCE', '') == '1':
  104. color = 'yes'
  105. p('-c', '--color', dest='colors', default=color, action='store', help='whether to use colors (yes/no/auto) [default: auto]', choices=('yes', 'no', 'auto'))
  106. p('-j', '--jobs', dest='jobs', default=jobs, type=int, help='amount of parallel jobs (%r)' % jobs)
  107. p('-k', '--keep', dest='keep', default=0, action='count', help='continue despite errors (-kk to try harder)')
  108. p('-v', '--verbose', dest='verbose', default=0, action='count', help='verbosity level -v -vv or -vvv [default: 0]')
  109. p('--zones', dest='zones', default='', action='store', help='debugging zones (task_gen, deps, tasks, etc)')
  110. p('--profile', dest='profile', default=0, action='store_true', help=argparse.SUPPRESS)
  111. p('--pdb', dest='pdb', default=0, action='store_true', help=argparse.SUPPRESS)
  112. p('-h', '--help', dest='whelp', default=0, action='store_true', help='show this help message and exit')
  113. p('--version', dest='version', default=False, action='store_true', help='show the Waf version and exit')
  114. gr = self.add_option_group('Configuration options')
  115. gr.add_option('-o', '--out', action='store', default='', help='build dir for the project', dest='out')
  116. gr.add_option('-t', '--top', action='store', default='', help='src dir for the project', dest='top')
  117. gr.add_option('--no-lock-in-run', action='store_true', default=os.environ.get('NO_LOCK_IN_RUN', ''), help=argparse.SUPPRESS, dest='no_lock_in_run')
  118. gr.add_option('--no-lock-in-out', action='store_true', default=os.environ.get('NO_LOCK_IN_OUT', ''), help=argparse.SUPPRESS, dest='no_lock_in_out')
  119. gr.add_option('--no-lock-in-top', action='store_true', default=os.environ.get('NO_LOCK_IN_TOP', ''), help=argparse.SUPPRESS, dest='no_lock_in_top')
  120. default_prefix = getattr(Context.g_module, 'default_prefix', os.environ.get('PREFIX'))
  121. if not default_prefix:
  122. if Utils.unversioned_sys_platform() == 'win32':
  123. d = tempfile.gettempdir()
  124. default_prefix = d[0].upper() + d[1:]
  125. # win32 preserves the case, but gettempdir does not
  126. else:
  127. default_prefix = '/usr/local/'
  128. gr.add_option('--prefix', dest='prefix', default=default_prefix, help='installation prefix [default: %r]' % default_prefix)
  129. gr.add_option('--bindir', dest='bindir', help='bindir')
  130. gr.add_option('--libdir', dest='libdir', help='libdir')
  131. gr = self.add_option_group('Build and installation options')
  132. gr.add_option('-p', '--progress', dest='progress_bar', default=0, action='count', help= '-p: progress bar; -pp: ide output')
  133. gr.add_option('--targets', dest='targets', default='', action='store', help='task generators, e.g. "target1,target2"')
  134. gr = self.add_option_group('Step options')
  135. gr.add_option('--files', dest='files', default='', action='store', help='files to process, by regexp, e.g. "*/main.c,*/test/main.o"')
  136. default_destdir = os.environ.get('DESTDIR', '')
  137. gr = self.add_option_group('Installation and uninstallation options')
  138. gr.add_option('--destdir', help='installation root [default: %r]' % default_destdir, default=default_destdir, dest='destdir')
  139. gr.add_option('-f', '--force', dest='force', default=False, action='store_true', help='disable file installation caching')
  140. gr.add_option('--distcheck-args', metavar='ARGS', help='arguments to pass to distcheck', default=None, action='store')
  141. def jobs(self):
  142. """
  143. Finds the optimal amount of cpu cores to use for parallel jobs.
  144. At runtime the options can be obtained from :py:const:`waflib.Options.options` ::
  145. from waflib.Options import options
  146. njobs = options.jobs
  147. :return: the amount of cpu cores
  148. :rtype: int
  149. """
  150. count = int(os.environ.get('JOBS', 0))
  151. if count < 1:
  152. if 'NUMBER_OF_PROCESSORS' in os.environ:
  153. # on Windows, use the NUMBER_OF_PROCESSORS environment variable
  154. count = int(os.environ.get('NUMBER_OF_PROCESSORS', 1))
  155. else:
  156. # on everything else, first try the POSIX sysconf values
  157. if hasattr(os, 'sysconf_names'):
  158. if 'SC_NPROCESSORS_ONLN' in os.sysconf_names:
  159. count = int(os.sysconf('SC_NPROCESSORS_ONLN'))
  160. elif 'SC_NPROCESSORS_CONF' in os.sysconf_names:
  161. count = int(os.sysconf('SC_NPROCESSORS_CONF'))
  162. if not count and os.name not in ('nt', 'java'):
  163. try:
  164. tmp = self.cmd_and_log(['sysctl', '-n', 'hw.ncpu'], quiet=0)
  165. except Errors.WafError:
  166. pass
  167. else:
  168. if re.match('^[0-9]+$', tmp):
  169. count = int(tmp)
  170. if count < 1:
  171. count = 1
  172. elif count > 1024:
  173. count = 1024
  174. return count
  175. def add_option(self, *k, **kw):
  176. if 'type' in kw and type(kw['type']) == str:
  177. Logs.warn('Invalid "type=str" in add_option (must be a class, not a string)')
  178. if kw['type'] == 'int':
  179. kw['type'] = int
  180. elif kw['type'] == 'string':
  181. kw['type'] = str
  182. return self.add_argument(*k, **kw)
  183. def add_argument(self, *k, **kw):
  184. """
  185. Wraps ``argparse.add_argument``::
  186. def options(ctx):
  187. ctx.add_option('-u', '--use', dest='use', default=False,
  188. action='store_true', help='a boolean option')
  189. :rtype: argparse option object
  190. """
  191. return self.parser.add_argument(*k, **kw)
  192. def add_option_group(self, *k, **kw):
  193. """
  194. Wraps ``optparse.add_option_group``::
  195. def options(ctx):
  196. gr = ctx.add_option_group('some options')
  197. gr.add_option('-u', '--use', dest='use', default=False, action='store_true')
  198. :rtype: optparse option group object
  199. """
  200. gr = self.get_option_group(k[0])
  201. if not gr:
  202. gr = self.parser.add_argument_group(*k, **kw)
  203. gr.add_option = gr.add_argument
  204. self.option_groups[k[0]] = gr
  205. return gr
  206. def get_option_group(self, opt_str):
  207. """
  208. Wraps ``optparse.get_option_group``::
  209. def options(ctx):
  210. gr = ctx.get_option_group('configure options')
  211. gr.add_option('-o', '--out', action='store', default='',
  212. help='build dir for the project', dest='out')
  213. :rtype: optparse option group object
  214. """
  215. try:
  216. return self.option_groups[opt_str]
  217. except KeyError:
  218. for group in self.parser._action_groups:
  219. if group.title == opt_str:
  220. return group
  221. return None
  222. def sanitize_path(self, path, cwd=None):
  223. if not cwd:
  224. cwd = Context.launch_dir
  225. p = os.path.expanduser(path)
  226. p = os.path.join(cwd, p)
  227. p = os.path.normpath(p)
  228. p = os.path.abspath(p)
  229. return p
  230. def parse_cmd_args(self, _args=None, cwd=None, allow_unknown=False):
  231. """
  232. Just parse the arguments
  233. """
  234. (options, leftover_args) = self.parser.parse_known_args(args=_args)
  235. commands = []
  236. for arg in leftover_args:
  237. if not allow_unknown and arg.startswith('-'):
  238. self.parser.print_help()
  239. raise Errors.WafError('Unknown option: %r' % arg)
  240. commands.append(arg)
  241. if options.jobs < 1:
  242. options.jobs = 1
  243. for name in 'top out destdir prefix bindir libdir'.split():
  244. # those paths are usually expanded from Context.launch_dir
  245. if getattr(options, name, None):
  246. path = self.sanitize_path(getattr(options, name), cwd)
  247. setattr(options, name, path)
  248. return options, commands
  249. def init_logs(self, options, commands):
  250. Logs.verbose = options.verbose
  251. if options.verbose >= 1:
  252. self.load('errcheck')
  253. colors = {'yes' : 2, 'auto' : 1, 'no' : 0}[options.colors]
  254. Logs.enable_colors(colors)
  255. if options.zones:
  256. Logs.zones = options.zones.split(',')
  257. if not Logs.verbose:
  258. Logs.verbose = 1
  259. elif Logs.verbose > 0:
  260. Logs.zones = ['runner']
  261. if Logs.verbose > 2:
  262. Logs.zones = ['*']
  263. def parse_args(self, _args=None):
  264. """
  265. Parses arguments from a list which is not necessarily the command-line.
  266. Initializes the module variables options and commands
  267. If help is requested, prints it and exit the application
  268. :param _args: arguments
  269. :type _args: list of strings
  270. """
  271. arg_options, arg_commands = self.parse_cmd_args(_args)
  272. self.init_logs(arg_options, commands)
  273. options.__dict__.clear()
  274. del commands[:]
  275. options.__dict__.update(arg_options.__dict__)
  276. commands.extend(arg_commands)
  277. def execute(self):
  278. """
  279. See :py:func:`waflib.Context.Context.execute`
  280. """
  281. super(OptionsContext, self).execute()
  282. self.parse_args()
  283. Utils.alloc_process_pool(options.jobs)