Subversion Repositories HelenOS

Rev

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

  1. #!/usr/bin/env python
  2. """
  3. HelenOS configuration script
  4. """
  5. import sys
  6. import os
  7. import re
  8. import commands
  9.  
  10. INPUT = sys.argv[1]
  11. OUTPUT = 'Makefile.config'
  12. TMPOUTPUT = 'Makefile.config.tmp'
  13.  
  14. class DefaultDialog:
  15.     "Wrapper dialog that tries to return default values"
  16.     def __init__(self, dlg):
  17.         self.dlg = dlg
  18.  
  19.     def set_title(self,text):
  20.         self.dlg.set_title(text)
  21.        
  22.     def yesno(self, text, default=None):
  23.         if default is not None:
  24.             return default
  25.         return self.dlg.yesno(text, default)
  26.     def noyes(self, text, default=None):
  27.         if default is not None:
  28.             return default
  29.         return self.dlg.noyes(text, default)
  30.    
  31.     def choice(self, text, choices, defopt=None):
  32.         if defopt is not None:
  33.             return choices[defopt][0]
  34.         return self.dlg.choice(text, choices, defopt)
  35.  
  36. class NoDialog:
  37.     def __init__(self):
  38.         self.printed = None
  39.         self.title = 'HelenOS Configuration'
  40.  
  41.     def print_title(self):
  42.         if not self.printed:
  43.             sys.stdout.write("\n*** %s ***\n" % self.title)
  44.             self.printed = True
  45.  
  46.     def set_title(self, text):
  47.         self.title = text
  48.         self.printed = False
  49.    
  50.     def noyes(self, text, default=None):
  51.         if not default:
  52.             default = 'n'
  53.         return self.yesno(text, default)
  54.    
  55.     def yesno(self, text, default=None):
  56.         self.print_title()
  57.        
  58.         if default != 'n':
  59.             default = 'y'
  60.         while 1:
  61.             sys.stdout.write("%s (y/n)[%s]: " % (text,default))
  62.             inp = sys.stdin.readline()
  63.             if not inp:
  64.                 raise EOFError
  65.             inp = inp.strip().lower()
  66.             if not inp:
  67.                 return default
  68.             if inp == 'y':
  69.                 return 'y'
  70.             elif inp == 'n':
  71.                 return 'n'
  72.  
  73.     def _print_choice(self, text, choices, defopt):
  74.         sys.stdout.write('%s:\n' % text)
  75.         for i,(text,descr) in enumerate(choices):
  76.             if descr is '':
  77.                 sys.stdout.write('\t%2d. %s\n' % (i, text))
  78.             else:
  79.                 sys.stdout.write('\t%2d. %s\n' % (i, descr))
  80.         if defopt is not None:
  81.             sys.stdout.write('Enter choice number[%d]: ' % defopt)
  82.         else:
  83.             sys.stdout.write('Enter choice number: ')
  84.  
  85.     def menu(self, text, choices, button, defopt=None):
  86.         self.title = 'Main menu'
  87.         menu = []
  88.         for key, descr in choices:
  89.             txt = key + (45-len(key))*' ' + ': ' + descr
  90.             menu.append((key, txt))
  91.            
  92.         return self.choice(text, [button] + menu)
  93.        
  94.     def choice(self, text, choices, defopt=None):
  95.         self.print_title()
  96.         while 1:
  97.             self._print_choice(text, choices, defopt)
  98.             inp = sys.stdin.readline()
  99.             if not inp:
  100.                 raise EOFError
  101.             if not inp.strip():
  102.                 if defopt is not None:
  103.                     return choices[defopt][0]
  104.                 continue
  105.             try:
  106.                 number = int(inp.strip())
  107.             except ValueError:
  108.                 continue
  109.             if number < 0 or number >= len(choices):
  110.                 continue
  111.             return choices[number][0]
  112.  
  113.  
  114. def eof_checker(fnc):
  115.     def wrapper(self, *args, **kw):
  116.         try:
  117.             return fnc(self, *args, **kw)
  118.         except EOFError:
  119.             return getattr(self.bckdialog,fnc.func_name)(*args, **kw)
  120.     return wrapper
  121.  
  122. class Dialog(NoDialog):
  123.     def __init__(self):
  124.         NoDialog.__init__(self)
  125.         self.dlgcmd = os.environ.get('DIALOG','dialog')
  126.         self.title = ''
  127.         self.backtitle = 'HelenOS Configuration'
  128.        
  129.         if os.system('%s --print-maxsize >/dev/null 2>&1' % self.dlgcmd) != 0:
  130.             raise NotImplementedError
  131.        
  132.         self.bckdialog = NoDialog()
  133.  
  134.     def set_title(self,text):
  135.         self.title = text
  136.         self.bckdialog.set_title(text)
  137.        
  138.     def calldlg(self,*args,**kw):
  139.         "Wrapper for calling 'dialog' program"
  140.         indesc, outdesc = os.pipe()
  141.         pid = os.fork()
  142.         if not pid:
  143.             os.close(2)
  144.             os.dup(outdesc)
  145.             os.close(indesc)
  146.            
  147.             dlgargs = [self.dlgcmd,'--title',self.title,
  148.                        '--backtitle', self.backtitle]
  149.             for key,val in kw.items():
  150.                 dlgargs.append('--'+key)
  151.                 dlgargs.append(val)
  152.             dlgargs += args            
  153.             os.execlp(self.dlgcmd,*dlgargs)
  154.  
  155.         os.close(outdesc)
  156.        
  157.         try:
  158.             errout = os.fdopen(indesc,'r')
  159.             data = errout.read()
  160.             errout.close()
  161.             pid,status = os.wait()
  162.         except:
  163.             os.system('reset') # Reset terminal
  164.             raise
  165.        
  166.         if not os.WIFEXITED(status):
  167.             os.system('reset') # Reset terminal
  168.             raise EOFError
  169.        
  170.         status = os.WEXITSTATUS(status)
  171.         if status == 255:
  172.             raise EOFError
  173.         return status,data
  174.        
  175.     def yesno(self, text, default=None):
  176.         if text[-1] not in ('?',':'):
  177.             text = text + ':'
  178.         width = '50'
  179.         height = '5'
  180.         if len(text) < 48:
  181.             text = ' '*int(((48-len(text))/2)) + text
  182.         else:
  183.             width = '0'
  184.             height = '0'
  185.         if default == 'n':
  186.             res,data = self.calldlg('--defaultno','--yesno',text,height,width)
  187.         else:
  188.             res,data = self.calldlg('--yesno',text,height,width)
  189.  
  190.         if res == 0:
  191.             return 'y'
  192.         return 'n'
  193.     yesno = eof_checker(yesno)
  194.  
  195.     def menu(self, text, choices, button, defopt=None):
  196.         self.title = 'Main menu'
  197.         text = text + ':'
  198.         width = '70'
  199.         height = str(8 + len(choices))
  200.         args = []
  201.         for key,val in choices:
  202.             args.append(key)
  203.             args.append(val)
  204.  
  205.         kw = {}
  206.         if defopt:
  207.             kw['default-item'] = choices[defopt][0]
  208.         res,data = self.calldlg('--ok-label','Change',
  209.                                 '--extra-label',button[1],
  210.                                 '--extra-button',
  211.                                 '--menu',text,height,width,
  212.                                 str(len(choices)),*args,**kw)
  213.         if res == 3:
  214.             return button[0]
  215.         if res == 1: # Cancel
  216.             sys.exit(1)
  217.         elif res:
  218.             print data
  219.             raise EOFError
  220.         return data
  221.     menu = eof_checker(menu)
  222.    
  223.     def choice(self, text, choices, defopt=None):
  224.         text = text + ':'
  225.         width = '50'
  226.         height = str(8 + len(choices))
  227.         args = []
  228.         for key,val in choices:
  229.             args.append(key)
  230.             args.append(val)
  231.  
  232.         kw = {}
  233.         if defopt:
  234.             kw['default-item'] = choices[defopt][0]
  235.         res,data = self.calldlg('--nocancel','--menu',text,height,width,
  236.                                 str(len(choices)),*args, **kw)
  237.         if res:
  238.             print data
  239.             raise EOFError
  240.         return data
  241.     choice = eof_checker(choice)
  242.    
  243. def read_defaults(fname,defaults):
  244.     "Read saved values from last configuration run"
  245.     f = file(fname,'r')
  246.     for line in f:
  247.         res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
  248.         if res:
  249.             defaults[res.group(1)] = res.group(2)
  250.     f.close()
  251.  
  252. def check_condition(text, defaults, asked_names):
  253.     seen_vars = [ x[0] for x in asked_names ]
  254.     ctype = 'cnf'
  255.     if ')|' in text or '|(' in text:
  256.         ctype = 'dnf'
  257.    
  258.     if ctype == 'cnf':
  259.         conds = text.split('&')
  260.     else:
  261.         conds = text.split('|')
  262.  
  263.     for cond in conds:
  264.         if cond.startswith('(') and cond.endswith(')'):
  265.             cond = cond[1:-1]
  266.            
  267.         inside = check_inside(cond, defaults, ctype, seen_vars)
  268.        
  269.         if ctype == 'cnf' and not inside:
  270.             return False
  271.         if ctype == 'dnf' and inside:
  272.             return True
  273.  
  274.     if ctype == 'cnf':
  275.         return True
  276.     return False
  277.  
  278. def check_inside(text, defaults, ctype, seen_vars):
  279.     """
  280.    Check that the condition specified on input line is True
  281.  
  282.    only CNF is supported
  283.    """
  284.     if ctype == 'cnf':
  285.         conds = text.split('|')
  286.     else:
  287.         conds = text.split('&')
  288.     for cond in conds:
  289.         res = re.match(r'^(.*?)(!?=)(.*)$', cond)
  290.         if not res:
  291.             raise RuntimeError("Invalid condition: %s" % cond)
  292.         condname = res.group(1)
  293.         oper = res.group(2)
  294.         condval = res.group(3)
  295.         if condname not in seen_vars:
  296.             varval = ''
  297. ##             raise RuntimeError("Variable %s not defined before being asked." %\
  298. ##                                condname)
  299.         elif not defaults.has_key(condname):
  300.             raise RuntimeError("Condition var %s does not exist: %s" % \
  301.                                (condname,text))
  302.         else:
  303.             varval = defaults[condname]
  304.         if ctype == 'cnf':
  305.             if oper == '=' and  condval == varval:
  306.                 return True
  307.             if oper == '!=' and condval != varval:
  308.                 return True
  309.         else:
  310.             if oper== '=' and condval != varval:
  311.                 return False
  312.             if oper== '!=' and condval == varval:
  313.                 return False
  314.     if ctype=='cnf':
  315.         return False
  316.     return True
  317.  
  318. def parse_config(input, output, dlg, defaults={}, askonly=None):
  319.     "Parse configuration file and create Makefile.config on the fly"
  320.     def ask_the_question(dialog):
  321.         "Ask question based on the type of variables to ask"
  322.         # This is quite a hack, this thingy is written just to
  323.         # have access to local variables..
  324.         if vartype == 'y/n':
  325.             return dialog.yesno(comment, default)
  326.         elif vartype == 'n/y':
  327.             return dialog.noyes(comment, default)
  328.         elif vartype == 'choice':
  329.             defopt = None
  330.             if default is not None:
  331.                 for i,(key,val) in enumerate(choices):
  332.                     if key == default:
  333.                         defopt = i
  334.                         break
  335.             return dialog.choice(comment, choices, defopt)
  336.         else:
  337.             raise RuntimeError("Bad method: %s" % vartype)
  338.  
  339.    
  340.     f = file(input, 'r')
  341.     outf = file(output, 'w')
  342.  
  343.     outf.write('#########################################\n')
  344.     outf.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
  345.     outf.write('#########################################\n\n')
  346.  
  347.     asked_names = []
  348.  
  349.     comment = ''
  350.     default = None
  351.     choices = []
  352.     for line in f:
  353.         if line.startswith('%'):
  354.             res = re.match(r'^%\s*(?:\[(.*?)\])?\s*(.*)$', line)
  355.             if not res:
  356.                 raise RuntimeError('Invalid command: %s' % line)
  357.             if res.group(1):
  358.                 if not check_condition(res.group(1), defaults,
  359.                                        asked_names):
  360.                     continue
  361.             args = res.group(2).strip().split(' ')
  362.             cmd = args[0].lower()
  363.             args = args[1:]
  364.             if cmd == 'saveas':
  365.                 outf.write('%s = %s\n' % (args[1],defaults[args[0]]))
  366.             elif cmd == 'shellcmd':
  367.                 varname = args[0]
  368.                 args = args[1:]
  369.                 for i,arg in enumerate(args):
  370.                     if arg.startswith('$'):
  371.                         args[i] = defaults[arg[1:]]
  372.                 data,status = commands.getstatusoutput(' '.join(args))
  373.                 if status:
  374.                     raise RuntimeError('Error running: %s' % ' '.join(args))
  375.                 outf.write('%s = %s\n' % (varname,data.strip()))
  376.             continue
  377.            
  378.         if line.startswith('!'):
  379.             # Ask a question
  380.             res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
  381.             if not res:
  382.                 raise RuntimeError("Weird line: %s" % line)
  383.             varname = res.group(2)
  384.             vartype = res.group(3)
  385.  
  386.             default = defaults.get(varname,None)
  387.            
  388.             if res.group(1):
  389.                 if not check_condition(res.group(1), defaults,
  390.                                        asked_names):
  391.                     if default is not None:
  392.                         outf.write('#!# %s = %s\n' % (varname, default))
  393.                     # Clear cumulated values
  394.                     comment = ''
  395.                     default = None
  396.                     choices = []
  397.                     continue
  398.                
  399.             asked_names.append((varname,comment))
  400.  
  401.             if default is None or not askonly or askonly == varname:
  402.                 default = ask_the_question(dlg)
  403.             else:
  404.                 default = ask_the_question(DefaultDialog(dlg))
  405.  
  406.             outf.write('%s = %s\n' % (varname, default))
  407.             # Remeber the selected value
  408.             defaults[varname] = default
  409.             # Clear cumulated values
  410.             comment = ''
  411.             default = None
  412.             choices = []
  413.             continue
  414.        
  415.         if line.startswith('@'):
  416.             # Add new line into the 'choice array'
  417.             res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
  418.             if not res:
  419.                 raise RuntimeError("Bad line: %s" % line)
  420.             if res.group(1):
  421.                 if not check_condition(res.group(1),defaults,
  422.                                        asked_names):
  423.                     continue
  424.             choices.append((res.group(2), res.group(3)))
  425.             continue
  426.  
  427.         # All other things print to output file
  428.         outf.write(line)
  429.         if re.match(r'^#[^#]', line):
  430.             # Last comment before question will be displayed to the user
  431.             comment = line[1:].strip()
  432.         elif line.startswith('## '):
  433.             # Set title of the dialog window
  434.             dlg.set_title(line[2:].strip())
  435.  
  436.     outf.write('\n')
  437.     outf.write('REVISION = %s\n' % commands.getoutput('svnversion . 2> /dev/null'))
  438.     outf.write('TIMESTAMP = %s\n' % commands.getoutput('date "+%Y-%m-%d %H:%M:%S"'))
  439.     outf.close()
  440.     f.close()
  441.     return asked_names
  442.  
  443. def main():
  444.     defaults = {}
  445.     try:
  446.         dlg = Dialog()
  447.     except NotImplementedError:
  448.         dlg = NoDialog()
  449.  
  450.     if len(sys.argv) >= 3 and sys.argv[2]=='default':
  451.         defmode = True
  452.     else:
  453.         defmode = False
  454.  
  455.     # Default run will update the configuration file
  456.     # with newest options
  457.     if os.path.exists(OUTPUT):
  458.         read_defaults(OUTPUT, defaults)
  459.  
  460.     # Get ARCH from command line if specified
  461.     if len(sys.argv) >= 4:
  462.         defaults['ARCH'] = sys.argv[3]
  463.         defaults['PLATFORM'] = sys.argv[3]
  464.    
  465.     # Get COMPILER from command line if specified
  466.     if len(sys.argv) >= 5:
  467.         defaults['COMPILER'] = sys.argv[4]
  468.    
  469.     # Get CONFIG_DEBUG from command line if specified
  470.     if len(sys.argv) >= 6:
  471.         defaults['CONFIG_DEBUG'] = sys.argv[5]
  472.    
  473.     # Get MACHINE/IMAGE from command line if specified
  474.     if len(sys.argv) >= 7:
  475.         defaults['MACHINE'] = sys.argv[6]
  476.         defaults['IMAGE'] = sys.argv[6]
  477.  
  478.     # Dry run only with defaults
  479.     varnames = parse_config(INPUT, TMPOUTPUT, DefaultDialog(dlg), defaults)
  480.     # If not in default mode, present selection of all possibilities
  481.     if not defmode:
  482.         defopt = 0
  483.         while 1:
  484.             # varnames contains variable names that were in the
  485.             # last question set
  486.             choices = [ (x[1],defaults[x[0]]) for x in varnames ]
  487.             res = dlg.menu('Configuration',choices,('save','Save'),defopt)
  488.             if res == 'save':
  489.                 parse_config(INPUT, TMPOUTPUT, DefaultDialog(dlg), defaults)
  490.                 break
  491.             # transfer description back to varname
  492.             for i,(vname,descr) in enumerate(varnames):
  493.                 if res == descr:
  494.                     defopt = i
  495.                     break
  496.             # Ask the user a simple question, produce output
  497.             # as if the user answered all the other questions
  498.             # with default answer
  499.             varnames = parse_config(INPUT, TMPOUTPUT, dlg, defaults,
  500.                                     askonly=varnames[i][0])
  501.        
  502.    
  503.     if os.path.exists(OUTPUT):
  504.         os.unlink(OUTPUT)
  505.     os.rename(TMPOUTPUT, OUTPUT)
  506.    
  507.     if not defmode and dlg.yesno('Rebuild everything?') == 'y':
  508.         os.execlp('make','make','clean','build')
  509.  
  510. if __name__ == '__main__':
  511.     main()
  512.