waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

errcheck.py (7826B)


      1 #! /usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2011 (ita)
      4 
      5 """
      6 Common mistakes highlighting.
      7 
      8 There is a performance impact, so this tool is only loaded when running ``waf -v``
      9 """
     10 
     11 typos = {
     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 
     24 meths_typos = ['__call__', 'program', 'shlib', 'stlib', 'objects']
     25 
     26 import sys
     27 from waflib import Logs, Build, Node, Task, TaskGen, ConfigSet, Errors, Utils
     28 from waflib.Tools import ccroot
     29 
     30 def 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 
     79 def 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 
    105 def 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 
    121 def 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 
    232 def 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