#!/usr/bin/env python # # Copyright (c) 2006 Ondrej Palkovsky # Copyright (c) 2009 Martin Decky # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # - Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # - Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # - The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ HelenOS configuration system """ import sys import os import re import commands import xtui INPUT = sys.argv[1] MAKEFILE = 'Makefile.config' MACROS = 'config.h' DEFS = 'config.defs' PRECONF = 'defaults' def read_defaults(fname, defaults): "Read saved values from last configuration run" inf = file(fname, 'r') for line in inf: res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line) if (res): defaults[res.group(1)] = res.group(2) inf.close() def check_condition(text, defaults, ask_names): "Check that the condition specified on input line is True (only CNF and DNF is supported)" ctype = 'cnf' if ((')|' in text) or ('|(' in text)): ctype = 'dnf' if (ctype == 'cnf'): conds = text.split('&') else: conds = text.split('|') for cond in conds: if (cond.startswith('(')) and (cond.endswith(')')): cond = cond[1:-1] inside = check_inside(cond, defaults, ctype) if (ctype == 'cnf') and (not inside): return False if (ctype == 'dnf') and (inside): return True if (ctype == 'cnf'): return True return False def check_inside(text, defaults, ctype): "Check for condition" if (ctype == 'cnf'): conds = text.split('|') else: conds = text.split('&') for cond in conds: res = re.match(r'^(.*?)(!?=)(.*)$', cond) if (not res): raise RuntimeError("Invalid condition: %s" % cond) condname = res.group(1) oper = res.group(2) condval = res.group(3) if (not defaults.has_key(condname)): varval = '' else: varval = defaults[condname] if (varval == '*'): varval = 'y' if (ctype == 'cnf'): if (oper == '=') and (condval == varval): return True if (oper == '!=') and (condval != varval): return True else: if (oper == '=') and (condval != varval): return False if (oper == '!=') and (condval == varval): return False if (ctype == 'cnf'): return False return True def parse_config(fname, ask_names): "Parse configuration file" inf = file(fname, 'r') name = '' choices = [] for line in inf: if (line.startswith('!')): # Ask a question res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line) if (not res): raise RuntimeError("Weird line: %s" % line) cond = res.group(1) varname = res.group(2) vartype = res.group(3) ask_names.append((varname, vartype, name, choices, cond)) name = '' choices = [] continue if (line.startswith('@')): # Add new line into the 'choices' array res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line) if not res: raise RuntimeError("Bad line: %s" % line) choices.append((res.group(2), res.group(3))) continue if (line.startswith('%')): # Name of the option name = line[1:].strip() continue if ((line.startswith('#')) or (line == '\n')): # Comment or empty line continue raise RuntimeError("Unknown syntax: %s" % line) inf.close() def yes_no(default): "Return '*' if yes, ' ' if no" if (default == 'y'): return '*' return ' ' def subchoice(screen, name, choices, default): "Return choice of choices" maxkey = 0 for key, val in choices: length = len(key) if (length > maxkey): maxkey = length options = [] position = None cnt = 0 for key, val in choices: if ((default) and (key == default)): position = cnt options.append(" %-*s %s " % (maxkey, key, val)) cnt += 1 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position) if (button == 'cancel'): return None return choices[value][0] def check_choices(defaults, ask_names): "Check whether all accessible variables have a default" for varname, vartype, name, choices, cond in ask_names: if ((cond) and (not check_condition(cond, defaults, ask_names))): continue if (not defaults.has_key(varname)): return False return True def create_output(mkname, mcname, dfname, defaults, ask_names): "Create output configuration" revision = commands.getoutput('svnversion . 2> /dev/null') timestamp = commands.getoutput('date "+%Y-%m-%d %H:%M:%S"') outmk = file(mkname, 'w') outmc = file(mcname, 'w') outdf = file(dfname, 'w') outmk.write('#########################################\n') outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n') outmk.write('#########################################\n\n') outmc.write('/***************************************\n') outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n') outmc.write(' ***************************************/\n\n') outdf.write('#########################################\n') outdf.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n') outdf.write('#########################################\n\n') outdf.write('CONFIG_DEFS =') for varname, vartype, name, choices, cond in ask_names: if ((cond) and (not check_condition(cond, defaults, ask_names))): continue if (not defaults.has_key(varname)): default = '' else: default = defaults[varname] if (default == '*'): default = 'y' outmk.write('# %s\n%s = %s\n\n' % (name, varname, default)) if ((vartype == "y") or (vartype == "n") or (vartype == "y/n") or (vartype == "n/y")): if (default == "y"): outmc.write('/* %s */\n#define %s\n\n' % (name, varname)) outdf.write(' -D%s' % varname) else: outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, default, varname, default)) outdf.write(' -D%s=%s -D%s_%s' % (varname, default, varname, default)) outmk.write('REVISION = %s\n' % revision) outmk.write('TIMESTAMP = %s\n' % timestamp) outmc.write('#define REVISION %s\n' % revision) outmc.write('#define TIMESTAMP %s\n' % timestamp) outdf.write(' "-DREVISION=%s" "-DTIMESTAMP=%s"\n' % (revision, timestamp)) outmk.close() outmc.close() outdf.close() def sorted_dir(root): list = os.listdir(root) list.sort() return list def read_preconfigured(root, fname, screen, defaults): options = [] opt2path = {} cnt = 0 # Look for profiles for name in sorted_dir(root): path = os.path.join(root, name) canon = os.path.join(path, fname) if ((os.path.isdir(path)) and (os.path.exists(canon)) and (os.path.isfile(canon))): subprofile = False # Look for subprofiles for subname in sorted_dir(path): subpath = os.path.join(path, subname) subcanon = os.path.join(subpath, fname) if ((os.path.isdir(subpath)) and (os.path.exists(subcanon)) and (os.path.isfile(subcanon))): subprofile = True options.append("%s (%s)" % (name, subname)) opt2path[cnt] = (canon, subcanon) cnt += 1 if (not subprofile): options.append(name) opt2path[cnt] = (canon, None) cnt += 1 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None) if (button == 'cancel'): return None read_defaults(opt2path[value][0], defaults) if (opt2path[value][1] != None): read_defaults(opt2path[value][1], defaults) def main(): defaults = {} ask_names = [] # Parse configuration file parse_config(INPUT, ask_names) # Read defaults from previous run if os.path.exists(MAKEFILE): read_defaults(MAKEFILE, defaults) # Default mode: only check defaults and regenerate configuration if ((len(sys.argv) >= 3) and (sys.argv[2] == 'default')): if (check_choices(defaults, ask_names)): create_output(MAKEFILE, MACROS, DEFS, defaults, ask_names) return 0 # Check mode: only check defaults if ((len(sys.argv) >= 3) and (sys.argv[2] == 'check')): if (check_choices(defaults, ask_names)): return 0 return 1 screen = xtui.screen_init() try: selname = None position = None while True: # Cancel out all defaults which have to be deduced for varname, vartype, name, choices, cond in ask_names: if ((vartype == 'y') and (defaults.has_key(varname)) and (defaults[varname] == '*')): defaults[varname] = None options = [] opt2row = {} cnt = 1 options.append(" --- Load preconfigured defaults ... ") for varname, vartype, name, choices, cond in ask_names: if ((cond) and (not check_condition(cond, defaults, ask_names))): continue if (varname == selname): position = cnt if (not defaults.has_key(varname)): default = None else: default = defaults[varname] if (vartype == 'choice'): # Check if the default is an acceptable value if ((default) and (not default in [choice[0] for choice in choices])): default = None defaults.pop(varname) # If there is just one option, use it if (len(choices) == 1): defaults[varname] = choices[0][0] continue if (default == None): options.append("? %s --> " % name) else: options.append(" %s [%s] --> " % (name, default)) elif (vartype == 'y'): defaults[varname] = '*' continue elif (vartype == 'n'): defaults[varname] = 'n' continue elif (vartype == 'y/n'): if (default == None): default = 'y' defaults[varname] = default options.append(" <%s> %s " % (yes_no(default), name)) elif (vartype == 'n/y'): if (default == None): default = 'n' defaults[varname] = default options.append(" <%s> %s " % (yes_no(default), name)) else: raise RuntimeError("Unknown variable type: %s" % vartype) opt2row[cnt] = (varname, vartype, name, choices) cnt += 1 if (position >= options): position = None (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position) if (button == 'cancel'): return 'Configuration canceled' if (button == 'done'): if (check_choices(defaults, ask_names)): break else: xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.') continue if (value == 0): read_preconfigured(PRECONF, MAKEFILE, screen, defaults) position = 1 continue position = None if (not opt2row.has_key(value)): raise RuntimeError("Error selecting value: %s" % value) (selname, seltype, name, choices) = opt2row[value] if (not defaults.has_key(selname)): default = None else: default = defaults[selname] if (seltype == 'choice'): defaults[selname] = subchoice(screen, name, choices, default) elif ((seltype == 'y/n') or (seltype == 'n/y')): if (defaults[selname] == 'y'): defaults[selname] = 'n' else: defaults[selname] = 'y' finally: xtui.screen_done(screen) create_output(MAKEFILE, MACROS, DEFS, defaults, ask_names) return 0 if __name__ == '__main__': sys.exit(main())