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