1#!/usr/bin/env python 2# encoding: utf-8 3# Thomas Nagy, 2005-2018 (ita) 4 5""" 6Task generators 7 8The class :py:class:`waflib.TaskGen.task_gen` encapsulates the creation of task objects (low-level code) 9The instances can have various parameters, but the creation of task nodes (Task.py) 10is deferred. To achieve this, various methods are called from the method "apply" 11""" 12 13import copy, re, os, functools 14from waflib import Task, Utils, Logs, Errors, ConfigSet, Node 15 16feats = Utils.defaultdict(set) 17"""remember the methods declaring features""" 18 19HEADER_EXTS = ['.h', '.hpp', '.hxx', '.hh'] 20 21class task_gen(object): 22 """ 23 Instances of this class create :py:class:`waflib.Task.Task` when 24 calling the method :py:meth:`waflib.TaskGen.task_gen.post` from the main thread. 25 A few notes: 26 27 * The methods to call (*self.meths*) can be specified dynamically (removing, adding, ..) 28 * The 'features' are used to add methods to self.meths and then execute them 29 * The attribute 'path' is a node representing the location of the task generator 30 * The tasks created are added to the attribute *tasks* 31 * The attribute 'idx' is a counter of task generators in the same path 32 """ 33 34 mappings = Utils.ordered_iter_dict() 35 """Mappings are global file extension mappings that are retrieved in the order of definition""" 36 37 prec = Utils.defaultdict(set) 38 """Dict that holds the precedence execution rules for task generator methods""" 39 40 def __init__(self, *k, **kw): 41 """ 42 Task generator objects predefine various attributes (source, target) for possible 43 processing by process_rule (make-like rules) or process_source (extensions, misc methods) 44 45 Tasks are stored on the attribute 'tasks'. They are created by calling methods 46 listed in ``self.meths`` or referenced in the attribute ``features`` 47 A topological sort is performed to execute the methods in correct order. 48 49 The extra key/value elements passed in ``kw`` are set as attributes 50 """ 51 self.source = [] 52 self.target = '' 53 54 self.meths = [] 55 """ 56 List of method names to execute (internal) 57 """ 58 59 self.features = [] 60 """ 61 List of feature names for bringing new methods in 62 """ 63 64 self.tasks = [] 65 """ 66 Tasks created are added to this list 67 """ 68 69 if not 'bld' in kw: 70 # task generators without a build context :-/ 71 self.env = ConfigSet.ConfigSet() 72 self.idx = 0 73 self.path = None 74 else: 75 self.bld = kw['bld'] 76 self.env = self.bld.env.derive() 77 self.path = kw.get('path', self.bld.path) # by default, emulate chdir when reading scripts 78 79 # Provide a unique index per folder 80 # This is part of a measure to prevent output file name collisions 81 path = self.path.abspath() 82 try: 83 self.idx = self.bld.idx[path] = self.bld.idx.get(path, 0) + 1 84 except AttributeError: 85 self.bld.idx = {} 86 self.idx = self.bld.idx[path] = 1 87 88 # Record the global task generator count 89 try: 90 self.tg_idx_count = self.bld.tg_idx_count = self.bld.tg_idx_count + 1 91 except AttributeError: 92 self.tg_idx_count = self.bld.tg_idx_count = 1 93 94 for key, val in kw.items(): 95 setattr(self, key, val) 96 97 def __str__(self): 98 """Debugging helper""" 99 return "<task_gen %r declared in %s>" % (self.name, self.path.abspath()) 100 101 def __repr__(self): 102 """Debugging helper""" 103 lst = [] 104 for x in self.__dict__: 105 if x not in ('env', 'bld', 'compiled_tasks', 'tasks'): 106 lst.append("%s=%s" % (x, repr(getattr(self, x)))) 107 return "bld(%s) in %s" % (", ".join(lst), self.path.abspath()) 108 109 def get_cwd(self): 110 """ 111 Current working directory for the task generator, defaults to the build directory. 112 This is still used in a few places but it should disappear at some point as the classes 113 define their own working directory. 114 115 :rtype: :py:class:`waflib.Node.Node` 116 """ 117 return self.bld.bldnode 118 119 def get_name(self): 120 """ 121 If the attribute ``name`` is not set on the instance, 122 the name is computed from the target name:: 123 124 def build(bld): 125 x = bld(name='foo') 126 x.get_name() # foo 127 y = bld(target='bar') 128 y.get_name() # bar 129 130 :rtype: string 131 :return: name of this task generator 132 """ 133 try: 134 return self._name 135 except AttributeError: 136 if isinstance(self.target, list): 137 lst = [str(x) for x in self.target] 138 name = self._name = ','.join(lst) 139 else: 140 name = self._name = str(self.target) 141 return name 142 def set_name(self, name): 143 self._name = name 144 145 name = property(get_name, set_name) 146 147 def to_list(self, val): 148 """ 149 Ensures that a parameter is a list, see :py:func:`waflib.Utils.to_list` 150 151 :type val: string or list of string 152 :param val: input to return as a list 153 :rtype: list 154 """ 155 if isinstance(val, str): 156 return val.split() 157 else: 158 return val 159 160 def post(self): 161 """ 162 Creates tasks for this task generators. The following operations are performed: 163 164 #. The body of this method is called only once and sets the attribute ``posted`` 165 #. The attribute ``features`` is used to add more methods in ``self.meths`` 166 #. The methods are sorted by the precedence table ``self.prec`` or `:waflib:attr:waflib.TaskGen.task_gen.prec` 167 #. The methods are then executed in order 168 #. The tasks created are added to :py:attr:`waflib.TaskGen.task_gen.tasks` 169 """ 170 if getattr(self, 'posted', None): 171 return False 172 self.posted = True 173 174 keys = set(self.meths) 175 keys.update(feats['*']) 176 177 # add the methods listed in the features 178 self.features = Utils.to_list(self.features) 179 for x in self.features: 180 st = feats[x] 181 if st: 182 keys.update(st) 183 elif not x in Task.classes: 184 Logs.warn('feature %r does not exist - bind at least one method to it?', x) 185 186 # copy the precedence table 187 prec = {} 188 prec_tbl = self.prec 189 for x in prec_tbl: 190 if x in keys: 191 prec[x] = prec_tbl[x] 192 193 # elements disconnected 194 tmp = [] 195 for a in keys: 196 for x in prec.values(): 197 if a in x: 198 break 199 else: 200 tmp.append(a) 201 202 tmp.sort(reverse=True) 203 204 # topological sort 205 out = [] 206 while tmp: 207 e = tmp.pop() 208 if e in keys: 209 out.append(e) 210 try: 211 nlst = prec[e] 212 except KeyError: 213 pass 214 else: 215 del prec[e] 216 for x in nlst: 217 for y in prec: 218 if x in prec[y]: 219 break 220 else: 221 tmp.append(x) 222 tmp.sort(reverse=True) 223 224 if prec: 225 buf = ['Cycle detected in the method execution:'] 226 for k, v in prec.items(): 227 buf.append('- %s after %s' % (k, [x for x in v if x in prec])) 228 raise Errors.WafError('\n'.join(buf)) 229 self.meths = out 230 231 # then we run the methods in order 232 Logs.debug('task_gen: posting %s %d', self, id(self)) 233 for x in out: 234 try: 235 v = getattr(self, x) 236 except AttributeError: 237 raise Errors.WafError('%r is not a valid task generator method' % x) 238 Logs.debug('task_gen: -> %s (%d)', x, id(self)) 239 v() 240 241 Logs.debug('task_gen: posted %s', self.name) 242 return True 243 244 def get_hook(self, node): 245 """ 246 Returns the ``@extension`` method to call for a Node of a particular extension. 247 248 :param node: Input file to process 249 :type node: :py:class:`waflib.Tools.Node.Node` 250 :return: A method able to process the input node by looking at the extension 251 :rtype: function 252 """ 253 name = node.name 254 for k in self.mappings: 255 try: 256 if name.endswith(k): 257 return self.mappings[k] 258 except TypeError: 259 # regexps objects 260 if k.match(name): 261 return self.mappings[k] 262 keys = list(self.mappings.keys()) 263 raise Errors.WafError("File %r has no mapping in %r (load a waf tool?)" % (node, keys)) 264 265 def create_task(self, name, src=None, tgt=None, **kw): 266 """ 267 Creates task instances. 268 269 :param name: task class name 270 :type name: string 271 :param src: input nodes 272 :type src: list of :py:class:`waflib.Tools.Node.Node` 273 :param tgt: output nodes 274 :type tgt: list of :py:class:`waflib.Tools.Node.Node` 275 :return: A task object 276 :rtype: :py:class:`waflib.Task.Task` 277 """ 278 task = Task.classes[name](env=self.env.derive(), generator=self) 279 if src: 280 task.set_inputs(src) 281 if tgt: 282 task.set_outputs(tgt) 283 task.__dict__.update(kw) 284 self.tasks.append(task) 285 return task 286 287 def clone(self, env): 288 """ 289 Makes a copy of a task generator. Once the copy is made, it is necessary to ensure that the 290 it does not create the same output files as the original, or the same files may 291 be compiled several times. 292 293 :param env: A configuration set 294 :type env: :py:class:`waflib.ConfigSet.ConfigSet` 295 :return: A copy 296 :rtype: :py:class:`waflib.TaskGen.task_gen` 297 """ 298 newobj = self.bld() 299 for x in self.__dict__: 300 if x in ('env', 'bld'): 301 continue 302 elif x in ('path', 'features'): 303 setattr(newobj, x, getattr(self, x)) 304 else: 305 setattr(newobj, x, copy.copy(getattr(self, x))) 306 307 newobj.posted = False 308 if isinstance(env, str): 309 newobj.env = self.bld.all_envs[env].derive() 310 else: 311 newobj.env = env.derive() 312 313 return newobj 314 315def declare_chain(name='', rule=None, reentrant=None, color='BLUE', 316 ext_in=[], ext_out=[], before=[], after=[], decider=None, scan=None, install_path=None, shell=False): 317 """ 318 Creates a new mapping and a task class for processing files by extension. 319 See Tools/flex.py for an example. 320 321 :param name: name for the task class 322 :type name: string 323 :param rule: function to execute or string to be compiled in a function 324 :type rule: string or function 325 :param reentrant: re-inject the output file in the process (done automatically, set to 0 to disable) 326 :type reentrant: int 327 :param color: color for the task output 328 :type color: string 329 :param ext_in: execute the task only after the files of such extensions are created 330 :type ext_in: list of string 331 :param ext_out: execute the task only before files of such extensions are processed 332 :type ext_out: list of string 333 :param before: execute instances of this task before classes of the given names 334 :type before: list of string 335 :param after: execute instances of this task after classes of the given names 336 :type after: list of string 337 :param decider: if present, function that returns a list of output file extensions (overrides ext_out for output files, but not for the build order) 338 :type decider: function 339 :param scan: scanner function for the task 340 :type scan: function 341 :param install_path: installation path for the output nodes 342 :type install_path: string 343 """ 344 ext_in = Utils.to_list(ext_in) 345 ext_out = Utils.to_list(ext_out) 346 if not name: 347 name = rule 348 cls = Task.task_factory(name, rule, color=color, ext_in=ext_in, ext_out=ext_out, before=before, after=after, scan=scan, shell=shell) 349 350 def x_file(self, node): 351 if ext_in: 352 _ext_in = ext_in[0] 353 354 tsk = self.create_task(name, node) 355 cnt = 0 356 357 ext = decider(self, node) if decider else cls.ext_out 358 for x in ext: 359 k = node.change_ext(x, ext_in=_ext_in) 360 tsk.outputs.append(k) 361 362 if reentrant != None: 363 if cnt < int(reentrant): 364 self.source.append(k) 365 else: 366 # reinject downstream files into the build 367 for y in self.mappings: # ~ nfile * nextensions :-/ 368 if k.name.endswith(y): 369 self.source.append(k) 370 break 371 cnt += 1 372 373 if install_path: 374 self.install_task = self.add_install_files(install_to=install_path, install_from=tsk.outputs) 375 return tsk 376 377 for x in cls.ext_in: 378 task_gen.mappings[x] = x_file 379 return x_file 380 381def taskgen_method(func): 382 """ 383 Decorator that registers method as a task generator method. 384 The function must accept a task generator as first parameter:: 385 386 from waflib.TaskGen import taskgen_method 387 @taskgen_method 388 def mymethod(self): 389 pass 390 391 :param func: task generator method to add 392 :type func: function 393 :rtype: function 394 """ 395 setattr(task_gen, func.__name__, func) 396 return func 397 398def feature(*k): 399 """ 400 Decorator that registers a task generator method that will be executed when the 401 object attribute ``feature`` contains the corresponding key(s):: 402 403 from waflib.Task import feature 404 @feature('myfeature') 405 def myfunction(self): 406 print('that is my feature!') 407 def build(bld): 408 bld(features='myfeature') 409 410 :param k: feature names 411 :type k: list of string 412 """ 413 def deco(func): 414 setattr(task_gen, func.__name__, func) 415 for name in k: 416 feats[name].update([func.__name__]) 417 return func 418 return deco 419 420def before_method(*k): 421 """ 422 Decorator that registera task generator method which will be executed 423 before the functions of given name(s):: 424 425 from waflib.TaskGen import feature, before 426 @feature('myfeature') 427 @before_method('fun2') 428 def fun1(self): 429 print('feature 1!') 430 @feature('myfeature') 431 def fun2(self): 432 print('feature 2!') 433 def build(bld): 434 bld(features='myfeature') 435 436 :param k: method names 437 :type k: list of string 438 """ 439 def deco(func): 440 setattr(task_gen, func.__name__, func) 441 for fun_name in k: 442 task_gen.prec[func.__name__].add(fun_name) 443 return func 444 return deco 445before = before_method 446 447def after_method(*k): 448 """ 449 Decorator that registers a task generator method which will be executed 450 after the functions of given name(s):: 451 452 from waflib.TaskGen import feature, after 453 @feature('myfeature') 454 @after_method('fun2') 455 def fun1(self): 456 print('feature 1!') 457 @feature('myfeature') 458 def fun2(self): 459 print('feature 2!') 460 def build(bld): 461 bld(features='myfeature') 462 463 :param k: method names 464 :type k: list of string 465 """ 466 def deco(func): 467 setattr(task_gen, func.__name__, func) 468 for fun_name in k: 469 task_gen.prec[fun_name].add(func.__name__) 470 return func 471 return deco 472after = after_method 473 474def extension(*k): 475 """ 476 Decorator that registers a task generator method which will be invoked during 477 the processing of source files for the extension given:: 478 479 from waflib import Task 480 class mytask(Task): 481 run_str = 'cp ${SRC} ${TGT}' 482 @extension('.moo') 483 def create_maa_file(self, node): 484 self.create_task('mytask', node, node.change_ext('.maa')) 485 def build(bld): 486 bld(source='foo.moo') 487 """ 488 def deco(func): 489 setattr(task_gen, func.__name__, func) 490 for x in k: 491 task_gen.mappings[x] = func 492 return func 493 return deco 494 495@taskgen_method 496def to_nodes(self, lst, path=None): 497 """ 498 Flatten the input list of string/nodes/lists into a list of nodes. 499 500 It is used by :py:func:`waflib.TaskGen.process_source` and :py:func:`waflib.TaskGen.process_rule`. 501 It is designed for source files, for folders, see :py:func:`waflib.Tools.ccroot.to_incnodes`: 502 503 :param lst: input list 504 :type lst: list of string and nodes 505 :param path: path from which to search the nodes (by default, :py:attr:`waflib.TaskGen.task_gen.path`) 506 :type path: :py:class:`waflib.Tools.Node.Node` 507 :rtype: list of :py:class:`waflib.Tools.Node.Node` 508 """ 509 tmp = [] 510 path = path or self.path 511 find = path.find_resource 512 513 if isinstance(lst, Node.Node): 514 lst = [lst] 515 516 for x in Utils.to_list(lst): 517 if isinstance(x, str): 518 node = find(x) 519 elif hasattr(x, 'name'): 520 node = x 521 else: 522 tmp.extend(self.to_nodes(x)) 523 continue 524 if not node: 525 raise Errors.WafError('source not found: %r in %r' % (x, self)) 526 tmp.append(node) 527 return tmp 528 529@feature('*') 530def process_source(self): 531 """ 532 Processes each element in the attribute ``source`` by extension. 533 534 #. The *source* list is converted through :py:meth:`waflib.TaskGen.to_nodes` to a list of :py:class:`waflib.Node.Node` first. 535 #. File extensions are mapped to methods having the signature: ``def meth(self, node)`` by :py:meth:`waflib.TaskGen.extension` 536 #. The method is retrieved through :py:meth:`waflib.TaskGen.task_gen.get_hook` 537 #. When called, the methods may modify self.source to append more source to process 538 #. The mappings can map an extension or a filename (see the code below) 539 """ 540 self.source = self.to_nodes(getattr(self, 'source', [])) 541 for node in self.source: 542 self.get_hook(node)(self, node) 543 544@feature('*') 545@before_method('process_source') 546def process_rule(self): 547 """ 548 Processes the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled:: 549 550 def build(bld): 551 bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt') 552 553 Main attributes processed: 554 555 * rule: command to execute, it can be a tuple of strings for multiple commands 556 * chmod: permissions for the resulting files (integer value such as Utils.O755) 557 * shell: set to False to execute the command directly (default is True to use a shell) 558 * scan: scanner function 559 * vars: list of variables to trigger rebuilds, such as CFLAGS 560 * cls_str: string to display when executing the task 561 * cls_keyword: label to display when executing the task 562 * cache_rule: by default, try to re-use similar classes, set to False to disable 563 * source: list of Node or string objects representing the source files required by this task 564 * target: list of Node or string objects representing the files that this task creates 565 * cwd: current working directory (Node or string) 566 * stdout: standard output, set to None to prevent waf from capturing the text 567 * stderr: standard error, set to None to prevent waf from capturing the text 568 * timeout: timeout for command execution (Python 3) 569 * always: whether to always run the command (False by default) 570 * deep_inputs: whether the task must depend on the input file tasks too (False by default) 571 """ 572 if not getattr(self, 'rule', None): 573 return 574 575 # create the task class 576 name = str(getattr(self, 'name', None) or self.target or getattr(self.rule, '__name__', self.rule)) 577 578 # or we can put the class in a cache for performance reasons 579 try: 580 cache = self.bld.cache_rule_attr 581 except AttributeError: 582 cache = self.bld.cache_rule_attr = {} 583 584 chmod = getattr(self, 'chmod', None) 585 shell = getattr(self, 'shell', True) 586 color = getattr(self, 'color', 'BLUE') 587 scan = getattr(self, 'scan', None) 588 _vars = getattr(self, 'vars', []) 589 cls_str = getattr(self, 'cls_str', None) 590 cls_keyword = getattr(self, 'cls_keyword', None) 591 use_cache = getattr(self, 'cache_rule', 'True') 592 deep_inputs = getattr(self, 'deep_inputs', False) 593 594 scan_val = has_deps = hasattr(self, 'deps') 595 if scan: 596 scan_val = id(scan) 597 598 key = Utils.h_list((name, self.rule, chmod, shell, color, cls_str, cls_keyword, scan_val, _vars, deep_inputs)) 599 600 cls = None 601 if use_cache: 602 try: 603 cls = cache[key] 604 except KeyError: 605 pass 606 if not cls: 607 rule = self.rule 608 if chmod is not None: 609 def chmod_fun(tsk): 610 for x in tsk.outputs: 611 os.chmod(x.abspath(), tsk.generator.chmod) 612 if isinstance(rule, tuple): 613 rule = list(rule) 614 rule.append(chmod_fun) 615 rule = tuple(rule) 616 else: 617 rule = (rule, chmod_fun) 618 619 cls = Task.task_factory(name, rule, _vars, shell=shell, color=color) 620 621 if cls_str: 622 setattr(cls, '__str__', self.cls_str) 623 624 if cls_keyword: 625 setattr(cls, 'keyword', self.cls_keyword) 626 627 if deep_inputs: 628 Task.deep_inputs(cls) 629 630 if scan: 631 cls.scan = self.scan 632 elif has_deps: 633 def scan(self): 634 nodes = [] 635 for x in self.generator.to_list(getattr(self.generator, 'deps', None)): 636 node = self.generator.path.find_resource(x) 637 if not node: 638 self.generator.bld.fatal('Could not find %r (was it declared?)' % x) 639 nodes.append(node) 640 return [nodes, []] 641 cls.scan = scan 642 643 if use_cache: 644 cache[key] = cls 645 646 # now create one instance 647 tsk = self.create_task(name) 648 649 for x in ('after', 'before', 'ext_in', 'ext_out'): 650 setattr(tsk, x, getattr(self, x, [])) 651 652 if hasattr(self, 'stdout'): 653 tsk.stdout = self.stdout 654 655 if hasattr(self, 'stderr'): 656 tsk.stderr = self.stderr 657 658 if getattr(self, 'timeout', None): 659 tsk.timeout = self.timeout 660 661 if getattr(self, 'always', None): 662 tsk.always_run = True 663 664 if getattr(self, 'target', None): 665 if isinstance(self.target, str): 666 self.target = self.target.split() 667 if not isinstance(self.target, list): 668 self.target = [self.target] 669 for x in self.target: 670 if isinstance(x, str): 671 tsk.outputs.append(self.path.find_or_declare(x)) 672 else: 673 x.parent.mkdir() # if a node was given, create the required folders 674 tsk.outputs.append(x) 675 if getattr(self, 'install_path', None): 676 self.install_task = self.add_install_files(install_to=self.install_path, 677 install_from=tsk.outputs, chmod=getattr(self, 'chmod', Utils.O644)) 678 679 if getattr(self, 'source', None): 680 tsk.inputs = self.to_nodes(self.source) 681 # bypass the execution of process_source by setting the source to an empty list 682 self.source = [] 683 684 if getattr(self, 'cwd', None): 685 tsk.cwd = self.cwd 686 687 if isinstance(tsk.run, functools.partial): 688 # Python documentation says: "partial objects defined in classes 689 # behave like static methods and do not transform into bound 690 # methods during instance attribute look-up." 691 tsk.run = functools.partial(tsk.run, tsk) 692 693@feature('seq') 694def sequence_order(self): 695 """ 696 Adds a strict sequential constraint between the tasks generated by task generators. 697 It works because task generators are posted in order. 698 It will not post objects which belong to other folders. 699 700 Example:: 701 702 bld(features='javac seq') 703 bld(features='jar seq') 704 705 To start a new sequence, set the attribute seq_start, for example:: 706 707 obj = bld(features='seq') 708 obj.seq_start = True 709 710 Note that the method is executed in last position. This is more an 711 example than a widely-used solution. 712 """ 713 if self.meths and self.meths[-1] != 'sequence_order': 714 self.meths.append('sequence_order') 715 return 716 717 if getattr(self, 'seq_start', None): 718 return 719 720 # all the tasks previously declared must be run before these 721 if getattr(self.bld, 'prev', None): 722 self.bld.prev.post() 723 for x in self.bld.prev.tasks: 724 for y in self.tasks: 725 y.set_run_after(x) 726 727 self.bld.prev = self 728 729 730re_m4 = re.compile(r'@(\w+)@', re.M) 731 732class subst_pc(Task.Task): 733 """ 734 Creates *.pc* files from *.pc.in*. The task is executed whenever an input variable used 735 in the substitution changes. 736 """ 737 738 def force_permissions(self): 739 "Private for the time being, we will probably refactor this into run_str=[run1,chmod]" 740 if getattr(self.generator, 'chmod', None): 741 for x in self.outputs: 742 os.chmod(x.abspath(), self.generator.chmod) 743 744 def run(self): 745 "Substitutes variables in a .in file" 746 747 if getattr(self.generator, 'is_copy', None): 748 for i, x in enumerate(self.outputs): 749 x.write(self.inputs[i].read('rb'), 'wb') 750 stat = os.stat(self.inputs[i].abspath()) # Preserve mtime of the copy 751 os.utime(self.outputs[i].abspath(), (stat.st_atime, stat.st_mtime)) 752 self.force_permissions() 753 return None 754 755 if getattr(self.generator, 'fun', None): 756 ret = self.generator.fun(self) 757 if not ret: 758 self.force_permissions() 759 return ret 760 761 code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'latin-1')) 762 if getattr(self.generator, 'subst_fun', None): 763 code = self.generator.subst_fun(self, code) 764 if code is not None: 765 self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1')) 766 self.force_permissions() 767 return None 768 769 # replace all % by %% to prevent errors by % signs 770 code = code.replace('%', '%%') 771 772 # extract the vars foo into lst and replace @foo@ by %(foo)s 773 lst = [] 774 def repl(match): 775 g = match.group 776 if g(1): 777 lst.append(g(1)) 778 return "%%(%s)s" % g(1) 779 return '' 780 code = getattr(self.generator, 're_m4', re_m4).sub(repl, code) 781 782 try: 783 d = self.generator.dct 784 except AttributeError: 785 d = {} 786 for x in lst: 787 tmp = getattr(self.generator, x, '') or self.env[x] or self.env[x.upper()] 788 try: 789 tmp = ''.join(tmp) 790 except TypeError: 791 tmp = str(tmp) 792 d[x] = tmp 793 794 code = code % d 795 self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1')) 796 self.generator.bld.raw_deps[self.uid()] = lst 797 798 # make sure the signature is updated 799 try: 800 delattr(self, 'cache_sig') 801 except AttributeError: 802 pass 803 804 self.force_permissions() 805 806 def sig_vars(self): 807 """ 808 Compute a hash (signature) of the variables used in the substitution 809 """ 810 bld = self.generator.bld 811 env = self.env 812 upd = self.m.update 813 814 if getattr(self.generator, 'fun', None): 815 upd(Utils.h_fun(self.generator.fun).encode()) 816 if getattr(self.generator, 'subst_fun', None): 817 upd(Utils.h_fun(self.generator.subst_fun).encode()) 818 819 # raw_deps: persistent custom values returned by the scanner 820 vars = self.generator.bld.raw_deps.get(self.uid(), []) 821 822 # hash both env vars and task generator attributes 823 act_sig = bld.hash_env_vars(env, vars) 824 upd(act_sig) 825 826 lst = [getattr(self.generator, x, '') for x in vars] 827 upd(Utils.h_list(lst)) 828 829 return self.m.digest() 830 831@extension('.pc.in') 832def add_pcfile(self, node): 833 """ 834 Processes *.pc.in* files to *.pc*. Installs the results to ``${PREFIX}/lib/pkgconfig/`` by default 835 836 def build(bld): 837 bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/') 838 """ 839 tsk = self.create_task('subst_pc', node, node.change_ext('.pc', '.pc.in')) 840 self.install_task = self.add_install_files( 841 install_to=getattr(self, 'install_path', '${LIBDIR}/pkgconfig/'), install_from=tsk.outputs) 842 843class subst(subst_pc): 844 pass 845 846@feature('subst') 847@before_method('process_source', 'process_rule') 848def process_subst(self): 849 """ 850 Defines a transformation that substitutes the contents of *source* files to *target* files:: 851 852 def build(bld): 853 bld( 854 features='subst', 855 source='foo.c.in', 856 target='foo.c', 857 install_path='${LIBDIR}/pkgconfig', 858 VAR = 'val' 859 ) 860 861 The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument 862 of the task generator object. 863 864 This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`. 865 """ 866 867 src = Utils.to_list(getattr(self, 'source', [])) 868 if isinstance(src, Node.Node): 869 src = [src] 870 tgt = Utils.to_list(getattr(self, 'target', [])) 871 if isinstance(tgt, Node.Node): 872 tgt = [tgt] 873 if len(src) != len(tgt): 874 raise Errors.WafError('invalid number of source/target for %r' % self) 875 876 for x, y in zip(src, tgt): 877 if not x or not y: 878 raise Errors.WafError('null source or target for %r' % self) 879 a, b = None, None 880 881 if isinstance(x, str) and isinstance(y, str) and x == y: 882 a = self.path.find_node(x) 883 b = self.path.get_bld().make_node(y) 884 if not os.path.isfile(b.abspath()): 885 b.parent.mkdir() 886 else: 887 if isinstance(x, str): 888 a = self.path.find_resource(x) 889 elif isinstance(x, Node.Node): 890 a = x 891 if isinstance(y, str): 892 b = self.path.find_or_declare(y) 893 elif isinstance(y, Node.Node): 894 b = y 895 896 if not a: 897 raise Errors.WafError('could not find %r for %r' % (x, self)) 898 899 tsk = self.create_task('subst', a, b) 900 for k in ('after', 'before', 'ext_in', 'ext_out'): 901 val = getattr(self, k, None) 902 if val: 903 setattr(tsk, k, val) 904 905 # paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies 906 for xt in HEADER_EXTS: 907 if b.name.endswith(xt): 908 tsk.ext_out = tsk.ext_out + ['.h'] 909 break 910 911 inst_to = getattr(self, 'install_path', None) 912 if inst_to: 913 self.install_task = self.add_install_files(install_to=inst_to, 914 install_from=b, chmod=getattr(self, 'chmod', Utils.O644)) 915 916 self.source = [] 917 918