#!/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): """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) # Save the list of existing items for later check. def config_items(parser): return set('%s:%s' % (section.split(':')[0], item) for section in parser.sections() for item in parser.options(section)) 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(): # Check targets. for ts in values: if ts is not None and ts not in subtargets: raise RuntimeError("unknown target %s" % ts) values_targets = reduce(lambda a, b: a + b, (subtargets[ts] for ts in values if ts is not None), [ ]) values_targets_set = set(values_targets) # Check for items with no default value. if values[None] == '': if not values_targets: raise RuntimeError("no value given for %s:%s" % (section, key)) else: if values_targets_set < targets: raise RuntimeError("no value given for %s:%s for" " targets: %s" % (section, key, ', '.join (targets - values_targets_set))) # Check for items overridden several times for the same target. if len(values_targets) != len(values_targets_set): raise RuntimeError("several values given for %s:%s for the" " same target" % (section, key)) def parse_targets(targets_option): """Parse a space separated target:subtarget list. Return a set of targets, and a mapping of each subtarget to a list of target.""" if targets_option is None: targets_option = '' targets = set() subtargets = collections.defaultdict(list) 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].append(target) return targets, dict(subtargets) def write_header(filename, section, section_dict): """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 = False for target, value in values.iteritems(): if target is None: continue item_fmt = ('#ifdef TARGET_{target}\n' '# define UCOO_CONFIG_{section}_{key} ({value})\n' '#endif') items.append(item_fmt.format(section=section, key=key.upper(), target=target, value=value)) cond = True item_fmt = '#define UCOO_CONFIG_{section}_{key} ({value})' if cond: item_fmt = '#ifndef UCOO_CONFIG_{section}_{key}\n# ' \ + item_fmt[1:] + '\n#endif' items.append(item_fmt.format(section=section, key=key.upper(), value=values[None])) 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): """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) 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 = parse_targets(options.targets) config = read_config(modules_configs, options.project_config) check_config(config, targets, subtargets) if options.c_header_template: write_headers(options.c_header_template, config) except RuntimeError, e: print >> sys.stderr, e sys.exit(1) except EnvironmentError, e: print >> sys.stderr, e sys.exit(1)