Subversion Repositories HelenOS

Rev

Rev 4080 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

#!/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())