summaryrefslogtreecommitdiff
path: root/scratch
diff options
context:
space:
mode:
authorKohei Yoshida <kyoshida@novell.com>2010-07-16 10:01:59 -0400
committerKohei Yoshida <kyoshida@novell.com>2010-07-16 10:01:59 -0400
commita244fcaffd926b995c2760698a37c345d92bf846 (patch)
tree270d700e32858152537f727d227e2f0bd27fb242 /scratch
parent58ff1086b6e23be82771d66ff29819e426d3ce6e (diff)
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:
Diffstat (limited to 'scratch')
-rwxr-xr-xscratch/build-analysis-tools/check-deps.py361
-rwxr-xr-xscratch/build-analysis-tools/parse-scp2.py829
2 files changed, 1190 insertions, 0 deletions
diff --git a/scratch/build-analysis-tools/check-deps.py b/scratch/build-analysis-tools/check-deps.py
new file mode 100755
index 000000000..48390c034
--- /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 000000000..90779c4a8
--- /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)