TaskGen.py (26525B)
1 #!/usr/bin/env python 2 # encoding: utf-8 3 # Thomas Nagy, 2005-2018 (ita) 4 5 """ 6 Task generators 7 8 The class :py:class:`waflib.TaskGen.task_gen` encapsulates the creation of task objects (low-level code) 9 The instances can have various parameters, but the creation of task nodes (Task.py) 10 is deferred. To achieve this, various methods are called from the method "apply" 11 """ 12 13 import copy, re, os, functools 14 from waflib import Task, Utils, Logs, Errors, ConfigSet, Node 15 16 feats = Utils.defaultdict(set) 17 """remember the methods declaring features""" 18 19 HEADER_EXTS = ['.h', '.hpp', '.hxx', '.hh'] 20 21 class 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 315 def 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 381 def 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 398 def 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.TaskGen 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 420 def 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 445 before = before_method 446 447 def 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 472 after = after_method 473 474 def 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 496 def 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('*') 530 def 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') 546 def 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 deps = getattr(self.generator, 'deps', None) 635 nodes = self.generator.to_nodes(deps) 636 return [nodes, []] 637 cls.scan = scan 638 639 if use_cache: 640 cache[key] = cls 641 642 # now create one instance 643 tsk = self.create_task(name) 644 645 for x in ('after', 'before', 'ext_in', 'ext_out'): 646 setattr(tsk, x, getattr(self, x, [])) 647 648 if hasattr(self, 'stdout'): 649 tsk.stdout = self.stdout 650 651 if hasattr(self, 'stderr'): 652 tsk.stderr = self.stderr 653 654 if getattr(self, 'timeout', None): 655 tsk.timeout = self.timeout 656 657 if getattr(self, 'always', None): 658 tsk.always_run = True 659 660 if getattr(self, 'target', None): 661 if isinstance(self.target, str): 662 self.target = self.target.split() 663 if not isinstance(self.target, list): 664 self.target = [self.target] 665 for x in self.target: 666 if isinstance(x, str): 667 tsk.outputs.append(self.path.find_or_declare(x)) 668 else: 669 x.parent.mkdir() # if a node was given, create the required folders 670 tsk.outputs.append(x) 671 if getattr(self, 'install_path', None): 672 self.install_task = self.add_install_files(install_to=self.install_path, 673 install_from=tsk.outputs, chmod=getattr(self, 'chmod', Utils.O644)) 674 675 if getattr(self, 'source', None): 676 tsk.inputs = self.to_nodes(self.source) 677 # bypass the execution of process_source by setting the source to an empty list 678 self.source = [] 679 680 if getattr(self, 'cwd', None): 681 tsk.cwd = self.cwd 682 683 if isinstance(tsk.run, functools.partial): 684 # Python documentation says: "partial objects defined in classes 685 # behave like static methods and do not transform into bound 686 # methods during instance attribute look-up." 687 tsk.run = functools.partial(tsk.run, tsk) 688 689 @feature('seq') 690 def sequence_order(self): 691 """ 692 Adds a strict sequential constraint between the tasks generated by task generators. 693 It works because task generators are posted in order. 694 It will not post objects which belong to other folders. 695 696 Example:: 697 698 bld(features='javac seq') 699 bld(features='jar seq') 700 701 To start a new sequence, set the attribute seq_start, for example:: 702 703 obj = bld(features='seq') 704 obj.seq_start = True 705 706 Note that the method is executed in last position. This is more an 707 example than a widely-used solution. 708 """ 709 if self.meths and self.meths[-1] != 'sequence_order': 710 self.meths.append('sequence_order') 711 return 712 713 if getattr(self, 'seq_start', None): 714 return 715 716 # all the tasks previously declared must be run before these 717 if getattr(self.bld, 'prev', None): 718 self.bld.prev.post() 719 for x in self.bld.prev.tasks: 720 for y in self.tasks: 721 y.set_run_after(x) 722 723 self.bld.prev = self 724 725 726 re_m4 = re.compile(r'@(\w+)@', re.M) 727 728 class subst_pc(Task.Task): 729 """ 730 Creates *.pc* files from *.pc.in*. The task is executed whenever an input variable used 731 in the substitution changes. 732 """ 733 734 def force_permissions(self): 735 "Private for the time being, we will probably refactor this into run_str=[run1,chmod]" 736 if getattr(self.generator, 'chmod', None): 737 for x in self.outputs: 738 os.chmod(x.abspath(), self.generator.chmod) 739 740 def run(self): 741 "Substitutes variables in a .in file" 742 743 if getattr(self.generator, 'is_copy', None): 744 for i, x in enumerate(self.outputs): 745 x.write(self.inputs[i].read('rb'), 'wb') 746 stat = os.stat(self.inputs[i].abspath()) # Preserve mtime of the copy 747 os.utime(self.outputs[i].abspath(), (stat.st_atime, stat.st_mtime)) 748 self.force_permissions() 749 return None 750 751 if getattr(self.generator, 'fun', None): 752 ret = self.generator.fun(self) 753 if not ret: 754 self.force_permissions() 755 return ret 756 757 code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'latin-1')) 758 if getattr(self.generator, 'subst_fun', None): 759 code = self.generator.subst_fun(self, code) 760 if code is not None: 761 self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1')) 762 self.force_permissions() 763 return None 764 765 # replace all % by %% to prevent errors by % signs 766 code = code.replace('%', '%%') 767 768 # extract the vars foo into lst and replace @foo@ by %(foo)s 769 lst = [] 770 def repl(match): 771 g = match.group 772 if g(1): 773 lst.append(g(1)) 774 return "%%(%s)s" % g(1) 775 return '' 776 code = getattr(self.generator, 're_m4', re_m4).sub(repl, code) 777 778 try: 779 d = self.generator.dct 780 except AttributeError: 781 d = {} 782 for x in lst: 783 tmp = getattr(self.generator, x, '') or self.env[x] or self.env[x.upper()] 784 try: 785 tmp = ''.join(tmp) 786 except TypeError: 787 tmp = str(tmp) 788 d[x] = tmp 789 790 code = code % d 791 self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1')) 792 self.generator.bld.raw_deps[self.uid()] = lst 793 794 # make sure the signature is updated 795 try: 796 delattr(self, 'cache_sig') 797 except AttributeError: 798 pass 799 800 self.force_permissions() 801 802 def sig_vars(self): 803 """ 804 Compute a hash (signature) of the variables used in the substitution 805 """ 806 bld = self.generator.bld 807 env = self.env 808 upd = self.m.update 809 810 if getattr(self.generator, 'fun', None): 811 upd(Utils.h_fun(self.generator.fun).encode()) 812 if getattr(self.generator, 'subst_fun', None): 813 upd(Utils.h_fun(self.generator.subst_fun).encode()) 814 815 # raw_deps: persistent custom values returned by the scanner 816 vars = self.generator.bld.raw_deps.get(self.uid(), []) 817 818 # hash both env vars and task generator attributes 819 act_sig = bld.hash_env_vars(env, vars) 820 upd(act_sig) 821 822 lst = [getattr(self.generator, x, '') for x in vars] 823 upd(Utils.h_list(lst)) 824 825 return self.m.digest() 826 827 @extension('.pc.in') 828 def add_pcfile(self, node): 829 """ 830 Processes *.pc.in* files to *.pc*. Installs the results to ``${PREFIX}/lib/pkgconfig/`` by default 831 832 def build(bld): 833 bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/') 834 """ 835 tsk = self.create_task('subst_pc', node, node.change_ext('.pc', '.pc.in')) 836 self.install_task = self.add_install_files( 837 install_to=getattr(self, 'install_path', '${LIBDIR}/pkgconfig/'), install_from=tsk.outputs) 838 839 class subst(subst_pc): 840 pass 841 842 @feature('subst') 843 @before_method('process_source', 'process_rule') 844 def process_subst(self): 845 """ 846 Defines a transformation that substitutes the contents of *source* files to *target* files:: 847 848 def build(bld): 849 bld( 850 features='subst', 851 source='foo.c.in', 852 target='foo.c', 853 install_path='${LIBDIR}/pkgconfig', 854 VAR = 'val' 855 ) 856 857 The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument 858 of the task generator object. 859 860 This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`. 861 """ 862 863 src = Utils.to_list(getattr(self, 'source', [])) 864 if isinstance(src, Node.Node): 865 src = [src] 866 tgt = Utils.to_list(getattr(self, 'target', [])) 867 if isinstance(tgt, Node.Node): 868 tgt = [tgt] 869 if len(src) != len(tgt): 870 raise Errors.WafError('invalid number of source/target for %r' % self) 871 872 for x, y in zip(src, tgt): 873 if not x or not y: 874 raise Errors.WafError('null source or target for %r' % self) 875 a, b = None, None 876 877 if isinstance(x, str) and isinstance(y, str) and x == y: 878 a = self.path.find_node(x) 879 b = self.path.get_bld().make_node(y) 880 if not os.path.isfile(b.abspath()): 881 b.parent.mkdir() 882 else: 883 if isinstance(x, str): 884 a = self.path.find_resource(x) 885 elif isinstance(x, Node.Node): 886 a = x 887 if isinstance(y, str): 888 b = self.path.find_or_declare(y) 889 elif isinstance(y, Node.Node): 890 b = y 891 892 if not a: 893 raise Errors.WafError('could not find %r for %r' % (x, self)) 894 895 tsk = self.create_task('subst', a, b) 896 for k in ('after', 'before', 'ext_in', 'ext_out'): 897 val = getattr(self, k, None) 898 if val: 899 setattr(tsk, k, val) 900 901 # paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies 902 for xt in HEADER_EXTS: 903 if b.name.endswith(xt): 904 tsk.ext_out = tsk.ext_out + ['.h'] 905 break 906 907 inst_to = getattr(self, 'install_path', None) 908 if inst_to: 909 self.install_task = self.add_install_files(install_to=inst_to, 910 install_from=b, chmod=getattr(self, 'chmod', Utils.O644)) 911 912 self.source = [] 913