source: mainline/tools/config.py@ 37ac7bb

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 37ac7bb was c01f8e6, checked in by Jiri Svoboda <jiri@…>, 15 years ago

Remove leftover code.

  • Property mode set to 100755
File size: 15.5 KB
RevLine 
[98376de]1#!/usr/bin/env python
[44882c8]2#
[5a55ae6]3# Copyright (c) 2006 Ondrej Palkovsky
[9a0367f]4# Copyright (c) 2009 Martin Decky
[62bb73e]5# Copyright (c) 2010 Jiri Svoboda
[44882c8]6# All rights reserved.
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions
10# are met:
11#
12# - Redistributions of source code must retain the above copyright
13# notice, this list of conditions and the following disclaimer.
14# - Redistributions in binary form must reproduce the above copyright
15# notice, this list of conditions and the following disclaimer in the
16# documentation and/or other materials provided with the distribution.
17# - The name of the author may not be used to endorse or promote products
18# derived from this software without specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30#
[3c80f2b]31
[98376de]32"""
[9a0367f]33HelenOS configuration system
[98376de]34"""
[3c80f2b]35
[98376de]36import sys
37import os
38import re
[5a8fbcb9]39import time
40import subprocess
[27fb3d6]41import xtui
[98376de]42
[62bb73e]43RULES_FILE = sys.argv[1]
[84266669]44MAKEFILE = 'Makefile.config'
45MACROS = 'config.h'
[62bb73e]46PRESETS_DIR = 'defaults'
[98376de]47
[62bb73e]48def read_config(fname, config):
[d40ffbb]49 "Read saved values from last configuration run or a preset file"
[9a0367f]50
[28f4adb]51 inf = open(fname, 'r')
[9a0367f]52
53 for line in inf:
54 res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
[ba8de9c3]55 if res:
[62bb73e]56 config[res.group(1)] = res.group(2)
[9a0367f]57
58 inf.close()
[98376de]59
[62bb73e]60def check_condition(text, config, rules):
[81c8d54]61 "Check that the condition specified on input line is True (only CNF and DNF is supported)"
[9a0367f]62
63 ctype = 'cnf'
64
[ba8de9c3]65 if (')|' in text) or ('|(' in text):
[9a0367f]66 ctype = 'dnf'
67
[ba8de9c3]68 if ctype == 'cnf':
[9a0367f]69 conds = text.split('&')
70 else:
71 conds = text.split('|')
72
73 for cond in conds:
[ba8de9c3]74 if cond.startswith('(') and cond.endswith(')'):
[9a0367f]75 cond = cond[1:-1]
76
[62bb73e]77 inside = check_inside(cond, config, ctype)
[9a0367f]78
79 if (ctype == 'cnf') and (not inside):
80 return False
81
[ba8de9c3]82 if (ctype == 'dnf') and inside:
[9a0367f]83 return True
84
[ba8de9c3]85 if ctype == 'cnf':
[9a0367f]86 return True
87 return False
[98376de]88
[62bb73e]89def check_inside(text, config, ctype):
[81c8d54]90 "Check for condition"
[9a0367f]91
[ba8de9c3]92 if ctype == 'cnf':
[9a0367f]93 conds = text.split('|')
94 else:
95 conds = text.split('&')
96
97 for cond in conds:
98 res = re.match(r'^(.*?)(!?=)(.*)$', cond)
[ba8de9c3]99 if not res:
[9a0367f]100 raise RuntimeError("Invalid condition: %s" % cond)
101
102 condname = res.group(1)
103 oper = res.group(2)
104 condval = res.group(3)
105
[ba8de9c3]106 if not condname in config:
[9a0367f]107 varval = ''
108 else:
[62bb73e]109 varval = config[condname]
[7aef7ee]110 if (varval == '*'):
111 varval = 'y'
[9a0367f]112
[ba8de9c3]113 if ctype == 'cnf':
[9a0367f]114 if (oper == '=') and (condval == varval):
115 return True
116
117 if (oper == '!=') and (condval != varval):
118 return True
119 else:
120 if (oper == '=') and (condval != varval):
121 return False
122
123 if (oper == '!=') and (condval == varval):
124 return False
125
[ba8de9c3]126 if ctype == 'cnf':
[9a0367f]127 return False
128
129 return True
[98376de]130
[62bb73e]131def parse_rules(fname, rules):
132 "Parse rules file"
[9a0367f]133
[28f4adb]134 inf = open(fname, 'r')
[9a0367f]135
136 name = ''
137 choices = []
138
139 for line in inf:
140
[ba8de9c3]141 if line.startswith('!'):
[9a0367f]142 # Ask a question
143 res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
144
[ba8de9c3]145 if not res:
[9a0367f]146 raise RuntimeError("Weird line: %s" % line)
147
148 cond = res.group(1)
149 varname = res.group(2)
150 vartype = res.group(3)
151
[62bb73e]152 rules.append((varname, vartype, name, choices, cond))
[9a0367f]153 name = ''
154 choices = []
155 continue
156
[ba8de9c3]157 if line.startswith('@'):
[9a0367f]158 # Add new line into the 'choices' array
159 res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
160
161 if not res:
162 raise RuntimeError("Bad line: %s" % line)
163
164 choices.append((res.group(2), res.group(3)))
165 continue
166
[ba8de9c3]167 if line.startswith('%'):
[9a0367f]168 # Name of the option
169 name = line[1:].strip()
170 continue
171
[ba8de9c3]172 if line.startswith('#') or (line == '\n'):
[9a0367f]173 # Comment or empty line
174 continue
175
176
177 raise RuntimeError("Unknown syntax: %s" % line)
178
179 inf.close()
[98376de]180
[9a0367f]181def yes_no(default):
182 "Return '*' if yes, ' ' if no"
183
[ba8de9c3]184 if default == 'y':
[9a0367f]185 return '*'
186
187 return ' '
[98376de]188
[27fb3d6]189def subchoice(screen, name, choices, default):
[9a0367f]190 "Return choice of choices"
191
[27fb3d6]192 maxkey = 0
193 for key, val in choices:
194 length = len(key)
195 if (length > maxkey):
196 maxkey = length
[9a0367f]197
198 options = []
[27fb3d6]199 position = None
200 cnt = 0
201 for key, val in choices:
[ba8de9c3]202 if (default) and (key == default):
[27fb3d6]203 position = cnt
204
205 options.append(" %-*s %s " % (maxkey, key, val))
206 cnt += 1
[9a0367f]207
[27fb3d6]208 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
[9a0367f]209
[ba8de9c3]210 if button == 'cancel':
[9a0367f]211 return None
212
[27fb3d6]213 return choices[value][0]
[98376de]214
[e4d540b]215## Infer and verify configuration values.
216#
[62bb73e]217# Augment @a config with values that can be inferred, purge invalid ones
[e4d540b]218# and verify that all variables have a value (previously specified or inferred).
219#
[d40ffbb]220# @param config Configuration to work on
221# @param rules Rules
[e4d540b]222#
[d40ffbb]223# @return True if configuration is complete and valid, False
224# otherwise.
[e4d540b]225#
[62bb73e]226def infer_verify_choices(config, rules):
[e4d540b]227 "Infer and verify configuration values."
[9a0367f]228
[ba8de9c3]229 for rule in rules:
230 varname, vartype, name, choices, cond = rule
[d40ffbb]231
[ba8de9c3]232 if cond and (not check_condition(cond, config, rules)):
[9a0367f]233 continue
234
[ba8de9c3]235 if not varname in config:
[4756634]236 value = None
[e4d540b]237 else:
[4756634]238 value = config[varname]
[d40ffbb]239
240 if not validate_rule_value(rule, value):
[4756634]241 value = None
[d40ffbb]242
243 default = get_default_rule(rule)
[c0bd08d]244
245 #
246 # If we don't have a value but we do have
247 # a default, use it.
248 #
249 if value == None and default != None:
250 value = default
[4756634]251 config[varname] = default
[d40ffbb]252
[ba8de9c3]253 if not varname in config:
[9a0367f]254 return False
255
256 return True
[98376de]257
[e4d540b]258## Get default value from a rule.
[d40ffbb]259def get_default_rule(rule):
[e4d540b]260 varname, vartype, name, choices, cond = rule
[d40ffbb]261
[e4d540b]262 default = None
[d40ffbb]263
[ba8de9c3]264 if vartype == 'choice':
[e4d540b]265 # If there is just one option, use it
[ba8de9c3]266 if len(choices) == 1:
[e4d540b]267 default = choices[0][0]
[ba8de9c3]268 elif vartype == 'y':
[e4d540b]269 default = '*'
[ba8de9c3]270 elif vartype == 'n':
[e4d540b]271 default = 'n'
[ba8de9c3]272 elif vartype == 'y/n':
[e4d540b]273 default = 'y'
[ba8de9c3]274 elif vartype == 'n/y':
[e4d540b]275 default = 'n'
276 else:
277 raise RuntimeError("Unknown variable type: %s" % vartype)
[d40ffbb]278
[e4d540b]279 return default
280
281## Get option from a rule.
282#
[d40ffbb]283# @param rule Rule for a variable
284# @param value Current value of the variable
[e4d540b]285#
286# @return Option (string) to ask or None which means not to ask.
287#
[d40ffbb]288def get_rule_option(rule, value):
[e4d540b]289 varname, vartype, name, choices, cond = rule
[d40ffbb]290
[e4d540b]291 option = None
[d40ffbb]292
[ba8de9c3]293 if vartype == 'choice':
[e4d540b]294 # If there is just one option, don't ask
[ba8de9c3]295 if len(choices) != 1:
[e4d540b]296 if (value == None):
297 option = "? %s --> " % name
298 else:
299 option = " %s [%s] --> " % (name, value)
[ba8de9c3]300 elif vartype == 'y':
[e4d540b]301 pass
[ba8de9c3]302 elif vartype == 'n':
[e4d540b]303 pass
[ba8de9c3]304 elif vartype == 'y/n':
[e4d540b]305 option = " <%s> %s " % (yes_no(value), name)
[ba8de9c3]306 elif vartype == 'n/y':
[e4d540b]307 option =" <%s> %s " % (yes_no(value), name)
308 else:
309 raise RuntimeError("Unknown variable type: %s" % vartype)
[d40ffbb]310
[e4d540b]311 return option
312
313## Check if variable value is valid.
314#
[d40ffbb]315# @param rule Rule for the variable
316# @param value Value of the variable
[e4d540b]317#
[d40ffbb]318# @return True if valid, False if not valid.
[e4d540b]319#
[d40ffbb]320def validate_rule_value(rule, value):
[e4d540b]321 varname, vartype, name, choices, cond = rule
322
323 if value == None:
324 return True
[d40ffbb]325
[ba8de9c3]326 if vartype == 'choice':
327 if not value in [choice[0] for choice in choices]:
[e4d540b]328 return False
[ba8de9c3]329 elif vartype == 'y':
[e4d540b]330 if value != 'y':
331 return False
[ba8de9c3]332 elif vartype == 'n':
[e4d540b]333 if value != 'n':
334 return False
[ba8de9c3]335 elif vartype == 'y/n':
[e4d540b]336 if not value in ['y', 'n']:
337 return False
[ba8de9c3]338 elif vartype == 'n/y':
[e4d540b]339 if not value in ['y', 'n']:
340 return False
341 else:
342 raise RuntimeError("Unknown variable type: %s" % vartype)
[d40ffbb]343
[e4d540b]344 return True
345
[62bb73e]346def create_output(mkname, mcname, config, rules):
[9a0367f]347 "Create output configuration"
348
[5a8fbcb9]349 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
[fe12f9f4]350
351 sys.stderr.write("Fetching current revision identifier ... ")
[7b76744]352
353 try:
[28f4adb]354 version = subprocess.Popen(['bzr', 'version-info', '--custom', '--template={clean}:{revno}:{revision_id}'], stdout = subprocess.PIPE).communicate()[0].decode().split(':')
[7b76744]355 sys.stderr.write("ok\n")
356 except:
357 version = [1, "unknown", "unknown"]
358 sys.stderr.write("failed\n")
[5a8fbcb9]359
[ba8de9c3]360 if len(version) == 3:
[5a8fbcb9]361 revision = version[1]
[ba8de9c3]362 if version[0] != 1:
[5a8fbcb9]363 revision += 'M'
364 revision += ' (%s)' % version[2]
365 else:
366 revision = None
[84266669]367
[28f4adb]368 outmk = open(mkname, 'w')
369 outmc = open(mcname, 'w')
[84266669]370
371 outmk.write('#########################################\n')
372 outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
373 outmk.write('#########################################\n\n')
[9a0367f]374
[84266669]375 outmc.write('/***************************************\n')
376 outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
377 outmc.write(' ***************************************/\n\n')
378
[4e9aaf5]379 defs = 'CONFIG_DEFS ='
[9a0367f]380
[62bb73e]381 for varname, vartype, name, choices, cond in rules:
[ba8de9c3]382 if cond and (not check_condition(cond, config, rules)):
[9a0367f]383 continue
384
[ba8de9c3]385 if not varname in config:
[4756634]386 value = ''
[9a0367f]387 else:
[4756634]388 value = config[varname]
389 if (value == '*'):
390 value = 'y'
[9a0367f]391
[4756634]392 outmk.write('# %s\n%s = %s\n\n' % (name, varname, value))
[84266669]393
[ba8de9c3]394 if vartype in ["y", "n", "y/n", "n/y"]:
395 if value == "y":
[84266669]396 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
[4e9aaf5]397 defs += ' -D%s' % varname
[84266669]398 else:
[4756634]399 outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, value, varname, value))
400 defs += ' -D%s=%s -D%s_%s' % (varname, value, varname, value)
[9a0367f]401
[ba8de9c3]402 if revision is not None:
[5a8fbcb9]403 outmk.write('REVISION = %s\n' % revision)
404 outmc.write('#define REVISION %s\n' % revision)
[4e9aaf5]405 defs += ' "-DREVISION=%s"' % revision
[84266669]406
[5a8fbcb9]407 outmk.write('TIMESTAMP = %s\n' % timestamp)
[84266669]408 outmc.write('#define TIMESTAMP %s\n' % timestamp)
[4e9aaf5]409 defs += ' "-DTIMESTAMP=%s"\n' % timestamp
410
411 outmk.write(defs)
[84266669]412
413 outmk.close()
414 outmc.close()
[98376de]415
[31fb9a0]416def sorted_dir(root):
417 list = os.listdir(root)
418 list.sort()
419 return list
420
[6ec0acd]421## Ask user to choose a configuration profile.
[62bb73e]422#
[d40ffbb]423def choose_profile(root, fname, screen, config):
[31fb9a0]424 options = []
425 opt2path = {}
426 cnt = 0
427
428 # Look for profiles
429 for name in sorted_dir(root):
430 path = os.path.join(root, name)
431 canon = os.path.join(path, fname)
432
[ba8de9c3]433 if os.path.isdir(path) and os.path.exists(canon) and os.path.isfile(canon):
[31fb9a0]434 subprofile = False
435
436 # Look for subprofiles
437 for subname in sorted_dir(path):
438 subpath = os.path.join(path, subname)
439 subcanon = os.path.join(subpath, fname)
440
[ba8de9c3]441 if os.path.isdir(subpath) and os.path.exists(subcanon) and os.path.isfile(subcanon):
[31fb9a0]442 subprofile = True
443 options.append("%s (%s)" % (name, subname))
[6ec0acd]444 opt2path[cnt] = [name, subname]
[31fb9a0]445 cnt += 1
446
[ba8de9c3]447 if not subprofile:
[31fb9a0]448 options.append(name)
[6ec0acd]449 opt2path[cnt] = [name]
[31fb9a0]450 cnt += 1
451
452 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
453
[ba8de9c3]454 if button == 'cancel':
[31fb9a0]455 return None
456
[6ec0acd]457 return opt2path[value]
458
459## Read presets from a configuration profile.
460#
[d40ffbb]461# @param profile Profile to load from (a list of string components)
462# @param config Output configuration
[6ec0acd]463#
[d40ffbb]464def read_presets(profile, config):
[6ec0acd]465 path = os.path.join(PRESETS_DIR, profile[0], MAKEFILE)
466 read_config(path, config)
[d40ffbb]467
[6ec0acd]468 if len(profile) > 1:
469 path = os.path.join(PRESETS_DIR, profile[0], profile[1], MAKEFILE)
470 read_config(path, config)
471
472## Parse profile name (relative OS path) into a list of components.
473#
[d40ffbb]474# @param profile_name Relative path (using OS separator)
475# @return List of components
[6ec0acd]476#
477def parse_profile_name(profile_name):
478 profile = []
[d40ffbb]479
[6ec0acd]480 head, tail = os.path.split(profile_name)
481 if head != '':
482 profile.append(head)
[d40ffbb]483
[6ec0acd]484 profile.append(tail)
485 return profile
[31fb9a0]486
[98376de]487def main():
[6ec0acd]488 profile = None
[62bb73e]489 config = {}
490 rules = []
[9a0367f]491
[62bb73e]492 # Parse rules file
493 parse_rules(RULES_FILE, rules)
[9a0367f]494
[421250e]495 # Input configuration file can be specified on command line
496 # otherwise configuration from previous run is used.
497 if len(sys.argv) >= 4:
[6ec0acd]498 profile = parse_profile_name(sys.argv[3])
[d40ffbb]499 read_presets(profile, config)
[6ec0acd]500 elif os.path.exists(MAKEFILE):
501 read_config(MAKEFILE, config)
[9a0367f]502
[e3c3172]503 # Default mode: check values and regenerate configuration files
[ba8de9c3]504 if (len(sys.argv) >= 3) and (sys.argv[2] == 'default'):
[62bb73e]505 if (infer_verify_choices(config, rules)):
506 create_output(MAKEFILE, MACROS, config, rules)
[9a0367f]507 return 0
508
[e3c3172]509 # Hands-off mode: check values and regenerate configuration files,
510 # but no interactive fallback
511 if (len(sys.argv) >= 3) and (sys.argv[2] == 'hands-off'):
512 # We deliberately test sys.argv >= 4 because we do not want
513 # to read implicitly any possible previous run configuration
514 if len(sys.argv) < 4:
515 sys.stderr.write("Configuration error: No presets specified\n")
516 return 2
517
518 if (infer_verify_choices(config, rules)):
519 create_output(MAKEFILE, MACROS, config, rules)
520 return 0
521
522 sys.stderr.write("Configuration error: The presets are ambiguous\n")
523 return 1
524
[62bb73e]525 # Check mode: only check configuration
[ba8de9c3]526 if (len(sys.argv) >= 3) and (sys.argv[2] == 'check'):
527 if infer_verify_choices(config, rules):
[48c3d50]528 return 0
529 return 1
530
[27fb3d6]531 screen = xtui.screen_init()
[9a0367f]532 try:
533 selname = None
[31fb9a0]534 position = None
[9a0367f]535 while True:
536
[62bb73e]537 # Cancel out all values which have to be deduced
538 for varname, vartype, name, choices, cond in rules:
[ba8de9c3]539 if (vartype == 'y') and (varname in config) and (config[varname] == '*'):
[62bb73e]540 config[varname] = None
[81c8d54]541
[9a0367f]542 options = []
543 opt2row = {}
[31fb9a0]544 cnt = 1
545
546 options.append(" --- Load preconfigured defaults ... ")
547
[62bb73e]548 for rule in rules:
[e4d540b]549 varname, vartype, name, choices, cond = rule
[9a0367f]550
[ba8de9c3]551 if cond and (not check_condition(cond, config, rules)):
[9a0367f]552 continue
553
[ba8de9c3]554 if varname == selname:
[9a0367f]555 position = cnt
556
[ba8de9c3]557 if not varname in config:
[4756634]558 value = None
[9a0367f]559 else:
[4756634]560 value = config[varname]
[9a0367f]561
[d40ffbb]562 if not validate_rule_value(rule, value):
[4756634]563 value = None
[d40ffbb]564
565 default = get_default_rule(rule)
[c0bd08d]566
567 #
568 # If we don't have a value but we do have
569 # a default, use it.
570 #
571 if value == None and default != None:
572 value = default
573 config[varname] = default
[d40ffbb]574
575 option = get_rule_option(rule, value)
[e4d540b]576 if option != None:
577 options.append(option)
[8fe3f832]578 else:
579 continue
[9a0367f]580
[27fb3d6]581 opt2row[cnt] = (varname, vartype, name, choices)
[9a0367f]582
583 cnt += 1
584
[28f4adb]585 if (position != None) and (position >= len(options)):
[31fb9a0]586 position = None
587
[27fb3d6]588 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
[9a0367f]589
[ba8de9c3]590 if button == 'cancel':
[9a0367f]591 return 'Configuration canceled'
592
[ba8de9c3]593 if button == 'done':
[62bb73e]594 if (infer_verify_choices(config, rules)):
[6346efd]595 break
596 else:
597 xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
598 continue
599
[ba8de9c3]600 if value == 0:
[d40ffbb]601 profile = choose_profile(PRESETS_DIR, MAKEFILE, screen, config)
[6ec0acd]602 if profile != None:
[d40ffbb]603 read_presets(profile, config)
[31fb9a0]604 position = 1
605 continue
606
607 position = None
[ba8de9c3]608 if not value in opt2row:
[27fb3d6]609 raise RuntimeError("Error selecting value: %s" % value)
610
611 (selname, seltype, name, choices) = opt2row[value]
[9a0367f]612
[ba8de9c3]613 if not selname in config:
[4756634]614 value = None
[27fb3d6]615 else:
[4756634]616 value = config[selname]
[9a0367f]617
[ba8de9c3]618 if seltype == 'choice':
[4756634]619 config[selname] = subchoice(screen, name, choices, value)
[ba8de9c3]620 elif (seltype == 'y/n') or (seltype == 'n/y'):
621 if config[selname] == 'y':
[62bb73e]622 config[selname] = 'n'
[9a0367f]623 else:
[62bb73e]624 config[selname] = 'y'
[9a0367f]625 finally:
[27fb3d6]626 xtui.screen_done(screen)
[9a0367f]627
[62bb73e]628 create_output(MAKEFILE, MACROS, config, rules)
[9a0367f]629 return 0
[98376de]630
631if __name__ == '__main__':
[43a10c4]632 sys.exit(main())
Note: See TracBrowser for help on using the repository browser.