Subversion Repositories HelenOS-historic

Rev

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

  1. #!/usr/bin/env python
  2. """
  3. Kernel configuration script
  4. """
  5. import sys
  6. import os
  7. import re
  8.  
  9. INPUT = 'kernel.config'
  10. OUTPUT = 'Makefile.config'
  11. TMPOUTPUT = 'Makefile.config.tmp'
  12.  
  13. class DefaultDialog:
  14.     "Wrapper dialog that tries to return default values"
  15.     def __init__(self, dlg):
  16.         self.dlg = dlg
  17.  
  18.     def set_title(self,text):
  19.         self.dlg.set_title(text)
  20.        
  21.     def yesno(self, text, default=None):
  22.         if default is not None:
  23.             return default
  24.         return self.dlg.yesno(text, default)
  25.     def noyes(self, text, default=None):
  26.         if default is not None:
  27.             return default
  28.         return self.dlg.noyes(text, default)
  29.    
  30.     def choice(self, text, choices, defopt=None):
  31.         if defopt is not None:
  32.             return choices[defopt][0]
  33.         return self.dlg.choice(text, choices, defopt)
  34.  
  35. class NoDialog:
  36.     def __init__(self):
  37.         self.printed = None
  38.         self.title = 'HelenOS Configuration'
  39.  
  40.     def print_title(self):
  41.         if not self.printed:
  42.             sys.stdout.write("\n*** %s ***\n" % self.title)
  43.             self.printed = True
  44.  
  45.     def set_title(self, text):
  46.         self.title = text
  47.         self.printed = False
  48.    
  49.     def noyes(self, text, default=None):
  50.         if not default:
  51.             default = 'n'
  52.         return self.yesno(text, default)
  53.    
  54.     def yesno(self, text, default=None):
  55.         self.print_title()
  56.        
  57.         if default != 'n':
  58.             default = 'y'
  59.         while 1:
  60.             sys.stdout.write("%s (y/n)[%s]: " % (text,default))
  61.             inp = sys.stdin.readline()
  62.             if not inp:
  63.                 raise EOFError
  64.             inp = inp.strip().lower()
  65.             if not inp:
  66.                 return default
  67.             if inp == 'y':
  68.                 return 'y'
  69.             elif inp == 'n':
  70.                 return 'n'
  71.  
  72.     def _print_choice(self, text, choices, defopt):
  73.         sys.stdout.write('%s:\n' % text)
  74.         for i,(text,descr) in enumerate(choices):
  75.             sys.stdout.write('\t%2d. %s\n' % (i, descr))
  76.         if defopt is not None:
  77.             sys.stdout.write('Enter choice number[%d]: ' % defopt)
  78.         else:
  79.             sys.stdout.write('Enter choice number: ')
  80.  
  81.     def menu(self, text, choices, button, defopt=None):
  82.         return self.choice(text, [button] + choices)
  83.        
  84.     def choice(self, text, choices, defopt=None):
  85.         self.print_title()
  86.         while 1:
  87.             self._print_choice(text, choices, defopt)
  88.             inp = sys.stdin.readline()
  89.             if not inp:
  90.                 raise EOFError
  91.             if not inp.strip():
  92.                 if defopt is not None:
  93.                     return choices[defopt][0]
  94.                 continue
  95.             try:
  96.                 number = int(inp.strip())
  97.             except ValueError:
  98.                 continue
  99.             if number < 0 or number >= len(choices):
  100.                 continue
  101.             return choices[number][0]
  102.  
  103.  
  104. class Dialog(NoDialog):
  105.     def __init__(self):
  106.         NoDialog.__init__(self)
  107.         self.dlgcmd = os.environ.get('DIALOG','dialog')
  108.         self.title = ''
  109.         self.backtitle = 'HelenOS Kernel Configuration'
  110.        
  111.         if os.system('%s --print-maxsize >/dev/null 2>&1' % self.dlgcmd) != 0:
  112.             raise NotImplementedError
  113.  
  114.     def set_title(self,text):
  115.         self.title = text
  116.        
  117.     def calldlg(self,*args,**kw):
  118.         "Wrapper for calling 'dialog' program"
  119.         indesc, outdesc = os.pipe()
  120.         pid = os.fork()
  121.         if not pid:
  122.             os.close(2)
  123.             os.dup(outdesc)
  124.             os.close(indesc)
  125.            
  126.             dlgargs = [self.dlgcmd,'--title',self.title,
  127.                        '--backtitle', self.backtitle]
  128.             for key,val in kw.items():
  129.                 dlgargs.append('--'+key)
  130.                 dlgargs.append(val)
  131.             dlgargs += args            
  132.             os.execlp(self.dlgcmd,*dlgargs)
  133.  
  134.         os.close(outdesc)
  135.         errout = os.fdopen(indesc,'r')
  136.         data = errout.read()
  137.         errout.close()
  138.            
  139.         pid,status = os.wait()
  140.         if not os.WIFEXITED(status):
  141.             raise EOFError
  142.         status = os.WEXITSTATUS(status)
  143.         if status == 255:
  144.             raise EOFError
  145.         return status,data
  146.        
  147.     def yesno(self, text, default=None):
  148.         text = text + ':'
  149.         width = '50'
  150.         height = '5'
  151.         if len(text) < 48:
  152.             text = ' '*int(((48-len(text))/2)) + text
  153.         else:
  154.             width = '0'
  155.             height = '0'
  156.         if default == 'n':
  157.             res,data = self.calldlg('--defaultno','--yesno',text,height,width)
  158.         else:
  159.             res,data = self.calldlg('--yesno',text,height,width)
  160.  
  161.         if res == 0:
  162.             return 'y'
  163.         return 'n'
  164.  
  165.     def menu(self, text, choices, button, defopt=None):
  166.         text = text + ':'
  167.         width = '70'
  168.         height = str(8 + len(choices))
  169.         args = []
  170.         for key,val in choices:
  171.             args.append(key)
  172.             args.append(val)
  173.  
  174.         kw = {}
  175.         if defopt:
  176.             kw['default-item'] = choices[defopt][0]
  177.         res,data = self.calldlg('--cancel-label',button[1],
  178.                                 '--menu',text,height,width,
  179.                                 str(len(choices)),*args,**kw)
  180.         if res == 1:
  181.             return button[0]
  182.         elif res:
  183.             print data
  184.             raise EOFError
  185.         return data
  186.    
  187.     def choice(self, text, choices, defopt=None):
  188.         text = text + ':'
  189.         width = '50'
  190.         height = str(8 + len(choices))
  191.         args = []
  192.         for key,val in choices:
  193.             args.append(key)
  194.             args.append(val)
  195.  
  196.         kw = {}
  197.         if defopt:
  198.             kw['default-item'] = choices[defopt][0]
  199.         res,data = self.calldlg('--nocancel','--menu',text,height,width,
  200.                                 str(len(choices)),*args, **kw)
  201.         if res:
  202.             print data
  203.             raise EOFError
  204.         return data
  205.    
  206. def read_defaults(fname,defaults):
  207.     "Read saved values from last configuration run"
  208.     f = file(fname,'r')
  209.     for line in f:
  210.         res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
  211.         if res:
  212.             defaults[res.group(1)] = res.group(2)
  213.     f.close()
  214.  
  215. def check_condition(text, defaults):
  216.     result = True
  217.     conds = text.split('&')
  218.     for cond in conds:
  219.         if cond.startswith('(') and cond.endswith(')'):
  220.             cond = cond[1:-1]
  221.         if not check_dnf(cond, defaults):
  222.             return False
  223.     return True
  224.  
  225. def check_dnf(text, defaults):
  226.     """
  227.    Check that the condition specified on input line is True
  228.  
  229.    only CNF is supported
  230.    """
  231.     conds = text.split('|')
  232.     for cond in conds:
  233.         res = re.match(r'^(.*?)(!?=)(.*)$', cond)
  234.         if not res:
  235.             raise RuntimeError("Invalid condition: %s" % cond)
  236.         condname = res.group(1)
  237.         oper = res.group(2)
  238.         condval = res.group(3)
  239.         if not defaults.has_key(condname):
  240.             raise RuntimeError("Condition var %s does not exist: %s" % \
  241.                                (condname,text))
  242.  
  243.         if oper=='=' and  condval == defaults[condname]:
  244.             return True
  245.         if oper == '!=' and condval != defaults[condname]:
  246.             return True
  247.     return False
  248.  
  249. def parse_config(input, output, dlg, defaults={}, askonly=None):
  250.     "Parse configuration file and create Makefile.config on the fly"
  251.     f = file(input, 'r')
  252.     outf = file(output, 'w')
  253.  
  254.     outf.write('#########################################\n')
  255.     outf.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
  256.     outf.write('#########################################\n\n')
  257.  
  258.     asked_names = []
  259.  
  260.     comment = ''
  261.     default = None
  262.     choices = []
  263.     for line in f:
  264.         if line.startswith('%'):
  265.             res = re.match(r'^%\s*(?:\[(.*?)\])?\s*(.*)$', line)
  266.             if not res:
  267.                 raise RuntimeError('Invalid command: %s' % line)
  268.             if res.group(1):
  269.                 if not check_condition(res.group(1), defaults):
  270.                     continue
  271.             args = res.group(2).strip().split(' ')
  272.             cmd = args[0].lower()
  273.             args = args[1:]
  274.             if cmd == 'saveas':
  275.                 outf.write('%s = %s\n' % (args[1],defaults[args[0]]))
  276.                
  277.             continue
  278.            
  279.         if line.startswith('!'):
  280.             # Ask a question
  281.             res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
  282.             if not res:
  283.                 raise RuntimeError("Weird line: %s" % line)
  284.             varname = res.group(2)
  285.             vartype = res.group(3)
  286.  
  287.             default = defaults.get(varname,None)
  288.            
  289.             if res.group(1):
  290.                 if not check_condition(res.group(1), defaults):
  291.                     if default is not None:
  292.                         outf.write('#!# %s = %s\n' % (varname, default))
  293.                     # Clear cumulated values
  294.                     comment = ''
  295.                     default = None
  296.                     choices = []
  297.                     continue
  298.                
  299.             asked_names.append((varname,comment))
  300.  
  301.             if default is not None and askonly and askonly != varname:
  302.                 outf.write('%s = %s\n' % (varname, default))
  303.                 continue
  304.  
  305.             if vartype == 'y/n':
  306.                 result = dlg.yesno(comment, default)
  307.             elif vartype == 'n/y':
  308.                 result = dlg.noyes(comment, default)
  309.             elif vartype == 'choice':
  310.                 defopt = None
  311.                 if default is not None:
  312.                     for i,(key,val) in enumerate(choices):
  313.                         if key == default:
  314.                             defopt = i
  315.                             break
  316.                 result = dlg.choice(comment, choices, defopt)
  317.             else:
  318.                 raise RuntimeError("Bad method: %s" % vartype)
  319.             outf.write('%s = %s\n' % (varname, result))
  320.             # Remeber the selected value
  321.             defaults[varname] = result
  322.             # Clear cumulated values
  323.             comment = ''
  324.             default = None
  325.             choices = []
  326.             continue
  327.        
  328.         if line.startswith('@'):
  329.             # Add new line into the 'choice array'
  330.             res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
  331.             if not res:
  332.                 raise RuntimeError("Bad line: %s" % line)
  333.             if res.group(1):
  334.                 if not check_condition(res.group(1),defaults):
  335.                     continue
  336.             choices.append((res.group(2), res.group(3)))
  337.             continue
  338.  
  339.         # All other things print to output file
  340.         outf.write(line)
  341.         if re.match(r'^#[^#]', line):
  342.             # Last comment before question will be displayed to the user
  343.             comment = line[1:].strip()
  344.         elif line.startswith('## '):
  345.             # Set title of the dialog window
  346.             dlg.set_title(line[2:].strip())
  347.        
  348.     outf.close()
  349.     f.close()
  350.     return asked_names
  351.  
  352. def main():
  353.     defaults = {}
  354.     try:
  355.         dlg = Dialog()
  356.     except NotImplementedError:
  357.         dlg = NoDialog()
  358.  
  359.     if len(sys.argv) == 2 and sys.argv[1]=='default':
  360.         defmode = True
  361.     else:
  362.         defmode = False
  363.  
  364.     # Default run will update the configuration file
  365.     # with newest options
  366.     if os.path.exists(OUTPUT):
  367.         read_defaults(OUTPUT, defaults)
  368.    
  369.     varnames = parse_config(INPUT, TMPOUTPUT, DefaultDialog(dlg), defaults)
  370.     # If not in default mode, present selection of all possibilities
  371.     if not defmode:
  372.         defopt = 0
  373.         while 1:
  374.             choices = [ (x[1],defaults[x[0]]) for x in varnames ]
  375.             res = dlg.menu('Configuration',choices,('save','Save'),defopt)
  376.             if res == 'save':
  377.                 parse_config(INPUT, TMPOUTPUT, DefaultDialog(dlg), defaults)
  378.                 break
  379.             # transfer description back to varname
  380.             for i,(vname,descr) in enumerate(varnames):
  381.                 if res == descr:
  382.                     defopt = i
  383.                     break
  384.             varnames = parse_config(INPUT, TMPOUTPUT, dlg, defaults,
  385.                                     askonly=varnames[i][0])
  386.        
  387.    
  388.     if os.path.exists(OUTPUT):
  389.         os.unlink(OUTPUT)
  390.     os.rename(TMPOUTPUT, OUTPUT)
  391.        
  392.  
  393. if __name__ == '__main__':
  394.     main()
  395.