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