1#!/usr/bin/env python 2# encoding: utf-8 3# Thomas Nagy, 2005-2018 (ita) 4 5""" 6Node: filesystem structure 7 8#. Each file/folder is represented by exactly one node. 9 10#. Some potential class properties are stored on :py:class:`waflib.Build.BuildContext` : nodes to depend on, etc. 11 Unused class members can increase the `.wafpickle` file size sensibly. 12 13#. Node objects should never be created directly, use 14 the methods :py:func:`Node.make_node` or :py:func:`Node.find_node` for the low-level operations 15 16#. The methods :py:func:`Node.find_resource`, :py:func:`Node.find_dir` :py:func:`Node.find_or_declare` must be 17 used when a build context is present 18 19#. Each instance of :py:class:`waflib.Context.Context` has a unique :py:class:`Node` subclass required for serialization. 20 (:py:class:`waflib.Node.Nod3`, see the :py:class:`waflib.Context.Context` initializer). A reference to the context 21 owning a node is held as *self.ctx* 22""" 23 24import os, re, sys, shutil 25from waflib import Utils, Errors 26 27exclude_regs = ''' 28**/*~ 29**/#*# 30**/.#* 31**/%*% 32**/._* 33**/*.swp 34**/CVS 35**/CVS/** 36**/.cvsignore 37**/SCCS 38**/SCCS/** 39**/vssver.scc 40**/.svn 41**/.svn/** 42**/BitKeeper 43**/.git 44**/.git/** 45**/.gitignore 46**/.bzr 47**/.bzrignore 48**/.bzr/** 49**/.hg 50**/.hg/** 51**/_MTN 52**/_MTN/** 53**/.arch-ids 54**/{arch} 55**/_darcs 56**/_darcs/** 57**/.intlcache 58**/.DS_Store''' 59""" 60Ant patterns for files and folders to exclude while doing the 61recursive traversal in :py:meth:`waflib.Node.Node.ant_glob` 62""" 63 64def ant_matcher(s, ignorecase): 65 reflags = re.I if ignorecase else 0 66 ret = [] 67 for x in Utils.to_list(s): 68 x = x.replace('\\', '/').replace('//', '/') 69 if x.endswith('/'): 70 x += '**' 71 accu = [] 72 for k in x.split('/'): 73 if k == '**': 74 accu.append(k) 75 else: 76 k = k.replace('.', '[.]').replace('*', '.*').replace('?', '.').replace('+', '\\+') 77 k = '^%s$' % k 78 try: 79 exp = re.compile(k, flags=reflags) 80 except Exception as e: 81 raise Errors.WafError('Invalid pattern: %s' % k, e) 82 else: 83 accu.append(exp) 84 ret.append(accu) 85 return ret 86 87def ant_sub_filter(name, nn): 88 ret = [] 89 for lst in nn: 90 if not lst: 91 pass 92 elif lst[0] == '**': 93 ret.append(lst) 94 if len(lst) > 1: 95 if lst[1].match(name): 96 ret.append(lst[2:]) 97 else: 98 ret.append([]) 99 elif lst[0].match(name): 100 ret.append(lst[1:]) 101 return ret 102 103def ant_sub_matcher(name, pats): 104 nacc = ant_sub_filter(name, pats[0]) 105 nrej = ant_sub_filter(name, pats[1]) 106 if [] in nrej: 107 nacc = [] 108 return [nacc, nrej] 109 110class Node(object): 111 """ 112 This class is organized in two parts: 113 114 * The basic methods meant for filesystem access (compute paths, create folders, etc) 115 * The methods bound to a :py:class:`waflib.Build.BuildContext` (require ``bld.srcnode`` and ``bld.bldnode``) 116 """ 117 118 dict_class = dict 119 """ 120 Subclasses can provide a dict class to enable case insensitivity for example. 121 """ 122 123 __slots__ = ('name', 'parent', 'children', 'cache_abspath', 'cache_isdir') 124 def __init__(self, name, parent): 125 """ 126 .. note:: Use :py:func:`Node.make_node` or :py:func:`Node.find_node` instead of calling this constructor 127 """ 128 self.name = name 129 self.parent = parent 130 if parent: 131 if name in parent.children: 132 raise Errors.WafError('node %s exists in the parent files %r already' % (name, parent)) 133 parent.children[name] = self 134 135 def __setstate__(self, data): 136 "Deserializes node information, used for persistence" 137 self.name = data[0] 138 self.parent = data[1] 139 if data[2] is not None: 140 # Issue 1480 141 self.children = self.dict_class(data[2]) 142 143 def __getstate__(self): 144 "Serializes node information, used for persistence" 145 return (self.name, self.parent, getattr(self, 'children', None)) 146 147 def __str__(self): 148 """ 149 String representation (abspath), for debugging purposes 150 151 :rtype: string 152 """ 153 return self.abspath() 154 155 def __repr__(self): 156 """ 157 String representation (abspath), for debugging purposes 158 159 :rtype: string 160 """ 161 return self.abspath() 162 163 def __copy__(self): 164 """ 165 Provided to prevent nodes from being copied 166 167 :raises: :py:class:`waflib.Errors.WafError` 168 """ 169 raise Errors.WafError('nodes are not supposed to be copied') 170 171 def read(self, flags='r', encoding='latin-1'): 172 """ 173 Reads and returns the contents of the file represented by this node, see :py:func:`waflib.Utils.readf`:: 174 175 def build(bld): 176 bld.path.find_node('wscript').read() 177 178 :param flags: Open mode 179 :type flags: string 180 :param encoding: encoding value for Python3 181 :type encoding: string 182 :rtype: string or bytes 183 :return: File contents 184 """ 185 return Utils.readf(self.abspath(), flags, encoding) 186 187 def write(self, data, flags='w', encoding='latin-1'): 188 """ 189 Writes data to the file represented by this node, see :py:func:`waflib.Utils.writef`:: 190 191 def build(bld): 192 bld.path.make_node('foo.txt').write('Hello, world!') 193 194 :param data: data to write 195 :type data: string 196 :param flags: Write mode 197 :type flags: string 198 :param encoding: encoding value for Python3 199 :type encoding: string 200 """ 201 Utils.writef(self.abspath(), data, flags, encoding) 202 203 def read_json(self, convert=True, encoding='utf-8'): 204 """ 205 Reads and parses the contents of this node as JSON (Python ≥ 2.6):: 206 207 def build(bld): 208 bld.path.find_node('abc.json').read_json() 209 210 Note that this by default automatically decodes unicode strings on Python2, unlike what the Python JSON module does. 211 212 :type convert: boolean 213 :param convert: Prevents decoding of unicode strings on Python2 214 :type encoding: string 215 :param encoding: The encoding of the file to read. This default to UTF8 as per the JSON standard 216 :rtype: object 217 :return: Parsed file contents 218 """ 219 import json # Python 2.6 and up 220 object_pairs_hook = None 221 if convert and sys.hexversion < 0x3000000: 222 try: 223 _type = unicode 224 except NameError: 225 _type = str 226 227 def convert(value): 228 if isinstance(value, list): 229 return [convert(element) for element in value] 230 elif isinstance(value, _type): 231 return str(value) 232 else: 233 return value 234 235 def object_pairs(pairs): 236 return dict((str(pair[0]), convert(pair[1])) for pair in pairs) 237 238 object_pairs_hook = object_pairs 239 240 return json.loads(self.read(encoding=encoding), object_pairs_hook=object_pairs_hook) 241 242 def write_json(self, data, pretty=True): 243 """ 244 Writes a python object as JSON to disk (Python ≥ 2.6) as UTF-8 data (JSON standard):: 245 246 def build(bld): 247 bld.path.find_node('xyz.json').write_json(199) 248 249 :type data: object 250 :param data: The data to write to disk 251 :type pretty: boolean 252 :param pretty: Determines if the JSON will be nicely space separated 253 """ 254 import json # Python 2.6 and up 255 indent = 2 256 separators = (',', ': ') 257 sort_keys = pretty 258 newline = os.linesep 259 if not pretty: 260 indent = None 261 separators = (',', ':') 262 newline = '' 263 output = json.dumps(data, indent=indent, separators=separators, sort_keys=sort_keys) + newline 264 self.write(output, encoding='utf-8') 265 266 def exists(self): 267 """ 268 Returns whether the Node is present on the filesystem 269 270 :rtype: bool 271 """ 272 return os.path.exists(self.abspath()) 273 274 def isdir(self): 275 """ 276 Returns whether the Node represents a folder 277 278 :rtype: bool 279 """ 280 return os.path.isdir(self.abspath()) 281 282 def chmod(self, val): 283 """ 284 Changes the file/dir permissions:: 285 286 def build(bld): 287 bld.path.chmod(493) # 0755 288 """ 289 os.chmod(self.abspath(), val) 290 291 def delete(self, evict=True): 292 """ 293 Removes the file/folder from the filesystem (equivalent to `rm -rf`), and remove this object from the Node tree. 294 Do not use this object after calling this method. 295 """ 296 try: 297 try: 298 if os.path.isdir(self.abspath()): 299 shutil.rmtree(self.abspath()) 300 else: 301 os.remove(self.abspath()) 302 except OSError: 303 if os.path.exists(self.abspath()): 304 raise 305 finally: 306 if evict: 307 self.evict() 308 309 def evict(self): 310 """ 311 Removes this node from the Node tree 312 """ 313 del self.parent.children[self.name] 314 315 def suffix(self): 316 """ 317 Returns the file rightmost extension, for example `a.b.c.d → .d` 318 319 :rtype: string 320 """ 321 k = max(0, self.name.rfind('.')) 322 return self.name[k:] 323 324 def height(self): 325 """ 326 Returns the depth in the folder hierarchy from the filesystem root or from all the file drives 327 328 :returns: filesystem depth 329 :rtype: integer 330 """ 331 d = self 332 val = -1 333 while d: 334 d = d.parent 335 val += 1 336 return val 337 338 def listdir(self): 339 """ 340 Lists the folder contents 341 342 :returns: list of file/folder names ordered alphabetically 343 :rtype: list of string 344 """ 345 lst = Utils.listdir(self.abspath()) 346 lst.sort() 347 return lst 348 349 def mkdir(self): 350 """ 351 Creates a folder represented by this node. Intermediate folders are created as needed. 352 353 :raises: :py:class:`waflib.Errors.WafError` when the folder is missing 354 """ 355 if self.isdir(): 356 return 357 358 try: 359 self.parent.mkdir() 360 except OSError: 361 pass 362 363 if self.name: 364 try: 365 os.makedirs(self.abspath()) 366 except OSError: 367 pass 368 369 if not self.isdir(): 370 raise Errors.WafError('Could not create the directory %r' % self) 371 372 try: 373 self.children 374 except AttributeError: 375 self.children = self.dict_class() 376 377 def find_node(self, lst): 378 """ 379 Finds a node on the file system (files or folders), and creates the corresponding Node objects if it exists 380 381 :param lst: relative path 382 :type lst: string or list of string 383 :returns: The corresponding Node object or None if no entry was found on the filesystem 384 :rtype: :py:class:´waflib.Node.Node´ 385 """ 386 387 if isinstance(lst, str): 388 lst = [x for x in Utils.split_path(lst) if x and x != '.'] 389 390 if lst and lst[0].startswith('\\\\') and not self.parent: 391 node = self.ctx.root.make_node(lst[0]) 392 node.cache_isdir = True 393 return node.find_node(lst[1:]) 394 395 cur = self 396 for x in lst: 397 if x == '..': 398 cur = cur.parent or cur 399 continue 400 401 try: 402 ch = cur.children 403 except AttributeError: 404 cur.children = self.dict_class() 405 else: 406 try: 407 cur = ch[x] 408 continue 409 except KeyError: 410 pass 411 412 # optimistic: create the node first then look if it was correct to do so 413 cur = self.__class__(x, cur) 414 if not cur.exists(): 415 cur.evict() 416 return None 417 418 if not cur.exists(): 419 cur.evict() 420 return None 421 422 return cur 423 424 def make_node(self, lst): 425 """ 426 Returns or creates a Node object corresponding to the input path without considering the filesystem. 427 428 :param lst: relative path 429 :type lst: string or list of string 430 :rtype: :py:class:´waflib.Node.Node´ 431 """ 432 if isinstance(lst, str): 433 lst = [x for x in Utils.split_path(lst) if x and x != '.'] 434 435 cur = self 436 for x in lst: 437 if x == '..': 438 cur = cur.parent or cur 439 continue 440 441 try: 442 cur = cur.children[x] 443 except AttributeError: 444 cur.children = self.dict_class() 445 except KeyError: 446 pass 447 else: 448 continue 449 cur = self.__class__(x, cur) 450 return cur 451 452 def search_node(self, lst): 453 """ 454 Returns a Node previously defined in the data structure. The filesystem is not considered. 455 456 :param lst: relative path 457 :type lst: string or list of string 458 :rtype: :py:class:´waflib.Node.Node´ or None if there is no entry in the Node datastructure 459 """ 460 if isinstance(lst, str): 461 lst = [x for x in Utils.split_path(lst) if x and x != '.'] 462 463 cur = self 464 for x in lst: 465 if x == '..': 466 cur = cur.parent or cur 467 else: 468 try: 469 cur = cur.children[x] 470 except (AttributeError, KeyError): 471 return None 472 return cur 473 474 def path_from(self, node): 475 """ 476 Path of this node seen from the other:: 477 478 def build(bld): 479 n1 = bld.path.find_node('foo/bar/xyz.txt') 480 n2 = bld.path.find_node('foo/stuff/') 481 n1.path_from(n2) # '../bar/xyz.txt' 482 483 :param node: path to use as a reference 484 :type node: :py:class:`waflib.Node.Node` 485 :returns: a relative path or an absolute one if that is better 486 :rtype: string 487 """ 488 c1 = self 489 c2 = node 490 491 c1h = c1.height() 492 c2h = c2.height() 493 494 lst = [] 495 up = 0 496 497 while c1h > c2h: 498 lst.append(c1.name) 499 c1 = c1.parent 500 c1h -= 1 501 502 while c2h > c1h: 503 up += 1 504 c2 = c2.parent 505 c2h -= 1 506 507 while not c1 is c2: 508 lst.append(c1.name) 509 up += 1 510 511 c1 = c1.parent 512 c2 = c2.parent 513 514 if c1.parent: 515 lst.extend(['..'] * up) 516 lst.reverse() 517 return os.sep.join(lst) or '.' 518 else: 519 return self.abspath() 520 521 def abspath(self): 522 """ 523 Returns the absolute path. A cache is kept in the context as ``cache_node_abspath`` 524 525 :rtype: string 526 """ 527 try: 528 return self.cache_abspath 529 except AttributeError: 530 pass 531 # think twice before touching this (performance + complexity + correctness) 532 533 if not self.parent: 534 val = os.sep 535 elif not self.parent.name: 536 val = os.sep + self.name 537 else: 538 val = self.parent.abspath() + os.sep + self.name 539 self.cache_abspath = val 540 return val 541 542 if Utils.is_win32: 543 def abspath(self): 544 try: 545 return self.cache_abspath 546 except AttributeError: 547 pass 548 if not self.parent: 549 val = '' 550 elif not self.parent.name: 551 val = self.name + os.sep 552 else: 553 val = self.parent.abspath().rstrip(os.sep) + os.sep + self.name 554 self.cache_abspath = val 555 return val 556 557 def is_child_of(self, node): 558 """ 559 Returns whether the object belongs to a subtree of the input node:: 560 561 def build(bld): 562 node = bld.path.find_node('wscript') 563 node.is_child_of(bld.path) # True 564 565 :param node: path to use as a reference 566 :type node: :py:class:`waflib.Node.Node` 567 :rtype: bool 568 """ 569 p = self 570 diff = self.height() - node.height() 571 while diff > 0: 572 diff -= 1 573 p = p.parent 574 return p is node 575 576 def ant_iter(self, accept=None, maxdepth=25, pats=[], dir=False, src=True, remove=True, quiet=False): 577 """ 578 Recursive method used by :py:meth:`waflib.Node.ant_glob`. 579 580 :param accept: function used for accepting/rejecting a node, returns the patterns that can be still accepted in recursion 581 :type accept: function 582 :param maxdepth: maximum depth in the filesystem (25) 583 :type maxdepth: int 584 :param pats: list of patterns to accept and list of patterns to exclude 585 :type pats: tuple 586 :param dir: return folders too (False by default) 587 :type dir: bool 588 :param src: return files (True by default) 589 :type src: bool 590 :param remove: remove files/folders that do not exist (True by default) 591 :type remove: bool 592 :param quiet: disable build directory traversal warnings (verbose mode) 593 :type quiet: bool 594 :returns: A generator object to iterate from 595 :rtype: iterator 596 """ 597 dircont = self.listdir() 598 599 try: 600 lst = set(self.children.keys()) 601 except AttributeError: 602 self.children = self.dict_class() 603 else: 604 if remove: 605 for x in lst - set(dircont): 606 self.children[x].evict() 607 608 for name in dircont: 609 npats = accept(name, pats) 610 if npats and npats[0]: 611 accepted = [] in npats[0] 612 613 node = self.make_node([name]) 614 615 isdir = node.isdir() 616 if accepted: 617 if isdir: 618 if dir: 619 yield node 620 elif src: 621 yield node 622 623 if isdir: 624 node.cache_isdir = True 625 if maxdepth: 626 for k in node.ant_iter(accept=accept, maxdepth=maxdepth - 1, pats=npats, dir=dir, src=src, remove=remove, quiet=quiet): 627 yield k 628 629 def ant_glob(self, *k, **kw): 630 """ 631 Finds files across folders and returns Node objects: 632 633 * ``**/*`` find all files recursively 634 * ``**/*.class`` find all files ending by .class 635 * ``..`` find files having two dot characters 636 637 For example:: 638 639 def configure(cfg): 640 # find all .cpp files 641 cfg.path.ant_glob('**/*.cpp') 642 # find particular files from the root filesystem (can be slow) 643 cfg.root.ant_glob('etc/*.txt') 644 # simple exclusion rule example 645 cfg.path.ant_glob('*.c*', excl=['*.c'], src=True, dir=False) 646 647 For more information about the patterns, consult http://ant.apache.org/manual/dirtasks.html 648 Please remember that the '..' sequence does not represent the parent directory:: 649 650 def configure(cfg): 651 cfg.path.ant_glob('../*.h') # incorrect 652 cfg.path.parent.ant_glob('*.h') # correct 653 654 The Node structure is itself a filesystem cache, so certain precautions must 655 be taken while matching files in the build or installation phases. 656 Nodes objects that do have a corresponding file or folder are garbage-collected by default. 657 This garbage collection is usually required to prevent returning files that do not 658 exist anymore. Yet, this may also remove Node objects of files that are yet-to-be built. 659 660 This typically happens when trying to match files in the build directory, 661 but there are also cases when files are created in the source directory. 662 Run ``waf -v`` to display any warnings, and try consider passing ``remove=False`` 663 when matching files in the build directory. 664 665 Since ant_glob can traverse both source and build folders, it is a best practice 666 to call this method only from the most specific build node:: 667 668 def build(bld): 669 # traverses the build directory, may need ``remove=False``: 670 bld.path.ant_glob('project/dir/**/*.h') 671 # better, no accidental build directory traversal: 672 bld.path.find_node('project/dir').ant_glob('**/*.h') # best 673 674 In addition, files and folders are listed immediately. When matching files in the 675 build folders, consider passing ``generator=True`` so that the generator object 676 returned can defer computation to a later stage. For example:: 677 678 def build(bld): 679 bld(rule='tar xvf ${SRC}', source='arch.tar') 680 bld.add_group() 681 gen = bld.bldnode.ant_glob("*.h", generator=True, remove=True) 682 # files will be listed only after the arch.tar is unpacked 683 bld(rule='ls ${SRC}', source=gen, name='XYZ') 684 685 686 :param incl: ant patterns or list of patterns to include 687 :type incl: string or list of strings 688 :param excl: ant patterns or list of patterns to exclude 689 :type excl: string or list of strings 690 :param dir: return folders too (False by default) 691 :type dir: bool 692 :param src: return files (True by default) 693 :type src: bool 694 :param maxdepth: maximum depth of recursion 695 :type maxdepth: int 696 :param ignorecase: ignore case while matching (False by default) 697 :type ignorecase: bool 698 :param generator: Whether to evaluate the Nodes lazily 699 :type generator: bool 700 :param remove: remove files/folders that do not exist (True by default) 701 :type remove: bool 702 :param quiet: disable build directory traversal warnings (verbose mode) 703 :type quiet: bool 704 :returns: The corresponding Node objects as a list or as a generator object (generator=True) 705 :rtype: by default, list of :py:class:`waflib.Node.Node` instances 706 """ 707 src = kw.get('src', True) 708 dir = kw.get('dir') 709 excl = kw.get('excl', exclude_regs) 710 incl = k and k[0] or kw.get('incl', '**') 711 remove = kw.get('remove', True) 712 maxdepth = kw.get('maxdepth', 25) 713 ignorecase = kw.get('ignorecase', False) 714 quiet = kw.get('quiet', False) 715 pats = (ant_matcher(incl, ignorecase), ant_matcher(excl, ignorecase)) 716 717 if kw.get('generator'): 718 return Utils.lazy_generator(self.ant_iter, (ant_sub_matcher, maxdepth, pats, dir, src, remove, quiet)) 719 720 it = self.ant_iter(ant_sub_matcher, maxdepth, pats, dir, src, remove, quiet) 721 if kw.get('flat'): 722 # returns relative paths as a space-delimited string 723 # prefer Node objects whenever possible 724 return ' '.join(x.path_from(self) for x in it) 725 return list(it) 726 727 # ---------------------------------------------------------------------------- 728 # the methods below require the source/build folders (bld.srcnode/bld.bldnode) 729 730 def is_src(self): 731 """ 732 Returns True if the node is below the source directory. Note that ``!is_src() ≠ is_bld()`` 733 734 :rtype: bool 735 """ 736 cur = self 737 x = self.ctx.srcnode 738 y = self.ctx.bldnode 739 while cur.parent: 740 if cur is y: 741 return False 742 if cur is x: 743 return True 744 cur = cur.parent 745 return False 746 747 def is_bld(self): 748 """ 749 Returns True if the node is below the build directory. Note that ``!is_bld() ≠ is_src()`` 750 751 :rtype: bool 752 """ 753 cur = self 754 y = self.ctx.bldnode 755 while cur.parent: 756 if cur is y: 757 return True 758 cur = cur.parent 759 return False 760 761 def get_src(self): 762 """ 763 Returns the corresponding Node object in the source directory (or self if already 764 under the source directory). Use this method only if the purpose is to create 765 a Node object (this is common with folders but not with files, see ticket 1937) 766 767 :rtype: :py:class:`waflib.Node.Node` 768 """ 769 cur = self 770 x = self.ctx.srcnode 771 y = self.ctx.bldnode 772 lst = [] 773 while cur.parent: 774 if cur is y: 775 lst.reverse() 776 return x.make_node(lst) 777 if cur is x: 778 return self 779 lst.append(cur.name) 780 cur = cur.parent 781 return self 782 783 def get_bld(self): 784 """ 785 Return the corresponding Node object in the build directory (or self if already 786 under the build directory). Use this method only if the purpose is to create 787 a Node object (this is common with folders but not with files, see ticket 1937) 788 789 :rtype: :py:class:`waflib.Node.Node` 790 """ 791 cur = self 792 x = self.ctx.srcnode 793 y = self.ctx.bldnode 794 lst = [] 795 while cur.parent: 796 if cur is y: 797 return self 798 if cur is x: 799 lst.reverse() 800 return self.ctx.bldnode.make_node(lst) 801 lst.append(cur.name) 802 cur = cur.parent 803 # the file is external to the current project, make a fake root in the current build directory 804 lst.reverse() 805 if lst and Utils.is_win32 and len(lst[0]) == 2 and lst[0].endswith(':'): 806 lst[0] = lst[0][0] 807 return self.ctx.bldnode.make_node(['__root__'] + lst) 808 809 def find_resource(self, lst): 810 """ 811 Use this method in the build phase to find source files corresponding to the relative path given. 812 813 First it looks up the Node data structure to find any declared Node object in the build directory. 814 If None is found, it then considers the filesystem in the source directory. 815 816 :param lst: relative path 817 :type lst: string or list of string 818 :returns: the corresponding Node object or None 819 :rtype: :py:class:`waflib.Node.Node` 820 """ 821 if isinstance(lst, str): 822 lst = [x for x in Utils.split_path(lst) if x and x != '.'] 823 824 node = self.get_bld().search_node(lst) 825 if not node: 826 node = self.get_src().find_node(lst) 827 if node and node.isdir(): 828 return None 829 return node 830 831 def find_or_declare(self, lst): 832 """ 833 Use this method in the build phase to declare output files which 834 are meant to be written in the build directory. 835 836 This method creates the Node object and its parent folder 837 as needed. 838 839 :param lst: relative path 840 :type lst: string or list of string 841 """ 842 if isinstance(lst, str) and os.path.isabs(lst): 843 node = self.ctx.root.make_node(lst) 844 else: 845 node = self.get_bld().make_node(lst) 846 node.parent.mkdir() 847 return node 848 849 def find_dir(self, lst): 850 """ 851 Searches for a folder on the filesystem (see :py:meth:`waflib.Node.Node.find_node`) 852 853 :param lst: relative path 854 :type lst: string or list of string 855 :returns: The corresponding Node object or None if there is no such folder 856 :rtype: :py:class:`waflib.Node.Node` 857 """ 858 if isinstance(lst, str): 859 lst = [x for x in Utils.split_path(lst) if x and x != '.'] 860 861 node = self.find_node(lst) 862 if node and not node.isdir(): 863 return None 864 return node 865 866 # helpers for building things 867 def change_ext(self, ext, ext_in=None): 868 """ 869 Declares a build node with a distinct extension; this is uses :py:meth:`waflib.Node.Node.find_or_declare` 870 871 :return: A build node of the same path, but with a different extension 872 :rtype: :py:class:`waflib.Node.Node` 873 """ 874 name = self.name 875 if ext_in is None: 876 k = name.rfind('.') 877 if k >= 0: 878 name = name[:k] + ext 879 else: 880 name = name + ext 881 else: 882 name = name[:- len(ext_in)] + ext 883 884 return self.parent.find_or_declare([name]) 885 886 def bldpath(self): 887 """ 888 Returns the relative path seen from the build directory ``src/foo.cpp`` 889 890 :rtype: string 891 """ 892 return self.path_from(self.ctx.bldnode) 893 894 def srcpath(self): 895 """ 896 Returns the relative path seen from the source directory ``../src/foo.cpp`` 897 898 :rtype: string 899 """ 900 return self.path_from(self.ctx.srcnode) 901 902 def relpath(self): 903 """ 904 If a file in the build directory, returns :py:meth:`waflib.Node.Node.bldpath`, 905 else returns :py:meth:`waflib.Node.Node.srcpath` 906 907 :rtype: string 908 """ 909 cur = self 910 x = self.ctx.bldnode 911 while cur.parent: 912 if cur is x: 913 return self.bldpath() 914 cur = cur.parent 915 return self.srcpath() 916 917 def bld_dir(self): 918 """ 919 Equivalent to self.parent.bldpath() 920 921 :rtype: string 922 """ 923 return self.parent.bldpath() 924 925 def h_file(self): 926 """ 927 See :py:func:`waflib.Utils.h_file` 928 929 :return: a hash representing the file contents 930 :rtype: string or bytes 931 """ 932 return Utils.h_file(self.abspath()) 933 934 def get_bld_sig(self): 935 """ 936 Returns a signature (see :py:meth:`waflib.Node.Node.h_file`) for the purpose 937 of build dependency calculation. This method uses a per-context cache. 938 939 :return: a hash representing the object contents 940 :rtype: string or bytes 941 """ 942 # previous behaviour can be set by returning self.ctx.node_sigs[self] when a build node 943 try: 944 cache = self.ctx.cache_sig 945 except AttributeError: 946 cache = self.ctx.cache_sig = {} 947 try: 948 ret = cache[self] 949 except KeyError: 950 p = self.abspath() 951 try: 952 ret = cache[self] = self.h_file() 953 except EnvironmentError: 954 if self.isdir(): 955 # allow folders as build nodes, do not use the creation time 956 st = os.stat(p) 957 ret = cache[self] = Utils.h_list([p, st.st_ino, st.st_mode]) 958 return ret 959 raise 960 return ret 961 962pickle_lock = Utils.threading.Lock() 963"""Lock mandatory for thread-safe node serialization""" 964 965class Nod3(Node): 966 """Mandatory subclass for thread-safe node serialization""" 967 pass # do not remove 968 969 970