[ooo-build-commit] .: bin/check-deps.py bin/parse-scp2.py scratch/build-analysis-tools

Kohei Yoshida kohei at kemper.freedesktop.org
Fri Jul 16 07:03:25 PDT 2010


 bin/check-deps.py                          |  361 ------------
 bin/parse-scp2.py                          |  829 -----------------------------
 scratch/build-analysis-tools/check-deps.py |  361 ++++++++++++
 scratch/build-analysis-tools/parse-scp2.py |  829 +++++++++++++++++++++++++++++
 4 files changed, 1190 insertions(+), 1190 deletions(-)

New commits:
commit a244fcaffd926b995c2760698a37c345d92bf846
Author: Kohei Yoshida <kyoshida at novell.com>
Date:   Fri Jul 16 10:01:59 2010 -0400

    Moved build analysis scripts into own directory under scratch.
    
    This is to avoid giving a false impression that these scripts are a part
    of the build system.  They are not.
    
    * bin/check-deps.py:
    * bin/parse-scp2.py:
    * scratch/build-analysis-tools/check-deps.py:
    * scratch/build-analysis-tools/parse-scp2.py:

diff --git a/bin/check-deps.py b/bin/check-deps.py
deleted file mode 100755
index 48390c0..0000000
--- a/bin/check-deps.py
+++ /dev/null
@@ -1,361 +0,0 @@
-#!/usr/bin/env python
-
-import sys, os, os.path, optparse, subprocess
-
-class ParseError(Exception): pass
-
-class SingleModeError(Exception): pass
-
-class ViewerError(Exception): pass
-
-arg_desc = "module1 module2 ..."
-
-desc = """
-Execute this script at the root directory of your OOo build tree.  It parses
-all build.lst files found in the modules and outputs module dependency data 
-in the dot compatible format.
-
-When no arguments are given, it prints dependencies of all discovered 
-modules.  When module names are given as arguments, it only traces 
-dependencies of those modules.
-
-Sometimes modules are referenced in the build.lst but are absent from the 
-source tree.  Those missing modules are displayed red in the dependency graph."""
-
-err_missing_modules = """
-The following modules are mentioned but not present in the source tree:"""
-
-class Module(object):
-
-    def __init__ (self, name):
-        self.name = name
-        self.deps  = {} # dependents
-        self.precs = {} # precedents
-
-# Store all unique dependency set, with no duplicates.
-class DependSet(object):
-
-    def __init__ (self):
-        self.modules = {}
-
-    def insert_depend (self, prec, dep):
-        if not self.modules.has_key(prec):
-            self.modules[prec] = {}
-        if dep != None:
-            self.modules[prec][dep] = True
-
-class DepsCheker(object):
-
-    def __init__ (self):
-        self.modules = {}         # all mentioned modules, whether present or not.
-        self.modules_present = {} # modules actually present in the source tree.
-        self.modules_used = {}    # modules displayed in the graph.
-        self.selected = []        # selected modules from the command line args.
-
-        self.modules_missing = None
-
-    def __normalize_name (self, name):
-        # Replace prohibited characters with someone sane.
-        name = name.replace('-', '_')
-        return name
-    
-    def __insert_depend (self, mod, dep):
-
-        # precedent to dependent
-        if not self.modules.has_key(mod):
-            self.modules[mod] = Module(mod)
-        obj = self.modules[mod]
-        obj.deps[dep] = True
-
-        # dependent to precedent
-        if not self.modules.has_key(dep):
-            self.modules[dep] = Module(dep)
-        obj = self.modules[dep]
-        obj.precs[mod] = True
-
-    def __parse_build_lst (self, build_lst):
-    
-        # Read only the first line
-        file = open(build_lst, 'r')
-        while True:
-            line = file.readline().strip()
-            if line[0] != '#':
-                break
-        file.close()
-    
-        words = line.split()
-        n = len(words)
-    
-        # Check line format to make sure it's formatted as expected.
-        if n < 4:
-            raise ParseError()
-        if words[2] != ':' and words[2] != '::':
-            raise ParseError()
-        if words[-1] != 'NULL':
-            raise ParseError()
-    
-        mod_name = self.__normalize_name(words[1])
-        depends = words[3:]
-        for dep in depends:
-            if dep == 'NULL':
-                break
-    
-            names = dep.split(':')
-            if len(names) > 2:
-                raise ParseError()
-            elif len(names) == 2:
-                dep = names[1]
-    
-            dep = self.__normalize_name(dep)
-            self.__insert_depend(mod_name, dep)
-
-    def run (self, selected):
-    
-        # modules we want to print dependency on.
-        self.selected = selected
-
-        # Find all build.lst files.
-        for mod in os.listdir(os.getcwd()):
-            if not os.path.isdir(mod):
-                # not a directory
-                continue
-            
-            build_lst = mod + '/prj/build.lst'
-            if not os.path.isfile(build_lst):
-                # no build.lst found
-                continue
-
-            self.modules_present[self.__normalize_name(mod)] = True
-            self.__parse_build_lst(build_lst)
-            
-    def __build_depset_all (self):
-        self.dep_set = DependSet() # reset
-        if len(self.selected) == 0:
-            mods = self.modules.keys()
-            for mod in mods:
-                deps = self.modules[mod].deps.keys()
-                for dep in deps:
-                    self.dep_set.insert_depend(mod, dep)
-        else:
-            # determine involved modules.
-            self.__processed_mods = {}
-            for selected in self.selected:
-                if not self.modules.has_key(selected):
-                    raise ParseError()
-
-                if len(self.modules[selected].deps) > 0:
-                    self.__trace_deps(self.modules[selected])
-
-    def __build_depset_single (self, mods):
-        self.dep_set = DependSet() # reset
-        for mod in mods:
-
-            if not self.modules.has_key(mod):
-                continue
-
-            obj = self.modules[mod]
-            if len(obj.precs) == 0 and len(obj.deps) == 0:
-                # No dependencies.  Just print the module.
-                self.dep_set.insert_depend(mod, None)
-                continue
-
-            for prec in obj.precs.keys():
-                self.dep_set.insert_depend(prec, obj.name)
-            for dep in obj.deps.keys():
-                self.dep_set.insert_depend(obj.name, dep)
-
-    def print_dot_all (self):
-        self.__build_depset_all()
-        s = "digraph modules {\n"
-        s += self.__print_dot_depset()
-        s += self.__print_dot_selected()
-        s += self.__print_dot_missing_modules()
-        s += "}\n"
-        return s
-
-    def print_dot_single (self, mods):
-        self.__build_depset_single(mods)
-        s = "digraph modules {\n"
-        s += self.__print_dot_depset()
-        s += self.__print_dot_selected()
-        s += self.__print_dot_missing_modules()
-        s += "}\n"
-        return s
-
-    def print_flat_all (self):
-        self.__build_depset_all()
-        return self.__print_flat_depset()
-
-    def print_flat_single (self, mods):
-        self.__build_depset_single(mods)
-        return self.__print_flat_depset()
-
-    def __calc_missing_modules (self):
-        if self.modules_missing != None:
-            # already calculated.
-            return
-
-        present = self.modules_present.keys()
-        self.modules_missing = {}
-        for mod in self.modules.keys():
-            if not self.modules_present.has_key(mod):
-                self.modules_missing[mod] = True
-
-    def print_missing_modules (self):
-        self.__calc_missing_modules()
-
-        if len(self.modules_missing) == 0:
-            return
-
-        sys.stderr.write(err_missing_modules + "\n")
-        keys = self.modules_missing.keys()
-        keys.sort()
-        for mod in keys:
-            sys.stderr.write("    " + mod + "\n")
-
-    def __trace_deps (self, obj):
-        if self.__processed_mods.has_key(obj.name):
-            return
-
-        self.__processed_mods[obj.name] = True
-
-        for dep_name in obj.deps.keys():
-            if not self.modules.has_key(dep_name):
-                raise ParseError()
-            self.dep_set.insert_depend(obj.name, dep_name)
-            self.__trace_deps(self.modules[dep_name])
-
-    def __print_flat_depset (self):
-        s = ''
-        mods = self.dep_set.modules.keys()
-        mods.sort()
-        for mod in mods:
-            deps = self.dep_set.modules[mod].keys()
-            if len(deps) == 0:
-                # this module has no dependency.
-                s += "%s\n"%mod
-            else:
-                deps.sort()
-                for dep in deps:
-                    s += "%s:%s\n"%(mod, dep)
-        return s
-
-    def __print_dot_depset (self):
-        s = ''
-        mods = self.dep_set.modules.keys()
-        for mod in mods:
-            deps = self.dep_set.modules[mod].keys()
-            if len(deps) == 0:
-                # this module has no dependency.
-                s += self.__print_dot_dep_line(mod, None)
-            else:
-                for dep in deps:
-                    s += self.__print_dot_dep_line(mod, dep)
-        return s
-
-    def __print_dot_selected (self):
-        s = ''
-        for mod in self.selected:
-            if not self.modules_used.has_key(mod):
-                continue
-            s += "    %s [color=lightblue,style=filled];\n"%mod
-        return s
-
-
-    def __print_dot_missing_modules (self):
-        self.__calc_missing_modules()
-        s = ''
-        for mod in self.modules_missing.keys():
-            if not self.modules_used.has_key(mod):
-                continue
-            s += "    %s [color=red,style=filled];\n"%mod
-
-        return s
-
-
-    def __print_dot_dep_line (self, prec, dep):
-        if prec == None:
-            raise ParseError()
-
-        self.modules_used[prec] = True
-        if dep == None:
-            # this module has no dependency.  I still need to mention the module name.
-            return "    %s;\n"%prec
-        self.modules_used[dep] = True
-        return "    %s -> %s;\n"%(prec, dep)
-
-def exec_exists (cmd):
-    retcode = subprocess.call("which %s >/dev/null 2>/dev/null"%cmd, shell=True)
-    return retcode == 0
-
-def error (msg):
-    sys.stderr.write(msg + "\n")
-    sys.exit(1)
-
-def launch_viewer (code):
-    tmpfile = '/tmp/check-deps.tmp'
-    tmpimage = '/tmp/check-deps-image.png'
-    file = open(tmpfile, 'w')
-    file.write(code)
-    file.close()
-    retcode = subprocess.call("dot -Tpng %s -o %s"%(tmpfile, tmpimage), shell=True)
-    if retcode != 0:
-        raise ViewerError()
-
-    retcode = subprocess.call("eog %s"%tmpimage, shell=True)
-    if retcode != 0:
-        raise ViewerError()
-
-
-if __name__ == '__main__':
-
-    # Process commnad line arguments.
-    parser = optparse.OptionParser()
-    parser.usage += " " + arg_desc + "\n" + desc
-    parser.add_option("-m", "--outout-mode", dest="output_mode", default="dot", metavar="MODE",
-        help="Specify output format mode.  Supported modes are 'dot' and 'flat'.")
-    parser.add_option("-s", "--single", action="store_true", dest="single", default=False,
-        help="Print only immediate dependencies of specified modules.")
-    parser.add_option("-g", "--gui", action="store_true", dest="gui", default=False,
-        help="Display dependency graph in image viewer.")
-    options, args = parser.parse_args()
-
-    if options.gui:
-        # Check to make sure 'dot' and 'eog' are present.
-        if not exec_exists('dot'):
-            error("'dot' not found.  Make sure you have 'dot' in your path.")
-        if not exec_exists('eog'):
-            error("'eog' not found.  Make sure you have 'eog' in your path.")
-
-        # GUI mode requires dot-compatible output.
-        options.output_mode = 'dot'
-
-    if options.output_mode != 'dot' and options.output_mode != 'flat':
-        error("Unrecognized output mode: %s"%options.output_mode)
-
-    checker = DepsCheker()
-    s = ''
-    if options.single:
-        if len(args) == 0:
-            # single mode requires module names.
-            raise SingleModeError()
-        checker.run(args)
-        if options.output_mode == 'dot':
-            s = checker.print_dot_single(args)
-        else:
-            s = checker.print_flat_single(args)
-
-    else:
-        checker.run(args)
-        if options.output_mode == 'dot':
-            s = checker.print_dot_all()
-        else:
-            s = checker.print_flat_all()
-
-    checker.print_missing_modules()
-
-    if options.gui:
-        launch_viewer(s)
-    else:
-        print (s)
-
diff --git a/bin/parse-scp2.py b/bin/parse-scp2.py
deleted file mode 100755
index 90779c4..0000000
--- a/bin/parse-scp2.py
+++ /dev/null
@@ -1,829 +0,0 @@
-#!/usr/bin/env python
-
-import sys, os, os.path, optparse, subprocess
-
-arg_desc = ""
-
-desc = """
-Run this script at the root of OOo source tree."""
-
-# taken from setup_native/source/packinfo/packinfo_office.txt
-top_modules = [
-    'gid_Module_Optional_Gnome',
-    'gid_Module_Optional_Kde',
-    'gid_Module_Root',
-    'gid_Module_Prg_Wrt_Bin',
-    'gid_Module_Prg_Calc_Bin',
-    'gid_Module_Prg_Draw_Bin',
-    'gid_Module_Prg_Impress_Bin',
-    'gid_Module_Prg_Base_Bin',
-    'gid_Module_Prg_Math_Bin',
-    'gid_Module_Optional_Binfilter',
-    'gid_Module_Optional_Grfflt',
-    'gid_Module_Oooimprovement',
-    'gid_Module_Optional_Testtool',
-    'gid_Module_Optional_Oo_English',
-    'gid_Module_Optional_Xsltfiltersamples',
-    'gid_Module_Optional_Javafilter',
-    'gid_Module_Optional_Activexcontrol',
-    'gid_Module_Optional_Onlineupdate',
-    'gid_Module_Optional_Pyuno',
-    'gid_Module_Optional_Pymailmerge',
-    'gid_Module_Optional_Headless',
-    'gid_Module_Root_Files_Images',
-    'gid_Module_Root_Fonts_OOo_Hidden',
-    'gid_Module_Oo_Linguistic',
-    'gid_Module_Root_Files_2',
-    'gid_Module_Root_Files_3',
-    'gid_Module_Root_Files_4',
-    'gid_Module_Root_Files_5',
-    'gid_Module_Root_Files_6',
-    'gid_Module_Root_Files_7',
-    'gid_Module_Root_Extension_Oooimprovement',
-    'gid_Module_Root_Extension_Dictionary_Af',
-    'gid_Module_Root_Extension_Dictionary_Ca',
-    'gid_Module_Root_Extension_Dictionary_Cs',
-    'gid_Module_Root_Extension_Dictionary_Da',
-    'gid_Module_Root_Extension_Dictionary_De_AT',
-    'gid_Module_Root_Extension_Dictionary_De_CH',
-    'gid_Module_Root_Extension_Dictionary_De_DE',
-    'gid_Module_Root_Extension_Dictionary_En',
-    'gid_Module_Root_Extension_Dictionary_Es',
-    'gid_Module_Root_Extension_Dictionary_Et',
-    'gid_Module_Root_Extension_Dictionary_Fr',
-    'gid_Module_Root_Extension_Dictionary_Gl',
-    'gid_Module_Root_Extension_Dictionary_He',
-    'gid_Module_Root_Extension_Dictionary_Hu',
-    'gid_Module_Root_Extension_Dictionary_It',
-    'gid_Module_Root_Extension_Dictionary_Ku_Tr',
-    'gid_Module_Root_Extension_Dictionary_Lt',
-    'gid_Module_Root_Extension_Dictionary_Ne',
-    'gid_Module_Root_Extension_Dictionary_Nl',
-    'gid_Module_Root_Extension_Dictionary_No',
-    'gid_Module_Root_Extension_Dictionary_Pl',
-    'gid_Module_Root_Extension_Dictionary_Pt',
-    'gid_Module_Root_Extension_Dictionary_Ro',
-    'gid_Module_Root_Extension_Dictionary_Ru',
-    'gid_Module_Root_Extension_Dictionary_Sk',
-    'gid_Module_Root_Extension_Dictionary_Sl',
-    'gid_Module_Root_Extension_Dictionary_Sr',
-    'gid_Module_Root_Extension_Dictionary_Sv',
-    'gid_Module_Root_Extension_Dictionary_Sw',
-    'gid_Module_Root_Extension_Dictionary_Th',
-    'gid_Module_Root_Extension_Dictionary_Vi',
-    'gid_Module_Root_Extension_Dictionary_Zu',
-    'gid_Module_Optional_OGLTrans'
-]
-
-class ErrorBase(Exception):
-    
-    def __init__ (self, name, msg, sev):
-        self.value = "%s: %s"%(name, msg)
-        self.sev = sev                    # error severity, 0 = least severe
-
-    def __str__ (self):
-        return repr(self.value)
-
-class ParseError(ErrorBase):
-    
-    def __init__ (self, msg, sev = 0):
-        ErrorBase.__init__(self, "ParseError", msg, sev)
-
-class DirError(ErrorBase):
-    def __init__ (self, msg):
-        ErrorBase.__init__(self, "DirError", msg, 0)
-
-class ModuleError(ErrorBase):
-    def __init__ (self, msg):
-        ErrorBase.__init__(self, "ModuleError", msg, 0)
-
-class LinkedNode(object):
-    def __init__ (self, name):
-        self.name = name
-        self.parent = None
-        self.children = []
-
-# ----------------------------------------------------------------------------
-
-def error (msg):
-    sys.stderr.write(msg + "\n")
-
-def get_attr_or_fail (name, key, attrs):
-    if not attrs.has_key(key):
-        raise ParseError("%s doesn't have %s attribute, but expected."%(name, key), 1)
-    return attrs[key]
-
-# ----------------------------------------------------------------------------
-
-class Scp2Tokenizer(object):
-    """Tokenizer for scp files."""
-
-    def __init__ (self, content):
-        self.content = content
-        self.tokens = []
-
-    def flush_buffer (self):
-        if len(self.buf) > 0:
-            self.tokens.append(self.buf)
-            self.buf = ''
-
-    def run (self):
-        self.tokens = []
-        i = 0
-        n = len(self.content)
-        self.buf = ''
-        while i < n:
-            c = self.content[i]
-            if c in '\t\n':
-                c = ' '
-
-            if c in ' ;':
-                self.flush_buffer()
-                if c == ';':
-                    self.tokens.append(c)
-            elif c == '"':
-                # String literal.  Parse until reaching the closing quote.
-                self.flush_buffer()
-                i += 1
-                c = self.content[i]
-                while c != '"':
-                    self.buf += c
-                    i += 1
-                    c = self.content[i]
-                self.flush_buffer()
-            else:
-                self.buf += c
-            i += 1
-
-# ----------------------------------------------------------------------------
-
-class Scp2Parser(object):
-    """Parser for scp files."""
-
-    class Type:
-        File       = 0
-        Directory  = 1
-        FolderItem = 2
-
-    NodeTypes = [
-        'DataCarrier',         # ignored
-        'Directory',           # ignored, referenced directly from File
-        'File',                # done, linked from within Module
-        'Folder',              # ignored
-        'FolderItem',          # ignored for now.  windows specific?
-        'Installation',        # ignored.  I don't know what this is for.
-        'Module',              # done
-        'Profile',             # ignored
-        'ProfileItem',         # ignored
-        'RegistryItem',        # done
-        'ScpAction',           # ignored
-        'Shortcut',            # linked to File?  Treat this as a child of File for now.
-        'StarRegistry',        # ignored, probably for StarOffice only
-        'Unixlink',            # done, linked from within Module
-        'WindowsCustomAction'  # ignored
-    ]
-
-    def __init__ (self, content, filename):
-        self.content = content
-        self.filename = filename
-        self.nodedata = {}
-
-    def tokenize (self):
-        tokenizer = Scp2Tokenizer(self.content)
-        tokenizer.run()
-        self.tokens = tokenizer.tokens
-
-    def next (self):
-        self.i += 1
-
-    def token (self):
-        return self.tokens[self.i]
-
-    def parse (self):
-        if len(self.tokens) == 0:
-            # No tokens to parse.  Bail out.
-            return
-            
-        self.i = 0
-        self.n = len(self.tokens)
-        while self.i < self.n:
-            t = self.token()
-            if t in Scp2Parser.NodeTypes:
-                name, attrs, values = self.__parseEntity()
-                attrs['__node_type__'] = t                 # type of node
-                attrs['__node_location__'] = self.filename # file where the node is defined
-                attrs['__node_values__'] = values          # list of values that are not attributes (i.e. not associated with names)
-                if self.nodedata.has_key(name):
-                    raise ParseError("node named %s already exists"%name, 1)
-                self.nodedata[name] = attrs
-            else:
-                raise ParseError("Unknown node type: %s"%t)
-
-            self.next()
-
-    def append_nodes (self, nodedata, nodetree):
-
-        for key in self.nodedata.keys():
-
-            if nodedata.has_key(key):
-                raise ParseError("node named %s already exists"%key, 1)
-
-            # Transfer all the node attributes to the caller instance.
-            nodedata[key] = self.nodedata[key]
-
-            # Now, add linkage data to the parent tree instance.
-
-            if not nodetree.has_key(key):
-                # Create a new linked node instance.
-                nodetree[key] = LinkedNode(key)
-
-            attrs = self.nodedata[key]
-
-            node_type = attrs['__node_type__']
-            if node_type == 'Module':
-                self.__link_module_node(key, attrs, nodetree)
-            elif node_type == 'RegistryItem':
-                # RegistryItem entries have ModuleID to link back to a module.
-                self.__link_simple(key, attrs, nodetree, 'ModuleID')
-            elif node_type == 'Shortcut':
-                self.__link_simple(key, attrs, nodetree, 'FileID')
-            elif node_type == 'Profile':
-                self.__link_simple(key, attrs, nodetree, 'ModuleID')
-            elif node_type == 'ProfileItem':
-                self.__link_simple(key, attrs, nodetree, 'ProfileID')
-                
-
-    def __link_simple (self, name, attrs, nodetree, pid_attr):
-        parentID = get_attr_or_fail(name, pid_attr, attrs)
-        if not nodetree.has_key(parentID):
-            nodetree[parentID] = LinkedNode(parentID)
-        if not nodetree.has_key(name):
-            nodetree[name] = LinkedNode(name)
-
-        nodetree[parentID].children.append(nodetree[name])
-        if nodetree[name].parent != None:
-            raise ParseError("parent node instance already exists for '%s'"%name, 1)
-        nodetree[name].parent = nodetree[parentID]
-
-
-    def __link_files (self, name, files, nodetree):
-
-        # file list strings are formatted like this '(file1,file2,file3,....)'
-        if files[0] != '(' or files[-1] != ')':
-            raise ParseError("file list string is not formatted correctly: %s"%files)
-        files = files[1:-1]
-        list = files.split(',')
-        for file in list:
-            if not nodetree.has_key(file):
-                nodetree[file] = LinkedNode(file)
-            nodetree[name].children.append(nodetree[file])
-
-
-    def __link_module_node (self, name, attrs, nodetree):
-
-        if attrs.has_key('ParentID'):
-            parentID = attrs['ParentID']
-
-            if not nodetree.has_key(parentID):
-                nodetree[parentID] = LinkedNode(parentID)
-
-            nodetree[parentID].children.append(nodetree[name])
-            if nodetree[name].parent != None:
-                raise ParseError("parent node instance already exists for '%s'"%name, 1)
-            nodetree[name].parent = nodetree[parentID]
-
-        if attrs.has_key('Files'):
-            self.__link_files(name, attrs['Files'], nodetree)
-
-        if attrs.has_key('Unixlinks'):
-            self.__link_files(name, attrs['Unixlinks'], nodetree)
-
-
-    def __parseEntity (self):
-        self.next()
-        name = self.token()
-        if len(name) == 0:
-            raise ParseError("empty name", 1)
-        left = True
-        attr_name = ''
-        attr_value = ''
-        attrs = {}
-        values = []
-        self.next()
-        while self.token() != 'End':
-            if self.token() == '=':
-                if not left:
-                    raise ParseError("multiple '='s in a single line")
-
-                if len(attr_name) == 0:
-                    raise ParseError("empty attribute name")
-
-                left = False
-            
-            elif left:
-                if self.token() == ';':
-                    # Not a valid attribute.  Store it as a 'value'.
-                    values.append(attr_name)
-                    attr_name = ''
-                else:
-                    attr_name += self.token()
-            else:
-                # Parse all the way up to ';'
-                attr_value = ''
-                while self.token() != ';':
-                    attr_value += self.token()
-                    self.next()
-                attrs[attr_name] = attr_value
-                left = True
-                attr_name = ''
-
-            self.next()
-
-        return name, attrs, values
-
-# ----------------------------------------------------------------------------
-
-class XMLFunc:
-
-    @staticmethod
-    def resolve_vars (s, vars):
-        """Replace all ${...}s with their respective values."""
-
-        ret = ''
-        
-        while True:
-            start = s.find('${')
-            if start == -1:
-                ret += s
-                break
-    
-            end = s.find('}', start+2)
-            if end == -1:
-                ret += s
-                break
-    
-            key = s[start+2:end]
-            if vars.has_key(key):
-                ret += s[:start] + vars[key]
-            s = s[end+1:]
-    
-        return ret
-
-    @staticmethod
-    def to_xml_name (name):
-        """CamelCase to camel-case"""
-        s = ''
-        n = len(name)
-        for i in xrange(0, n):
-            c = name[i]
-            if 'A' <= c and c <= 'Z':
-                if i > 0:
-                    s += '-'
-                s += c.lower()
-            else:
-                s += c
-        return s
-
-    @staticmethod
-    def add_attr (attrs, key):
-        s = ''
-        if attrs.has_key(key):
-            s = " %s=\"%s\""%(XMLFunc.to_xml_name(key), attrs[key])
-        return s
-
-    @staticmethod
-    def add_attr_localized (attrs, key, locale):
-        if attrs.has_key(key):
-            # Try non-localized name first.
-            return " %s=\"%s\""%(XMLFunc.to_xml_name(key), attrs[key])
-        
-        key_localized = key + "(%s)"%locale
-        if attrs.has_key(key_localized):
-            # Try non-localized name first.
-            return " %s=\"%s\" locale=\"%s\""%(XMLFunc.to_xml_name(key), attrs[key_localized], locale)
-
-        return ''
-
-    @staticmethod
-    def add_attr_vars (attrs, key, vars):
-        if not attrs.has_key(key):
-            return ''
-
-        s = " %s=\"%s\""%(XMLFunc.to_xml_name(key), XMLFunc.resolve_vars(attrs[key], vars))
-        return s
-
-
-    @staticmethod
-    def add_attr_array (attrs, key):
-
-        if not attrs.has_key(key):
-            return ''
-
-        raw_str = attrs[key]
-        if len(raw_str) == 0 or raw_str[0] != '(' or raw_str[-1] != ')':
-            raise ParseError("%s attribute is not formatted properly: '%s'"%(key, raw_str), 1)
-
-        if raw_str == '()':
-            return ''
-
-        val = raw_str[1:-1].lower().replace('_', '-')
-        s = " %s=\"%s\""%(XMLFunc.to_xml_name(key), val)
-        return s
-
-
-
-class Scp2Processor(object):
-    """Collect all .scp files in scp2 directory, and run preprocessor."""
-
-    tmpin  = "/tmp/parse-scp2.py.cpp"
-    tmpout = "/tmp/parse-scp2.py.out"
-
-    SkipList = {
-        'scp2/source/ooo/ure_standalone.scp': True,
-        'scp2/source/sdkoo/sdkoo.scp': True,
-        'scp2/source/ooo/starregistry_ooo.scp': True
-    }
-
-    def __init__ (self, cur_dir, mod_output_dir, vars):
-        self.cur_dir = cur_dir
-        self.mod_output_dir = mod_output_dir
-        self.vars = vars
-        self.scp_files = []
-        self.nodedata = {}
-        self.nodetree = {}
-        self.locale = 'en-US'
-
-        # Check file paths first.
-        if not os.path.isfile("%s/scp2/inc/macros.inc"%self.cur_dir):
-            raise ParseError("You don't appear to be at the root of OOo's source tree.")
-        if not os.path.isdir("%s/scp2/%s/inc"%(self.cur_dir, self.mod_output_dir)):
-            raise ParseError("You don't appear to be at the root of OOo's source tree.")
-
-    def to_relative (self, fullpath):
-        i = fullpath.find("/scp2/")
-        if i < 0:
-            return fullpath
-        i += 1 # skip '/' before 'scp2'
-        return fullpath[i:]
-
-    def run (self):
-        # Collect all .scp files under scp2.
-        os.path.walk(self.cur_dir + "/scp2", Scp2Processor.visit, self)
-
-        # Process each .scp file.
-        for scp in self.scp_files:
-            relpath = self.to_relative(scp)
-            if Scp2Processor.SkipList.has_key(relpath):
-                error("skipping %s"%scp)
-                continue
-
-            self.process_scp(scp)
-
-    def process_scp (self, scp):
-        ret = subprocess.call("cp %s %s"%(scp, Scp2Processor.tmpin), shell=True)
-        if ret > 0:
-            raise ParseError("failed to copy scp file to a temporary location.")
-
-        subprocess.call("gcc -E -I./scp2/inc -I./scp2/%s/inc -DUNX %s 2>/dev/null | grep -v -E \"^\#\" > %s"%
-            (self.mod_output_dir, Scp2Processor.tmpin, Scp2Processor.tmpout), shell=True)
-
-        file = open(Scp2Processor.tmpout, 'r')
-        content = file.read()
-        file.close()
-        parser = Scp2Parser(content, self.to_relative(scp))
-        parser.tokenize()
-        try:
-            parser.parse()
-            parser.append_nodes(self.nodedata, self.nodetree)
-        except ParseError as e:
-            # Skip mal-formed files, instead of exit with error.
-            error (e.value)
-            error ("Error parsing %s"%scp)
-            if e.sev > 0:
-                # This is a severe error.  Exit right away.
-                sys.exit(1)
-
-    def print_summary_flat (self):
-        names = self.nodedata.keys()
-        names.sort()
-        for name in names:
-            attrs = self.nodedata[name]
-            node_type = attrs['__node_type__']
-            print ('-'*70)
-            print ("%s (%s)"%(name, node_type))
-            print ("[node location: %s]"%attrs['__node_location__'])
-
-            # Print values first.
-            values = attrs['__node_values__']
-            for value in values:
-                print("  %s"%value)
-
-            # Print all attributes.
-            attr_names = attrs.keys()
-            attr_names.sort()
-            for attr_name in attr_names:
-                if attr_name in ['__node_type__', '__node_location__', '__node_values__']:
-                    # Skip special attributes.
-                    continue
-                print ("  %s = %s"%(attr_name, attrs[attr_name]))
-
-    def print_summary_tree (self, root):
-
-        if not self.nodetree.has_key(root):
-            raise ModuleError("module %s not found."%root)
-
-        node = self.nodetree[root]
-        self.__print_summary_tree_node(node, 0)
-
-    def __get_fullpath (self, fileID, locale):
-        """Given a file identifier, construct the absolute path for that file."""
-
-        nodedata = self.nodedata[fileID]
-        filename = None
-        localized = False
-        key_localized = "Name(%s)"%locale
-        if nodedata.has_key('Name'):
-            filename = nodedata['Name']
-        elif nodedata.has_key(key_localized):
-            filename = nodedata[key_localized]
-            localized = True
-        else:
-            raise DirError("%s doesn't have a name attribute."%fileID)
-
-        if not nodedata.has_key('Dir'):
-            raise DirError("file %s doesn't have Dir attribute."%fileID)
-
-        parent_dir_name = nodedata['Dir']
-
-        while parent_dir_name != None:
-
-            if parent_dir_name == 'PREDEFINED_PROGDIR':
-                # special directory name
-                filename = parent_dir_name + '/' + filename
-                break
-
-            if not self.nodedata.has_key(parent_dir_name):
-                # directory is referenced but not defined.  Skip it for now.
-                raise DirError("directory '%s' is referenced but not defined."%parent_dir_name)
-    
-            nodedata = self.nodedata[parent_dir_name]
-            if nodedata.has_key('DosName'):
-                filename = nodedata['DosName'] + "/" + filename
-            elif nodedata.has_key('DosName(en-US)'):
-                filename = nodedata['DosName(en-US)'] + "/" + filename
-            elif nodedata.has_key('HostName'):
-                filename = nodedata['HostName'] + "/" + filename
-            else:
-                raise DirError("directory '%s' does not have either DosName or HostName attribute."%parent_dir_name)
-
-            if nodedata.has_key('ParentID'):
-                parent_dir_name = nodedata['ParentID']
-            else:
-                parent_dir_name = None
-
-        return filename, localized
-
-    def __print_summary_tree_node (self, node, level):
-
-        indent = '    '*level
-
-        if node == None:
-            return
-
-        if not self.nodedata.has_key(node.name):
-            # This node is referenced but is not defined.  Skip it.
-            return
-
-        nodedata = self.nodedata[node.name]
-        if not self.nodedata.has_key(node.name):
-            raise ParseError("there is no associated node data for '%s'"%node.name)
-
-        node_type = nodedata['__node_type__']
-
-        name = ''
-        localized = False
-        if node_type in ['File', 'Unixlink', 'Shortcut', 'Profile']:
-            try:
-                name, localized = self.__get_fullpath(node.name, self.locale)
-                name = XMLFunc.resolve_vars(name, self.vars)
-            except DirError as e:
-                error(e.value)
-                return
-
-        s = indent + "<%s id=\"%s\""%(XMLFunc.to_xml_name(node_type), node.name)
-
-        if len(name) > 0:
-            s += " name=\"%s\""%name
-
-        if node_type == 'Module':
-            s += XMLFunc.add_attr_array(nodedata, 'Styles')
-
-        elif node_type == 'File':
-            s += XMLFunc.add_attr(nodedata, 'UnixRights')
-            s += XMLFunc.add_attr_array(nodedata, 'Styles')
-
-        elif node_type == 'Profile':
-            s += XMLFunc.add_attr_array(nodedata, 'Styles')
-
-        elif node_type == 'ProfileItem':
-            s += XMLFunc.add_attr(nodedata, 'Section')
-            s += XMLFunc.add_attr(nodedata, 'Key')
-            s += XMLFunc.add_attr_vars(nodedata, 'Value', self.vars)
-
-        elif node_type == 'Unixlink':
-            s += XMLFunc.add_attr_vars(nodedata, 'Target', self.vars)
-
-        elif node_type == 'RegistryItem':
-            val_path = get_attr_or_fail(node.name, 'ParentID', nodedata)
-            val_path += '\\' + get_attr_or_fail(node.name, 'Subkey', nodedata)
-            s += " path=\"%s\""%val_path
-            s += XMLFunc.add_attr_localized(nodedata, 'Value', self.locale)
-
-        if localized:
-            s += " locale=\"%s\""%self.locale
-
-        if len(node.children) > 0:
-            s += ">"
-            print (s)
-    
-            children = node.children
-            children.sort()
-            for child in children:
-                self.__print_summary_tree_node(child, level+1)
-    
-            print (indent + "</%s>"%XMLFunc.to_xml_name(node_type))
-        else:
-            s += "/>"
-            print (s)
-
-    @staticmethod
-    def visit (arg, dirname, names):
-        instance = arg
-        for name in names:
-            filepath = dirname + "/" + name
-            if os.path.splitext(filepath)[1] == '.scp':
-                instance.scp_files.append(filepath)
-
-# ----------------------------------------------------------------------------
-
-class OOLstParser(object):
-    """Parser for openoffice.lst file."""
-
-    def __init__ (self):
-        self.vars = {}
-
-    def __repr__ (self):
-        s = ''
-        scope_names = self.vars.keys()
-        scope_names.sort()
-        for scope in scope_names:
-            s += "%s\n"%scope
-            attrs = self.vars[scope]
-            keys = attrs.keys()
-            keys.sort()
-            for key in keys:
-                s += "    %s"%key
-                if attrs[key] != None:
-                    s += " = %s"%attrs[key]
-                else:
-                    s += " ="
-                s += "\n"
-
-        return s
-
-    def get_vars (self, scopes):
-        vars = {}
-        for scope in scopes:
-            for key in self.vars[scope].keys():
-                vars[key] = self.vars[scope][key]
-        return vars
-
-    def parse_openoffice_lst (self, lines):
-    
-        class _Error(ParseError):
-            def __init__ (self, msg, sev=0):
-                ParseError.__init__(self, "(openoffice.lst) " + msg, sev)
-    
-        self.ns = [] # namespace stack
-        n = len(lines)
-        self.last = None
-        for i in xrange(0, n):
-            words = lines[i].split()
-            if len(words) == 0:
-                # empty line
-                continue
-    
-            if words[0] == '{':
-                # new scope begins
-                if len(words) != 1:
-                    raise _Error("{ is followed by a token.", 1)
-                if self.last == None:
-                    raise _Error("fail to find a namespace token in the previous line.", 1)
-                if len(self.last) != 1:
-                    raise _Error("line contains multiple tokens when only one token is expected.", 1)
-                t = self.last[0]
-                self.ns.append(t)
-    
-            elif words[0] == '}':
-                # current scope ends
-                self.__check_last_line()
-
-                if len(words) != 1:
-                    raise _Error("} is followed by a token.", 1)
-                self.ns.pop()
-    
-            else:
-                # check the last line
-                self.__check_last_line()
-    
-            self.last = words
-
-    def __check_last_line (self):
-        if self.last == None or len(self.last) == 0:
-            return
-
-        if self.last[0] in '{}':
-            return
-
-        key = self.last[0]
-        val = None
-        if len(self.last) > 1:
-            sep = ' '
-            val = sep.join(self.last[1:])
-        self.__insert_attr(self.ns, key, val)
-
-
-    def __insert_attr (self, ns, key, val):
-        ns_str = '' # aggregate namespaces, separated by '::'s.
-        for name in ns:
-            if len(ns_str) == 0:
-                ns_str = name
-            else:
-                ns_str += '::' + name
-
-        if not self.vars.has_key(ns_str):
-            # Create this namespace entry.
-            self.vars[ns_str] = {}
-        self.vars[ns_str][key] = val
-
-
-# ----------------------------------------------------------------------------
-
-if __name__ == '__main__':
-
-    parser = optparse.OptionParser()
-    parser.usage += " " + arg_desc + "\n" + desc
-    parser.add_option("", "--module-output-dir", dest="mod_output_dir", default="unxlngi6.pro", metavar="DIR",
-        help="Specify the name of module output directory.  The default value is 'unxlngi6.pro'.")
-    parser.add_option("-m", "--output-mode", dest="mode", default='tree', metavar="MODE",
-        help="Specify output mode.  Allowed values are 'tree' and 'flat.  The default mode is 'tree'.")
-    parser.add_option("", "--openoffice-lst", dest="openoffice_lst", default="instsetoo_native/util/openoffice.lst", metavar="FILE",
-        help="Specify the location of openoffice.lst file which contains variables used by the scp files.  The default value is 'instsetoo_native/util/openoffice.lst'.")
-
-    options, args = parser.parse_args()
-
-    if not options.mode in ['tree', 'flat']:
-        error("unknown output mode '%s'"%options.mode)
-        sys.exit(1)
-
-    cur_dir = os.getcwd()
-    oo_lst_path = cur_dir + '/' + options.openoffice_lst
-    if not os.path.isfile(oo_lst_path):
-        error("failed to find the openoffice.lst file at (%s)."%oo_lst_path)
-        sys.exit(1)
-
-    oolst_parser = OOLstParser()
-    try:
-        file = open(oo_lst_path, 'r')
-        oolst_parser.parse_openoffice_lst(file.readlines())
-        file.close()
-    except ParseError as e:
-        error(e.value)
-        if e.sev > 0:
-            sys.exit(1)
-
-    # For now, just pull variables from these two namespaces.
-    scopes_to_use = ['Globals::Settings::variables', 'OpenOffice::Settings::variables']
-    vars = oolst_parser.get_vars(scopes_to_use)
-    if vars.has_key('PRODUCTNAME'):
-        # Special variable
-        vars['UNIXPRODUCTNAME'] = vars['PRODUCTNAME'].lower()
-
-    try:
-        processor = Scp2Processor(cur_dir, options.mod_output_dir, vars)
-        processor.run()
-        if options.mode == 'tree':
-            for module in top_modules:
-                try:
-                    processor.print_summary_tree(module)
-                except ModuleError as e:
-                    error(e.value)
-
-        elif options.mode == 'flat':
-            processor.print_summary_flat()
-        else:
-            raise ParseError("unknown output mode '%s'"%options.mode)
-
-    except ParseError as e:
-        error (e.value)
-        sys.exit(1)
diff --git a/scratch/build-analysis-tools/check-deps.py b/scratch/build-analysis-tools/check-deps.py
new file mode 100755
index 0000000..48390c0
--- /dev/null
+++ b/scratch/build-analysis-tools/check-deps.py
@@ -0,0 +1,361 @@
+#!/usr/bin/env python
+
+import sys, os, os.path, optparse, subprocess
+
+class ParseError(Exception): pass
+
+class SingleModeError(Exception): pass
+
+class ViewerError(Exception): pass
+
+arg_desc = "module1 module2 ..."
+
+desc = """
+Execute this script at the root directory of your OOo build tree.  It parses
+all build.lst files found in the modules and outputs module dependency data 
+in the dot compatible format.
+
+When no arguments are given, it prints dependencies of all discovered 
+modules.  When module names are given as arguments, it only traces 
+dependencies of those modules.
+
+Sometimes modules are referenced in the build.lst but are absent from the 
+source tree.  Those missing modules are displayed red in the dependency graph."""
+
+err_missing_modules = """
+The following modules are mentioned but not present in the source tree:"""
+
+class Module(object):
+
+    def __init__ (self, name):
+        self.name = name
+        self.deps  = {} # dependents
+        self.precs = {} # precedents
+
+# Store all unique dependency set, with no duplicates.
+class DependSet(object):
+
+    def __init__ (self):
+        self.modules = {}
+
+    def insert_depend (self, prec, dep):
+        if not self.modules.has_key(prec):
+            self.modules[prec] = {}
+        if dep != None:
+            self.modules[prec][dep] = True
+
+class DepsCheker(object):
+
+    def __init__ (self):
+        self.modules = {}         # all mentioned modules, whether present or not.
+        self.modules_present = {} # modules actually present in the source tree.
+        self.modules_used = {}    # modules displayed in the graph.
+        self.selected = []        # selected modules from the command line args.
+
+        self.modules_missing = None
+
+    def __normalize_name (self, name):
+        # Replace prohibited characters with someone sane.
+        name = name.replace('-', '_')
+        return name
+    
+    def __insert_depend (self, mod, dep):
+
+        # precedent to dependent
+        if not self.modules.has_key(mod):
+            self.modules[mod] = Module(mod)
+        obj = self.modules[mod]
+        obj.deps[dep] = True
+
+        # dependent to precedent
+        if not self.modules.has_key(dep):
+            self.modules[dep] = Module(dep)
+        obj = self.modules[dep]
+        obj.precs[mod] = True
+
+    def __parse_build_lst (self, build_lst):
+    
+        # Read only the first line
+        file = open(build_lst, 'r')
+        while True:
+            line = file.readline().strip()
+            if line[0] != '#':
+                break
+        file.close()
+    
+        words = line.split()
+        n = len(words)
+    
+        # Check line format to make sure it's formatted as expected.
+        if n < 4:
+            raise ParseError()
+        if words[2] != ':' and words[2] != '::':
+            raise ParseError()
+        if words[-1] != 'NULL':
+            raise ParseError()
+    
+        mod_name = self.__normalize_name(words[1])
+        depends = words[3:]
+        for dep in depends:
+            if dep == 'NULL':
+                break
+    
+            names = dep.split(':')
+            if len(names) > 2:
+                raise ParseError()
+            elif len(names) == 2:
+                dep = names[1]
+    
+            dep = self.__normalize_name(dep)
+            self.__insert_depend(mod_name, dep)
+
+    def run (self, selected):
+    
+        # modules we want to print dependency on.
+        self.selected = selected
+
+        # Find all build.lst files.
+        for mod in os.listdir(os.getcwd()):
+            if not os.path.isdir(mod):
+                # not a directory
+                continue
+            
+            build_lst = mod + '/prj/build.lst'
+            if not os.path.isfile(build_lst):
+                # no build.lst found
+                continue
+
+            self.modules_present[self.__normalize_name(mod)] = True
+            self.__parse_build_lst(build_lst)
+            
+    def __build_depset_all (self):
+        self.dep_set = DependSet() # reset
+        if len(self.selected) == 0:
+            mods = self.modules.keys()
+            for mod in mods:
+                deps = self.modules[mod].deps.keys()
+                for dep in deps:
+                    self.dep_set.insert_depend(mod, dep)
+        else:
+            # determine involved modules.
+            self.__processed_mods = {}
+            for selected in self.selected:
+                if not self.modules.has_key(selected):
+                    raise ParseError()
+
+                if len(self.modules[selected].deps) > 0:
+                    self.__trace_deps(self.modules[selected])
+
+    def __build_depset_single (self, mods):
+        self.dep_set = DependSet() # reset
+        for mod in mods:
+
+            if not self.modules.has_key(mod):
+                continue
+
+            obj = self.modules[mod]
+            if len(obj.precs) == 0 and len(obj.deps) == 0:
+                # No dependencies.  Just print the module.
+                self.dep_set.insert_depend(mod, None)
+                continue
+
+            for prec in obj.precs.keys():
+                self.dep_set.insert_depend(prec, obj.name)
+            for dep in obj.deps.keys():
+                self.dep_set.insert_depend(obj.name, dep)
+
+    def print_dot_all (self):
+        self.__build_depset_all()
+        s = "digraph modules {\n"
+        s += self.__print_dot_depset()
+        s += self.__print_dot_selected()
+        s += self.__print_dot_missing_modules()
+        s += "}\n"
+        return s
+
+    def print_dot_single (self, mods):
+        self.__build_depset_single(mods)
+        s = "digraph modules {\n"
+        s += self.__print_dot_depset()
+        s += self.__print_dot_selected()
+        s += self.__print_dot_missing_modules()
+        s += "}\n"
+        return s
+
+    def print_flat_all (self):
+        self.__build_depset_all()
+        return self.__print_flat_depset()
+
+    def print_flat_single (self, mods):
+        self.__build_depset_single(mods)
+        return self.__print_flat_depset()
+
+    def __calc_missing_modules (self):
+        if self.modules_missing != None:
+            # already calculated.
+            return
+
+        present = self.modules_present.keys()
+        self.modules_missing = {}
+        for mod in self.modules.keys():
+            if not self.modules_present.has_key(mod):
+                self.modules_missing[mod] = True
+
+    def print_missing_modules (self):
+        self.__calc_missing_modules()
+
+        if len(self.modules_missing) == 0:
+            return
+
+        sys.stderr.write(err_missing_modules + "\n")
+        keys = self.modules_missing.keys()
+        keys.sort()
+        for mod in keys:
+            sys.stderr.write("    " + mod + "\n")
+
+    def __trace_deps (self, obj):
+        if self.__processed_mods.has_key(obj.name):
+            return
+
+        self.__processed_mods[obj.name] = True
+
+        for dep_name in obj.deps.keys():
+            if not self.modules.has_key(dep_name):
+                raise ParseError()
+            self.dep_set.insert_depend(obj.name, dep_name)
+            self.__trace_deps(self.modules[dep_name])
+
+    def __print_flat_depset (self):
+        s = ''
+        mods = self.dep_set.modules.keys()
+        mods.sort()
+        for mod in mods:
+            deps = self.dep_set.modules[mod].keys()
+            if len(deps) == 0:
+                # this module has no dependency.
+                s += "%s\n"%mod
+            else:
+                deps.sort()
+                for dep in deps:
+                    s += "%s:%s\n"%(mod, dep)
+        return s
+
+    def __print_dot_depset (self):
+        s = ''
+        mods = self.dep_set.modules.keys()
+        for mod in mods:
+            deps = self.dep_set.modules[mod].keys()
+            if len(deps) == 0:
+                # this module has no dependency.
+                s += self.__print_dot_dep_line(mod, None)
+            else:
+                for dep in deps:
+                    s += self.__print_dot_dep_line(mod, dep)
+        return s
+
+    def __print_dot_selected (self):
+        s = ''
+        for mod in self.selected:
+            if not self.modules_used.has_key(mod):
+                continue
+            s += "    %s [color=lightblue,style=filled];\n"%mod
+        return s
+
+
+    def __print_dot_missing_modules (self):
+        self.__calc_missing_modules()
+        s = ''
+        for mod in self.modules_missing.keys():
+            if not self.modules_used.has_key(mod):
+                continue
+            s += "    %s [color=red,style=filled];\n"%mod
+
+        return s
+
+
+    def __print_dot_dep_line (self, prec, dep):
+        if prec == None:
+            raise ParseError()
+
+        self.modules_used[prec] = True
+        if dep == None:
+            # this module has no dependency.  I still need to mention the module name.
+            return "    %s;\n"%prec
+        self.modules_used[dep] = True
+        return "    %s -> %s;\n"%(prec, dep)
+
+def exec_exists (cmd):
+    retcode = subprocess.call("which %s >/dev/null 2>/dev/null"%cmd, shell=True)
+    return retcode == 0
+
+def error (msg):
+    sys.stderr.write(msg + "\n")
+    sys.exit(1)
+
+def launch_viewer (code):
+    tmpfile = '/tmp/check-deps.tmp'
+    tmpimage = '/tmp/check-deps-image.png'
+    file = open(tmpfile, 'w')
+    file.write(code)
+    file.close()
+    retcode = subprocess.call("dot -Tpng %s -o %s"%(tmpfile, tmpimage), shell=True)
+    if retcode != 0:
+        raise ViewerError()
+
+    retcode = subprocess.call("eog %s"%tmpimage, shell=True)
+    if retcode != 0:
+        raise ViewerError()
+
+
+if __name__ == '__main__':
+
+    # Process commnad line arguments.
+    parser = optparse.OptionParser()
+    parser.usage += " " + arg_desc + "\n" + desc
+    parser.add_option("-m", "--outout-mode", dest="output_mode", default="dot", metavar="MODE",
+        help="Specify output format mode.  Supported modes are 'dot' and 'flat'.")
+    parser.add_option("-s", "--single", action="store_true", dest="single", default=False,
+        help="Print only immediate dependencies of specified modules.")
+    parser.add_option("-g", "--gui", action="store_true", dest="gui", default=False,
+        help="Display dependency graph in image viewer.")
+    options, args = parser.parse_args()
+
+    if options.gui:
+        # Check to make sure 'dot' and 'eog' are present.
+        if not exec_exists('dot'):
+            error("'dot' not found.  Make sure you have 'dot' in your path.")
+        if not exec_exists('eog'):
+            error("'eog' not found.  Make sure you have 'eog' in your path.")
+
+        # GUI mode requires dot-compatible output.
+        options.output_mode = 'dot'
+
+    if options.output_mode != 'dot' and options.output_mode != 'flat':
+        error("Unrecognized output mode: %s"%options.output_mode)
+
+    checker = DepsCheker()
+    s = ''
+    if options.single:
+        if len(args) == 0:
+            # single mode requires module names.
+            raise SingleModeError()
+        checker.run(args)
+        if options.output_mode == 'dot':
+            s = checker.print_dot_single(args)
+        else:
+            s = checker.print_flat_single(args)
+
+    else:
+        checker.run(args)
+        if options.output_mode == 'dot':
+            s = checker.print_dot_all()
+        else:
+            s = checker.print_flat_all()
+
+    checker.print_missing_modules()
+
+    if options.gui:
+        launch_viewer(s)
+    else:
+        print (s)
+
diff --git a/scratch/build-analysis-tools/parse-scp2.py b/scratch/build-analysis-tools/parse-scp2.py
new file mode 100755
index 0000000..90779c4
--- /dev/null
+++ b/scratch/build-analysis-tools/parse-scp2.py
@@ -0,0 +1,829 @@
+#!/usr/bin/env python
+
+import sys, os, os.path, optparse, subprocess
+
+arg_desc = ""
+
+desc = """
+Run this script at the root of OOo source tree."""
+
+# taken from setup_native/source/packinfo/packinfo_office.txt
+top_modules = [
+    'gid_Module_Optional_Gnome',
+    'gid_Module_Optional_Kde',
+    'gid_Module_Root',
+    'gid_Module_Prg_Wrt_Bin',
+    'gid_Module_Prg_Calc_Bin',
+    'gid_Module_Prg_Draw_Bin',
+    'gid_Module_Prg_Impress_Bin',
+    'gid_Module_Prg_Base_Bin',
+    'gid_Module_Prg_Math_Bin',
+    'gid_Module_Optional_Binfilter',
+    'gid_Module_Optional_Grfflt',
+    'gid_Module_Oooimprovement',
+    'gid_Module_Optional_Testtool',
+    'gid_Module_Optional_Oo_English',
+    'gid_Module_Optional_Xsltfiltersamples',
+    'gid_Module_Optional_Javafilter',
+    'gid_Module_Optional_Activexcontrol',
+    'gid_Module_Optional_Onlineupdate',
+    'gid_Module_Optional_Pyuno',
+    'gid_Module_Optional_Pymailmerge',
+    'gid_Module_Optional_Headless',
+    'gid_Module_Root_Files_Images',
+    'gid_Module_Root_Fonts_OOo_Hidden',
+    'gid_Module_Oo_Linguistic',
+    'gid_Module_Root_Files_2',
+    'gid_Module_Root_Files_3',
+    'gid_Module_Root_Files_4',
+    'gid_Module_Root_Files_5',
+    'gid_Module_Root_Files_6',
+    'gid_Module_Root_Files_7',
+    'gid_Module_Root_Extension_Oooimprovement',
+    'gid_Module_Root_Extension_Dictionary_Af',
+    'gid_Module_Root_Extension_Dictionary_Ca',
+    'gid_Module_Root_Extension_Dictionary_Cs',
+    'gid_Module_Root_Extension_Dictionary_Da',
+    'gid_Module_Root_Extension_Dictionary_De_AT',
+    'gid_Module_Root_Extension_Dictionary_De_CH',
+    'gid_Module_Root_Extension_Dictionary_De_DE',
+    'gid_Module_Root_Extension_Dictionary_En',
+    'gid_Module_Root_Extension_Dictionary_Es',
+    'gid_Module_Root_Extension_Dictionary_Et',
+    'gid_Module_Root_Extension_Dictionary_Fr',
+    'gid_Module_Root_Extension_Dictionary_Gl',
+    'gid_Module_Root_Extension_Dictionary_He',
+    'gid_Module_Root_Extension_Dictionary_Hu',
+    'gid_Module_Root_Extension_Dictionary_It',
+    'gid_Module_Root_Extension_Dictionary_Ku_Tr',
+    'gid_Module_Root_Extension_Dictionary_Lt',
+    'gid_Module_Root_Extension_Dictionary_Ne',
+    'gid_Module_Root_Extension_Dictionary_Nl',
+    'gid_Module_Root_Extension_Dictionary_No',
+    'gid_Module_Root_Extension_Dictionary_Pl',
+    'gid_Module_Root_Extension_Dictionary_Pt',
+    'gid_Module_Root_Extension_Dictionary_Ro',
+    'gid_Module_Root_Extension_Dictionary_Ru',
+    'gid_Module_Root_Extension_Dictionary_Sk',
+    'gid_Module_Root_Extension_Dictionary_Sl',
+    'gid_Module_Root_Extension_Dictionary_Sr',
+    'gid_Module_Root_Extension_Dictionary_Sv',
+    'gid_Module_Root_Extension_Dictionary_Sw',
+    'gid_Module_Root_Extension_Dictionary_Th',
+    'gid_Module_Root_Extension_Dictionary_Vi',
+    'gid_Module_Root_Extension_Dictionary_Zu',
+    'gid_Module_Optional_OGLTrans'
+]
+
+class ErrorBase(Exception):
+    
+    def __init__ (self, name, msg, sev):
+        self.value = "%s: %s"%(name, msg)
+        self.sev = sev                    # error severity, 0 = least severe
+
+    def __str__ (self):
+        return repr(self.value)
+
+class ParseError(ErrorBase):
+    
+    def __init__ (self, msg, sev = 0):
+        ErrorBase.__init__(self, "ParseError", msg, sev)
+
+class DirError(ErrorBase):
+    def __init__ (self, msg):
+        ErrorBase.__init__(self, "DirError", msg, 0)
+
+class ModuleError(ErrorBase):
+    def __init__ (self, msg):
+        ErrorBase.__init__(self, "ModuleError", msg, 0)
+
+class LinkedNode(object):
+    def __init__ (self, name):
+        self.name = name
+        self.parent = None
+        self.children = []
+
+# ----------------------------------------------------------------------------
+
+def error (msg):
+    sys.stderr.write(msg + "\n")
+
+def get_attr_or_fail (name, key, attrs):
+    if not attrs.has_key(key):
+        raise ParseError("%s doesn't have %s attribute, but expected."%(name, key), 1)
+    return attrs[key]
+
+# ----------------------------------------------------------------------------
+
+class Scp2Tokenizer(object):
+    """Tokenizer for scp files."""
+
+    def __init__ (self, content):
+        self.content = content
+        self.tokens = []
+
+    def flush_buffer (self):
+        if len(self.buf) > 0:
+            self.tokens.append(self.buf)
+            self.buf = ''
+
+    def run (self):
+        self.tokens = []
+        i = 0
+        n = len(self.content)
+        self.buf = ''
+        while i < n:
+            c = self.content[i]
+            if c in '\t\n':
+                c = ' '
+
+            if c in ' ;':
+                self.flush_buffer()
+                if c == ';':
+                    self.tokens.append(c)
+            elif c == '"':
+                # String literal.  Parse until reaching the closing quote.
+                self.flush_buffer()
+                i += 1
+                c = self.content[i]
+                while c != '"':
+                    self.buf += c
+                    i += 1
+                    c = self.content[i]
+                self.flush_buffer()
+            else:
+                self.buf += c
+            i += 1
+
+# ----------------------------------------------------------------------------
+
+class Scp2Parser(object):
+    """Parser for scp files."""
+
+    class Type:
+        File       = 0
+        Directory  = 1
+        FolderItem = 2
+
+    NodeTypes = [
+        'DataCarrier',         # ignored
+        'Directory',           # ignored, referenced directly from File
+        'File',                # done, linked from within Module
+        'Folder',              # ignored
+        'FolderItem',          # ignored for now.  windows specific?
+        'Installation',        # ignored.  I don't know what this is for.
+        'Module',              # done
+        'Profile',             # ignored
+        'ProfileItem',         # ignored
+        'RegistryItem',        # done
+        'ScpAction',           # ignored
+        'Shortcut',            # linked to File?  Treat this as a child of File for now.
+        'StarRegistry',        # ignored, probably for StarOffice only
+        'Unixlink',            # done, linked from within Module
+        'WindowsCustomAction'  # ignored
+    ]
+
+    def __init__ (self, content, filename):
+        self.content = content
+        self.filename = filename
+        self.nodedata = {}
+
+    def tokenize (self):
+        tokenizer = Scp2Tokenizer(self.content)
+        tokenizer.run()
+        self.tokens = tokenizer.tokens
+
+    def next (self):
+        self.i += 1
+
+    def token (self):
+        return self.tokens[self.i]
+
+    def parse (self):
+        if len(self.tokens) == 0:
+            # No tokens to parse.  Bail out.
+            return
+            
+        self.i = 0
+        self.n = len(self.tokens)
+        while self.i < self.n:
+            t = self.token()
+            if t in Scp2Parser.NodeTypes:
+                name, attrs, values = self.__parseEntity()
+                attrs['__node_type__'] = t                 # type of node
+                attrs['__node_location__'] = self.filename # file where the node is defined
+                attrs['__node_values__'] = values          # list of values that are not attributes (i.e. not associated with names)
+                if self.nodedata.has_key(name):
+                    raise ParseError("node named %s already exists"%name, 1)
+                self.nodedata[name] = attrs
+            else:
+                raise ParseError("Unknown node type: %s"%t)
+
+            self.next()
+
+    def append_nodes (self, nodedata, nodetree):
+
+        for key in self.nodedata.keys():
+
+            if nodedata.has_key(key):
+                raise ParseError("node named %s already exists"%key, 1)
+
+            # Transfer all the node attributes to the caller instance.
+            nodedata[key] = self.nodedata[key]
+
+            # Now, add linkage data to the parent tree instance.
+
+            if not nodetree.has_key(key):
+                # Create a new linked node instance.
+                nodetree[key] = LinkedNode(key)
+
+            attrs = self.nodedata[key]
+
+            node_type = attrs['__node_type__']
+            if node_type == 'Module':
+                self.__link_module_node(key, attrs, nodetree)
+            elif node_type == 'RegistryItem':
+                # RegistryItem entries have ModuleID to link back to a module.
+                self.__link_simple(key, attrs, nodetree, 'ModuleID')
+            elif node_type == 'Shortcut':
+                self.__link_simple(key, attrs, nodetree, 'FileID')
+            elif node_type == 'Profile':
+                self.__link_simple(key, attrs, nodetree, 'ModuleID')
+            elif node_type == 'ProfileItem':
+                self.__link_simple(key, attrs, nodetree, 'ProfileID')
+                
+
+    def __link_simple (self, name, attrs, nodetree, pid_attr):
+        parentID = get_attr_or_fail(name, pid_attr, attrs)
+        if not nodetree.has_key(parentID):
+            nodetree[parentID] = LinkedNode(parentID)
+        if not nodetree.has_key(name):
+            nodetree[name] = LinkedNode(name)
+
+        nodetree[parentID].children.append(nodetree[name])
+        if nodetree[name].parent != None:
+            raise ParseError("parent node instance already exists for '%s'"%name, 1)
+        nodetree[name].parent = nodetree[parentID]
+
+
+    def __link_files (self, name, files, nodetree):
+
+        # file list strings are formatted like this '(file1,file2,file3,....)'
+        if files[0] != '(' or files[-1] != ')':
+            raise ParseError("file list string is not formatted correctly: %s"%files)
+        files = files[1:-1]
+        list = files.split(',')
+        for file in list:
+            if not nodetree.has_key(file):
+                nodetree[file] = LinkedNode(file)
+            nodetree[name].children.append(nodetree[file])
+
+
+    def __link_module_node (self, name, attrs, nodetree):
+
+        if attrs.has_key('ParentID'):
+            parentID = attrs['ParentID']
+
+            if not nodetree.has_key(parentID):
+                nodetree[parentID] = LinkedNode(parentID)
+
+            nodetree[parentID].children.append(nodetree[name])
+            if nodetree[name].parent != None:
+                raise ParseError("parent node instance already exists for '%s'"%name, 1)
+            nodetree[name].parent = nodetree[parentID]
+
+        if attrs.has_key('Files'):
+            self.__link_files(name, attrs['Files'], nodetree)
+
+        if attrs.has_key('Unixlinks'):
+            self.__link_files(name, attrs['Unixlinks'], nodetree)
+
+
+    def __parseEntity (self):
+        self.next()
+        name = self.token()
+        if len(name) == 0:
+            raise ParseError("empty name", 1)
+        left = True
+        attr_name = ''
+        attr_value = ''
+        attrs = {}
+        values = []
+        self.next()
+        while self.token() != 'End':
+            if self.token() == '=':
+                if not left:
+                    raise ParseError("multiple '='s in a single line")
+
+                if len(attr_name) == 0:
+                    raise ParseError("empty attribute name")
+
+                left = False
+            
+            elif left:
+                if self.token() == ';':
+                    # Not a valid attribute.  Store it as a 'value'.
+                    values.append(attr_name)
+                    attr_name = ''
+                else:
+                    attr_name += self.token()
+            else:
+                # Parse all the way up to ';'
+                attr_value = ''
+                while self.token() != ';':
+                    attr_value += self.token()
+                    self.next()
+                attrs[attr_name] = attr_value
+                left = True
+                attr_name = ''
+
+            self.next()
+
+        return name, attrs, values
+
+# ----------------------------------------------------------------------------
+
+class XMLFunc:
+
+    @staticmethod
+    def resolve_vars (s, vars):
+        """Replace all ${...}s with their respective values."""
+
+        ret = ''
+        
+        while True:
+            start = s.find('${')
+            if start == -1:
+                ret += s
+                break
+    
+            end = s.find('}', start+2)
+            if end == -1:
+                ret += s
+                break
+    
+            key = s[start+2:end]
+            if vars.has_key(key):
+                ret += s[:start] + vars[key]
+            s = s[end+1:]
+    
+        return ret
+
+    @staticmethod
+    def to_xml_name (name):
+        """CamelCase to camel-case"""
+        s = ''
+        n = len(name)
+        for i in xrange(0, n):
+            c = name[i]
+            if 'A' <= c and c <= 'Z':
+                if i > 0:
+                    s += '-'
+                s += c.lower()
+            else:
+                s += c
+        return s
+
+    @staticmethod
+    def add_attr (attrs, key):
+        s = ''
+        if attrs.has_key(key):
+            s = " %s=\"%s\""%(XMLFunc.to_xml_name(key), attrs[key])
+        return s
+
+    @staticmethod
+    def add_attr_localized (attrs, key, locale):
+        if attrs.has_key(key):
+            # Try non-localized name first.
+            return " %s=\"%s\""%(XMLFunc.to_xml_name(key), attrs[key])
+        
+        key_localized = key + "(%s)"%locale
+        if attrs.has_key(key_localized):
+            # Try non-localized name first.
+            return " %s=\"%s\" locale=\"%s\""%(XMLFunc.to_xml_name(key), attrs[key_localized], locale)
+
+        return ''
+
+    @staticmethod
+    def add_attr_vars (attrs, key, vars):
+        if not attrs.has_key(key):
+            return ''
+
+        s = " %s=\"%s\""%(XMLFunc.to_xml_name(key), XMLFunc.resolve_vars(attrs[key], vars))
+        return s
+
+
+    @staticmethod
+    def add_attr_array (attrs, key):
+
+        if not attrs.has_key(key):
+            return ''
+
+        raw_str = attrs[key]
+        if len(raw_str) == 0 or raw_str[0] != '(' or raw_str[-1] != ')':
+            raise ParseError("%s attribute is not formatted properly: '%s'"%(key, raw_str), 1)
+
+        if raw_str == '()':
+            return ''
+
+        val = raw_str[1:-1].lower().replace('_', '-')
+        s = " %s=\"%s\""%(XMLFunc.to_xml_name(key), val)
+        return s
+
+
+
+class Scp2Processor(object):
+    """Collect all .scp files in scp2 directory, and run preprocessor."""
+
+    tmpin  = "/tmp/parse-scp2.py.cpp"
+    tmpout = "/tmp/parse-scp2.py.out"
+
+    SkipList = {
+        'scp2/source/ooo/ure_standalone.scp': True,
+        'scp2/source/sdkoo/sdkoo.scp': True,
+        'scp2/source/ooo/starregistry_ooo.scp': True
+    }
+
+    def __init__ (self, cur_dir, mod_output_dir, vars):
+        self.cur_dir = cur_dir
+        self.mod_output_dir = mod_output_dir
+        self.vars = vars
+        self.scp_files = []
+        self.nodedata = {}
+        self.nodetree = {}
+        self.locale = 'en-US'
+
+        # Check file paths first.
+        if not os.path.isfile("%s/scp2/inc/macros.inc"%self.cur_dir):
+            raise ParseError("You don't appear to be at the root of OOo's source tree.")
+        if not os.path.isdir("%s/scp2/%s/inc"%(self.cur_dir, self.mod_output_dir)):
+            raise ParseError("You don't appear to be at the root of OOo's source tree.")
+
+    def to_relative (self, fullpath):
+        i = fullpath.find("/scp2/")
+        if i < 0:
+            return fullpath
+        i += 1 # skip '/' before 'scp2'
+        return fullpath[i:]
+
+    def run (self):
+        # Collect all .scp files under scp2.
+        os.path.walk(self.cur_dir + "/scp2", Scp2Processor.visit, self)
+
+        # Process each .scp file.
+        for scp in self.scp_files:
+            relpath = self.to_relative(scp)
+            if Scp2Processor.SkipList.has_key(relpath):
+                error("skipping %s"%scp)
+                continue
+
+            self.process_scp(scp)
+
+    def process_scp (self, scp):
+        ret = subprocess.call("cp %s %s"%(scp, Scp2Processor.tmpin), shell=True)
+        if ret > 0:
+            raise ParseError("failed to copy scp file to a temporary location.")
+
+        subprocess.call("gcc -E -I./scp2/inc -I./scp2/%s/inc -DUNX %s 2>/dev/null | grep -v -E \"^\#\" > %s"%
+            (self.mod_output_dir, Scp2Processor.tmpin, Scp2Processor.tmpout), shell=True)
+
+        file = open(Scp2Processor.tmpout, 'r')
+        content = file.read()
+        file.close()
+        parser = Scp2Parser(content, self.to_relative(scp))
+        parser.tokenize()
+        try:
+            parser.parse()
+            parser.append_nodes(self.nodedata, self.nodetree)
+        except ParseError as e:
+            # Skip mal-formed files, instead of exit with error.
+            error (e.value)
+            error ("Error parsing %s"%scp)
+            if e.sev > 0:
+                # This is a severe error.  Exit right away.
+                sys.exit(1)
+
+    def print_summary_flat (self):
+        names = self.nodedata.keys()
+        names.sort()
+        for name in names:
+            attrs = self.nodedata[name]
+            node_type = attrs['__node_type__']
+            print ('-'*70)
+            print ("%s (%s)"%(name, node_type))
+            print ("[node location: %s]"%attrs['__node_location__'])
+
+            # Print values first.
+            values = attrs['__node_values__']
+            for value in values:
+                print("  %s"%value)
+
+            # Print all attributes.
+            attr_names = attrs.keys()
+            attr_names.sort()
+            for attr_name in attr_names:
+                if attr_name in ['__node_type__', '__node_location__', '__node_values__']:
+                    # Skip special attributes.
+                    continue
+                print ("  %s = %s"%(attr_name, attrs[attr_name]))
+
+    def print_summary_tree (self, root):
+
+        if not self.nodetree.has_key(root):
+            raise ModuleError("module %s not found."%root)
+
+        node = self.nodetree[root]
+        self.__print_summary_tree_node(node, 0)
+
+    def __get_fullpath (self, fileID, locale):
+        """Given a file identifier, construct the absolute path for that file."""
+
+        nodedata = self.nodedata[fileID]
+        filename = None
+        localized = False
+        key_localized = "Name(%s)"%locale
+        if nodedata.has_key('Name'):
+            filename = nodedata['Name']
+        elif nodedata.has_key(key_localized):
+            filename = nodedata[key_localized]
+            localized = True
+        else:
+            raise DirError("%s doesn't have a name attribute."%fileID)
+
+        if not nodedata.has_key('Dir'):
+            raise DirError("file %s doesn't have Dir attribute."%fileID)
+
+        parent_dir_name = nodedata['Dir']
+
+        while parent_dir_name != None:
+
+            if parent_dir_name == 'PREDEFINED_PROGDIR':
+                # special directory name
+                filename = parent_dir_name + '/' + filename
+                break
+
+            if not self.nodedata.has_key(parent_dir_name):
+                # directory is referenced but not defined.  Skip it for now.
+                raise DirError("directory '%s' is referenced but not defined."%parent_dir_name)
+    
+            nodedata = self.nodedata[parent_dir_name]
+            if nodedata.has_key('DosName'):
+                filename = nodedata['DosName'] + "/" + filename
+            elif nodedata.has_key('DosName(en-US)'):
+                filename = nodedata['DosName(en-US)'] + "/" + filename
+            elif nodedata.has_key('HostName'):
+                filename = nodedata['HostName'] + "/" + filename
+            else:
+                raise DirError("directory '%s' does not have either DosName or HostName attribute."%parent_dir_name)
+
+            if nodedata.has_key('ParentID'):
+                parent_dir_name = nodedata['ParentID']
+            else:
+                parent_dir_name = None
+
+        return filename, localized
+
+    def __print_summary_tree_node (self, node, level):
+
+        indent = '    '*level
+
+        if node == None:
+            return
+
+        if not self.nodedata.has_key(node.name):
+            # This node is referenced but is not defined.  Skip it.
+            return
+
+        nodedata = self.nodedata[node.name]
+        if not self.nodedata.has_key(node.name):
+            raise ParseError("there is no associated node data for '%s'"%node.name)
+
+        node_type = nodedata['__node_type__']
+
+        name = ''
+        localized = False
+        if node_type in ['File', 'Unixlink', 'Shortcut', 'Profile']:
+            try:
+                name, localized = self.__get_fullpath(node.name, self.locale)
+                name = XMLFunc.resolve_vars(name, self.vars)
+            except DirError as e:
+                error(e.value)
+                return
+
+        s = indent + "<%s id=\"%s\""%(XMLFunc.to_xml_name(node_type), node.name)
+
+        if len(name) > 0:
+            s += " name=\"%s\""%name
+
+        if node_type == 'Module':
+            s += XMLFunc.add_attr_array(nodedata, 'Styles')
+
+        elif node_type == 'File':
+            s += XMLFunc.add_attr(nodedata, 'UnixRights')
+            s += XMLFunc.add_attr_array(nodedata, 'Styles')
+
+        elif node_type == 'Profile':
+            s += XMLFunc.add_attr_array(nodedata, 'Styles')
+
+        elif node_type == 'ProfileItem':
+            s += XMLFunc.add_attr(nodedata, 'Section')
+            s += XMLFunc.add_attr(nodedata, 'Key')
+            s += XMLFunc.add_attr_vars(nodedata, 'Value', self.vars)
+
+        elif node_type == 'Unixlink':
+            s += XMLFunc.add_attr_vars(nodedata, 'Target', self.vars)
+
+        elif node_type == 'RegistryItem':
+            val_path = get_attr_or_fail(node.name, 'ParentID', nodedata)
+            val_path += '\\' + get_attr_or_fail(node.name, 'Subkey', nodedata)
+            s += " path=\"%s\""%val_path
+            s += XMLFunc.add_attr_localized(nodedata, 'Value', self.locale)
+
+        if localized:
+            s += " locale=\"%s\""%self.locale
+
+        if len(node.children) > 0:
+            s += ">"
+            print (s)
+    
+            children = node.children
+            children.sort()
+            for child in children:
+                self.__print_summary_tree_node(child, level+1)
+    
+            print (indent + "</%s>"%XMLFunc.to_xml_name(node_type))
+        else:
+            s += "/>"
+            print (s)
+
+    @staticmethod
+    def visit (arg, dirname, names):
+        instance = arg
+        for name in names:
+            filepath = dirname + "/" + name
+            if os.path.splitext(filepath)[1] == '.scp':
+                instance.scp_files.append(filepath)
+
+# ----------------------------------------------------------------------------
+
+class OOLstParser(object):
+    """Parser for openoffice.lst file."""
+
+    def __init__ (self):
+        self.vars = {}
+
+    def __repr__ (self):
+        s = ''
+        scope_names = self.vars.keys()
+        scope_names.sort()
+        for scope in scope_names:
+            s += "%s\n"%scope
+            attrs = self.vars[scope]
+            keys = attrs.keys()
+            keys.sort()
+            for key in keys:
+                s += "    %s"%key
+                if attrs[key] != None:
+                    s += " = %s"%attrs[key]
+                else:
+                    s += " ="
+                s += "\n"
+
+        return s
+
+    def get_vars (self, scopes):
+        vars = {}
+        for scope in scopes:
+            for key in self.vars[scope].keys():
+                vars[key] = self.vars[scope][key]
+        return vars
+
+    def parse_openoffice_lst (self, lines):
+    
+        class _Error(ParseError):
+            def __init__ (self, msg, sev=0):
+                ParseError.__init__(self, "(openoffice.lst) " + msg, sev)
+    
+        self.ns = [] # namespace stack
+        n = len(lines)
+        self.last = None
+        for i in xrange(0, n):
+            words = lines[i].split()
+            if len(words) == 0:
+                # empty line
+                continue
+    
+            if words[0] == '{':
+                # new scope begins
+                if len(words) != 1:
+                    raise _Error("{ is followed by a token.", 1)
+                if self.last == None:
+                    raise _Error("fail to find a namespace token in the previous line.", 1)
+                if len(self.last) != 1:
+                    raise _Error("line contains multiple tokens when only one token is expected.", 1)
+                t = self.last[0]
+                self.ns.append(t)
+    
+            elif words[0] == '}':
+                # current scope ends
+                self.__check_last_line()
+
+                if len(words) != 1:
+                    raise _Error("} is followed by a token.", 1)
+                self.ns.pop()
+    
+            else:
+                # check the last line
+                self.__check_last_line()
+    
+            self.last = words
+
+    def __check_last_line (self):
+        if self.last == None or len(self.last) == 0:
+            return
+
+        if self.last[0] in '{}':
+            return
+
+        key = self.last[0]
+        val = None
+        if len(self.last) > 1:
+            sep = ' '
+            val = sep.join(self.last[1:])
+        self.__insert_attr(self.ns, key, val)
+
+
+    def __insert_attr (self, ns, key, val):
+        ns_str = '' # aggregate namespaces, separated by '::'s.
+        for name in ns:
+            if len(ns_str) == 0:
+                ns_str = name
+            else:
+                ns_str += '::' + name
+
+        if not self.vars.has_key(ns_str):
+            # Create this namespace entry.
+            self.vars[ns_str] = {}
+        self.vars[ns_str][key] = val
+
+
+# ----------------------------------------------------------------------------
+
+if __name__ == '__main__':
+
+    parser = optparse.OptionParser()
+    parser.usage += " " + arg_desc + "\n" + desc
+    parser.add_option("", "--module-output-dir", dest="mod_output_dir", default="unxlngi6.pro", metavar="DIR",
+        help="Specify the name of module output directory.  The default value is 'unxlngi6.pro'.")
+    parser.add_option("-m", "--output-mode", dest="mode", default='tree', metavar="MODE",
+        help="Specify output mode.  Allowed values are 'tree' and 'flat.  The default mode is 'tree'.")
+    parser.add_option("", "--openoffice-lst", dest="openoffice_lst", default="instsetoo_native/util/openoffice.lst", metavar="FILE",
+        help="Specify the location of openoffice.lst file which contains variables used by the scp files.  The default value is 'instsetoo_native/util/openoffice.lst'.")
+
+    options, args = parser.parse_args()
+
+    if not options.mode in ['tree', 'flat']:
+        error("unknown output mode '%s'"%options.mode)
+        sys.exit(1)
+
+    cur_dir = os.getcwd()
+    oo_lst_path = cur_dir + '/' + options.openoffice_lst
+    if not os.path.isfile(oo_lst_path):
+        error("failed to find the openoffice.lst file at (%s)."%oo_lst_path)
+        sys.exit(1)
+
+    oolst_parser = OOLstParser()
+    try:
+        file = open(oo_lst_path, 'r')
+        oolst_parser.parse_openoffice_lst(file.readlines())
+        file.close()
+    except ParseError as e:
+        error(e.value)
+        if e.sev > 0:
+            sys.exit(1)
+
+    # For now, just pull variables from these two namespaces.
+    scopes_to_use = ['Globals::Settings::variables', 'OpenOffice::Settings::variables']
+    vars = oolst_parser.get_vars(scopes_to_use)
+    if vars.has_key('PRODUCTNAME'):
+        # Special variable
+        vars['UNIXPRODUCTNAME'] = vars['PRODUCTNAME'].lower()
+
+    try:
+        processor = Scp2Processor(cur_dir, options.mod_output_dir, vars)
+        processor.run()
+        if options.mode == 'tree':
+            for module in top_modules:
+                try:
+                    processor.print_summary_tree(module)
+                except ModuleError as e:
+                    error(e.value)
+
+        elif options.mode == 'flat':
+            processor.print_summary_flat()
+        else:
+            raise ParseError("unknown output mode '%s'"%options.mode)
+
+    except ParseError as e:
+        error (e.value)
+        sys.exit(1)


More information about the ooo-build-commit mailing list