errcheck.py 7.6 KB

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