diff options
-rw-r--r-- | scons/custom.py | 36 | ||||
-rw-r--r-- | scons/source_list.py | 123 |
2 files changed, 159 insertions, 0 deletions
diff --git a/scons/custom.py b/scons/custom.py index df7ac93bb00..b6d716cf437 100644 --- a/scons/custom.py +++ b/scons/custom.py @@ -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 index 00000000000..fbd3ef7dc17 --- /dev/null +++ b/scons/source_list.py @@ -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 |