#!/usr/bin/python """Read build configuration and generate header files.""" import optparse import ConfigParser import collections import sys import re import os def read_config(modules_configs, project_config, known_targets): """Read configuration definitions and default values from module configuration files, then merge project configuration.""" parser = ConfigParser.SafeConfigParser() # Read definitions and default values. for mc in modules_configs: try: mcf = open(mc) except IOError: pass else: with mcf: parser.readfp(mcf) # Removed unknown targets and undefined overridden values. for section in parser.sections(): if ':' in section: name, target = section.split(':', 1) if target not in known_targets: parser.remove_section(section) if not parser.has_section(name): parser.remove_section(section) # Save the list of existing items for later check. def config_items(parser): s = set() for section in parser.sections(): items = parser.options(section) section = section.split(':')[0] if section != 'project': s.update(['%s:%s' % (section, item) for item in items]) return s modules_items = config_items(parser) # Now read project configuration. if project_config is not None: try: pcf = open(project_config) except IOError: pass else: with pcf: parser.readfp(pcf) # Check for unknown items. project_items = config_items(parser) unknown_items = project_items - modules_items if unknown_items: raise RuntimeError("unknown configuration item: " + " ".join(unknown_items)) # OK, convert to more natural structure. config = collections.defaultdict (lambda: collections.defaultdict(dict)) for section in parser.sections(): items = parser.items(section) if ':' in section: section, target = section.split(':', 1) else: target = None for k, v in items: config[section][k][target] = v return config def check_config(config, targets, subtargets): """Run consistency checks on configuration.""" for section, section_dict in config.iteritems(): for key, values in section_dict.iteritems(): level_per_target = dict() for st in values: if st is None: continue # Check for unknown target (they are removed from default # configuration, but not from project configuration). if st not in subtargets: raise RuntimeError("unknown target %s" % st) # Check for ambiguous configuration (configuration for which # there is no rule to decide which is valid as they have the # same level). level = subtargets[st].level for t in subtargets[st].targets: if t in level_per_target: if level_per_target[t] == level: raise RuntimeError("ambiguous configuration for" " %s:%s" % (section, key)) elif level_per_target[t] < level: level_per_target[t] = level else: level_per_target[t] = level # If no default value, each target should have a value. if values[None] == '': if not level_per_target: raise RuntimeError("no value given for %s:%s" % (section, key)) elif len(level_per_target) < len(targets): targets_set = set(level_per_target.keys()) raise RuntimeError("no value given for %s:%s for" " targets: %s" % (section, key, ', '.join (targets - targets_set))) def parse_targets(targets_option): """Parse a space separated target:subtarget list. Return a set of targets, a mapping of each subtarget to a level and list of target, and a sorted list of targets, per level. A subtarget level encodes relation between subtargets. Higher level means that this subtarget is a refinement of lower level subtargets and that configuration can be more specific.""" if targets_option is None: targets_option = '' targets = set() class Subtarget: def __init__(self): self.level = 0 self.targets = [] subtargets = collections.defaultdict(Subtarget) # Parse. for tpair in targets_option.split(): tpairl = tpair.split(':') if len(tpairl) != 2: raise RuntimeError("bad target:subtarget pair %s" % tpair) target, subtarget = tpairl targets.add(target) subtargets[subtarget].targets.append(target) # Compute level. again = True while again: again = False for k in subtargets: if subtargets[k].level > 100: raise RuntimeError("loop in targets declaration") for t in subtargets[k].targets: if t != k and subtargets[t].level <= subtargets[k].level: subtargets[t].level = subtargets[k].level + 1 again = True # Sort targets. targets_sorted = sorted(subtargets.keys(), key=lambda k: subtargets[k].level, reverse=True) targets_sorted.append(None) # Done. return targets, dict(subtargets), targets_sorted def write_header(filename, section, section_dict, targets_sorted): """Write (update) a section to a C header file.""" # Prepare new content. items = [ ] section = section.replace('/', '_').upper() for key, values in section_dict.iteritems(): cond = 'if' for target in targets_sorted: if target in values: value = values[target] item_fmt = '#define CONFIG_{section}_{key} ({value})' if target is not None: item_fmt = ('#{cond} defined(TARGET_{target})\n# ' + item_fmt[1:]) elif cond != 'if': item_fmt = '#else\n# ' \ + item_fmt[1:] items.append(item_fmt.format(section=section, key=key.upper(), target=target, value=value, cond=cond)) cond = 'elif' if len(values) > 1: items.append('#endif') guard = re.sub(r'\W', '_', filename) content = '\n'.join([ '#ifndef %s' % guard, '#define %s' % guard, '// Generated from configuration.', ] + items + [ '#endif', '' ]) # Check old content. old_content = '' try: hf = open(filename) except IOError: pass else: with hf: old_content = hf.read() if old_content == content: return # Create output directory if needed. dirname = os.path.dirname(filename) if not os.path.exists(dirname): os.makedirs(dirname) # Write new content. with open(filename, 'w') as hf: hf.write(content) def write_headers(filename_pattern, config, targets_sorted): """Write (update) all sections to C header files.""" for section, section_dict in config.iteritems(): filename = filename_pattern.replace('%', section) write_header(filename, section, section_dict, targets_sorted) if __name__ == '__main__': parser = optparse.OptionParser( usage='%prog [options] modules configuration files...') parser.add_option('-p', '--project-config', metavar='FILE', help="project configuration file") parser.add_option('-H', '--c-header-template', metavar='TEMPLATE', help="name template for C header files" + " (use % as section placeholder)") parser.add_option('-T', '--targets', metavar='"LIST"', help="space separated list of target:subtarget pairs (used for" + " error checking)") options, modules_configs = parser.parse_args() try: targets, subtargets, targets_sorted = parse_targets(options.targets) config = read_config(modules_configs, options.project_config, subtargets.keys()) check_config(config, targets, subtargets) if options.c_header_template: write_headers(options.c_header_template, config, targets_sorted) except RuntimeError, e: print >> sys.stderr, e sys.exit(1) except EnvironmentError, e: print >> sys.stderr, e sys.exit(1)