jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

861 lines
24KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2010 (ita)
  4. """
  5. Task generators
  6. The class :py:class:`waflib.TaskGen.task_gen` encapsulates the creation of task objects (low-level code)
  7. The instances can have various parameters, but the creation of task nodes (Task.py)
  8. is always postponed. To achieve this, various methods are called from the method "apply"
  9. """
  10. import copy, re, os
  11. from waflib import Task, Utils, Logs, Errors, ConfigSet, Node
  12. feats = Utils.defaultdict(set)
  13. """remember the methods declaring features"""
  14. HEADER_EXTS = ['.h', '.hpp', '.hxx', '.hh']
  15. class task_gen(object):
  16. """
  17. Instances of this class create :py:class:`waflib.Task.TaskBase` when
  18. calling the method :py:meth:`waflib.TaskGen.task_gen.post` from the main thread.
  19. A few notes:
  20. * The methods to call (*self.meths*) can be specified dynamically (removing, adding, ..)
  21. * The 'features' are used to add methods to self.meths and then execute them
  22. * The attribute 'path' is a node representing the location of the task generator
  23. * The tasks created are added to the attribute *tasks*
  24. * The attribute 'idx' is a counter of task generators in the same path
  25. """
  26. mappings = Utils.ordered_iter_dict()
  27. """Mappings are global file extension mappings, they are retrieved in the order of definition"""
  28. prec = Utils.defaultdict(list)
  29. """Dict holding the precedence rules for task generator methods"""
  30. def __init__(self, *k, **kw):
  31. """
  32. The task generator objects predefine various attributes (source, target) for possible
  33. processing by process_rule (make-like rules) or process_source (extensions, misc methods)
  34. The tasks are stored on the attribute 'tasks'. They are created by calling methods
  35. listed in self.meths *or* referenced in the attribute features
  36. A topological sort is performed to ease the method re-use.
  37. The extra key/value elements passed in kw are set as attributes
  38. """
  39. # so we will have to play with directed acyclic graphs
  40. # detect cycles, etc
  41. self.source = ''
  42. self.target = ''
  43. self.meths = []
  44. """
  45. List of method names to execute (it is usually a good idea to avoid touching this)
  46. """
  47. self.prec = Utils.defaultdict(list)
  48. """
  49. Precedence table for sorting the methods in self.meths
  50. """
  51. self.mappings = {}
  52. """
  53. List of mappings {extension -> function} for processing files by extension
  54. This is very rarely used, so we do not use an ordered dict here
  55. """
  56. self.features = []
  57. """
  58. List of feature names for bringing new methods in
  59. """
  60. self.tasks = []
  61. """
  62. List of tasks created.
  63. """
  64. if not 'bld' in kw:
  65. # task generators without a build context :-/
  66. self.env = ConfigSet.ConfigSet()
  67. self.idx = 0
  68. self.path = None
  69. else:
  70. self.bld = kw['bld']
  71. self.env = self.bld.env.derive()
  72. self.path = self.bld.path # emulate chdir when reading scripts
  73. # provide a unique id
  74. try:
  75. self.idx = self.bld.idx[id(self.path)] = self.bld.idx.get(id(self.path), 0) + 1
  76. except AttributeError:
  77. self.bld.idx = {}
  78. self.idx = self.bld.idx[id(self.path)] = 1
  79. for key, val in kw.items():
  80. setattr(self, key, val)
  81. def __str__(self):
  82. """for debugging purposes"""
  83. return "<task_gen %r declared in %s>" % (self.name, self.path.abspath())
  84. def __repr__(self):
  85. """for debugging purposes"""
  86. lst = []
  87. for x in self.__dict__.keys():
  88. if x not in ('env', 'bld', 'compiled_tasks', 'tasks'):
  89. lst.append("%s=%s" % (x, repr(getattr(self, x))))
  90. return "bld(%s) in %s" % (", ".join(lst), self.path.abspath())
  91. def get_name(self):
  92. """
  93. If not set, the name is computed from the target name::
  94. def build(bld):
  95. x = bld(name='foo')
  96. x.get_name() # foo
  97. y = bld(target='bar')
  98. y.get_name() # bar
  99. :rtype: string
  100. :return: name of this task generator
  101. """
  102. try:
  103. return self._name
  104. except AttributeError:
  105. if isinstance(self.target, list):
  106. lst = [str(x) for x in self.target]
  107. name = self._name = ','.join(lst)
  108. else:
  109. name = self._name = str(self.target)
  110. return name
  111. def set_name(self, name):
  112. self._name = name
  113. name = property(get_name, set_name)
  114. def to_list(self, val):
  115. """
  116. Ensure that a parameter is a list
  117. :type val: string or list of string
  118. :param val: input to return as a list
  119. :rtype: list
  120. """
  121. if isinstance(val, str): return val.split()
  122. else: return val
  123. def post(self):
  124. """
  125. Create task objects. The following operations are performed:
  126. #. The body of this method is called only once and sets the attribute ``posted``
  127. #. The attribute ``features`` is used to add more methods in ``self.meths``
  128. #. The methods are sorted by the precedence table ``self.prec`` or `:waflib:attr:waflib.TaskGen.task_gen.prec`
  129. #. The methods are then executed in order
  130. #. The tasks created are added to :py:attr:`waflib.TaskGen.task_gen.tasks`
  131. """
  132. # we could add a decorator to let the task run once, but then python 2.3 will be difficult to support
  133. if getattr(self, 'posted', None):
  134. #error("OBJECT ALREADY POSTED" + str( self))
  135. return False
  136. self.posted = True
  137. keys = set(self.meths)
  138. # add the methods listed in the features
  139. self.features = Utils.to_list(self.features)
  140. for x in self.features + ['*']:
  141. st = feats[x]
  142. if not st:
  143. if not x in Task.classes:
  144. Logs.warn('feature %r does not exist - bind at least one method to it' % x)
  145. keys.update(list(st)) # ironpython 2.7 wants the cast to list
  146. # copy the precedence table
  147. prec = {}
  148. prec_tbl = self.prec or task_gen.prec
  149. for x in prec_tbl:
  150. if x in keys:
  151. prec[x] = prec_tbl[x]
  152. # elements disconnected
  153. tmp = []
  154. for a in keys:
  155. for x in prec.values():
  156. if a in x: break
  157. else:
  158. tmp.append(a)
  159. tmp.sort()
  160. # topological sort
  161. out = []
  162. while tmp:
  163. e = tmp.pop()
  164. if e in keys: out.append(e)
  165. try:
  166. nlst = prec[e]
  167. except KeyError:
  168. pass
  169. else:
  170. del prec[e]
  171. for x in nlst:
  172. for y in prec:
  173. if x in prec[y]:
  174. break
  175. else:
  176. tmp.append(x)
  177. if prec:
  178. txt = '\n'.join(['- %s after %s' % (k, repr(v)) for k, v in prec.items()])
  179. raise Errors.WafError('Cycle detected in the method execution\n%s' % txt)
  180. out.reverse()
  181. self.meths = out
  182. # then we run the methods in order
  183. Logs.debug('task_gen: posting %s %d' % (self, id(self)))
  184. for x in out:
  185. try:
  186. v = getattr(self, x)
  187. except AttributeError:
  188. raise Errors.WafError('%r is not a valid task generator method' % x)
  189. Logs.debug('task_gen: -> %s (%d)' % (x, id(self)))
  190. v()
  191. Logs.debug('task_gen: posted %s' % self.name)
  192. return True
  193. def get_hook(self, node):
  194. """
  195. :param node: Input file to process
  196. :type node: :py:class:`waflib.Tools.Node.Node`
  197. :return: A method able to process the input node by looking at the extension
  198. :rtype: function
  199. """
  200. name = node.name
  201. if self.mappings:
  202. for k in self.mappings:
  203. if name.endswith(k):
  204. return self.mappings[k]
  205. for k in task_gen.mappings:
  206. if name.endswith(k):
  207. return task_gen.mappings[k]
  208. raise Errors.WafError("File %r has no mapping in %r (have you forgotten to load a waf tool?)" % (node, task_gen.mappings.keys()))
  209. def create_task(self, name, src=None, tgt=None, **kw):
  210. """
  211. Wrapper for creating task instances. The classes are retrieved from the
  212. context class if possible, then from the global dict Task.classes.
  213. :param name: task class name
  214. :type name: string
  215. :param src: input nodes
  216. :type src: list of :py:class:`waflib.Tools.Node.Node`
  217. :param tgt: output nodes
  218. :type tgt: list of :py:class:`waflib.Tools.Node.Node`
  219. :return: A task object
  220. :rtype: :py:class:`waflib.Task.TaskBase`
  221. """
  222. task = Task.classes[name](env=self.env.derive(), generator=self)
  223. if src:
  224. task.set_inputs(src)
  225. if tgt:
  226. task.set_outputs(tgt)
  227. task.__dict__.update(kw)
  228. self.tasks.append(task)
  229. return task
  230. def clone(self, env):
  231. """
  232. Make a copy of a task generator. Once the copy is made, it is necessary to ensure that the
  233. it does not create the same output files as the original, or the same files may
  234. be compiled several times.
  235. :param env: A configuration set
  236. :type env: :py:class:`waflib.ConfigSet.ConfigSet`
  237. :return: A copy
  238. :rtype: :py:class:`waflib.TaskGen.task_gen`
  239. """
  240. newobj = self.bld()
  241. for x in self.__dict__:
  242. if x in ('env', 'bld'):
  243. continue
  244. elif x in ('path', 'features'):
  245. setattr(newobj, x, getattr(self, x))
  246. else:
  247. setattr(newobj, x, copy.copy(getattr(self, x)))
  248. newobj.posted = False
  249. if isinstance(env, str):
  250. newobj.env = self.bld.all_envs[env].derive()
  251. else:
  252. newobj.env = env.derive()
  253. return newobj
  254. def declare_chain(name='', rule=None, reentrant=None, color='BLUE',
  255. ext_in=[], ext_out=[], before=[], after=[], decider=None, scan=None, install_path=None, shell=False):
  256. """
  257. Create a new mapping and a task class for processing files by extension.
  258. See Tools/flex.py for an example.
  259. :param name: name for the task class
  260. :type name: string
  261. :param rule: function to execute or string to be compiled in a function
  262. :type rule: string or function
  263. :param reentrant: re-inject the output file in the process (done automatically, set to 0 to disable)
  264. :type reentrant: int
  265. :param color: color for the task output
  266. :type color: string
  267. :param ext_in: execute the task only after the files of such extensions are created
  268. :type ext_in: list of string
  269. :param ext_out: execute the task only before files of such extensions are processed
  270. :type ext_out: list of string
  271. :param before: execute instances of this task before classes of the given names
  272. :type before: list of string
  273. :param after: execute instances of this task after classes of the given names
  274. :type after: list of string
  275. :param decider: if present, use it to create the output nodes for the task
  276. :type decider: function
  277. :param scan: scanner function for the task
  278. :type scan: function
  279. :param install_path: installation path for the output nodes
  280. :type install_path: string
  281. """
  282. ext_in = Utils.to_list(ext_in)
  283. ext_out = Utils.to_list(ext_out)
  284. if not name:
  285. name = rule
  286. cls = Task.task_factory(name, rule, color=color, ext_in=ext_in, ext_out=ext_out, before=before, after=after, scan=scan, shell=shell)
  287. def x_file(self, node):
  288. ext = decider and decider(self, node) or cls.ext_out
  289. if ext_in:
  290. _ext_in = ext_in[0]
  291. tsk = self.create_task(name, node)
  292. cnt = 0
  293. keys = set(self.mappings.keys()) | set(self.__class__.mappings.keys())
  294. for x in ext:
  295. k = node.change_ext(x, ext_in=_ext_in)
  296. tsk.outputs.append(k)
  297. if reentrant != None:
  298. if cnt < int(reentrant):
  299. self.source.append(k)
  300. else:
  301. # reinject downstream files into the build
  302. for y in keys: # ~ nfile * nextensions :-/
  303. if k.name.endswith(y):
  304. self.source.append(k)
  305. break
  306. cnt += 1
  307. if install_path:
  308. self.bld.install_files(install_path, tsk.outputs)
  309. return tsk
  310. for x in cls.ext_in:
  311. task_gen.mappings[x] = x_file
  312. return x_file
  313. def taskgen_method(func):
  314. """
  315. Decorator: register a method as a task generator method.
  316. The function must accept a task generator as first parameter::
  317. from waflib.TaskGen import taskgen_method
  318. @taskgen_method
  319. def mymethod(self):
  320. pass
  321. :param func: task generator method to add
  322. :type func: function
  323. :rtype: function
  324. """
  325. setattr(task_gen, func.__name__, func)
  326. return func
  327. def feature(*k):
  328. """
  329. Decorator: register a task generator method that will be executed when the
  330. object attribute 'feature' contains the corresponding key(s)::
  331. from waflib.Task import feature
  332. @feature('myfeature')
  333. def myfunction(self):
  334. print('that is my feature!')
  335. def build(bld):
  336. bld(features='myfeature')
  337. :param k: feature names
  338. :type k: list of string
  339. """
  340. def deco(func):
  341. setattr(task_gen, func.__name__, func)
  342. for name in k:
  343. feats[name].update([func.__name__])
  344. return func
  345. return deco
  346. def before_method(*k):
  347. """
  348. Decorator: register a task generator method which will be executed
  349. before the functions of given name(s)::
  350. from waflib.TaskGen import feature, before
  351. @feature('myfeature')
  352. @before_method('fun2')
  353. def fun1(self):
  354. print('feature 1!')
  355. @feature('myfeature')
  356. def fun2(self):
  357. print('feature 2!')
  358. def build(bld):
  359. bld(features='myfeature')
  360. :param k: method names
  361. :type k: list of string
  362. """
  363. def deco(func):
  364. setattr(task_gen, func.__name__, func)
  365. for fun_name in k:
  366. if not func.__name__ in task_gen.prec[fun_name]:
  367. task_gen.prec[fun_name].append(func.__name__)
  368. #task_gen.prec[fun_name].sort()
  369. return func
  370. return deco
  371. before = before_method
  372. def after_method(*k):
  373. """
  374. Decorator: register a task generator method which will be executed
  375. after the functions of given name(s)::
  376. from waflib.TaskGen import feature, after
  377. @feature('myfeature')
  378. @after_method('fun2')
  379. def fun1(self):
  380. print('feature 1!')
  381. @feature('myfeature')
  382. def fun2(self):
  383. print('feature 2!')
  384. def build(bld):
  385. bld(features='myfeature')
  386. :param k: method names
  387. :type k: list of string
  388. """
  389. def deco(func):
  390. setattr(task_gen, func.__name__, func)
  391. for fun_name in k:
  392. if not fun_name in task_gen.prec[func.__name__]:
  393. task_gen.prec[func.__name__].append(fun_name)
  394. #task_gen.prec[func.__name__].sort()
  395. return func
  396. return deco
  397. after = after_method
  398. def extension(*k):
  399. """
  400. Decorator: register a task generator method which will be invoked during
  401. the processing of source files for the extension given::
  402. from waflib import Task
  403. class mytask(Task):
  404. run_str = 'cp ${SRC} ${TGT}'
  405. @extension('.moo')
  406. def create_maa_file(self, node):
  407. self.create_task('mytask', node, node.change_ext('.maa'))
  408. def build(bld):
  409. bld(source='foo.moo')
  410. """
  411. def deco(func):
  412. setattr(task_gen, func.__name__, func)
  413. for x in k:
  414. task_gen.mappings[x] = func
  415. return func
  416. return deco
  417. # ---------------------------------------------------------------
  418. # The following methods are task generator methods commonly used
  419. # they are almost examples, the rest of waf core does not depend on them
  420. @taskgen_method
  421. def to_nodes(self, lst, path=None):
  422. """
  423. Convert the input list into a list of nodes.
  424. It is used by :py:func:`waflib.TaskGen.process_source` and :py:func:`waflib.TaskGen.process_rule`.
  425. It is designed for source files, for folders, see :py:func:`waflib.Tools.ccroot.to_incnodes`:
  426. :param lst: input list
  427. :type lst: list of string and nodes
  428. :param path: path from which to search the nodes (by default, :py:attr:`waflib.TaskGen.task_gen.path`)
  429. :type path: :py:class:`waflib.Tools.Node.Node`
  430. :rtype: list of :py:class:`waflib.Tools.Node.Node`
  431. """
  432. tmp = []
  433. path = path or self.path
  434. find = path.find_resource
  435. if isinstance(lst, Node.Node):
  436. lst = [lst]
  437. # either a list or a string, convert to a list of nodes
  438. for x in Utils.to_list(lst):
  439. if isinstance(x, str):
  440. node = find(x)
  441. else:
  442. node = x
  443. if not node:
  444. raise Errors.WafError("source not found: %r in %r" % (x, self))
  445. tmp.append(node)
  446. return tmp
  447. @feature('*')
  448. def process_source(self):
  449. """
  450. Process each element in the attribute ``source`` by extension.
  451. #. The *source* list is converted through :py:meth:`waflib.TaskGen.to_nodes` to a list of :py:class:`waflib.Node.Node` first.
  452. #. File extensions are mapped to methods having the signature: ``def meth(self, node)`` by :py:meth:`waflib.TaskGen.extension`
  453. #. The method is retrieved through :py:meth:`waflib.TaskGen.task_gen.get_hook`
  454. #. When called, the methods may modify self.source to append more source to process
  455. #. The mappings can map an extension or a filename (see the code below)
  456. """
  457. self.source = self.to_nodes(getattr(self, 'source', []))
  458. for node in self.source:
  459. self.get_hook(node)(self, node)
  460. @feature('*')
  461. @before_method('process_source')
  462. def process_rule(self):
  463. """
  464. Process the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled::
  465. def build(bld):
  466. bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt')
  467. """
  468. if not getattr(self, 'rule', None):
  469. return
  470. # create the task class
  471. name = str(getattr(self, 'name', None) or self.target or getattr(self.rule, '__name__', self.rule))
  472. # or we can put the class in a cache for performance reasons
  473. try:
  474. cache = self.bld.cache_rule_attr
  475. except AttributeError:
  476. cache = self.bld.cache_rule_attr = {}
  477. cls = None
  478. if getattr(self, 'cache_rule', 'True'):
  479. try:
  480. cls = cache[(name, self.rule)]
  481. except KeyError:
  482. pass
  483. if not cls:
  484. rule = self.rule
  485. if hasattr(self, 'chmod'):
  486. def chmod_fun(tsk):
  487. for x in tsk.outputs:
  488. os.chmod(x.abspath(), self.chmod)
  489. rule = (self.rule, chmod_fun)
  490. cls = Task.task_factory(name, rule,
  491. getattr(self, 'vars', []),
  492. shell=getattr(self, 'shell', True), color=getattr(self, 'color', 'BLUE'),
  493. scan = getattr(self, 'scan', None))
  494. if getattr(self, 'scan', None):
  495. cls.scan = self.scan
  496. elif getattr(self, 'deps', None):
  497. def scan(self):
  498. nodes = []
  499. for x in self.generator.to_list(getattr(self.generator, 'deps', None)):
  500. node = self.generator.path.find_resource(x)
  501. if not node:
  502. self.generator.bld.fatal('Could not find %r (was it declared?)' % x)
  503. nodes.append(node)
  504. return [nodes, []]
  505. cls.scan = scan
  506. if getattr(self, 'update_outputs', None):
  507. Task.update_outputs(cls)
  508. if getattr(self, 'always', None):
  509. Task.always_run(cls)
  510. for x in ('after', 'before', 'ext_in', 'ext_out'):
  511. setattr(cls, x, getattr(self, x, []))
  512. if getattr(self, 'cache_rule', 'True'):
  513. cache[(name, self.rule)] = cls
  514. if getattr(self, 'cls_str', None):
  515. setattr(cls, '__str__', self.cls_str)
  516. if getattr(self, 'cls_keyword', None):
  517. setattr(cls, 'keyword', self.cls_keyword)
  518. # now create one instance
  519. tsk = self.create_task(name)
  520. if getattr(self, 'target', None):
  521. if isinstance(self.target, str):
  522. self.target = self.target.split()
  523. if not isinstance(self.target, list):
  524. self.target = [self.target]
  525. for x in self.target:
  526. if isinstance(x, str):
  527. tsk.outputs.append(self.path.find_or_declare(x))
  528. else:
  529. x.parent.mkdir() # if a node was given, create the required folders
  530. tsk.outputs.append(x)
  531. if getattr(self, 'install_path', None):
  532. self.bld.install_files(self.install_path, tsk.outputs, chmod=getattr(self, 'chmod', Utils.O644))
  533. if getattr(self, 'source', None):
  534. tsk.inputs = self.to_nodes(self.source)
  535. # bypass the execution of process_source by setting the source to an empty list
  536. self.source = []
  537. if getattr(self, 'cwd', None):
  538. tsk.cwd = self.cwd
  539. @feature('seq')
  540. def sequence_order(self):
  541. """
  542. Add a strict sequential constraint between the tasks generated by task generators.
  543. It works because task generators are posted in order.
  544. It will not post objects which belong to other folders.
  545. Example::
  546. bld(features='javac seq')
  547. bld(features='jar seq')
  548. To start a new sequence, set the attribute seq_start, for example::
  549. obj = bld(features='seq')
  550. obj.seq_start = True
  551. Note that the method is executed in last position. This is more an
  552. example than a widely-used solution.
  553. """
  554. if self.meths and self.meths[-1] != 'sequence_order':
  555. self.meths.append('sequence_order')
  556. return
  557. if getattr(self, 'seq_start', None):
  558. return
  559. # all the tasks previously declared must be run before these
  560. if getattr(self.bld, 'prev', None):
  561. self.bld.prev.post()
  562. for x in self.bld.prev.tasks:
  563. for y in self.tasks:
  564. y.set_run_after(x)
  565. self.bld.prev = self
  566. re_m4 = re.compile('@(\w+)@', re.M)
  567. class subst_pc(Task.Task):
  568. """
  569. Create *.pc* files from *.pc.in*. The task is executed whenever an input variable used
  570. in the substitution changes.
  571. """
  572. def force_permissions(self):
  573. "Private for the time being, we will probably refactor this into run_str=[run1,chmod]"
  574. if getattr(self.generator, 'chmod', None):
  575. for x in self.outputs:
  576. os.chmod(x.abspath(), self.generator.chmod)
  577. def run(self):
  578. "Substitutes variables in a .in file"
  579. if getattr(self.generator, 'is_copy', None):
  580. for i, x in enumerate(self.outputs):
  581. x.write(self.inputs[i].read('rb'), 'wb')
  582. self.force_permissions()
  583. return None
  584. if getattr(self.generator, 'fun', None):
  585. ret = self.generator.fun(self)
  586. if not ret:
  587. self.force_permissions()
  588. return ret
  589. code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
  590. if getattr(self.generator, 'subst_fun', None):
  591. code = self.generator.subst_fun(self, code)
  592. if code is not None:
  593. self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
  594. self.force_permissions()
  595. return None
  596. # replace all % by %% to prevent errors by % signs
  597. code = code.replace('%', '%%')
  598. # extract the vars foo into lst and replace @foo@ by %(foo)s
  599. lst = []
  600. def repl(match):
  601. g = match.group
  602. if g(1):
  603. lst.append(g(1))
  604. return "%%(%s)s" % g(1)
  605. return ''
  606. global re_m4
  607. code = getattr(self.generator, 're_m4', re_m4).sub(repl, code)
  608. try:
  609. d = self.generator.dct
  610. except AttributeError:
  611. d = {}
  612. for x in lst:
  613. tmp = getattr(self.generator, x, '') or self.env[x] or self.env[x.upper()]
  614. try:
  615. tmp = ''.join(tmp)
  616. except TypeError:
  617. tmp = str(tmp)
  618. d[x] = tmp
  619. code = code % d
  620. self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
  621. self.generator.bld.raw_deps[self.uid()] = self.dep_vars = lst
  622. # make sure the signature is updated
  623. try: delattr(self, 'cache_sig')
  624. except AttributeError: pass
  625. self.force_permissions()
  626. def sig_vars(self):
  627. """
  628. Compute a hash (signature) of the variables used in the substitution
  629. """
  630. bld = self.generator.bld
  631. env = self.env
  632. upd = self.m.update
  633. if getattr(self.generator, 'fun', None):
  634. upd(Utils.h_fun(self.generator.fun).encode())
  635. if getattr(self.generator, 'subst_fun', None):
  636. upd(Utils.h_fun(self.generator.subst_fun).encode())
  637. # raw_deps: persistent custom values returned by the scanner
  638. vars = self.generator.bld.raw_deps.get(self.uid(), [])
  639. # hash both env vars and task generator attributes
  640. act_sig = bld.hash_env_vars(env, vars)
  641. upd(act_sig)
  642. lst = [getattr(self.generator, x, '') for x in vars]
  643. upd(Utils.h_list(lst))
  644. return self.m.digest()
  645. @extension('.pc.in')
  646. def add_pcfile(self, node):
  647. """
  648. Process *.pc.in* files to *.pc*. Install the results to ``${PREFIX}/lib/pkgconfig/``
  649. def build(bld):
  650. bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
  651. """
  652. tsk = self.create_task('subst_pc', node, node.change_ext('.pc', '.pc.in'))
  653. self.bld.install_files(getattr(self, 'install_path', '${LIBDIR}/pkgconfig/'), tsk.outputs)
  654. class subst(subst_pc):
  655. pass
  656. @feature('subst')
  657. @before_method('process_source', 'process_rule')
  658. def process_subst(self):
  659. """
  660. Define a transformation that substitutes the contents of *source* files to *target* files::
  661. def build(bld):
  662. bld(
  663. features='subst',
  664. source='foo.c.in',
  665. target='foo.c',
  666. install_path='${LIBDIR}/pkgconfig',
  667. VAR = 'val'
  668. )
  669. The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument
  670. of the task generator object.
  671. This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`.
  672. """
  673. src = Utils.to_list(getattr(self, 'source', []))
  674. if isinstance(src, Node.Node):
  675. src = [src]
  676. tgt = Utils.to_list(getattr(self, 'target', []))
  677. if isinstance(tgt, Node.Node):
  678. tgt = [tgt]
  679. if len(src) != len(tgt):
  680. raise Errors.WafError('invalid number of source/target for %r' % self)
  681. for x, y in zip(src, tgt):
  682. if not x or not y:
  683. raise Errors.WafError('null source or target for %r' % self)
  684. a, b = None, None
  685. if isinstance(x, str) and isinstance(y, str) and x == y:
  686. a = self.path.find_node(x)
  687. b = self.path.get_bld().make_node(y)
  688. if not os.path.isfile(b.abspath()):
  689. b.sig = None
  690. b.parent.mkdir()
  691. else:
  692. if isinstance(x, str):
  693. a = self.path.find_resource(x)
  694. elif isinstance(x, Node.Node):
  695. a = x
  696. if isinstance(y, str):
  697. b = self.path.find_or_declare(y)
  698. elif isinstance(y, Node.Node):
  699. b = y
  700. if not a:
  701. raise Errors.WafError('could not find %r for %r' % (x, self))
  702. has_constraints = False
  703. tsk = self.create_task('subst', a, b)
  704. for k in ('after', 'before', 'ext_in', 'ext_out'):
  705. val = getattr(self, k, None)
  706. if val:
  707. has_constraints = True
  708. setattr(tsk, k, val)
  709. # paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies
  710. if not has_constraints:
  711. global HEADER_EXTS
  712. for xt in HEADER_EXTS:
  713. if b.name.endswith(xt):
  714. tsk.before = [k for k in ('c', 'cxx') if k in Task.classes]
  715. break
  716. inst_to = getattr(self, 'install_path', None)
  717. if inst_to:
  718. self.bld.install_files(inst_to, b, chmod=getattr(self, 'chmod', Utils.O644))
  719. self.source = []