1#! /usr/bin/env python
2# encoding: utf-8
3# Thomas Nagy, 2011 (ita)
4
5"""
6Common mistakes highlighting.
7
8There is a performance impact, so this tool is only loaded when running ``waf -v``
9"""
10
11typos = {
12'feature':'features',
13'sources':'source',
14'targets':'target',
15'include':'includes',
16'export_include':'export_includes',
17'define':'defines',
18'importpath':'includes',
19'installpath':'install_path',
20'iscopy':'is_copy',
21'uses':'use',
22}
23
24meths_typos = ['__call__', 'program', 'shlib', 'stlib', 'objects']
25
26import sys
27from waflib import Logs, Build, Node, Task, TaskGen, ConfigSet, Errors, Utils
28from waflib.Tools import ccroot
29
30def check_same_targets(self):
31	mp = Utils.defaultdict(list)
32	uids = {}
33
34	def check_task(tsk):
35		if not isinstance(tsk, Task.Task):
36			return
37		if hasattr(tsk, 'no_errcheck_out'):
38			return
39
40		for node in tsk.outputs:
41			mp[node].append(tsk)
42		try:
43			uids[tsk.uid()].append(tsk)
44		except KeyError:
45			uids[tsk.uid()] = [tsk]
46
47	for g in self.groups:
48		for tg in g:
49			try:
50				for tsk in tg.tasks:
51					check_task(tsk)
52			except AttributeError:
53				# raised if not a task generator, which should be uncommon
54				check_task(tg)
55
56	dupe = False
57	for (k, v) in mp.items():
58		if len(v) > 1:
59			dupe = True
60			msg = '* Node %r is created more than once%s. The task generators are:' % (k, Logs.verbose == 1 and " (full message on 'waf -v -v')" or "")
61			Logs.error(msg)
62			for x in v:
63				if Logs.verbose > 1:
64					Logs.error('  %d. %r', 1 + v.index(x), x.generator)
65				else:
66					Logs.error('  %d. %r in %r', 1 + v.index(x), x.generator.name, getattr(x.generator, 'path', None))
67			Logs.error('If you think that this is an error, set no_errcheck_out on the task instance')
68
69	if not dupe:
70		for (k, v) in uids.items():
71			if len(v) > 1:
72				Logs.error('* Several tasks use the same identifier. Please check the information on\n   https://waf.io/apidocs/Task.html?highlight=uid#waflib.Task.Task.uid')
73				tg_details = tsk.generator.name
74				if Logs.verbose > 2:
75					tg_details = tsk.generator
76				for tsk in v:
77					Logs.error('  - object %r (%r) defined in %r', tsk.__class__.__name__, tsk, tg_details)
78
79def check_invalid_constraints(self):
80	feat = set()
81	for x in list(TaskGen.feats.values()):
82		feat.union(set(x))
83	for (x, y) in TaskGen.task_gen.prec.items():
84		feat.add(x)
85		feat.union(set(y))
86	ext = set()
87	for x in TaskGen.task_gen.mappings.values():
88		ext.add(x.__name__)
89	invalid = ext & feat
90	if invalid:
91		Logs.error('The methods %r have invalid annotations:  @extension <-> @feature/@before_method/@after_method', list(invalid))
92
93	# the build scripts have been read, so we can check for invalid after/before attributes on task classes
94	for cls in list(Task.classes.values()):
95		if sys.hexversion > 0x3000000 and issubclass(cls, Task.Task) and isinstance(cls.hcode, str):
96			raise Errors.WafError('Class %r has hcode value %r of type <str>, expecting <bytes> (use Utils.h_cmd() ?)' % (cls, cls.hcode))
97
98		for x in ('before', 'after'):
99			for y in Utils.to_list(getattr(cls, x, [])):
100				if not Task.classes.get(y):
101					Logs.error('Erroneous order constraint %r=%r on task class %r', x, y, cls.__name__)
102		if getattr(cls, 'rule', None):
103			Logs.error('Erroneous attribute "rule" on task class %r (rename to "run_str")', cls.__name__)
104
105def replace(m):
106	"""
107	Replaces existing BuildContext methods to verify parameter names,
108	for example ``bld(source=)`` has no ending *s*
109	"""
110	oldcall = getattr(Build.BuildContext, m)
111	def call(self, *k, **kw):
112		ret = oldcall(self, *k, **kw)
113		for x in typos:
114			if x in kw:
115				if x == 'iscopy' and 'subst' in getattr(self, 'features', ''):
116					continue
117				Logs.error('Fix the typo %r -> %r on %r', x, typos[x], ret)
118		return ret
119	setattr(Build.BuildContext, m, call)
120
121def enhance_lib():
122	"""
123	Modifies existing classes and methods to enable error verification
124	"""
125	for m in meths_typos:
126		replace(m)
127
128	# catch '..' in ant_glob patterns
129	def ant_glob(self, *k, **kw):
130		if k:
131			lst = Utils.to_list(k[0])
132			for pat in lst:
133				sp = pat.split('/')
134				if '..' in sp:
135					Logs.error("In ant_glob pattern %r: '..' means 'two dots', not 'parent directory'", k[0])
136				if '.' in sp:
137					Logs.error("In ant_glob pattern %r: '.' means 'one dot', not 'current directory'", k[0])
138		return self.old_ant_glob(*k, **kw)
139	Node.Node.old_ant_glob = Node.Node.ant_glob
140	Node.Node.ant_glob = ant_glob
141
142	# catch ant_glob on build folders
143	def ant_iter(self, accept=None, maxdepth=25, pats=[], dir=False, src=True, remove=True, quiet=False):
144		if remove:
145			try:
146				if self.is_child_of(self.ctx.bldnode) and not quiet:
147					quiet = True
148					Logs.error('Calling ant_glob on build folders (%r) is dangerous: add quiet=True / remove=False', self)
149			except AttributeError:
150				pass
151		return self.old_ant_iter(accept, maxdepth, pats, dir, src, remove, quiet)
152	Node.Node.old_ant_iter = Node.Node.ant_iter
153	Node.Node.ant_iter = ant_iter
154
155	# catch conflicting ext_in/ext_out/before/after declarations
156	old = Task.is_before
157	def is_before(t1, t2):
158		ret = old(t1, t2)
159		if ret and old(t2, t1):
160			Logs.error('Contradictory order constraints in classes %r %r', t1, t2)
161		return ret
162	Task.is_before = is_before
163
164	# check for bld(feature='cshlib') where no 'c' is given - this can be either a mistake or on purpose
165	# so we only issue a warning
166	def check_err_features(self):
167		lst = self.to_list(self.features)
168		if 'shlib' in lst:
169			Logs.error('feature shlib -> cshlib, dshlib or cxxshlib')
170		for x in ('c', 'cxx', 'd', 'fc'):
171			if not x in lst and lst and lst[0] in [x+y for y in ('program', 'shlib', 'stlib')]:
172				Logs.error('%r features is probably missing %r', self, x)
173	TaskGen.feature('*')(check_err_features)
174
175	# check for erroneous order constraints
176	def check_err_order(self):
177		if not hasattr(self, 'rule') and not 'subst' in Utils.to_list(self.features):
178			for x in ('before', 'after', 'ext_in', 'ext_out'):
179				if hasattr(self, x):
180					Logs.warn('Erroneous order constraint %r on non-rule based task generator %r', x, self)
181		else:
182			for x in ('before', 'after'):
183				for y in self.to_list(getattr(self, x, [])):
184					if not Task.classes.get(y):
185						Logs.error('Erroneous order constraint %s=%r on %r (no such class)', x, y, self)
186	TaskGen.feature('*')(check_err_order)
187
188	# check for @extension used with @feature/@before_method/@after_method
189	def check_compile(self):
190		check_invalid_constraints(self)
191		try:
192			ret = self.orig_compile()
193		finally:
194			check_same_targets(self)
195		return ret
196	Build.BuildContext.orig_compile = Build.BuildContext.compile
197	Build.BuildContext.compile = check_compile
198
199	# check for invalid build groups #914
200	def use_rec(self, name, **kw):
201		try:
202			y = self.bld.get_tgen_by_name(name)
203		except Errors.WafError:
204			pass
205		else:
206			idx = self.bld.get_group_idx(self)
207			odx = self.bld.get_group_idx(y)
208			if odx > idx:
209				msg = "Invalid 'use' across build groups:"
210				if Logs.verbose > 1:
211					msg += '\n  target %r\n  uses:\n  %r' % (self, y)
212				else:
213					msg += " %r uses %r (try 'waf -v -v' for the full error)" % (self.name, name)
214				raise Errors.WafError(msg)
215		self.orig_use_rec(name, **kw)
216	TaskGen.task_gen.orig_use_rec = TaskGen.task_gen.use_rec
217	TaskGen.task_gen.use_rec = use_rec
218
219	# check for env.append
220	def _getattr(self, name, default=None):
221		if name == 'append' or name == 'add':
222			raise Errors.WafError('env.append and env.add do not exist: use env.append_value/env.append_unique')
223		elif name == 'prepend':
224			raise Errors.WafError('env.prepend does not exist: use env.prepend_value')
225		if name in self.__slots__:
226			return super(ConfigSet.ConfigSet, self).__getattr__(name, default)
227		else:
228			return self[name]
229	ConfigSet.ConfigSet.__getattr__ = _getattr
230
231
232def options(opt):
233	"""
234	Error verification can be enabled by default (not just on ``waf -v``) by adding to the user script options
235	"""
236	enhance_lib()
237
238