#! /usr/bin/env python # encoding: utf-8 # Thomas Nagy, 2005 (ita) import os import os.path from stat import * import shutil import cPickle import Node import Params from Params import debug, error, trace, fatal class Deptree: def __init__(self): # filesystem root - root name is Params.g_rootname self.m_root = Node.Node('', None) self.m_name2nodes = {} # access nodes quickly self.m_bldnode = None self.m_srcnode = None # source directory # get bld nodes from src nodes quickly self.m_src_to_bld = {} # get src nodes from bld nodes quickly self.m_bld_to_src = {} # one node has nodes it depends on, tasks cannot be stored # node -> [node; node; node ..] - all dependencies # m_depends_on[node] = [node1, node2, ..] self.m_depends_on = {} # m_deps_tstamp[node] = represents the timestamp for the node last scan self.m_deps_tstamp = {} # results of a scan: self.m_raw_deps[node] = [filename1, filename2, filename3] # for example, find headers in c files self.m_raw_deps = {} # signatures for nodes that are created in the builddir self.m_sigs = {} # give flags to nodes (eg: existing->1, not existing->0) self.m_flags = {} # IMPORTANT # Fast node access - feed an internal dictionary (to keep between runs -> TODO not sure) def store_node(self, node): nn=node.m_name try: # prevent silly errors if node in self.m_name2nodes[nn]: print "BUG: name2nodes already contains node!", nn else: self.m_name2nodes[nn].append(node) except: self.m_name2nodes[nn] = [node] def get_depends_on(self, node): try: return self.m_depends_on[node] except: return [] # TODO we might need to add the name of the scanner, but that can be too complicated # tell if a node has changed, to update the cache def needs_rescan(self, node): #print "needs_rescan for ", node, node.m_tstamp try: if self.m_deps_tstamp[node] == node.m_tstamp: #print "no need to rescan", node.m_tstamp return 0 except: pass return 1 # scan for dependencies, store them in cache, and keep the date handy def rescan(self, node, scanner_function, hashparams): debug("rescanning "+str(node)) (nodes, names) = scanner_function(node, **hashparams) self.m_depends_on[node] = nodes self.m_raw_deps[node] = names self.m_deps_tstamp[node] = node.m_tstamp def get_raw_deps(self, node): try: return self.m_raw_deps[node] except: return [] def is_a_built_node(self, node): return node.is_child_of(self.m_bldnode) ## IMPORTANT - for debugging def dump(self): def printspaces(count): if count>0: return printspaces(count-1)+"-" return "" def recu(node, count): accu = printspaces(count) accu+= "> "+node.m_name+" (d)\n" for child in node.m_files: accu+= printspaces(count) accu+= '> '+child.m_name+' ' accu+= ' '+str(child.m_tstamp)+'\n' # TODO #if node.m_files[file].m_newstamp != node.m_files[file].m_oldstamp: accu += "\t\t\t(modified)" #accu+= node.m_files[file].m_newstamp + "< >" + node.m_files[file].m_oldstamp + "\n" for dir in node.m_dirs: accu += recu(dir, count+1) return accu Params.pprint('CYAN', recu(self.m_root, 0) ) Params.pprint('CYAN', 'size is '+str(self.m_root.size())) #keys = self.m_name2nodes.keys() #for k in keys: # print k, '\t\t', self.m_name2nodes[k] # return the mirror of a node in a particular builddir, both nodes must exist def get_mirror_node(self, dirnode): # get the difference between srcnode and dirnode lst = self.m_srcnode.difflst( dirnode ) if not lst: debug("lst is empty, this should not happen") # TODO: use name2nodes ? a cache ? to investigate # get find the node from the build directory using the difference above mirrornode = self.m_bldnode.find_node(lst) return mirrornode # return the node in the source directory, given a node in the build directory def get_src_from_mirror(self, bldnode): return self.m_srcnode.find_node( self.m_bldnode.difflst(bldnode) ) # TODO FIXME : why the .. def get_bld_from_src(self, srcnode): return self.m_src_to_bld[srcnode] # the following is used for launching the commands to create the nodes # the node must exist (and so it has a mirror), but the filename may not exist up to that point def mirror_file(self, dirnode, filename): if os.sep in filename: lst = filename.split(os.sep) dirnode = dirnode.find_node(lst[:len(lst)-1]) filename = lst[len(lst)-1] # get the the dirnode mirror mirrornode = self.get_mirror_node(dirnode) # now that we have the mirror of dirnode, try to find the filename ret=None for file in mirrornode.m_files: if file.m_name==filename: ret=file break # if that node does not exist, just create it if not ret: ret = Node.Node(filename, mirrornode) mirrornode.m_files.append(ret) if ret: trace("mirror node is found "+str(ret)) else: trace("mirror node is NOT FOUND"+str(ret)) return ret # the first important algorithm of the app: # -> replicate files into the builddir # -> find the nodes to scan for dependencies def scanner_mirror(self, dir): trace("scanner mirror for dir "+dir) lst = dir.split(os.sep) src_node = self.ensure_node_from_lst( self.m_srcnode, lst ) (exists, mir_node) = self.ensure_directory_lst( self.m_bldnode, lst ) # the source node must be stat'd - this is a folder so we use the stat src_sig = os.stat(src_node.abspath()).st_mtime if Params.g_strong_hash: src_sig = str(src_sig) # two essential conditions 1. the mirror node exists and 2. the src folder has not changed C1 = exists C2 = (src_sig == src_node.m_tstamp) #print "C1 and C2 ", C1, " ",C2 # first one, condition to skip everything (folders untouched) if C1 and C2: if len(src_node.m_files) == len(mir_node.m_files): # we only need to update the hash sig of each source file for node in src_node.m_files: #node.m_tstamp = os.stat(node.abspath()).st_mtime node.m_tstamp = Params.h_file(node.abspath()) return if C1: #print "untested part" # difficult part, find what nodes to update, etc # C1&!C2 | C1&C2&!C3 # FIXME TODO this is inefficient and written very late names_read = os.listdir( src_node.abspath() ) removed = [] added = [] e_files = [] e_dirs = [] e_unknown = [] for file in src_node.m_files: try: # try to stat the file .. st = os.stat(file.abspath()) ts = file.m_tstamp #file.m_tstamp = st.st_mtime file.m_tstamp = Params.h_file(file.abspath()) e_files.append(file.m_name) # if the timestamp has changed, we need to re-scan the file - not here though if ts!=file.m_tstamp: trace("A file changed! %s %s now %s" % (str(file),str(ts),str(file.m_tstamp)) ) except: # remove the node here and in the builddir name = file.m_name src_node.m_files.remove(file) for mn in mir_node.m_files: if mn.m_name == name: mir_node.m_files.remove(mn) #print "remove trailing file %s from builddir "%mn.m_name² try: os.remove(os.path.join(mir_node.abspath(), mn.m_name)) except: pass break for file in src_node.m_dirs: # same as above comment, if fails then the dir does not exist anymore st = os.stat(file.abspath()) file.m_tstamp = st.st_mtime e_dirs.append(file.m_name) excl = e_files+e_dirs+Params.g_excludes #print excl for name in names_read: if name in excl: continue e_unknown.append(name) #print "unkn ", e_unknown for name in e_unknown: # new files or dirs - create the corresponding nodes child_node = Node.Node(name, src_node) #print 'adding child node '+str(child_node) st = os.stat( child_node.abspath() ) relp = src_node.relpath_gen(mir_node) # in case of a regular file, create a mirror node in the builddir if S_ISREG( st[ST_MODE] ): #print "regular file" src_node.m_files.append(child_node) #child_node.m_tstamp = st.st_mtime child_node.m_tstamp = Params.h_file(child_node.abspath()) # mirror the new node mir_child_node = Node.Node(name, mir_node) mir_node.m_files.append(mir_child_node) #mir_child_node.m_tstamp = st.st_mtime mir_child_node.m_tstamp = src_node.m_tstamp dupe = mir_child_node.abspath() self.m_bld_to_src[mir_child_node]=child_node self.m_src_to_bld[child_node]=mir_child_node else: src_node.m_dirs.append(child_node) src_node.m_tstamp = src_sig else: relp = src_node.relpath_gen(mir_node) if C2: # replicate the files in the newly created destdir blindly for child_node in src_node.m_files: name = child_node.m_name mir_child_node = Node.Node(child_node.m_name, mir_node) mir_node.m_files.append( mir_child_node ) # same signature of course mir_child_node.m_tstamp = child_node.m_tstamp dupe = mir_child_node.abspath() self.m_bld_to_src[mir_child_node]=child_node self.m_src_to_bld[child_node]=mir_child_node else: # we need to update the info in the srcdir first - remove the m_files nodes src_node.m_files = [] names_read = os.listdir( src_node.abspath() ) abspath = src_node.abspath() for file_or_dir in names_read: subpath = os.path.join(abspath, file_or_dir) st = os.stat( subpath ) if not S_ISREG(st[ST_MODE]): # this could be left aside, unfortunately that's a bad idea src_node.m_dirs.append( Node.Node(file_or_dir, src_node) ) continue # create the corresponding nodes in src and mir folders child_node = Node.Node(file_or_dir, src_node) src_node.m_files.append( child_node ) mir_child_node = Node.Node(file_or_dir, mir_node) mir_node.m_files.append( mir_child_node ) # timestamp is the same (sig) #tstamp = st.st_mtime tstamp = Params.h_file(subpath) child_node.m_tstamp = tstamp mir_child_node.m_tstamp = tstamp dupe = mir_child_node.abspath() self.m_bld_to_src[mir_child_node]=child_node self.m_src_to_bld[child_node]=mir_child_node src_node.m_tstamp = src_sig # ensure a directory node from a list, given a node to start from def ensure_node_from_lst(self, node, plst): curnode=node for dirname in plst: if 0: if not dirname: continue nc = Params.g_build.m_cache_node_content try: htbl = nc[curnode] except: nc[curnode] = {} htbl = nc[curnode] for file in curnode.m_dirs+curnode.m_files: htbl[file.m_name]=file try: #print "found !" found = htbl[dirname] except: found = Node.Node(dirname, curnode) curnode.m_dirs.append(found) htbl[dirname]=found curnode = found if 1: if not dirname: continue found=None for cand in curnode.m_dirs: if cand.m_name == dirname: found = cand break if not found: found = Node.Node(dirname, curnode) curnode.m_dirs.append(found) curnode = found return curnode def ensure_directory_lst(self, node, plst): curnode = node exists = 1 for dirname in plst: #print "finding ", dirname if not dirname: continue # try to find the node in existing deptree found=None for cand in curnode.m_dirs: if cand.m_name == dirname: found = cand break # the node is found and is already scanned, keep walking try: if found and self.m_flags[found]>0: curnode=found continue except: pass # the node is not found, add it if not found: found = Node.Node(dirname, curnode) curnode.m_dirs.append(found) # we have a node, but it is not scanned curnode = found try: os.stat(curnode.abspath()) except OSError: exists = 0 trace("make dir %s"%curnode.abspath()) try: os.mkdir(curnode.abspath()) except: trace('mkdir failed '+curnode.abspath()) #st=os.stat(curnode.abspath()) # TRICK_1: the subtree is obsolete -> forget sub-nodes recursively curnode.m_dirs=[] curnode.m_files=[] self.m_flags[curnode] = 1 return (exists, curnode) # FIXME: duplicates some code # return a node corresponding to an absolute path, creates nodes if necessary def ensure_node_from_path(self, abspath): trace('Deptree:ensure_node_from_path %s' % (abspath)) plst = abspath.split(os.sep) curnode = self.m_root # root of the tree for dirname in plst: if not dirname: continue found=None for cand in curnode.m_dirs: if cand.m_name == dirname: found = cand break if not found: found = Node.Node(dirname, curnode) curnode.m_dirs.append(found) curnode = found return curnode # FIXME: duplicates some code # Creates the folders if they do not exist # Flag the intermediate nodes as 'already existing' with m_flag=1 def ensure_directory(self, path): trace('Deptree:ensure_directory %s' % (path)) dir_lst = path.split(os.sep) curnode = self.m_root # root of the tree for dirname in dir_lst: if not dirname: continue # try to find the node in existing deptree found=None for cand in curnode.m_dirs: if cand.m_name == dirname: found = cand break # the node is found and is already scanned, keep walking try: if found and self.m_flags[found]>0: curnode=found continue except: pass # the node is not found, add it if not found: found = Node.Node(dirname, curnode) curnode.m_dirs.append(found) # we have a node, but it is not scanned curnode = found try: st = os.stat(curnode.abspath()) except OSError: trace("make dir %s"%curnode.abspath()) os.mkdir(curnode.abspath()) st=os.stat(curnode.abspath()) # TRICK_1: the subtree is obsolete -> forget sub-nodes recursively curnode.m_dirs=[] curnode.m_files=[] self.m_flags[curnode] = 1 return curnode