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