summaryrefslogtreecommitdiff
path: root/buildtools/wafadmin/Node.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildtools/wafadmin/Node.py')
-rw-r--r--buildtools/wafadmin/Node.py695
1 files changed, 695 insertions, 0 deletions
diff --git a/buildtools/wafadmin/Node.py b/buildtools/wafadmin/Node.py
new file mode 100644
index 0000000000..236dd0d2b3
--- /dev/null
+++ b/buildtools/wafadmin/Node.py
@@ -0,0 +1,695 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005 (ita)
+
+"""
+Node: filesystem structure, contains lists of nodes
+
+IMPORTANT:
+1. Each file/folder is represented by exactly one node.
+
+2. Most would-be class properties are stored in Build: nodes to depend on, signature, flags, ..
+unused class members increase the .wafpickle file size sensibly with lots of objects.
+
+3. The build is launched from the top of the build dir (for example, in _build_/).
+
+4. Node should not be instantiated directly.
+Each instance of Build.BuildContext has a Node subclass.
+(aka: 'Nodu', see BuildContext initializer)
+The BuildContext is referenced here as self.__class__.bld
+Its Node class is referenced here as self.__class__
+
+The public and advertised apis are the following:
+${TGT} -> dir/to/file.ext
+${TGT[0].base()} -> dir/to/file
+${TGT[0].dir(env)} -> dir/to
+${TGT[0].file()} -> file.ext
+${TGT[0].file_base()} -> file
+${TGT[0].suffix()} -> .ext
+${TGT[0].abspath(env)} -> /path/to/dir/to/file.ext
+
+"""
+
+import os, sys, fnmatch, re, stat
+import Utils, Constants
+
+UNDEFINED = 0
+DIR = 1
+FILE = 2
+BUILD = 3
+
+type_to_string = {UNDEFINED: "unk", DIR: "dir", FILE: "src", BUILD: "bld"}
+
+# These fnmatch expressions are used by default to prune the directory tree
+# while doing the recursive traversal in the find_iter method of the Node class.
+prune_pats = '.git .bzr .hg .svn _MTN _darcs CVS SCCS'.split()
+
+# These fnmatch expressions are used by default to exclude files and dirs
+# while doing the recursive traversal in the find_iter method of the Node class.
+exclude_pats = prune_pats + '*~ #*# .#* %*% ._* .gitignore .cvsignore vssver.scc .DS_Store'.split()
+
+# These Utils.jar_regexp expressions are used by default to exclude files and dirs and also prune the directory tree
+# while doing the recursive traversal in the ant_glob method of the Node class.
+exclude_regs = '''
+**/*~
+**/#*#
+**/.#*
+**/%*%
+**/._*
+**/CVS
+**/CVS/**
+**/.cvsignore
+**/SCCS
+**/SCCS/**
+**/vssver.scc
+**/.svn
+**/.svn/**
+**/.git
+**/.git/**
+**/.gitignore
+**/.bzr
+**/.bzr/**
+**/.hg
+**/.hg/**
+**/_MTN
+**/_MTN/**
+**/_darcs
+**/_darcs/**
+**/.DS_Store'''
+
+class Node(object):
+ __slots__ = ("name", "parent", "id", "childs")
+ def __init__(self, name, parent, node_type = UNDEFINED):
+ self.name = name
+ self.parent = parent
+
+ # assumption: one build object at a time
+ self.__class__.bld.id_nodes += 4
+ self.id = self.__class__.bld.id_nodes + node_type
+
+ if node_type == DIR: self.childs = {}
+
+ # We do not want to add another type attribute (memory)
+ # use the id to find out: type = id & 3
+ # for setting: new type = type + x - type & 3
+
+ if parent and name in parent.childs:
+ raise Utils.WafError('node %s exists in the parent files %r already' % (name, parent))
+
+ if parent: parent.childs[name] = self
+
+ def __setstate__(self, data):
+ if len(data) == 4:
+ (self.parent, self.name, self.id, self.childs) = data
+ else:
+ (self.parent, self.name, self.id) = data
+
+ def __getstate__(self):
+ if getattr(self, 'childs', None) is None:
+ return (self.parent, self.name, self.id)
+ else:
+ return (self.parent, self.name, self.id, self.childs)
+
+ def __str__(self):
+ if not self.parent: return ''
+ return "%s://%s" % (type_to_string[self.id & 3], self.abspath())
+
+ def __repr__(self):
+ return self.__str__()
+
+ def __hash__(self):
+ "expensive, make certain it is not used"
+ raise Utils.WafError('nodes, you are doing it wrong')
+
+ def __copy__(self):
+ "nodes are not supposed to be copied"
+ raise Utils.WafError('nodes are not supposed to be cloned')
+
+ def get_type(self):
+ return self.id & 3
+
+ def set_type(self, t):
+ "dangerous, you are not supposed to use this"
+ self.id = self.id + t - self.id & 3
+
+ def dirs(self):
+ return [x for x in self.childs.values() if x.id & 3 == DIR]
+
+ def files(self):
+ return [x for x in self.childs.values() if x.id & 3 == FILE]
+
+ def get_dir(self, name, default=None):
+ node = self.childs.get(name, None)
+ if not node or node.id & 3 != DIR: return default
+ return node
+
+ def get_file(self, name, default=None):
+ node = self.childs.get(name, None)
+ if not node or node.id & 3 != FILE: return default
+ return node
+
+ def get_build(self, name, default=None):
+ node = self.childs.get(name, None)
+ if not node or node.id & 3 != BUILD: return default
+ return node
+
+ def find_resource(self, lst):
+ "Find an existing input file: either a build node declared previously or a source node"
+ if isinstance(lst, str):
+ lst = Utils.split_path(lst)
+
+ if len(lst) == 1:
+ parent = self
+ else:
+ parent = self.find_dir(lst[:-1])
+ if not parent: return None
+ self.__class__.bld.rescan(parent)
+
+ name = lst[-1]
+ node = parent.childs.get(name, None)
+ if node:
+ tp = node.id & 3
+ if tp == FILE or tp == BUILD:
+ return node
+ else:
+ return None
+
+ tree = self.__class__.bld
+ if not name in tree.cache_dir_contents[parent.id]:
+ return None
+
+ path = parent.abspath() + os.sep + name
+ try:
+ st = Utils.h_file(path)
+ except IOError:
+ return None
+
+ child = self.__class__(name, parent, FILE)
+ tree.node_sigs[0][child.id] = st
+ return child
+
+ def find_or_declare(self, lst):
+ "Used for declaring a build node representing a file being built"
+ if isinstance(lst, str):
+ lst = Utils.split_path(lst)
+
+ if len(lst) == 1:
+ parent = self
+ else:
+ parent = self.find_dir(lst[:-1])
+ if not parent: return None
+ self.__class__.bld.rescan(parent)
+
+ name = lst[-1]
+ node = parent.childs.get(name, None)
+ if node:
+ tp = node.id & 3
+ if tp != BUILD:
+ raise Utils.WafError('find_or_declare found a source file where a build file was expected %r' % '/'.join(lst))
+ return node
+ node = self.__class__(name, parent, BUILD)
+ return node
+
+ def find_dir(self, lst):
+ "search a folder in the filesystem"
+
+ if isinstance(lst, str):
+ lst = Utils.split_path(lst)
+
+ current = self
+ for name in lst:
+ self.__class__.bld.rescan(current)
+ prev = current
+
+ if not current.parent and name == current.name:
+ continue
+ elif not name:
+ continue
+ elif name == '.':
+ continue
+ elif name == '..':
+ current = current.parent or current
+ else:
+ current = prev.childs.get(name, None)
+ if current is None:
+ dir_cont = self.__class__.bld.cache_dir_contents
+ if prev.id in dir_cont and name in dir_cont[prev.id]:
+ if not prev.name:
+ if os.sep == '/':
+ # cygwin //machine/share
+ dirname = os.sep + name
+ else:
+ # windows c:
+ dirname = name
+ else:
+ # regular path
+ dirname = prev.abspath() + os.sep + name
+ if not os.path.isdir(dirname):
+ return None
+ current = self.__class__(name, prev, DIR)
+ elif (not prev.name and len(name) == 2 and name[1] == ':') or name.startswith('\\\\'):
+ # drive letter or \\ path for windows
+ current = self.__class__(name, prev, DIR)
+ else:
+ return None
+ else:
+ if current.id & 3 != DIR:
+ return None
+ return current
+
+ def ensure_dir_node_from_path(self, lst):
+ "used very rarely, force the construction of a branch of node instance for representing folders"
+
+ if isinstance(lst, str):
+ lst = Utils.split_path(lst)
+
+ current = self
+ for name in lst:
+ if not name:
+ continue
+ elif name == '.':
+ continue
+ elif name == '..':
+ current = current.parent or current
+ else:
+ prev = current
+ current = prev.childs.get(name, None)
+ if current is None:
+ current = self.__class__(name, prev, DIR)
+ return current
+
+ def exclusive_build_node(self, path):
+ """
+ create a hierarchy in the build dir (no source folders) for ill-behaving compilers
+ the node is not hashed, so you must do it manually
+
+ after declaring such a node, find_dir and find_resource should work as expected
+ """
+ lst = Utils.split_path(path)
+ name = lst[-1]
+ if len(lst) > 1:
+ parent = None
+ try:
+ parent = self.find_dir(lst[:-1])
+ except OSError:
+ pass
+ if not parent:
+ parent = self.ensure_dir_node_from_path(lst[:-1])
+ self.__class__.bld.rescan(parent)
+ else:
+ try:
+ self.__class__.bld.rescan(parent)
+ except OSError:
+ pass
+ else:
+ parent = self
+
+ node = parent.childs.get(name, None)
+ if not node:
+ node = self.__class__(name, parent, BUILD)
+
+ return node
+
+ def path_to_parent(self, parent):
+ "path relative to a direct ancestor, as string"
+ lst = []
+ p = self
+ h1 = parent.height()
+ h2 = p.height()
+ while h2 > h1:
+ h2 -= 1
+ lst.append(p.name)
+ p = p.parent
+ if lst:
+ lst.reverse()
+ ret = os.path.join(*lst)
+ else:
+ ret = ''
+ return ret
+
+ def find_ancestor(self, node):
+ "find a common ancestor for two nodes - for the shortest path in hierarchy"
+ dist = self.height() - node.height()
+ if dist < 0: return node.find_ancestor(self)
+ # now the real code
+ cand = self
+ while dist > 0:
+ cand = cand.parent
+ dist -= 1
+ if cand == node: return cand
+ cursor = node
+ while cand.parent:
+ cand = cand.parent
+ cursor = cursor.parent
+ if cand == cursor: return cand
+
+ def relpath_gen(self, from_node):
+ "string representing a relative path between self to another node"
+
+ if self == from_node: return '.'
+ if from_node.parent == self: return '..'
+
+ # up_path is '../../../' and down_path is 'dir/subdir/subdir/file'
+ ancestor = self.find_ancestor(from_node)
+ lst = []
+ cand = self
+ while not cand.id == ancestor.id:
+ lst.append(cand.name)
+ cand = cand.parent
+ cand = from_node
+ while not cand.id == ancestor.id:
+ lst.append('..')
+ cand = cand.parent
+ lst.reverse()
+ return os.sep.join(lst)
+
+ def nice_path(self, env=None):
+ "printed in the console, open files easily from the launch directory"
+ tree = self.__class__.bld
+ ln = tree.launch_node()
+
+ if self.id & 3 == FILE: return self.relpath_gen(ln)
+ else: return os.path.join(tree.bldnode.relpath_gen(ln), env.variant(), self.relpath_gen(tree.srcnode))
+
+ def is_child_of(self, node):
+ "does this node belong to the subtree node"
+ p = self
+ diff = self.height() - node.height()
+ while diff > 0:
+ diff -= 1
+ p = p.parent
+ return p.id == node.id
+
+ def variant(self, env):
+ "variant, or output directory for this node, a source has for variant 0"
+ if not env: return 0
+ elif self.id & 3 == FILE: return 0
+ else: return env.variant()
+
+ def height(self):
+ "amount of parents"
+ # README a cache can be added here if necessary
+ d = self
+ val = -1
+ while d:
+ d = d.parent
+ val += 1
+ return val
+
+ # helpers for building things
+
+ def abspath(self, env=None):
+ """
+ absolute path
+ @param env [Environment]:
+ * obligatory for build nodes: build/variant/src/dir/bar.o
+ * optional for dirs: get either src/dir or build/variant/src/dir
+ * excluded for source nodes: src/dir/bar.c
+
+ Instead of computing the absolute path each time again,
+ store the already-computed absolute paths in one of (variants+1) dictionaries:
+ bld.cache_node_abspath[0] holds absolute paths for source nodes.
+ bld.cache_node_abspath[variant] holds the absolute path for the build nodes
+ which reside in the variant given by env.
+ """
+ ## absolute path - hot zone, so do not touch
+
+ # less expensive
+ variant = (env and (self.id & 3 != FILE) and env.variant()) or 0
+
+ ret = self.__class__.bld.cache_node_abspath[variant].get(self.id, None)
+ if ret: return ret
+
+ if not variant:
+ # source directory
+ if not self.parent:
+ val = os.sep == '/' and os.sep or ''
+ elif not self.parent.name: # root
+ val = (os.sep == '/' and os.sep or '') + self.name
+ else:
+ val = self.parent.abspath() + os.sep + self.name
+ else:
+ # build directory
+ val = os.sep.join((self.__class__.bld.bldnode.abspath(), variant, self.path_to_parent(self.__class__.bld.srcnode)))
+ self.__class__.bld.cache_node_abspath[variant][self.id] = val
+ return val
+
+ def change_ext(self, ext):
+ "node of the same path, but with a different extension - hot zone so do not touch"
+ name = self.name
+ k = name.rfind('.')
+ if k >= 0:
+ name = name[:k] + ext
+ else:
+ name = name + ext
+
+ return self.parent.find_or_declare([name])
+
+ def src_dir(self, env):
+ "src path without the file name"
+ return self.parent.srcpath(env)
+
+ def bld_dir(self, env):
+ "build path without the file name"
+ return self.parent.bldpath(env)
+
+ def bld_base(self, env):
+ "build path without the extension: src/dir/foo(.cpp)"
+ s = os.path.splitext(self.name)[0]
+ return os.path.join(self.bld_dir(env), s)
+
+ def bldpath(self, env=None):
+ "path seen from the build dir default/src/foo.cpp"
+ if self.id & 3 == FILE:
+ return self.relpath_gen(self.__class__.bld.bldnode)
+ p = self.path_to_parent(self.__class__.bld.srcnode)
+ if p is not '':
+ return env.variant() + os.sep + p
+ return env.variant()
+
+ def srcpath(self, env=None):
+ "path in the srcdir from the build dir ../src/foo.cpp"
+ if self.id & 3 == BUILD:
+ return self.bldpath(env)
+ return self.relpath_gen(self.__class__.bld.bldnode)
+
+ def read(self, env):
+ "get the contents of a file, it is not used anywhere for the moment"
+ return Utils.readf(self.abspath(env))
+
+ def dir(self, env):
+ "scons-like"
+ return self.parent.abspath(env)
+
+ def file(self):
+ "scons-like"
+ return self.name
+
+ def file_base(self):
+ "scons-like"
+ return os.path.splitext(self.name)[0]
+
+ def suffix(self):
+ "scons-like - hot zone so do not touch"
+ k = max(0, self.name.rfind('.'))
+ return self.name[k:]
+
+ def find_iter_impl(self, src=True, bld=True, dir=True, accept_name=None, is_prune=None, maxdepth=25):
+ """find nodes in the filesystem hierarchy, try to instanciate the nodes passively; same gotcha as ant_glob"""
+ bld_ctx = self.__class__.bld
+ bld_ctx.rescan(self)
+ for name in bld_ctx.cache_dir_contents[self.id]:
+ if accept_name(self, name):
+ node = self.find_resource(name)
+ if node:
+ if src and node.id & 3 == FILE:
+ yield node
+ else:
+ node = self.find_dir(name)
+ if node and node.id != bld_ctx.bldnode.id:
+ if dir:
+ yield node
+ if not is_prune(self, name):
+ if maxdepth:
+ for k in node.find_iter_impl(src, bld, dir, accept_name, is_prune, maxdepth=maxdepth - 1):
+ yield k
+ else:
+ if not is_prune(self, name):
+ node = self.find_resource(name)
+ if not node:
+ # not a file, it is a dir
+ node = self.find_dir(name)
+ if node and node.id != bld_ctx.bldnode.id:
+ if maxdepth:
+ for k in node.find_iter_impl(src, bld, dir, accept_name, is_prune, maxdepth=maxdepth - 1):
+ yield k
+
+ if bld:
+ for node in self.childs.values():
+ if node.id == bld_ctx.bldnode.id:
+ continue
+ if node.id & 3 == BUILD:
+ if accept_name(self, node.name):
+ yield node
+ raise StopIteration
+
+ def find_iter(self, in_pat=['*'], ex_pat=exclude_pats, prune_pat=prune_pats, src=True, bld=True, dir=False, maxdepth=25, flat=False):
+ """find nodes recursively, this returns everything but folders by default; same gotcha as ant_glob"""
+
+ if not (src or bld or dir):
+ raise StopIteration
+
+ if self.id & 3 != DIR:
+ raise StopIteration
+
+ in_pat = Utils.to_list(in_pat)
+ ex_pat = Utils.to_list(ex_pat)
+ prune_pat = Utils.to_list(prune_pat)
+
+ def accept_name(node, name):
+ for pat in ex_pat:
+ if fnmatch.fnmatchcase(name, pat):
+ return False
+ for pat in in_pat:
+ if fnmatch.fnmatchcase(name, pat):
+ return True
+ return False
+
+ def is_prune(node, name):
+ for pat in prune_pat:
+ if fnmatch.fnmatchcase(name, pat):
+ return True
+ return False
+
+ ret = self.find_iter_impl(src, bld, dir, accept_name, is_prune, maxdepth=maxdepth)
+ if flat:
+ return " ".join([x.relpath_gen(self) for x in ret])
+
+ return ret
+
+ def ant_glob(self, *k, **kw):
+ """
+ known gotcha: will enumerate the files, but only if the folder exists in the source directory
+ """
+
+ src=kw.get('src', 1)
+ bld=kw.get('bld', 0)
+ dir=kw.get('dir', 0)
+ excl = kw.get('excl', exclude_regs)
+ incl = k and k[0] or kw.get('incl', '**')
+
+ def to_pat(s):
+ lst = Utils.to_list(s)
+ ret = []
+ for x in lst:
+ x = x.replace('//', '/')
+ if x.endswith('/'):
+ x += '**'
+ lst2 = x.split('/')
+ accu = []
+ for k in lst2:
+ if k == '**':
+ accu.append(k)
+ else:
+ k = k.replace('.', '[.]').replace('*', '.*').replace('?', '.')
+ k = '^%s$' % k
+ #print "pattern", k
+ accu.append(re.compile(k))
+ ret.append(accu)
+ return ret
+
+ def filtre(name, nn):
+ ret = []
+ for lst in nn:
+ if not lst:
+ pass
+ elif lst[0] == '**':
+ ret.append(lst)
+ if len(lst) > 1:
+ if lst[1].match(name):
+ ret.append(lst[2:])
+ else:
+ ret.append([])
+ elif lst[0].match(name):
+ ret.append(lst[1:])
+ return ret
+
+ def accept(name, pats):
+ nacc = filtre(name, pats[0])
+ nrej = filtre(name, pats[1])
+ if [] in nrej:
+ nacc = []
+ return [nacc, nrej]
+
+ def ant_iter(nodi, maxdepth=25, pats=[]):
+ nodi.__class__.bld.rescan(nodi)
+ tmp = list(nodi.__class__.bld.cache_dir_contents[nodi.id])
+ tmp.sort()
+ for name in tmp:
+ npats = accept(name, pats)
+ if npats and npats[0]:
+ accepted = [] in npats[0]
+ #print accepted, nodi, name
+
+ node = nodi.find_resource(name)
+ if node and accepted:
+ if src and node.id & 3 == FILE:
+ yield node
+ else:
+ node = nodi.find_dir(name)
+ if node and node.id != nodi.__class__.bld.bldnode.id:
+ if accepted and dir:
+ yield node
+ if maxdepth:
+ for k in ant_iter(node, maxdepth=maxdepth - 1, pats=npats):
+ yield k
+ if bld:
+ for node in nodi.childs.values():
+ if node.id == nodi.__class__.bld.bldnode.id:
+ continue
+ if node.id & 3 == BUILD:
+ npats = accept(node.name, pats)
+ if npats and npats[0] and [] in npats[0]:
+ yield node
+ raise StopIteration
+
+ ret = [x for x in ant_iter(self, pats=[to_pat(incl), to_pat(excl)])]
+
+ if kw.get('flat', True):
+ return " ".join([x.relpath_gen(self) for x in ret])
+
+ return ret
+
+ def update_build_dir(self, env=None):
+
+ if not env:
+ for env in bld.all_envs:
+ self.update_build_dir(env)
+ return
+
+ path = self.abspath(env)
+
+ lst = Utils.listdir(path)
+ try:
+ self.__class__.bld.cache_dir_contents[self.id].update(lst)
+ except KeyError:
+ self.__class__.bld.cache_dir_contents[self.id] = set(lst)
+ self.__class__.bld.cache_scanned_folders[self.id] = True
+
+ for k in lst:
+ npath = path + os.sep + k
+ st = os.stat(npath)
+ if stat.S_ISREG(st[stat.ST_MODE]):
+ ick = self.find_or_declare(k)
+ if not (ick.id in self.__class__.bld.node_sigs[env.variant()]):
+ self.__class__.bld.node_sigs[env.variant()][ick.id] = Constants.SIG_NIL
+ elif stat.S_ISDIR(st[stat.ST_MODE]):
+ child = self.find_dir(k)
+ if not child:
+ child = self.ensure_dir_node_from_path(k)
+ child.update_build_dir(env)
+
+
+class Nodu(Node):
+ pass
+