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