3
# Thomas Nagy, 2005-2010 (ita)
6
Node: filesystem structure, contains lists of nodes
8
#. Each file/folder is represented by exactly one node.
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.
13
#. Node objects should never be created directly, use
14
the methods :py:func:`Node.make_node` or :py:func:`Node.find_node`
16
#. The methods :py:func:`Node.find_resource`, :py:func:`Node.find_dir` :py:func:`Node.find_or_declare` should be
17
used when a build context is present
19
#. Each instance of :py:class:`waflib.Context.Context` has a unique :py:class:`Node` subclass.
20
(: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
23
import os, re, sys, shutil
24
from waflib import Utils, Errors
57
Ant patterns for files and folders to exclude while doing the
58
recursive traversal in :py:meth:`waflib.Node.Node.ant_glob`
61
# TODO optimize split_path by performing a replacement when unpacking?
65
Split a path by os.sep (This is not os.path.split)
67
:param path: path to split
69
:rtype: list of string
70
:return: the path, split
72
return path.split('/')
74
def split_path_cygwin(path):
75
if path.startswith('//'):
76
ret = path.split('/')[2:]
79
return path.split('/')
81
re_sp = re.compile('[/\\\\]')
82
def split_path_win32(path):
83
if path.startswith('\\\\'):
84
ret = re.split(re_sp, path)[2:]
85
ret[0] = '\\' + ret[0]
87
return re.split(re_sp, path)
89
if sys.platform == 'cygwin':
90
split_path = split_path_cygwin
92
split_path = split_path_win32
96
This class is organized in two parts
98
* The basic methods meant for filesystem access (compute paths, create folders, etc)
99
* The methods bound to a :py:class:`waflib.Build.BuildContext` (require ``bld.srcnode`` and ``bld.bldnode``)
101
The Node objects are not thread safe in any way.
104
__slots__ = ('name', 'sig', 'children', 'parent', 'cache_abspath', 'cache_isdir')
105
def __init__(self, name, parent):
110
if name in parent.children:
111
raise Errors.WafError('node %s exists in the parent files %r already' % (name, parent))
112
parent.children[name] = self
114
def __setstate__(self, data):
115
"Deserializes from data"
117
self.parent = data[1]
118
if data[2] is not None:
119
self.children = data[2]
120
if data[3] is not None:
123
def __getstate__(self):
124
"Serialize the node info"
125
return (self.name, self.parent, getattr(self, 'children', None), getattr(self, 'sig', None))
128
"String representation (name), for debugging purposes"
132
"String representation (abspath), for debugging purposes"
133
return self.abspath()
136
"Node hash, used for storage in dicts. This hash is not persistent."
139
def __eq__(self, node):
140
"Node comparison, based on the IDs"
141
return id(self) == id(node)
144
"Implemented to prevent nodes from being copied (raises an exception)"
145
raise Errors.WafError('nodes are not supposed to be copied')
147
def read(self, flags='r'):
149
Return the contents of the file represented by this node::
152
bld.path.find_node('wscript').read()
155
:param fname: Path to file
159
:return: File contents
161
return Utils.readf(self.abspath(), flags)
163
def write(self, data, flags='w'):
165
Write some text to the physical file represented by this node::
168
bld.path.make_node('foo.txt').write('Hello, world!')
171
:param data: data to write
173
:param flags: Write mode
177
f = open(self.abspath(), flags)
183
def chmod(self, val):
185
Change file/dir permissions::
188
bld.path.chmod(493) # 0755
190
os.chmod(self.abspath(), val)
193
"""Delete the file/folder physically (but not the node)"""
195
if getattr(self, 'children', None):
196
shutil.rmtree(self.abspath())
198
os.unlink(self.abspath())
203
delattr(self, 'children')
208
"""Return the file extension"""
209
k = max(0, self.name.rfind('.'))
213
"""Depth in the folder hierarchy from the filesystem root or from all the file drives"""
222
"""List the folder contents"""
223
lst = Utils.listdir(self.abspath())
229
Create a folder represented by this node, creating intermediate nodes as needed
230
An exception will be raised only when the folder cannot possibly exist there
232
if getattr(self, 'cache_isdir', None):
242
os.makedirs(self.abspath())
246
if not os.path.isdir(self.abspath()):
247
raise Errors.WafError('Could not create the directory %s' % self.abspath())
254
self.cache_isdir = True
256
def find_node(self, lst):
258
Find a node on the file system (files or folders), create intermediate nodes as needed
261
:type lst: string or list of string
264
if isinstance(lst, str):
265
lst = [x for x in split_path(lst) if x and x != '.']
270
cur = cur.parent or cur
274
if x in cur.children:
275
cur = cur.children[x]
280
# optimistic: create the node first then look if it was correct to do so
281
cur = self.__class__(x, cur)
283
os.stat(cur.abspath())
285
del cur.parent.children[x]
291
os.stat(ret.abspath())
293
del ret.parent.children[ret.name]
297
while not getattr(cur.parent, 'cache_isdir', None):
299
cur.cache_isdir = True
300
except AttributeError:
305
def make_node(self, lst):
307
Find or create a node without looking on the filesystem
310
:type lst: string or list of string
312
if isinstance(lst, str):
313
lst = [x for x in split_path(lst) if x and x != '.']
318
cur = cur.parent or cur
321
if getattr(cur, 'children', {}):
322
if x in cur.children:
323
cur = cur.children[x]
327
cur = self.__class__(x, cur)
330
def search(self, lst):
332
Search for a node without looking on the filesystem
335
:type lst: string or list of string
337
if isinstance(lst, str):
338
lst = [x for x in split_path(lst) if x and x != '.']
344
cur = cur.parent or cur
346
cur = cur.children[x]
351
def path_from(self, node):
353
Path of this node seen from the other::
356
n1 = bld.path.find_node('foo/bar/xyz.txt')
357
n2 = bld.path.find_node('foo/stuff/')
358
n1.path_from(n2) # './bar/xyz.txt'
360
:param node: path to use as a reference
361
:type node: :py:class:`waflib.Node.Node`
383
while id(c1) != id(c2):
393
return os.sep.join(lst) or '.'
397
Absolute path. A cache is kept in the context as ``cache_node_abspath``
400
return self.cache_abspath
403
# think twice before touching this (performance + complexity + correctness)
408
elif not self.parent.name:
409
val = os.sep + self.name
411
val = self.parent.abspath() + os.sep + self.name
415
elif not self.parent.name:
416
val = self.name + os.sep
418
val = self.parent.abspath().rstrip(os.sep) + os.sep + self.name
420
self.cache_abspath = val
423
def is_child_of(self, node):
425
Does this node belong to the subtree node?::
428
node = bld.path.find_node('wscript')
429
node.is_child_of(bld.path) # True
431
:param node: path to use as a reference
432
:type node: :py:class:`waflib.Node.Node`
435
diff = self.height() - node.height()
439
return id(p) == id(node)
441
def ant_iter(self, accept=None, maxdepth=25, pats=[], dir=False, src=True, remove=True):
443
Semi-private and recursive method used by ant_glob.
445
:param accept: function used for accepting/rejecting a node, returns the patterns that can be still accepted in recursion
446
:type accept: function
447
:param maxdepth: maximum depth in the filesystem (25)
449
:param pats: list of patterns to accept and list of patterns to exclude
451
:param dir: return folders too (False by default)
453
:param src: return files (True by default)
455
:param remove: remove files/folders that do not exist (True by default)
458
dircont = self.listdir()
462
lst = set(self.children.keys())
464
for x in lst - set(dircont):
470
npats = accept(name, pats)
471
if npats and npats[0]:
472
accepted = [] in npats[0]
474
node = self.make_node([name])
476
isdir = os.path.isdir(node.abspath())
485
if getattr(node, 'cache_isdir', None) or isdir:
486
node.cache_isdir = True
488
for k in node.ant_iter(accept=accept, maxdepth=maxdepth - 1, pats=npats, dir=dir, src=src, remove=remove):
492
def ant_glob(self, *k, **kw):
494
This method is used for finding files across folders. It behaves like ant patterns:
496
* ``**/*`` find all files recursively
497
* ``**/*.class`` find all files ending by .class
498
* ``..`` find files having two dot characters
503
cfg.path.ant_glob('**/*.cpp') # find all .cpp files
504
cfg.root.ant_glob('etc/*.txt') # using the filesystem root can be slow
505
cfg.path.ant_glob('*.cpp', excl=['*.c'], src=True, dir=False)
507
For more information see http://ant.apache.org/manual/dirtasks.html
509
The nodes that correspond to files and folders that do not exist will be removed. To prevent this
510
behaviour, pass 'remove=False'
512
:param incl: ant patterns or list of patterns to include
513
:type incl: string or list of strings
514
:param excl: ant patterns or list of patterns to exclude
515
:type excl: string or list of strings
516
:param dir: return folders too (False by default)
518
:param src: return files (True by default)
520
:param remove: remove files/folders that do not exist (True by default)
522
:param maxdepth: maximum depth of recursion
526
src = kw.get('src', True)
527
dir = kw.get('dir', False)
529
excl = kw.get('excl', exclude_regs)
530
incl = k and k[0] or kw.get('incl', '**')
533
lst = Utils.to_list(s)
536
x = x.replace('\\', '/').replace('//', '/')
545
k = k.replace('.', '[.]').replace('*','.*').replace('?', '.').replace('+', '\\+')
549
accu.append(re.compile(k))
550
except Exception as e:
551
raise Errors.WafError("Invalid pattern: %s" % k, e)
555
def filtre(name, nn):
563
if lst[1].match(name):
567
elif lst[0].match(name):
571
def accept(name, pats):
572
nacc = filtre(name, pats[0])
573
nrej = filtre(name, pats[1])
578
ret = [x for x in self.ant_iter(accept=accept, pats=[to_pat(incl), to_pat(excl)], maxdepth=25, dir=dir, src=src, remove=kw.get('remove', True))]
579
if kw.get('flat', False):
580
return ' '.join([x.path_from(self) for x in ret])
584
def find_nodes(self, find_dirs=True, find_files=True, match_fun=lambda x: True):
585
# FIXME not part of the stable API: find_node vs find_nodes? consistency with argument names on other functions?
587
Recursively finds nodes::
592
:param find_dirs: whether to return directories
593
:param find_files: whether to return files
594
:param match_fun: matching function, taking a node as parameter
596
:return: a generator that iterates over all the requested files
598
files = self.listdir()
600
node = self.make_node([f])
601
if os.path.isdir(node.abspath()):
602
if find_dirs and match_fun(node):
604
gen = node.find_nodes(find_dirs, find_files, match_fun)
608
if find_files and match_fun(node):
612
# --------------------------------------------------------------------------------
613
# the following methods require the source/build folders (bld.srcnode/bld.bldnode)
614
# using a subclass is a possibility, but is that really necessary?
615
# --------------------------------------------------------------------------------
619
True if the node is below the source directory
620
note: !is_src does not imply is_bld()
625
x = id(self.ctx.srcnode)
626
y = id(self.ctx.bldnode)
637
True if the node is below the build directory
638
note: !is_bld does not imply is_src
643
y = id(self.ctx.bldnode)
652
Return the equivalent src node (or self if not possible)
654
:rtype: :py:class:`waflib.Node.Node`
657
x = id(self.ctx.srcnode)
658
y = id(self.ctx.bldnode)
663
return self.ctx.srcnode.make_node(lst)
672
Return the equivalent bld node (or self if not possible)
674
:rtype: :py:class:`waflib.Node.Node`
677
x = id(self.ctx.srcnode)
678
y = id(self.ctx.bldnode)
685
return self.ctx.bldnode.make_node(lst)
688
# the file is external to the current project, make a fake root in the current build directory
690
if lst and Utils.is_win32 and len(lst[0]) == 2 and lst[0].endswith(':'):
692
return self.ctx.bldnode.make_node(['__root__'] + lst)
694
def find_resource(self, lst):
696
Try to find a declared build node or a source file
699
:type lst: string or list of string
701
if isinstance(lst, str):
702
lst = [x for x in split_path(lst) if x and x != '.']
704
node = self.get_bld().search(lst)
706
self = self.get_src()
707
node = self.find_node(lst)
710
if os.path.isdir(pat):
716
def find_or_declare(self, lst):
718
if 'self' is in build directory, try to return an existing node
719
if no node is found, go to the source directory
720
try to find an existing node in the source directory
721
if no node is found, create it in the build directory
724
:type lst: string or list of string
726
if isinstance(lst, str):
727
lst = [x for x in split_path(lst) if x and x != '.']
729
node = self.get_bld().search(lst)
731
if not os.path.isfile(node.abspath()):
738
self = self.get_src()
739
node = self.find_node(lst)
741
if not os.path.isfile(node.abspath()):
748
node = self.get_bld().make_node(lst)
752
def find_dir(self, lst):
754
Search for a folder in the filesystem
757
:type lst: string or list of string
759
if isinstance(lst, str):
760
lst = [x for x in split_path(lst) if x and x != '.']
762
node = self.find_node(lst)
764
if not os.path.isdir(node.abspath()):
766
except (OSError, AttributeError):
767
# the node might be None, and raise an AttributeError
771
# helpers for building things
772
def change_ext(self, ext, ext_in=None):
774
:return: A build node of the same path, but with a different extension
775
:rtype: :py:class:`waflib.Node.Node`
781
name = name[:k] + ext
785
name = name[:- len(ext_in)] + ext
787
return self.parent.find_or_declare([name])
789
def nice_path(self, env=None):
791
Return the path seen from the launch directory. It is often used for printing nodes in the console to open
794
:param env: unused, left for compatibility with waf 1.5
796
return self.path_from(self.ctx.launch_node())
799
"Path seen from the build directory default/src/foo.cpp"
800
return self.path_from(self.ctx.bldnode)
803
"Path seen from the source directory ../src/foo.cpp"
804
return self.path_from(self.ctx.srcnode)
807
"If a file in the build directory, bldpath, else srcpath"
809
x = id(self.ctx.bldnode)
812
return self.bldpath()
814
return self.srcpath()
817
"Build path without the file name"
818
return self.parent.bldpath()
821
"Build path without the extension: src/dir/foo(.cpp)"
822
s = os.path.splitext(self.name)[0]
823
return self.bld_dir() + os.sep + s
825
def get_bld_sig(self):
827
Node signature, assuming the file is in the build directory
830
ret = self.ctx.hash_cache[id(self)]
833
except AttributeError:
834
self.ctx.hash_cache = {}
838
if not self.is_bld() or self.ctx.bldnode is self.ctx.srcnode:
839
self.sig = Utils.h_file(self.abspath())
840
self.ctx.hash_cache[id(self)] = ret = self.sig
843
pickle_lock = Utils.threading.Lock()
844
"""Lock mandatory for thread-safe node serialization"""
847
"""Mandatory subclass for thread-safe node serialization"""