scons: add ParseSourceList method
authorChia-I Wu <olv@lunarg.com>
Thu, 18 Aug 2011 09:12:29 +0000 (17:12 +0800)
committerChia-I Wu <olv@lunarg.com>
Tue, 23 Aug 2011 01:11:58 +0000 (09:11 +0800)
ParseSourceList() can be used to parse a source list file and returns
the source files defined in it.  It is supposed to be used like this

  # get the list of source files from C_SOURCES in Makefile.sources
  sources = env.ParseSourceList('Makefile.sources', 'C_SOURCES')

The syntax of a source list file is compatible with GNU Make.  This
effectively allows SConscript and Makefile to share the source lists.

Acked-by: José Fonseca <jfonseca@vmware.com>
Acked-by: Chad Versace <chad@chad-versace.us>
scons/custom.py
scons/source_list.py [new file with mode: 0644]

index df7ac93bb006492e401f8e66332b4a7b462e31c3..b6d716cf43778052597b51a2f987f208787f2600 100644 (file)
@@ -42,6 +42,7 @@ import SCons.Scanner
 
 import fixes
 
+import source_list
 
 def quietCommandLines(env):
     # Quiet command lines
@@ -229,6 +230,40 @@ def createPkgConfigMethods(env):
     env.AddMethod(pkg_use_modules, 'PkgUseModules')
 
 
+def parse_source_list(env, filename, names=None):
+    # parse the source list file
+    parser = source_list.SourceListParser()
+    src = env.File(filename).srcnode()
+    sym_table = parser.parse(src.abspath)
+
+    if names:
+        if isinstance(names, basestring):
+            names = [names]
+
+        symbols = names
+    else:
+        symbols = sym_table.keys()
+
+    # convert the symbol table to source lists
+    src_lists = {}
+    for sym in symbols:
+        val = sym_table[sym]
+        src_lists[sym] = [f for f in val.split(' ') if f]
+
+    # if names are given, concatenate the lists
+    if names:
+        srcs = []
+        for name in names:
+            srcs.extend(src_lists[name])
+
+        return srcs
+    else:
+        return src_lists
+
+def createParseSourceListMethod(env):
+    env.AddMethod(parse_source_list, 'ParseSourceList')
+
+
 def generate(env):
     """Common environment generation code"""
 
@@ -240,6 +275,7 @@ def generate(env):
     createConvenienceLibBuilder(env)
     createCodeGenerateMethod(env)
     createPkgConfigMethods(env)
+    createParseSourceListMethod(env)
 
     # for debugging
     #print env.Dump()
diff --git a/scons/source_list.py b/scons/source_list.py
new file mode 100644 (file)
index 0000000..fbd3ef7
--- /dev/null
@@ -0,0 +1,123 @@
+"""Source List Parser
+
+The syntax of a source list file is a very small subset of GNU Make.  These
+features are supported
+
+ operators: +=, :=
+ line continuation
+ non-nested variable expansion
+ comment
+
+The goal is to allow Makefile's and SConscript's to share source listing.
+"""
+
+class SourceListParser(object):
+    def __init__(self):
+        self._reset()
+
+    def _reset(self, filename=None):
+        self.filename = filename
+
+        self.line_no = 1
+        self.line_cont = ''
+        self.symbol_table = {}
+
+    def _error(self, msg):
+        raise RuntimeError('%s:%d: %s' % (self.filename, self.line_no, msg))
+
+    def _next_dereference(self, val, cur):
+        """Locate the next $(...) in value."""
+        deref_pos = val.find('$', cur)
+        if deref_pos < 0:
+            return (-1, -1)
+        elif val[deref_pos + 1] != '(':
+            self._error('non-variable dereference')
+
+        deref_end = val.find(')', deref_pos + 2)
+        if deref_end < 0:
+            self._error('unterminated variable dereference')
+
+        return (deref_pos, deref_end + 1)
+
+    def _expand_value(self, val):
+        """Perform variable expansion."""
+        expanded = ''
+        cur = 0
+        while True:
+            deref_pos, deref_end = self._next_dereference(val, cur)
+            if deref_pos < 0:
+                expanded += val[cur:]
+                break
+
+            sym = val[(deref_pos + 2):(deref_end - 1)]
+            expanded += val[cur:deref_pos] + self.symbol_table[sym]
+            cur = deref_end
+
+        return expanded
+
+    def _parse_definition(self, line):
+        """Parse a variable definition line."""
+        op_pos = line.find('=')
+        op_end = op_pos + 1
+        if op_pos < 0:
+            self._error('not a variable definition')
+
+        if op_pos > 0 and line[op_pos - 1] in [':', '+']:
+            op_pos -= 1
+        else:
+            self._error('only := and += are supported')
+
+        # set op, sym, and val
+        op = line[op_pos:op_end]
+        sym = line[:op_pos].strip()
+        val = self._expand_value(line[op_end:].lstrip())
+
+        if op == ':=':
+            self.symbol_table[sym] = val
+        elif op == '+=':
+            self.symbol_table[sym] += ' ' + val
+
+    def _parse_line(self, line):
+        """Parse a source list line."""
+        # more lines to come
+        if line and line[-1] == '\\':
+            # spaces around "\\\n" are replaced by a single space
+            if self.line_cont:
+                self.line_cont += line[:-1].strip() + ' '
+            else:
+                self.line_cont = line[:-1].rstrip() + ' '
+            return 0
+
+        # combine with previous lines
+        if self.line_cont:
+            line = self.line_cont + line.lstrip()
+            self.line_cont = ''
+
+        if line:
+            begins_with_tab = (line[0] == '\t')
+
+            line = line.lstrip()
+            if line[0] != '#':
+                if begins_with_tab:
+                    self._error('recipe line not supported')
+                else:
+                    self._parse_definition(line)
+
+        return 1
+
+    def parse(self, filename):
+        """Parse a source list file."""
+        if self.filename != filename:
+            fp = open(filename)
+            lines = fp.read().splitlines()
+            fp.close()
+
+            try:
+                self._reset(filename)
+                for line in lines:
+                    self.line_no += self._parse_line(line)
+            except:
+                self._reset()
+                raise
+
+        return self.symbol_table