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		dircont.sort()
599
600		try:
601			lst = set(self.children.keys())
602		except AttributeError:
603			self.children = self.dict_class()
604		else:
605			if remove:
606				for x in lst - set(dircont):
607					self.children[x].evict()
608
609		for name in dircont:
610			npats = accept(name, pats)
611			if npats and npats[0]:
612				accepted = [] in npats[0]
613
614				node = self.make_node([name])
615
616				isdir = node.isdir()
617				if accepted:
618					if isdir:
619						if dir:
620							yield node
621					elif src:
622						yield node
623
624				if isdir:
625					node.cache_isdir = True
626					if maxdepth:
627						for k in node.ant_iter(accept=accept, maxdepth=maxdepth - 1, pats=npats, dir=dir, src=src, remove=remove, quiet=quiet):
628							yield k
629
630	def ant_glob(self, *k, **kw):
631		"""
632		Finds files across folders and returns Node objects:
633
634		* ``**/*`` find all files recursively
635		* ``**/*.class`` find all files ending by .class
636		* ``..`` find files having two dot characters
637
638		For example::
639
640			def configure(cfg):
641				# find all .cpp files
642				cfg.path.ant_glob('**/*.cpp')
643				# find particular files from the root filesystem (can be slow)
644				cfg.root.ant_glob('etc/*.txt')
645				# simple exclusion rule example
646				cfg.path.ant_glob('*.c*', excl=['*.c'], src=True, dir=False)
647
648		For more information about the patterns, consult http://ant.apache.org/manual/dirtasks.html
649		Please remember that the '..' sequence does not represent the parent directory::
650
651			def configure(cfg):
652				cfg.path.ant_glob('../*.h') # incorrect
653				cfg.path.parent.ant_glob('*.h') # correct
654
655		The Node structure is itself a filesystem cache, so certain precautions must
656		be taken while matching files in the build or installation phases.
657		Nodes objects that do have a corresponding file or folder are garbage-collected by default.
658		This garbage collection is usually required to prevent returning files that do not
659		exist anymore. Yet, this may also remove Node objects of files that are yet-to-be built.
660
661		This typically happens when trying to match files in the build directory,
662		but there are also cases when files are created in the source directory.
663		Run ``waf -v`` to display any warnings, and try consider passing ``remove=False``
664		when matching files in the build directory.
665
666		Since ant_glob can traverse both source and build folders, it is a best practice
667		to call this method only from the most specific build node::
668
669			def build(bld):
670				# traverses the build directory, may need ``remove=False``:
671				bld.path.ant_glob('project/dir/**/*.h')
672				# better, no accidental build directory traversal:
673				bld.path.find_node('project/dir').ant_glob('**/*.h') # best
674
675		In addition, files and folders are listed immediately. When matching files in the
676		build folders, consider passing ``generator=True`` so that the generator object
677		returned can defer computation to a later stage. For example::
678
679			def build(bld):
680				bld(rule='tar xvf ${SRC}', source='arch.tar')
681				bld.add_group()
682				gen = bld.bldnode.ant_glob("*.h", generator=True, remove=True)
683				# files will be listed only after the arch.tar is unpacked
684				bld(rule='ls ${SRC}', source=gen, name='XYZ')
685
686
687		:param incl: ant patterns or list of patterns to include
688		:type incl: string or list of strings
689		:param excl: ant patterns or list of patterns to exclude
690		:type excl: string or list of strings
691		:param dir: return folders too (False by default)
692		:type dir: bool
693		:param src: return files (True by default)
694		:type src: bool
695		:param maxdepth: maximum depth of recursion
696		:type maxdepth: int
697		:param ignorecase: ignore case while matching (False by default)
698		:type ignorecase: bool
699		:param generator: Whether to evaluate the Nodes lazily
700		:type generator: bool
701		:param remove: remove files/folders that do not exist (True by default)
702		:type remove: bool
703		:param quiet: disable build directory traversal warnings (verbose mode)
704		:type quiet: bool
705		:returns: The corresponding Node objects as a list or as a generator object (generator=True)
706		:rtype: by default, list of :py:class:`waflib.Node.Node` instances
707		"""
708		src = kw.get('src', True)
709		dir = kw.get('dir')
710		excl = kw.get('excl', exclude_regs)
711		incl = k and k[0] or kw.get('incl', '**')
712		remove = kw.get('remove', True)
713		maxdepth = kw.get('maxdepth', 25)
714		ignorecase = kw.get('ignorecase', False)
715		quiet = kw.get('quiet', False)
716		pats = (ant_matcher(incl, ignorecase), ant_matcher(excl, ignorecase))
717
718		if kw.get('generator'):
719			return Utils.lazy_generator(self.ant_iter, (ant_sub_matcher, maxdepth, pats, dir, src, remove, quiet))
720
721		it = self.ant_iter(ant_sub_matcher, maxdepth, pats, dir, src, remove, quiet)
722		if kw.get('flat'):
723			# returns relative paths as a space-delimited string
724			# prefer Node objects whenever possible
725			return ' '.join(x.path_from(self) for x in it)
726		return list(it)
727
728	# ----------------------------------------------------------------------------
729	# the methods below require the source/build folders (bld.srcnode/bld.bldnode)
730
731	def is_src(self):
732		"""
733		Returns True if the node is below the source directory. Note that ``!is_src() ≠ is_bld()``
734
735		:rtype: bool
736		"""
737		cur = self
738		x = self.ctx.srcnode
739		y = self.ctx.bldnode
740		while cur.parent:
741			if cur is y:
742				return False
743			if cur is x:
744				return True
745			cur = cur.parent
746		return False
747
748	def is_bld(self):
749		"""
750		Returns True if the node is below the build directory. Note that ``!is_bld() ≠ is_src()``
751
752		:rtype: bool
753		"""
754		cur = self
755		y = self.ctx.bldnode
756		while cur.parent:
757			if cur is y:
758				return True
759			cur = cur.parent
760		return False
761
762	def get_src(self):
763		"""
764		Returns the corresponding Node object in the source directory (or self if already
765		under the source directory). Use this method only if the purpose is to create
766		a Node object (this is common with folders but not with files, see ticket 1937)
767
768		:rtype: :py:class:`waflib.Node.Node`
769		"""
770		cur = self
771		x = self.ctx.srcnode
772		y = self.ctx.bldnode
773		lst = []
774		while cur.parent:
775			if cur is y:
776				lst.reverse()
777				return x.make_node(lst)
778			if cur is x:
779				return self
780			lst.append(cur.name)
781			cur = cur.parent
782		return self
783
784	def get_bld(self):
785		"""
786		Return the corresponding Node object in the build directory (or self if already
787		under the build directory). Use this method only if the purpose is to create
788		a Node object (this is common with folders but not with files, see ticket 1937)
789
790		:rtype: :py:class:`waflib.Node.Node`
791		"""
792		cur = self
793		x = self.ctx.srcnode
794		y = self.ctx.bldnode
795		lst = []
796		while cur.parent:
797			if cur is y:
798				return self
799			if cur is x:
800				lst.reverse()
801				return self.ctx.bldnode.make_node(lst)
802			lst.append(cur.name)
803			cur = cur.parent
804		# the file is external to the current project, make a fake root in the current build directory
805		lst.reverse()
806		if lst and Utils.is_win32 and len(lst[0]) == 2 and lst[0].endswith(':'):
807			lst[0] = lst[0][0]
808		return self.ctx.bldnode.make_node(['__root__'] + lst)
809
810	def find_resource(self, lst):
811		"""
812		Use this method in the build phase to find source files corresponding to the relative path given.
813
814		First it looks up the Node data structure to find any declared Node object in the build directory.
815		If None is found, it then considers the filesystem in the source directory.
816
817		:param lst: relative path
818		:type lst: string or list of string
819		:returns: the corresponding Node object or None
820		:rtype: :py:class:`waflib.Node.Node`
821		"""
822		if isinstance(lst, str):
823			lst = [x for x in Utils.split_path(lst) if x and x != '.']
824
825		node = self.get_bld().search_node(lst)
826		if not node:
827			node = self.get_src().find_node(lst)
828		if node and node.isdir():
829			return None
830		return node
831
832	def find_or_declare(self, lst):
833		"""
834		Use this method in the build phase to declare output files which
835		are meant to be written in the build directory.
836
837		This method creates the Node object and its parent folder
838		as needed.
839
840		:param lst: relative path
841		:type lst: string or list of string
842		"""
843		if isinstance(lst, str) and os.path.isabs(lst):
844			node = self.ctx.root.make_node(lst)
845		else:
846			node = self.get_bld().make_node(lst)
847		node.parent.mkdir()
848		return node
849
850	def find_dir(self, lst):
851		"""
852		Searches for a folder on the filesystem (see :py:meth:`waflib.Node.Node.find_node`)
853
854		:param lst: relative path
855		:type lst: string or list of string
856		:returns: The corresponding Node object or None if there is no such folder
857		:rtype: :py:class:`waflib.Node.Node`
858		"""
859		if isinstance(lst, str):
860			lst = [x for x in Utils.split_path(lst) if x and x != '.']
861
862		node = self.find_node(lst)
863		if node and not node.isdir():
864			return None
865		return node
866
867	# helpers for building things
868	def change_ext(self, ext, ext_in=None):
869		"""
870		Declares a build node with a distinct extension; this is uses :py:meth:`waflib.Node.Node.find_or_declare`
871
872		:return: A build node of the same path, but with a different extension
873		:rtype: :py:class:`waflib.Node.Node`
874		"""
875		name = self.name
876		if ext_in is None:
877			k = name.rfind('.')
878			if k >= 0:
879				name = name[:k] + ext
880			else:
881				name = name + ext
882		else:
883			name = name[:- len(ext_in)] + ext
884
885		return self.parent.find_or_declare([name])
886
887	def bldpath(self):
888		"""
889		Returns the relative path seen from the build directory ``src/foo.cpp``
890
891		:rtype: string
892		"""
893		return self.path_from(self.ctx.bldnode)
894
895	def srcpath(self):
896		"""
897		Returns the relative path seen from the source directory ``../src/foo.cpp``
898
899		:rtype: string
900		"""
901		return self.path_from(self.ctx.srcnode)
902
903	def relpath(self):
904		"""
905		If a file in the build directory, returns :py:meth:`waflib.Node.Node.bldpath`,
906		else returns :py:meth:`waflib.Node.Node.srcpath`
907
908		:rtype: string
909		"""
910		cur = self
911		x = self.ctx.bldnode
912		while cur.parent:
913			if cur is x:
914				return self.bldpath()
915			cur = cur.parent
916		return self.srcpath()
917
918	def bld_dir(self):
919		"""
920		Equivalent to self.parent.bldpath()
921
922		:rtype: string
923		"""
924		return self.parent.bldpath()
925
926	def h_file(self):
927		"""
928		See :py:func:`waflib.Utils.h_file`
929
930		:return: a hash representing the file contents
931		:rtype: string or bytes
932		"""
933		return Utils.h_file(self.abspath())
934
935	def get_bld_sig(self):
936		"""
937		Returns a signature (see :py:meth:`waflib.Node.Node.h_file`) for the purpose
938		of build dependency calculation. This method uses a per-context cache.
939
940		:return: a hash representing the object contents
941		:rtype: string or bytes
942		"""
943		# previous behaviour can be set by returning self.ctx.node_sigs[self] when a build node
944		try:
945			cache = self.ctx.cache_sig
946		except AttributeError:
947			cache = self.ctx.cache_sig = {}
948		try:
949			ret = cache[self]
950		except KeyError:
951			p = self.abspath()
952			try:
953				ret = cache[self] = self.h_file()
954			except EnvironmentError:
955				if self.isdir():
956					# allow folders as build nodes, do not use the creation time
957					st = os.stat(p)
958					ret = cache[self] = Utils.h_list([p, st.st_ino, st.st_mode])
959					return ret
960				raise
961		return ret
962
963pickle_lock = Utils.threading.Lock()
964"""Lock mandatory for thread-safe node serialization"""
965
966class Nod3(Node):
967	"""Mandatory subclass for thread-safe node serialization"""
968	pass # do not remove
969
970
971