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.

837 lines
23KB

  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. raise Errors.WafError('Cycle detected in the method execution %r' % prec)
  179. out.reverse()
  180. self.meths = out
  181. # then we run the methods in order
  182. Logs.debug('task_gen: posting %s %d' % (self, id(self)))
  183. for x in out:
  184. try:
  185. v = getattr(self, x)
  186. except AttributeError:
  187. raise Errors.WafError('%r is not a valid task generator method' % x)
  188. Logs.debug('task_gen: -> %s (%d)' % (x, id(self)))
  189. v()
  190. Logs.debug('task_gen: posted %s' % self.name)
  191. return True
  192. def get_hook(self, node):
  193. """
  194. :param node: Input file to process
  195. :type node: :py:class:`waflib.Tools.Node.Node`
  196. :return: A method able to process the input node by looking at the extension
  197. :rtype: function
  198. """
  199. name = node.name
  200. if self.mappings:
  201. for k in self.mappings:
  202. if name.endswith(k):
  203. return self.mappings[k]
  204. for k in task_gen.mappings:
  205. if name.endswith(k):
  206. return task_gen.mappings[k]
  207. raise Errors.WafError("File %r has no mapping in %r (have you forgotten to load a waf tool?)" % (node, task_gen.mappings.keys()))
  208. def create_task(self, name, src=None, tgt=None, **kw):
  209. """
  210. Wrapper for creating task instances. The classes are retrieved from the
  211. context class if possible, then from the global dict Task.classes.
  212. :param name: task class name
  213. :type name: string
  214. :param src: input nodes
  215. :type src: list of :py:class:`waflib.Tools.Node.Node`
  216. :param tgt: output nodes
  217. :type tgt: list of :py:class:`waflib.Tools.Node.Node`
  218. :return: A task object
  219. :rtype: :py:class:`waflib.Task.TaskBase`
  220. """
  221. task = Task.classes[name](env=self.env.derive(), generator=self)
  222. if src:
  223. task.set_inputs(src)
  224. if tgt:
  225. task.set_outputs(tgt)
  226. task.__dict__.update(kw)
  227. self.tasks.append(task)
  228. return task
  229. def clone(self, env):
  230. """
  231. Make a copy of a task generator. Once the copy is made, it is necessary to ensure that the
  232. it does not create the same output files as the original, or the same files may
  233. be compiled several times.
  234. :param env: A configuration set
  235. :type env: :py:class:`waflib.ConfigSet.ConfigSet`
  236. :return: A copy
  237. :rtype: :py:class:`waflib.TaskGen.task_gen`
  238. """
  239. newobj = self.bld()
  240. for x in self.__dict__:
  241. if x in ('env', 'bld'):
  242. continue
  243. elif x in ('path', 'features'):
  244. setattr(newobj, x, getattr(self, x))
  245. else:
  246. setattr(newobj, x, copy.copy(getattr(self, x)))
  247. newobj.posted = False
  248. if isinstance(env, str):
  249. newobj.env = self.bld.all_envs[env].derive()
  250. else:
  251. newobj.env = env.derive()
  252. return newobj
  253. def declare_chain(name='', rule=None, reentrant=None, color='BLUE',
  254. ext_in=[], ext_out=[], before=[], after=[], decider=None, scan=None, install_path=None, shell=False):
  255. """
  256. Create a new mapping and a task class for processing files by extension.
  257. See Tools/flex.py for an example.
  258. :param name: name for the task class
  259. :type name: string
  260. :param rule: function to execute or string to be compiled in a function
  261. :type rule: string or function
  262. :param reentrant: re-inject the output file in the process (done automatically, set to 0 to disable)
  263. :type reentrant: int
  264. :param color: color for the task output
  265. :type color: string
  266. :param ext_in: execute the task only after the files of such extensions are created
  267. :type ext_in: list of string
  268. :param ext_out: execute the task only before files of such extensions are processed
  269. :type ext_out: list of string
  270. :param before: execute instances of this task before classes of the given names
  271. :type before: list of string
  272. :param after: execute instances of this task after classes of the given names
  273. :type after: list of string
  274. :param decider: if present, use it to create the output nodes for the task
  275. :type decider: function
  276. :param scan: scanner function for the task
  277. :type scan: function
  278. :param install_path: installation path for the output nodes
  279. :type install_path: string
  280. """
  281. ext_in = Utils.to_list(ext_in)
  282. ext_out = Utils.to_list(ext_out)
  283. if not name:
  284. name = rule
  285. cls = Task.task_factory(name, rule, color=color, ext_in=ext_in, ext_out=ext_out, before=before, after=after, scan=scan, shell=shell)
  286. def x_file(self, node):
  287. ext = decider and decider(self, node) or cls.ext_out
  288. if ext_in:
  289. _ext_in = ext_in[0]
  290. tsk = self.create_task(name, node)
  291. cnt = 0
  292. keys = set(self.mappings.keys()) | set(self.__class__.mappings.keys())
  293. for x in ext:
  294. k = node.change_ext(x, ext_in=_ext_in)
  295. tsk.outputs.append(k)
  296. if reentrant != None:
  297. if cnt < int(reentrant):
  298. self.source.append(k)
  299. else:
  300. # reinject downstream files into the build
  301. for y in keys: # ~ nfile * nextensions :-/
  302. if k.name.endswith(y):
  303. self.source.append(k)
  304. break
  305. cnt += 1
  306. if install_path:
  307. self.bld.install_files(install_path, tsk.outputs)
  308. return tsk
  309. for x in cls.ext_in:
  310. task_gen.mappings[x] = x_file
  311. return x_file
  312. def taskgen_method(func):
  313. """
  314. Decorator: register a method as a task generator method.
  315. The function must accept a task generator as first parameter::
  316. from waflib.TaskGen import taskgen_method
  317. @taskgen_method
  318. def mymethod(self):
  319. pass
  320. :param func: task generator method to add
  321. :type func: function
  322. :rtype: function
  323. """
  324. setattr(task_gen, func.__name__, func)
  325. return func
  326. def feature(*k):
  327. """
  328. Decorator: register a task generator method that will be executed when the
  329. object attribute 'feature' contains the corresponding key(s)::
  330. from waflib.Task import feature
  331. @feature('myfeature')
  332. def myfunction(self):
  333. print('that is my feature!')
  334. def build(bld):
  335. bld(features='myfeature')
  336. :param k: feature names
  337. :type k: list of string
  338. """
  339. def deco(func):
  340. setattr(task_gen, func.__name__, func)
  341. for name in k:
  342. feats[name].update([func.__name__])
  343. return func
  344. return deco
  345. def before_method(*k):
  346. """
  347. Decorator: register a task generator method which will be executed
  348. before the functions of given name(s)::
  349. from waflib.TaskGen import feature, before
  350. @feature('myfeature')
  351. @before_method('fun2')
  352. def fun1(self):
  353. print('feature 1!')
  354. @feature('myfeature')
  355. def fun2(self):
  356. print('feature 2!')
  357. def build(bld):
  358. bld(features='myfeature')
  359. :param k: method names
  360. :type k: list of string
  361. """
  362. def deco(func):
  363. setattr(task_gen, func.__name__, func)
  364. for fun_name in k:
  365. if not func.__name__ in task_gen.prec[fun_name]:
  366. task_gen.prec[fun_name].append(func.__name__)
  367. #task_gen.prec[fun_name].sort()
  368. return func
  369. return deco
  370. before = before_method
  371. def after_method(*k):
  372. """
  373. Decorator: register a task generator method which will be executed
  374. after the functions of given name(s)::
  375. from waflib.TaskGen import feature, after
  376. @feature('myfeature')
  377. @after_method('fun2')
  378. def fun1(self):
  379. print('feature 1!')
  380. @feature('myfeature')
  381. def fun2(self):
  382. print('feature 2!')
  383. def build(bld):
  384. bld(features='myfeature')
  385. :param k: method names
  386. :type k: list of string
  387. """
  388. def deco(func):
  389. setattr(task_gen, func.__name__, func)
  390. for fun_name in k:
  391. if not fun_name in task_gen.prec[func.__name__]:
  392. task_gen.prec[func.__name__].append(fun_name)
  393. #task_gen.prec[func.__name__].sort()
  394. return func
  395. return deco
  396. after = after_method
  397. def extension(*k):
  398. """
  399. Decorator: register a task generator method which will be invoked during
  400. the processing of source files for the extension given::
  401. from waflib import Task
  402. class mytask(Task):
  403. run_str = 'cp ${SRC} ${TGT}'
  404. @extension('.moo')
  405. def create_maa_file(self, node):
  406. self.create_task('mytask', node, node.change_ext('.maa'))
  407. def build(bld):
  408. bld(source='foo.moo')
  409. """
  410. def deco(func):
  411. setattr(task_gen, func.__name__, func)
  412. for x in k:
  413. task_gen.mappings[x] = func
  414. return func
  415. return deco
  416. # ---------------------------------------------------------------
  417. # The following methods are task generator methods commonly used
  418. # they are almost examples, the rest of waf core does not depend on them
  419. @taskgen_method
  420. def to_nodes(self, lst, path=None):
  421. """
  422. Convert the input list into a list of nodes.
  423. It is used by :py:func:`waflib.TaskGen.process_source` and :py:func:`waflib.TaskGen.process_rule`.
  424. It is designed for source files, for folders, see :py:func:`waflib.Tools.ccroot.to_incnodes`:
  425. :param lst: input list
  426. :type lst: list of string and nodes
  427. :param path: path from which to search the nodes (by default, :py:attr:`waflib.TaskGen.task_gen.path`)
  428. :type path: :py:class:`waflib.Tools.Node.Node`
  429. :rtype: list of :py:class:`waflib.Tools.Node.Node`
  430. """
  431. tmp = []
  432. path = path or self.path
  433. find = path.find_resource
  434. if isinstance(lst, Node.Node):
  435. lst = [lst]
  436. # either a list or a string, convert to a list of nodes
  437. for x in Utils.to_list(lst):
  438. if isinstance(x, str):
  439. node = find(x)
  440. else:
  441. node = x
  442. if not node:
  443. raise Errors.WafError("source not found: %r in %r" % (x, self))
  444. tmp.append(node)
  445. return tmp
  446. @feature('*')
  447. def process_source(self):
  448. """
  449. Process each element in the attribute ``source`` by extension.
  450. #. The *source* list is converted through :py:meth:`waflib.TaskGen.to_nodes` to a list of :py:class:`waflib.Node.Node` first.
  451. #. File extensions are mapped to methods having the signature: ``def meth(self, node)`` by :py:meth:`waflib.TaskGen.extension`
  452. #. The method is retrieved through :py:meth:`waflib.TaskGen.task_gen.get_hook`
  453. #. When called, the methods may modify self.source to append more source to process
  454. #. The mappings can map an extension or a filename (see the code below)
  455. """
  456. self.source = self.to_nodes(getattr(self, 'source', []))
  457. for node in self.source:
  458. self.get_hook(node)(self, node)
  459. @feature('*')
  460. @before_method('process_source')
  461. def process_rule(self):
  462. """
  463. Process the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled::
  464. def build(bld):
  465. bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt')
  466. """
  467. if not getattr(self, 'rule', None):
  468. return
  469. # create the task class
  470. name = str(getattr(self, 'name', None) or self.target or getattr(self.rule, '__name__', self.rule))
  471. # or we can put the class in a cache for performance reasons
  472. try:
  473. cache = self.bld.cache_rule_attr
  474. except AttributeError:
  475. cache = self.bld.cache_rule_attr = {}
  476. cls = None
  477. if getattr(self, 'cache_rule', 'True'):
  478. try:
  479. cls = cache[(name, self.rule)]
  480. except KeyError:
  481. pass
  482. if not cls:
  483. cls = Task.task_factory(name, self.rule,
  484. getattr(self, 'vars', []),
  485. shell=getattr(self, 'shell', True), color=getattr(self, 'color', 'BLUE'),
  486. scan = getattr(self, 'scan', None))
  487. if getattr(self, 'scan', None):
  488. cls.scan = self.scan
  489. elif getattr(self, 'deps', None):
  490. def scan(self):
  491. nodes = []
  492. for x in self.generator.to_list(getattr(self.generator, 'deps', None)):
  493. node = self.generator.path.find_resource(x)
  494. if not node:
  495. self.generator.bld.fatal('Could not find %r (was it declared?)' % x)
  496. nodes.append(node)
  497. return [nodes, []]
  498. cls.scan = scan
  499. if getattr(self, 'update_outputs', None):
  500. Task.update_outputs(cls)
  501. if getattr(self, 'always', None):
  502. Task.always_run(cls)
  503. for x in ('after', 'before', 'ext_in', 'ext_out'):
  504. setattr(cls, x, getattr(self, x, []))
  505. if getattr(self, 'cache_rule', 'True'):
  506. cache[(name, self.rule)] = cls
  507. # now create one instance
  508. tsk = self.create_task(name)
  509. if getattr(self, 'target', None):
  510. if isinstance(self.target, str):
  511. self.target = self.target.split()
  512. if not isinstance(self.target, list):
  513. self.target = [self.target]
  514. for x in self.target:
  515. if isinstance(x, str):
  516. tsk.outputs.append(self.path.find_or_declare(x))
  517. else:
  518. x.parent.mkdir() # if a node was given, create the required folders
  519. tsk.outputs.append(x)
  520. if getattr(self, 'install_path', None):
  521. self.bld.install_files(self.install_path, tsk.outputs)
  522. if getattr(self, 'source', None):
  523. tsk.inputs = self.to_nodes(self.source)
  524. # bypass the execution of process_source by setting the source to an empty list
  525. self.source = []
  526. if getattr(self, 'cwd', None):
  527. tsk.cwd = self.cwd
  528. @feature('seq')
  529. def sequence_order(self):
  530. """
  531. Add a strict sequential constraint between the tasks generated by task generators.
  532. It works because task generators are posted in order.
  533. It will not post objects which belong to other folders.
  534. Example::
  535. bld(features='javac seq')
  536. bld(features='jar seq')
  537. To start a new sequence, set the attribute seq_start, for example::
  538. obj = bld(features='seq')
  539. obj.seq_start = True
  540. Note that the method is executed in last position. This is more an
  541. example than a widely-used solution.
  542. """
  543. if self.meths and self.meths[-1] != 'sequence_order':
  544. self.meths.append('sequence_order')
  545. return
  546. if getattr(self, 'seq_start', None):
  547. return
  548. # all the tasks previously declared must be run before these
  549. if getattr(self.bld, 'prev', None):
  550. self.bld.prev.post()
  551. for x in self.bld.prev.tasks:
  552. for y in self.tasks:
  553. y.set_run_after(x)
  554. self.bld.prev = self
  555. re_m4 = re.compile('@(\w+)@', re.M)
  556. class subst_pc(Task.Task):
  557. """
  558. Create *.pc* files from *.pc.in*. The task is executed whenever an input variable used
  559. in the substitution changes.
  560. """
  561. def run(self):
  562. "Substitutes variables in a .in file"
  563. if getattr(self.generator, 'is_copy', None):
  564. self.outputs[0].write(self.inputs[0].read('rb'), 'wb')
  565. if getattr(self.generator, 'chmod', None):
  566. os.chmod(self.outputs[0].abspath(), self.generator.chmod)
  567. return None
  568. if getattr(self.generator, 'fun', None):
  569. return self.generator.fun(self)
  570. code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
  571. if getattr(self.generator, 'subst_fun', None):
  572. code = self.generator.subst_fun(self, code)
  573. if code is not None:
  574. self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
  575. return
  576. # replace all % by %% to prevent errors by % signs
  577. code = code.replace('%', '%%')
  578. # extract the vars foo into lst and replace @foo@ by %(foo)s
  579. lst = []
  580. def repl(match):
  581. g = match.group
  582. if g(1):
  583. lst.append(g(1))
  584. return "%%(%s)s" % g(1)
  585. return ''
  586. global re_m4
  587. code = getattr(self.generator, 're_m4', re_m4).sub(repl, code)
  588. try:
  589. d = self.generator.dct
  590. except AttributeError:
  591. d = {}
  592. for x in lst:
  593. tmp = getattr(self.generator, x, '') or self.env[x] or self.env[x.upper()]
  594. try:
  595. tmp = ''.join(tmp)
  596. except TypeError:
  597. tmp = str(tmp)
  598. d[x] = tmp
  599. code = code % d
  600. self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
  601. self.generator.bld.raw_deps[self.uid()] = self.dep_vars = lst
  602. # make sure the signature is updated
  603. try: delattr(self, 'cache_sig')
  604. except AttributeError: pass
  605. if getattr(self.generator, 'chmod', None):
  606. os.chmod(self.outputs[0].abspath(), self.generator.chmod)
  607. def sig_vars(self):
  608. """
  609. Compute a hash (signature) of the variables used in the substitution
  610. """
  611. bld = self.generator.bld
  612. env = self.env
  613. upd = self.m.update
  614. if getattr(self.generator, 'fun', None):
  615. upd(Utils.h_fun(self.generator.fun).encode())
  616. if getattr(self.generator, 'subst_fun', None):
  617. upd(Utils.h_fun(self.generator.subst_fun).encode())
  618. # raw_deps: persistent custom values returned by the scanner
  619. vars = self.generator.bld.raw_deps.get(self.uid(), [])
  620. # hash both env vars and task generator attributes
  621. act_sig = bld.hash_env_vars(env, vars)
  622. upd(act_sig)
  623. lst = [getattr(self.generator, x, '') for x in vars]
  624. upd(Utils.h_list(lst))
  625. return self.m.digest()
  626. @extension('.pc.in')
  627. def add_pcfile(self, node):
  628. """
  629. Process *.pc.in* files to *.pc*. Install the results to ``${PREFIX}/lib/pkgconfig/``
  630. def build(bld):
  631. bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
  632. """
  633. tsk = self.create_task('subst_pc', node, node.change_ext('.pc', '.pc.in'))
  634. self.bld.install_files(getattr(self, 'install_path', '${LIBDIR}/pkgconfig/'), tsk.outputs)
  635. class subst(subst_pc):
  636. pass
  637. @feature('subst')
  638. @before_method('process_source', 'process_rule')
  639. def process_subst(self):
  640. """
  641. Define a transformation that substitutes the contents of *source* files to *target* files::
  642. def build(bld):
  643. bld(
  644. features='subst',
  645. source='foo.c.in',
  646. target='foo.c',
  647. install_path='${LIBDIR}/pkgconfig',
  648. VAR = 'val'
  649. )
  650. The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument
  651. of the task generator object.
  652. This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`.
  653. """
  654. src = Utils.to_list(getattr(self, 'source', []))
  655. if isinstance(src, Node.Node):
  656. src = [src]
  657. tgt = Utils.to_list(getattr(self, 'target', []))
  658. if isinstance(tgt, Node.Node):
  659. tgt = [tgt]
  660. if len(src) != len(tgt):
  661. raise Errors.WafError('invalid number of source/target for %r' % self)
  662. for x, y in zip(src, tgt):
  663. if not x or not y:
  664. raise Errors.WafError('null source or target for %r' % self)
  665. a, b = None, None
  666. if isinstance(x, str) and isinstance(y, str) and x == y:
  667. a = self.path.find_node(x)
  668. b = self.path.get_bld().make_node(y)
  669. if not os.path.isfile(b.abspath()):
  670. b.sig = None
  671. b.parent.mkdir()
  672. else:
  673. if isinstance(x, str):
  674. a = self.path.find_resource(x)
  675. elif isinstance(x, Node.Node):
  676. a = x
  677. if isinstance(y, str):
  678. b = self.path.find_or_declare(y)
  679. elif isinstance(y, Node.Node):
  680. b = y
  681. if not a:
  682. raise Errors.WafError('cound not find %r for %r' % (x, self))
  683. has_constraints = False
  684. tsk = self.create_task('subst', a, b)
  685. for k in ('after', 'before', 'ext_in', 'ext_out'):
  686. val = getattr(self, k, None)
  687. if val:
  688. has_constraints = True
  689. setattr(tsk, k, val)
  690. # paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies
  691. if not has_constraints:
  692. global HEADER_EXTS
  693. for xt in HEADER_EXTS:
  694. if b.name.endswith(xt):
  695. tsk.before = [k for k in ('c', 'cxx') if k in Task.classes]
  696. break
  697. inst_to = getattr(self, 'install_path', None)
  698. if inst_to:
  699. self.bld.install_files(inst_to, b, chmod=getattr(self, 'chmod', Utils.O644))
  700. self.source = []