Subversion Repositories HelenOS

Rev

Rev 4058 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. #!/usr/bin/env python
  2. #
  3. # Copyright (c) 2006 Ondrej Palkovsky
  4. # Copyright (c) 2009 Martin Decky
  5. # All rights reserved.
  6. #
  7. # Redistribution and use in source and binary forms, with or without
  8. # modification, are permitted provided that the following conditions
  9. # are met:
  10. #
  11. # - Redistributions of source code must retain the above copyright
  12. #   notice, this list of conditions and the following disclaimer.
  13. # - Redistributions in binary form must reproduce the above copyright
  14. #   notice, this list of conditions and the following disclaimer in the
  15. #   documentation and/or other materials provided with the distribution.
  16. # - The name of the author may not be used to endorse or promote products
  17. #   derived from this software without specific prior written permission.
  18. #
  19. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  20. # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  21. # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  22. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  23. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  24. # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  28. # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. #
  30. """
  31. HelenOS configuration system
  32. """
  33. import sys
  34. import os
  35. import re
  36. import commands
  37. import xtui
  38.  
  39. INPUT = sys.argv[1]
  40. MAKEFILE = 'Makefile.config'
  41. MACROS = 'config.h'
  42. DEFS = 'config.defs'
  43. PRECONF = 'defaults'
  44.  
  45. def read_defaults(fname, defaults):
  46.     "Read saved values from last configuration run"
  47.    
  48.     inf = file(fname, 'r')
  49.    
  50.     for line in inf:
  51.         res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
  52.         if (res):
  53.             defaults[res.group(1)] = res.group(2)
  54.    
  55.     inf.close()
  56.  
  57. def check_condition(text, defaults, ask_names):
  58.     "Check that the condition specified on input line is True (only CNF and DNF is supported)"
  59.    
  60.     ctype = 'cnf'
  61.    
  62.     if ((')|' in text) or ('|(' in text)):
  63.         ctype = 'dnf'
  64.    
  65.     if (ctype == 'cnf'):
  66.         conds = text.split('&')
  67.     else:
  68.         conds = text.split('|')
  69.    
  70.     for cond in conds:
  71.         if (cond.startswith('(')) and (cond.endswith(')')):
  72.             cond = cond[1:-1]
  73.        
  74.         inside = check_inside(cond, defaults, ctype)
  75.        
  76.         if (ctype == 'cnf') and (not inside):
  77.             return False
  78.        
  79.         if (ctype == 'dnf') and (inside):
  80.             return True
  81.    
  82.     if (ctype == 'cnf'):
  83.         return True
  84.     return False
  85.  
  86. def check_inside(text, defaults, ctype):
  87.     "Check for condition"
  88.    
  89.     if (ctype == 'cnf'):
  90.         conds = text.split('|')
  91.     else:
  92.         conds = text.split('&')
  93.    
  94.     for cond in conds:
  95.         res = re.match(r'^(.*?)(!?=)(.*)$', cond)
  96.         if (not res):
  97.             raise RuntimeError("Invalid condition: %s" % cond)
  98.        
  99.         condname = res.group(1)
  100.         oper = res.group(2)
  101.         condval = res.group(3)
  102.        
  103.         if (not defaults.has_key(condname)):
  104.             varval = ''
  105.         else:
  106.             varval = defaults[condname]
  107.             if (varval == '*'):
  108.                 varval = 'y'
  109.        
  110.         if (ctype == 'cnf'):
  111.             if (oper == '=') and (condval == varval):
  112.                 return True
  113.        
  114.             if (oper == '!=') and (condval != varval):
  115.                 return True
  116.         else:
  117.             if (oper == '=') and (condval != varval):
  118.                 return False
  119.            
  120.             if (oper == '!=') and (condval == varval):
  121.                 return False
  122.    
  123.     if (ctype == 'cnf'):
  124.         return False
  125.    
  126.     return True
  127.  
  128. def parse_config(fname, ask_names):
  129.     "Parse configuration file"
  130.    
  131.     inf = file(fname, 'r')
  132.    
  133.     name = ''
  134.     choices = []
  135.    
  136.     for line in inf:
  137.        
  138.         if (line.startswith('!')):
  139.             # Ask a question
  140.             res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
  141.            
  142.             if (not res):
  143.                 raise RuntimeError("Weird line: %s" % line)
  144.            
  145.             cond = res.group(1)
  146.             varname = res.group(2)
  147.             vartype = res.group(3)
  148.            
  149.             ask_names.append((varname, vartype, name, choices, cond))
  150.             name = ''
  151.             choices = []
  152.             continue
  153.        
  154.         if (line.startswith('@')):
  155.             # Add new line into the 'choices' array
  156.             res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
  157.            
  158.             if not res:
  159.                 raise RuntimeError("Bad line: %s" % line)
  160.            
  161.             choices.append((res.group(2), res.group(3)))
  162.             continue
  163.        
  164.         if (line.startswith('%')):
  165.             # Name of the option
  166.             name = line[1:].strip()
  167.             continue
  168.        
  169.         if ((line.startswith('#')) or (line == '\n')):
  170.             # Comment or empty line
  171.             continue
  172.        
  173.        
  174.         raise RuntimeError("Unknown syntax: %s" % line)
  175.    
  176.     inf.close()
  177.  
  178. def yes_no(default):
  179.     "Return '*' if yes, ' ' if no"
  180.    
  181.     if (default == 'y'):
  182.         return '*'
  183.    
  184.     return ' '
  185.  
  186. def subchoice(screen, name, choices, default):
  187.     "Return choice of choices"
  188.    
  189.     maxkey = 0
  190.     for key, val in choices:
  191.         length = len(key)
  192.         if (length > maxkey):
  193.             maxkey = length
  194.    
  195.     options = []
  196.     position = None
  197.     cnt = 0
  198.     for key, val in choices:
  199.         if ((default) and (key == default)):
  200.             position = cnt
  201.        
  202.         options.append(" %-*s  %s " % (maxkey, key, val))
  203.         cnt += 1
  204.    
  205.     (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
  206.    
  207.     if (button == 'cancel'):
  208.         return None
  209.    
  210.     return choices[value][0]
  211.  
  212. def check_choices(defaults, ask_names):
  213.     "Check whether all accessible variables have a default"
  214.    
  215.     for varname, vartype, name, choices, cond in ask_names:
  216.         if ((cond) and (not check_condition(cond, defaults, ask_names))):
  217.             continue
  218.        
  219.         if (not defaults.has_key(varname)):
  220.             return False
  221.    
  222.     return True
  223.  
  224. def create_output(mkname, mcname, dfname, defaults, ask_names):
  225.     "Create output configuration"
  226.    
  227.     revision = commands.getoutput('svnversion . 2> /dev/null')
  228.     timestamp = commands.getoutput('date "+%Y-%m-%d %H:%M:%S"')
  229.    
  230.     outmk = file(mkname, 'w')
  231.     outmc = file(mcname, 'w')
  232.     outdf = file(dfname, 'w')
  233.    
  234.     outmk.write('#########################################\n')
  235.     outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
  236.     outmk.write('#########################################\n\n')
  237.    
  238.     outmc.write('/***************************************\n')
  239.     outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
  240.     outmc.write(' ***************************************/\n\n')
  241.    
  242.     outdf.write('#########################################\n')
  243.     outdf.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
  244.     outdf.write('#########################################\n\n')
  245.     outdf.write('CONFIG_DEFS =')
  246.    
  247.     for varname, vartype, name, choices, cond in ask_names:
  248.         if ((cond) and (not check_condition(cond, defaults, ask_names))):
  249.             continue
  250.        
  251.         if (not defaults.has_key(varname)):
  252.             default = ''
  253.         else:
  254.             default = defaults[varname]
  255.             if (default == '*'):
  256.                 default = 'y'
  257.        
  258.         outmk.write('# %s\n%s = %s\n\n' % (name, varname, default))
  259.        
  260.         if ((vartype == "y") or (vartype == "y/n") or (vartype == "n/y")):
  261.             if (default == "y"):
  262.                 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
  263.                 outdf.write(' -D%s' % varname)
  264.         else:
  265.             outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, default, varname, default))
  266.             outdf.write(' -D%s=%s -D%s_%s' % (varname, default, varname, default))
  267.    
  268.     outmk.write('REVISION = %s\n' % revision)
  269.     outmk.write('TIMESTAMP = %s\n' % timestamp)
  270.    
  271.     outmc.write('#define REVISION %s\n' % revision)
  272.     outmc.write('#define TIMESTAMP %s\n' % timestamp)
  273.    
  274.     outdf.write(' "-DREVISION=%s" "-DTIMESTAMP=%s"\n' % (revision, timestamp))
  275.    
  276.     outmk.close()
  277.     outmc.close()
  278.     outdf.close()
  279.  
  280. def sorted_dir(root):
  281.     list = os.listdir(root)
  282.     list.sort()
  283.     return list
  284.  
  285. def read_preconfigured(root, fname, screen, defaults):
  286.     options = []
  287.     opt2path = {}
  288.     cnt = 0
  289.    
  290.     # Look for profiles
  291.     for name in sorted_dir(root):
  292.         path = os.path.join(root, name)
  293.         canon = os.path.join(path, fname)
  294.        
  295.         if ((os.path.isdir(path)) and (os.path.exists(canon)) and (os.path.isfile(canon))):
  296.             subprofile = False
  297.            
  298.             # Look for subprofiles
  299.             for subname in sorted_dir(path):
  300.                 subpath = os.path.join(path, subname)
  301.                 subcanon = os.path.join(subpath, fname)
  302.                
  303.                 if ((os.path.isdir(subpath)) and (os.path.exists(subcanon)) and (os.path.isfile(subcanon))):
  304.                     subprofile = True
  305.                     options.append("%s (%s)" % (name, subname))
  306.                     opt2path[cnt] = (canon, subcanon)
  307.                     cnt += 1
  308.            
  309.             if (not subprofile):
  310.                 options.append(name)
  311.                 opt2path[cnt] = (canon, None)
  312.                 cnt += 1
  313.    
  314.     (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
  315.    
  316.     if (button == 'cancel'):
  317.         return None
  318.    
  319.     read_defaults(opt2path[value][0], defaults)
  320.     if (opt2path[value][1] != None):
  321.         read_defaults(opt2path[value][1], defaults)
  322.  
  323. def main():
  324.     defaults = {}
  325.     ask_names = []
  326.    
  327.     # Parse configuration file
  328.     parse_config(INPUT, ask_names)
  329.    
  330.     # Read defaults from previous run
  331.     if os.path.exists(MAKEFILE):
  332.         read_defaults(MAKEFILE, defaults)
  333.    
  334.     # Default mode: only check defaults and regenerate configuration
  335.     if ((len(sys.argv) >= 3) and (sys.argv[2] == 'default')):
  336.         if (check_choices(defaults, ask_names)):
  337.             create_output(MAKEFILE, MACROS, DEFS, defaults, ask_names)
  338.             return 0
  339.    
  340.     # Check mode: only check defaults
  341.     if ((len(sys.argv) >= 3) and (sys.argv[2] == 'check')):
  342.         if (check_choices(defaults, ask_names)):
  343.             return 0
  344.         return 1
  345.    
  346.     screen = xtui.screen_init()
  347.     try:
  348.         selname = None
  349.         position = None
  350.         while True:
  351.            
  352.             # Cancel out all defaults which have to be deduced
  353.             for varname, vartype, name, choices, cond in ask_names:
  354.                 if ((vartype == 'y') and (defaults.has_key(varname)) and (defaults[varname] == '*')):
  355.                     defaults[varname] = None
  356.            
  357.             options = []
  358.             opt2row = {}
  359.             cnt = 1
  360.            
  361.             options.append("  --- Load preconfigured defaults ... ")
  362.            
  363.             for varname, vartype, name, choices, cond in ask_names:
  364.                
  365.                 if ((cond) and (not check_condition(cond, defaults, ask_names))):
  366.                     continue
  367.                
  368.                 if (varname == selname):
  369.                     position = cnt
  370.                
  371.                 if (not defaults.has_key(varname)):
  372.                     default = None
  373.                 else:
  374.                     default = defaults[varname]
  375.                
  376.                 if (vartype == 'choice'):
  377.                     # Check if the default is an acceptable value
  378.                     if ((default) and (not default in [choice[0] for choice in choices])):
  379.                         default = None
  380.                         defaults.pop(varname)
  381.                    
  382.                     # If there is just one option, use it
  383.                     if (len(choices) == 1):
  384.                         defaults[varname] = choices[0][0]
  385.                         continue
  386.                    
  387.                     if (default == None):
  388.                         options.append("?     %s --> " % name)
  389.                     else:
  390.                         options.append("      %s [%s] --> " % (name, default))
  391.                 elif (vartype == 'y'):
  392.                     defaults[varname] = '*'
  393.                     continue
  394.                 elif (vartype == 'y/n'):
  395.                     if (default == None):
  396.                         default = 'y'
  397.                         defaults[varname] = default
  398.                     options.append("  <%s> %s " % (yes_no(default), name))
  399.                 elif (vartype == 'n/y'):
  400.                     if (default == None):
  401.                         default = 'n'
  402.                         defaults[varname] = default
  403.                     options.append("  <%s> %s " % (yes_no(default), name))
  404.                 else:
  405.                     raise RuntimeError("Unknown variable type: %s" % vartype)
  406.                
  407.                 opt2row[cnt] = (varname, vartype, name, choices)
  408.                
  409.                 cnt += 1
  410.            
  411.             if (position >= options):
  412.                 position = None
  413.            
  414.             (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
  415.            
  416.             if (button == 'cancel'):
  417.                 return 'Configuration canceled'
  418.            
  419.             if (button == 'done'):
  420.                 if (check_choices(defaults, ask_names)):
  421.                     break
  422.                 else:
  423.                     xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
  424.                     continue
  425.            
  426.             if (value == 0):
  427.                 read_preconfigured(PRECONF, MAKEFILE, screen, defaults)
  428.                 position = 1
  429.                 continue
  430.            
  431.             position = None
  432.             if (not opt2row.has_key(value)):
  433.                 raise RuntimeError("Error selecting value: %s" % value)
  434.            
  435.             (selname, seltype, name, choices) = opt2row[value]
  436.            
  437.             if (not defaults.has_key(selname)):
  438.                     default = None
  439.             else:
  440.                 default = defaults[selname]
  441.            
  442.             if (seltype == 'choice'):
  443.                 defaults[selname] = subchoice(screen, name, choices, default)
  444.             elif ((seltype == 'y/n') or (seltype == 'n/y')):
  445.                 if (defaults[selname] == 'y'):
  446.                     defaults[selname] = 'n'
  447.                 else:
  448.                     defaults[selname] = 'y'
  449.     finally:
  450.         xtui.screen_done(screen)
  451.    
  452.     create_output(MAKEFILE, MACROS, DEFS, defaults, ask_names)
  453.     return 0
  454.  
  455. if __name__ == '__main__':
  456.     sys.exit(main())
  457.