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.

789 lines
19KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2010 (ita)
  4. """
  5. Node: filesystem structure, contains lists of nodes
  6. #. Each file/folder is represented by exactly one node.
  7. #. Some potential class properties are stored on :py:class:`waflib.Build.BuildContext` : nodes to depend on, etc.
  8. Unused class members can increase the `.wafpickle` file size sensibly.
  9. #. Node objects should never be created directly, use
  10. the methods :py:func:`Node.make_node` or :py:func:`Node.find_node`
  11. #. The methods :py:func:`Node.find_resource`, :py:func:`Node.find_dir` :py:func:`Node.find_or_declare` should be
  12. used when a build context is present
  13. #. Each instance of :py:class:`waflib.Context.Context` has a unique :py:class:`Node` subclass.
  14. (:py:class:`waflib.Node.Nod3`, see the :py:class:`waflib.Context.Context` initializer). A reference to the context owning a node is held as self.ctx
  15. """
  16. import os, re, sys, shutil
  17. from waflib import Utils, Errors
  18. exclude_regs = '''
  19. **/*~
  20. **/#*#
  21. **/.#*
  22. **/%*%
  23. **/._*
  24. **/CVS
  25. **/CVS/**
  26. **/.cvsignore
  27. **/SCCS
  28. **/SCCS/**
  29. **/vssver.scc
  30. **/.svn
  31. **/.svn/**
  32. **/BitKeeper
  33. **/.git
  34. **/.git/**
  35. **/.gitignore
  36. **/.bzr
  37. **/.bzrignore
  38. **/.bzr/**
  39. **/.hg
  40. **/.hg/**
  41. **/_MTN
  42. **/_MTN/**
  43. **/.arch-ids
  44. **/{arch}
  45. **/_darcs
  46. **/_darcs/**
  47. **/.intlcache
  48. **/.DS_Store'''
  49. """
  50. Ant patterns for files and folders to exclude while doing the
  51. recursive traversal in :py:meth:`waflib.Node.Node.ant_glob`
  52. """
  53. # TODO waf 1.9
  54. split_path = Utils.split_path_unix
  55. split_path_cygwin = Utils.split_path_cygwin
  56. split_path_win32 = Utils.split_path_win32
  57. if sys.platform == 'cygwin':
  58. split_path = split_path_cygwin
  59. elif Utils.is_win32:
  60. split_path = split_path_win32
  61. class Node(object):
  62. """
  63. This class is organized in two parts
  64. * The basic methods meant for filesystem access (compute paths, create folders, etc)
  65. * The methods bound to a :py:class:`waflib.Build.BuildContext` (require ``bld.srcnode`` and ``bld.bldnode``)
  66. The Node objects are not thread safe in any way.
  67. """
  68. dict_class = dict
  69. __slots__ = ('name', 'sig', 'children', 'parent', 'cache_abspath', 'cache_isdir', 'cache_sig')
  70. def __init__(self, name, parent):
  71. self.name = name
  72. self.parent = parent
  73. if parent:
  74. if name in parent.children:
  75. raise Errors.WafError('node %s exists in the parent files %r already' % (name, parent))
  76. parent.children[name] = self
  77. def __setstate__(self, data):
  78. "Deserializes from data"
  79. self.name = data[0]
  80. self.parent = data[1]
  81. if data[2] is not None:
  82. # Issue 1480
  83. self.children = self.dict_class(data[2])
  84. if data[3] is not None:
  85. self.sig = data[3]
  86. def __getstate__(self):
  87. "Serialize the node info"
  88. return (self.name, self.parent, getattr(self, 'children', None), getattr(self, 'sig', None))
  89. def __str__(self):
  90. "String representation (name), for debugging purposes"
  91. return self.name
  92. def __repr__(self):
  93. "String representation (abspath), for debugging purposes"
  94. return self.abspath()
  95. def __hash__(self):
  96. "Node hash, used for storage in dicts. This hash is not persistent."
  97. return id(self)
  98. def __eq__(self, node):
  99. "Node comparison, based on the IDs"
  100. return id(self) == id(node)
  101. def __copy__(self):
  102. "Implemented to prevent nodes from being copied (raises an exception)"
  103. raise Errors.WafError('nodes are not supposed to be copied')
  104. def read(self, flags='r', encoding='ISO8859-1'):
  105. """
  106. Return the contents of the file represented by this node::
  107. def build(bld):
  108. bld.path.find_node('wscript').read()
  109. :type fname: string
  110. :param fname: Path to file
  111. :type m: string
  112. :param m: Open mode
  113. :rtype: string
  114. :return: File contents
  115. """
  116. return Utils.readf(self.abspath(), flags, encoding)
  117. def write(self, data, flags='w', encoding='ISO8859-1'):
  118. """
  119. Write some text to the physical file represented by this node::
  120. def build(bld):
  121. bld.path.make_node('foo.txt').write('Hello, world!')
  122. :type data: string
  123. :param data: data to write
  124. :type flags: string
  125. :param flags: Write mode
  126. """
  127. Utils.writef(self.abspath(), data, flags, encoding)
  128. def chmod(self, val):
  129. """
  130. Change file/dir permissions::
  131. def build(bld):
  132. bld.path.chmod(493) # 0755
  133. """
  134. os.chmod(self.abspath(), val)
  135. def delete(self):
  136. """Delete the file/folder, and remove this node from the tree. Do not use this object after calling this method."""
  137. try:
  138. try:
  139. if hasattr(self, 'children'):
  140. shutil.rmtree(self.abspath())
  141. else:
  142. os.remove(self.abspath())
  143. except OSError as e:
  144. if os.path.exists(self.abspath()):
  145. raise e
  146. finally:
  147. self.evict()
  148. def evict(self):
  149. """Internal - called when a node is removed"""
  150. del self.parent.children[self.name]
  151. def suffix(self):
  152. """Return the file extension"""
  153. k = max(0, self.name.rfind('.'))
  154. return self.name[k:]
  155. def height(self):
  156. """Depth in the folder hierarchy from the filesystem root or from all the file drives"""
  157. d = self
  158. val = -1
  159. while d:
  160. d = d.parent
  161. val += 1
  162. return val
  163. def listdir(self):
  164. """List the folder contents"""
  165. lst = Utils.listdir(self.abspath())
  166. lst.sort()
  167. return lst
  168. def mkdir(self):
  169. """
  170. Create a folder represented by this node, creating intermediate nodes as needed
  171. An exception will be raised only when the folder cannot possibly exist there
  172. """
  173. if getattr(self, 'cache_isdir', None):
  174. return
  175. try:
  176. self.parent.mkdir()
  177. except OSError:
  178. pass
  179. if self.name:
  180. try:
  181. os.makedirs(self.abspath())
  182. except OSError:
  183. pass
  184. if not os.path.isdir(self.abspath()):
  185. raise Errors.WafError('Could not create the directory %s' % self.abspath())
  186. try:
  187. self.children
  188. except AttributeError:
  189. self.children = self.dict_class()
  190. self.cache_isdir = True
  191. def find_node(self, lst):
  192. """
  193. Find a node on the file system (files or folders), create intermediate nodes as needed
  194. :param lst: path
  195. :type lst: string or list of string
  196. """
  197. if isinstance(lst, str):
  198. lst = [x for x in split_path(lst) if x and x != '.']
  199. cur = self
  200. for x in lst:
  201. if x == '..':
  202. cur = cur.parent or cur
  203. continue
  204. try:
  205. ch = cur.children
  206. except AttributeError:
  207. cur.children = self.dict_class()
  208. else:
  209. try:
  210. cur = cur.children[x]
  211. continue
  212. except KeyError:
  213. pass
  214. # optimistic: create the node first then look if it was correct to do so
  215. cur = self.__class__(x, cur)
  216. try:
  217. os.stat(cur.abspath())
  218. except OSError:
  219. cur.evict()
  220. return None
  221. ret = cur
  222. try:
  223. os.stat(ret.abspath())
  224. except OSError:
  225. ret.evict()
  226. return None
  227. try:
  228. while not getattr(cur.parent, 'cache_isdir', None):
  229. cur = cur.parent
  230. cur.cache_isdir = True
  231. except AttributeError:
  232. pass
  233. return ret
  234. def make_node(self, lst):
  235. """
  236. Find or create a node without looking on the filesystem
  237. :param lst: path
  238. :type lst: string or list of string
  239. """
  240. if isinstance(lst, str):
  241. lst = [x for x in split_path(lst) if x and x != '.']
  242. cur = self
  243. for x in lst:
  244. if x == '..':
  245. cur = cur.parent or cur
  246. continue
  247. if getattr(cur, 'children', {}):
  248. if x in cur.children:
  249. cur = cur.children[x]
  250. continue
  251. else:
  252. cur.children = self.dict_class()
  253. cur = self.__class__(x, cur)
  254. return cur
  255. def search_node(self, lst):
  256. """
  257. Search for a node without looking on the filesystem
  258. :param lst: path
  259. :type lst: string or list of string
  260. """
  261. if isinstance(lst, str):
  262. lst = [x for x in split_path(lst) if x and x != '.']
  263. cur = self
  264. for x in lst:
  265. if x == '..':
  266. cur = cur.parent or cur
  267. else:
  268. try:
  269. cur = cur.children[x]
  270. except (AttributeError, KeyError):
  271. return None
  272. return cur
  273. def path_from(self, node):
  274. """
  275. Path of this node seen from the other::
  276. def build(bld):
  277. n1 = bld.path.find_node('foo/bar/xyz.txt')
  278. n2 = bld.path.find_node('foo/stuff/')
  279. n1.path_from(n2) # '../bar/xyz.txt'
  280. :param node: path to use as a reference
  281. :type node: :py:class:`waflib.Node.Node`
  282. """
  283. c1 = self
  284. c2 = node
  285. c1h = c1.height()
  286. c2h = c2.height()
  287. lst = []
  288. up = 0
  289. while c1h > c2h:
  290. lst.append(c1.name)
  291. c1 = c1.parent
  292. c1h -= 1
  293. while c2h > c1h:
  294. up += 1
  295. c2 = c2.parent
  296. c2h -= 1
  297. while id(c1) != id(c2):
  298. lst.append(c1.name)
  299. up += 1
  300. c1 = c1.parent
  301. c2 = c2.parent
  302. if c1.parent:
  303. for i in range(up):
  304. lst.append('..')
  305. else:
  306. if lst and not Utils.is_win32:
  307. lst.append('')
  308. lst.reverse()
  309. return os.sep.join(lst) or '.'
  310. def abspath(self):
  311. """
  312. Absolute path. A cache is kept in the context as ``cache_node_abspath``
  313. """
  314. try:
  315. return self.cache_abspath
  316. except AttributeError:
  317. pass
  318. # think twice before touching this (performance + complexity + correctness)
  319. if not self.parent:
  320. val = os.sep
  321. elif not self.parent.name:
  322. val = os.sep + self.name
  323. else:
  324. val = self.parent.abspath() + os.sep + self.name
  325. self.cache_abspath = val
  326. return val
  327. if Utils.is_win32:
  328. def abspath(self):
  329. try:
  330. return self.cache_abspath
  331. except AttributeError:
  332. pass
  333. if not self.parent:
  334. val = ''
  335. elif not self.parent.name:
  336. val = self.name + os.sep
  337. else:
  338. val = self.parent.abspath().rstrip(os.sep) + os.sep + self.name
  339. self.cache_abspath = val
  340. return val
  341. def is_child_of(self, node):
  342. """
  343. Does this node belong to the subtree node?::
  344. def build(bld):
  345. node = bld.path.find_node('wscript')
  346. node.is_child_of(bld.path) # True
  347. :param node: path to use as a reference
  348. :type node: :py:class:`waflib.Node.Node`
  349. """
  350. p = self
  351. diff = self.height() - node.height()
  352. while diff > 0:
  353. diff -= 1
  354. p = p.parent
  355. return id(p) == id(node)
  356. def ant_iter(self, accept=None, maxdepth=25, pats=[], dir=False, src=True, remove=True):
  357. """
  358. Semi-private and recursive method used by ant_glob.
  359. :param accept: function used for accepting/rejecting a node, returns the patterns that can be still accepted in recursion
  360. :type accept: function
  361. :param maxdepth: maximum depth in the filesystem (25)
  362. :type maxdepth: int
  363. :param pats: list of patterns to accept and list of patterns to exclude
  364. :type pats: tuple
  365. :param dir: return folders too (False by default)
  366. :type dir: bool
  367. :param src: return files (True by default)
  368. :type src: bool
  369. :param remove: remove files/folders that do not exist (True by default)
  370. :type remove: bool
  371. """
  372. dircont = self.listdir()
  373. dircont.sort()
  374. try:
  375. lst = set(self.children.keys())
  376. except AttributeError:
  377. self.children = self.dict_class()
  378. else:
  379. if remove:
  380. for x in lst - set(dircont):
  381. self.children[x].evict()
  382. for name in dircont:
  383. npats = accept(name, pats)
  384. if npats and npats[0]:
  385. accepted = [] in npats[0]
  386. node = self.make_node([name])
  387. isdir = os.path.isdir(node.abspath())
  388. if accepted:
  389. if isdir:
  390. if dir:
  391. yield node
  392. else:
  393. if src:
  394. yield node
  395. if getattr(node, 'cache_isdir', None) or isdir:
  396. node.cache_isdir = True
  397. if maxdepth:
  398. for k in node.ant_iter(accept=accept, maxdepth=maxdepth - 1, pats=npats, dir=dir, src=src, remove=remove):
  399. yield k
  400. raise StopIteration
  401. def ant_glob(self, *k, **kw):
  402. """
  403. This method is used for finding files across folders. It behaves like ant patterns:
  404. * ``**/*`` find all files recursively
  405. * ``**/*.class`` find all files ending by .class
  406. * ``..`` find files having two dot characters
  407. For example::
  408. def configure(cfg):
  409. cfg.path.ant_glob('**/*.cpp') # find all .cpp files
  410. cfg.root.ant_glob('etc/*.txt') # using the filesystem root can be slow
  411. cfg.path.ant_glob('*.cpp', excl=['*.c'], src=True, dir=False)
  412. For more information see http://ant.apache.org/manual/dirtasks.html
  413. The nodes that correspond to files and folders that do not exist will be removed. To prevent this
  414. behaviour, pass 'remove=False'
  415. :param incl: ant patterns or list of patterns to include
  416. :type incl: string or list of strings
  417. :param excl: ant patterns or list of patterns to exclude
  418. :type excl: string or list of strings
  419. :param dir: return folders too (False by default)
  420. :type dir: bool
  421. :param src: return files (True by default)
  422. :type src: bool
  423. :param remove: remove files/folders that do not exist (True by default)
  424. :type remove: bool
  425. :param maxdepth: maximum depth of recursion
  426. :type maxdepth: int
  427. :param ignorecase: ignore case while matching (False by default)
  428. :type ignorecase: bool
  429. """
  430. src = kw.get('src', True)
  431. dir = kw.get('dir', False)
  432. excl = kw.get('excl', exclude_regs)
  433. incl = k and k[0] or kw.get('incl', '**')
  434. reflags = kw.get('ignorecase', 0) and re.I
  435. def to_pat(s):
  436. lst = Utils.to_list(s)
  437. ret = []
  438. for x in lst:
  439. x = x.replace('\\', '/').replace('//', '/')
  440. if x.endswith('/'):
  441. x += '**'
  442. lst2 = x.split('/')
  443. accu = []
  444. for k in lst2:
  445. if k == '**':
  446. accu.append(k)
  447. else:
  448. k = k.replace('.', '[.]').replace('*','.*').replace('?', '.').replace('+', '\\+')
  449. k = '^%s$' % k
  450. try:
  451. #print "pattern", k
  452. accu.append(re.compile(k, flags=reflags))
  453. except Exception as e:
  454. raise Errors.WafError("Invalid pattern: %s" % k, e)
  455. ret.append(accu)
  456. return ret
  457. def filtre(name, nn):
  458. ret = []
  459. for lst in nn:
  460. if not lst:
  461. pass
  462. elif lst[0] == '**':
  463. ret.append(lst)
  464. if len(lst) > 1:
  465. if lst[1].match(name):
  466. ret.append(lst[2:])
  467. else:
  468. ret.append([])
  469. elif lst[0].match(name):
  470. ret.append(lst[1:])
  471. return ret
  472. def accept(name, pats):
  473. nacc = filtre(name, pats[0])
  474. nrej = filtre(name, pats[1])
  475. if [] in nrej:
  476. nacc = []
  477. return [nacc, nrej]
  478. ret = [x for x in self.ant_iter(accept=accept, pats=[to_pat(incl), to_pat(excl)], maxdepth=kw.get('maxdepth', 25), dir=dir, src=src, remove=kw.get('remove', True))]
  479. if kw.get('flat', False):
  480. return ' '.join([x.path_from(self) for x in ret])
  481. return ret
  482. # --------------------------------------------------------------------------------
  483. # the following methods require the source/build folders (bld.srcnode/bld.bldnode)
  484. # using a subclass is a possibility, but is that really necessary?
  485. # --------------------------------------------------------------------------------
  486. def is_src(self):
  487. """
  488. True if the node is below the source directory
  489. note: !is_src does not imply is_bld()
  490. :rtype: bool
  491. """
  492. cur = self
  493. x = id(self.ctx.srcnode)
  494. y = id(self.ctx.bldnode)
  495. while cur.parent:
  496. if id(cur) == y:
  497. return False
  498. if id(cur) == x:
  499. return True
  500. cur = cur.parent
  501. return False
  502. def is_bld(self):
  503. """
  504. True if the node is below the build directory
  505. note: !is_bld does not imply is_src
  506. :rtype: bool
  507. """
  508. cur = self
  509. y = id(self.ctx.bldnode)
  510. while cur.parent:
  511. if id(cur) == y:
  512. return True
  513. cur = cur.parent
  514. return False
  515. def get_src(self):
  516. """
  517. Return the equivalent src node (or self if not possible)
  518. :rtype: :py:class:`waflib.Node.Node`
  519. """
  520. cur = self
  521. x = id(self.ctx.srcnode)
  522. y = id(self.ctx.bldnode)
  523. lst = []
  524. while cur.parent:
  525. if id(cur) == y:
  526. lst.reverse()
  527. return self.ctx.srcnode.make_node(lst)
  528. if id(cur) == x:
  529. return self
  530. lst.append(cur.name)
  531. cur = cur.parent
  532. return self
  533. def get_bld(self):
  534. """
  535. Return the equivalent bld node (or self if not possible)
  536. :rtype: :py:class:`waflib.Node.Node`
  537. """
  538. cur = self
  539. x = id(self.ctx.srcnode)
  540. y = id(self.ctx.bldnode)
  541. lst = []
  542. while cur.parent:
  543. if id(cur) == y:
  544. return self
  545. if id(cur) == x:
  546. lst.reverse()
  547. return self.ctx.bldnode.make_node(lst)
  548. lst.append(cur.name)
  549. cur = cur.parent
  550. # the file is external to the current project, make a fake root in the current build directory
  551. lst.reverse()
  552. if lst and Utils.is_win32 and len(lst[0]) == 2 and lst[0].endswith(':'):
  553. lst[0] = lst[0][0]
  554. return self.ctx.bldnode.make_node(['__root__'] + lst)
  555. def find_resource(self, lst):
  556. """
  557. Try to find a declared build node or a source file
  558. :param lst: path
  559. :type lst: string or list of string
  560. """
  561. if isinstance(lst, str):
  562. lst = [x for x in split_path(lst) if x and x != '.']
  563. node = self.get_bld().search_node(lst)
  564. if not node:
  565. self = self.get_src()
  566. node = self.find_node(lst)
  567. if node:
  568. if os.path.isdir(node.abspath()):
  569. return None
  570. return node
  571. def find_or_declare(self, lst):
  572. """
  573. if 'self' is in build directory, try to return an existing node
  574. if no node is found, go to the source directory
  575. try to find an existing node in the source directory
  576. if no node is found, create it in the build directory
  577. :param lst: path
  578. :type lst: string or list of string
  579. """
  580. if isinstance(lst, str):
  581. lst = [x for x in split_path(lst) if x and x != '.']
  582. node = self.get_bld().search_node(lst)
  583. if node:
  584. if not os.path.isfile(node.abspath()):
  585. node.sig = None
  586. node.parent.mkdir()
  587. return node
  588. self = self.get_src()
  589. node = self.find_node(lst)
  590. if node:
  591. if not os.path.isfile(node.abspath()):
  592. node.sig = None
  593. node.parent.mkdir()
  594. return node
  595. node = self.get_bld().make_node(lst)
  596. node.parent.mkdir()
  597. return node
  598. def find_dir(self, lst):
  599. """
  600. Search for a folder in the filesystem
  601. :param lst: path
  602. :type lst: string or list of string
  603. """
  604. if isinstance(lst, str):
  605. lst = [x for x in split_path(lst) if x and x != '.']
  606. node = self.find_node(lst)
  607. try:
  608. if not os.path.isdir(node.abspath()):
  609. return None
  610. except (OSError, AttributeError):
  611. # the node might be None, and raise an AttributeError
  612. return None
  613. return node
  614. # helpers for building things
  615. def change_ext(self, ext, ext_in=None):
  616. """
  617. :return: A build node of the same path, but with a different extension
  618. :rtype: :py:class:`waflib.Node.Node`
  619. """
  620. name = self.name
  621. if ext_in is None:
  622. k = name.rfind('.')
  623. if k >= 0:
  624. name = name[:k] + ext
  625. else:
  626. name = name + ext
  627. else:
  628. name = name[:- len(ext_in)] + ext
  629. return self.parent.find_or_declare([name])
  630. def bldpath(self):
  631. "Path seen from the build directory default/src/foo.cpp"
  632. return self.path_from(self.ctx.bldnode)
  633. def srcpath(self):
  634. "Path seen from the source directory ../src/foo.cpp"
  635. return self.path_from(self.ctx.srcnode)
  636. def relpath(self):
  637. "If a file in the build directory, bldpath, else srcpath"
  638. cur = self
  639. x = id(self.ctx.bldnode)
  640. while cur.parent:
  641. if id(cur) == x:
  642. return self.bldpath()
  643. cur = cur.parent
  644. return self.srcpath()
  645. def bld_dir(self):
  646. "Build path without the file name"
  647. return self.parent.bldpath()
  648. def get_bld_sig(self):
  649. """
  650. Node signature, assuming the file is in the build directory
  651. """
  652. try:
  653. return self.cache_sig
  654. except AttributeError:
  655. pass
  656. if not self.is_bld() or self.ctx.bldnode is self.ctx.srcnode:
  657. self.sig = Utils.h_file(self.abspath())
  658. self.cache_sig = ret = self.sig
  659. return ret
  660. pickle_lock = Utils.threading.Lock()
  661. """Lock mandatory for thread-safe node serialization"""
  662. class Nod3(Node):
  663. """Mandatory subclass for thread-safe node serialization"""
  664. pass # do not remove