bootloader: fix the pte_level0_descriptor_t structure
[helenos.git] / tools / config.py
blob04832256f61d52eb5310f6ec58fcda08835eeb25
1 #!/usr/bin/env python
3 # Copyright (c) 2006 Ondrej Palkovsky
4 # Copyright (c) 2009 Martin Decky
5 # Copyright (c) 2010 Jiri Svoboda
6 # All rights reserved.
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
10 # are met:
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.
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.
32 """
33 HelenOS configuration system
34 """
36 import sys
37 import os
38 import re
39 import time
40 import subprocess
41 import xtui
43 RULES_FILE = sys.argv[1]
44 MAKEFILE = 'Makefile.config'
45 MACROS = 'config.h'
46 PRESETS_DIR = 'defaults'
48 def read_config(fname, config):
49 "Read saved values from last configuration run or a preset file"
51 inf = open(fname, 'r')
53 for line in inf:
54 res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
55 if res:
56 config[res.group(1)] = res.group(2)
58 inf.close()
60 def check_condition(text, config, rules):
61 "Check that the condition specified on input line is True (only CNF and DNF is supported)"
63 ctype = 'cnf'
65 if (')|' in text) or ('|(' in text):
66 ctype = 'dnf'
68 if ctype == 'cnf':
69 conds = text.split('&')
70 else:
71 conds = text.split('|')
73 for cond in conds:
74 if cond.startswith('(') and cond.endswith(')'):
75 cond = cond[1:-1]
77 inside = check_inside(cond, config, ctype)
79 if (ctype == 'cnf') and (not inside):
80 return False
82 if (ctype == 'dnf') and inside:
83 return True
85 if ctype == 'cnf':
86 return True
88 return False
90 def check_inside(text, config, ctype):
91 "Check for condition"
93 if ctype == 'cnf':
94 conds = text.split('|')
95 else:
96 conds = text.split('&')
98 for cond in conds:
99 res = re.match(r'^(.*?)(!?=)(.*)$', cond)
100 if not res:
101 raise RuntimeError("Invalid condition: %s" % cond)
103 condname = res.group(1)
104 oper = res.group(2)
105 condval = res.group(3)
107 if not condname in config:
108 varval = ''
109 else:
110 varval = config[condname]
111 if (varval == '*'):
112 varval = 'y'
114 if ctype == 'cnf':
115 if (oper == '=') and (condval == varval):
116 return True
118 if (oper == '!=') and (condval != varval):
119 return True
120 else:
121 if (oper == '=') and (condval != varval):
122 return False
124 if (oper == '!=') and (condval == varval):
125 return False
127 if ctype == 'cnf':
128 return False
130 return True
132 def parse_rules(fname, rules):
133 "Parse rules file"
135 inf = open(fname, 'r')
137 name = ''
138 choices = []
140 for line in inf:
142 if line.startswith('!'):
143 # Ask a question
144 res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
146 if not res:
147 raise RuntimeError("Weird line: %s" % line)
149 cond = res.group(1)
150 varname = res.group(2)
151 vartype = res.group(3)
153 rules.append((varname, vartype, name, choices, cond))
154 name = ''
155 choices = []
156 continue
158 if line.startswith('@'):
159 # Add new line into the 'choices' array
160 res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
162 if not res:
163 raise RuntimeError("Bad line: %s" % line)
165 choices.append((res.group(2), res.group(3)))
166 continue
168 if line.startswith('%'):
169 # Name of the option
170 name = line[1:].strip()
171 continue
173 if line.startswith('#') or (line == '\n'):
174 # Comment or empty line
175 continue
178 raise RuntimeError("Unknown syntax: %s" % line)
180 inf.close()
182 def yes_no(default):
183 "Return '*' if yes, ' ' if no"
185 if default == 'y':
186 return '*'
188 return ' '
190 def subchoice(screen, name, choices, default):
191 "Return choice of choices"
193 maxkey = 0
194 for key, val in choices:
195 length = len(key)
196 if (length > maxkey):
197 maxkey = length
199 options = []
200 position = None
201 cnt = 0
202 for key, val in choices:
203 if (default) and (key == default):
204 position = cnt
206 options.append(" %-*s %s " % (maxkey, key, val))
207 cnt += 1
209 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
211 if button == 'cancel':
212 return None
214 return choices[value][0]
216 ## Infer and verify configuration values.
218 # Augment @a config with values that can be inferred, purge invalid ones
219 # and verify that all variables have a value (previously specified or inferred).
221 # @param config Configuration to work on
222 # @param rules Rules
224 # @return True if configuration is complete and valid, False
225 # otherwise.
227 def infer_verify_choices(config, rules):
228 "Infer and verify configuration values."
230 for rule in rules:
231 varname, vartype, name, choices, cond = rule
233 if cond and (not check_condition(cond, config, rules)):
234 continue
236 if not varname in config:
237 value = None
238 else:
239 value = config[varname]
241 if not validate_rule_value(rule, value):
242 value = None
244 default = get_default_rule(rule)
247 # If we don't have a value but we do have
248 # a default, use it.
250 if value == None and default != None:
251 value = default
252 config[varname] = default
254 if not varname in config:
255 return False
257 return True
259 ## Get default value from a rule.
260 def get_default_rule(rule):
261 varname, vartype, name, choices, cond = rule
263 default = None
265 if vartype == 'choice':
266 # If there is just one option, use it
267 if len(choices) == 1:
268 default = choices[0][0]
269 elif vartype == 'y':
270 default = '*'
271 elif vartype == 'n':
272 default = 'n'
273 elif vartype == 'y/n':
274 default = 'y'
275 elif vartype == 'n/y':
276 default = 'n'
277 else:
278 raise RuntimeError("Unknown variable type: %s" % vartype)
280 return default
282 ## Get option from a rule.
284 # @param rule Rule for a variable
285 # @param value Current value of the variable
287 # @return Option (string) to ask or None which means not to ask.
289 def get_rule_option(rule, value):
290 varname, vartype, name, choices, cond = rule
292 option = None
294 if vartype == 'choice':
295 # If there is just one option, don't ask
296 if len(choices) != 1:
297 if (value == None):
298 option = "? %s --> " % name
299 else:
300 option = " %s [%s] --> " % (name, value)
301 elif vartype == 'y':
302 pass
303 elif vartype == 'n':
304 pass
305 elif vartype == 'y/n':
306 option = " <%s> %s " % (yes_no(value), name)
307 elif vartype == 'n/y':
308 option =" <%s> %s " % (yes_no(value), name)
309 else:
310 raise RuntimeError("Unknown variable type: %s" % vartype)
312 return option
314 ## Check if variable value is valid.
316 # @param rule Rule for the variable
317 # @param value Value of the variable
319 # @return True if valid, False if not valid.
321 def validate_rule_value(rule, value):
322 varname, vartype, name, choices, cond = rule
324 if value == None:
325 return True
327 if vartype == 'choice':
328 if not value in [choice[0] for choice in choices]:
329 return False
330 elif vartype == 'y':
331 if value != 'y':
332 return False
333 elif vartype == 'n':
334 if value != 'n':
335 return False
336 elif vartype == 'y/n':
337 if not value in ['y', 'n']:
338 return False
339 elif vartype == 'n/y':
340 if not value in ['y', 'n']:
341 return False
342 else:
343 raise RuntimeError("Unknown variable type: %s" % vartype)
345 return True
347 def preprocess_config(config, rules):
348 "Preprocess configuration"
350 varname_mode = 'CONFIG_BFB_MODE'
351 varname_width = 'CONFIG_BFB_WIDTH'
352 varname_height = 'CONFIG_BFB_HEIGHT'
354 if varname_mode in config:
355 mode = config[varname_mode].partition('x')
357 config[varname_width] = mode[0]
358 rules.append((varname_width, 'choice', 'Default framebuffer width', None, None))
360 config[varname_height] = mode[2]
361 rules.append((varname_height, 'choice', 'Default framebuffer height', None, None))
363 def create_output(mkname, mcname, config, rules):
364 "Create output configuration"
366 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
368 sys.stderr.write("Fetching current revision identifier ... ")
370 try:
371 version = subprocess.Popen(['bzr', 'version-info', '--custom', '--template={clean}:{revno}:{revision_id}'], stdout = subprocess.PIPE).communicate()[0].decode().split(':')
372 sys.stderr.write("ok\n")
373 except:
374 version = [1, "unknown", "unknown"]
375 sys.stderr.write("failed\n")
377 if len(version) == 3:
378 revision = version[1]
379 if version[0] != 1:
380 revision += 'M'
381 revision += ' (%s)' % version[2]
382 else:
383 revision = None
385 outmk = open(mkname, 'w')
386 outmc = open(mcname, 'w')
388 outmk.write('#########################################\n')
389 outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
390 outmk.write('## Generated by: tools/config.py ##\n')
391 outmk.write('#########################################\n\n')
393 outmc.write('/***************************************\n')
394 outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
395 outmc.write(' * Generated by: tools/config.py *\n')
396 outmc.write(' ***************************************/\n\n')
398 defs = 'CONFIG_DEFS ='
400 for varname, vartype, name, choices, cond in rules:
401 if cond and (not check_condition(cond, config, rules)):
402 continue
404 if not varname in config:
405 value = ''
406 else:
407 value = config[varname]
408 if (value == '*'):
409 value = 'y'
411 outmk.write('# %s\n%s = %s\n\n' % (name, varname, value))
413 if vartype in ["y", "n", "y/n", "n/y"]:
414 if value == "y":
415 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
416 defs += ' -D%s' % varname
417 else:
418 outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, value, varname, value))
419 defs += ' -D%s=%s -D%s_%s' % (varname, value, varname, value)
421 if revision is not None:
422 outmk.write('REVISION = %s\n' % revision)
423 outmc.write('#define REVISION %s\n' % revision)
424 defs += ' "-DREVISION=%s"' % revision
426 outmk.write('TIMESTAMP = %s\n' % timestamp)
427 outmc.write('#define TIMESTAMP %s\n' % timestamp)
428 defs += ' "-DTIMESTAMP=%s"\n' % timestamp
430 outmk.write(defs)
432 outmk.close()
433 outmc.close()
435 def sorted_dir(root):
436 list = os.listdir(root)
437 list.sort()
438 return list
440 ## Ask user to choose a configuration profile.
442 def choose_profile(root, fname, screen, config):
443 options = []
444 opt2path = {}
445 cnt = 0
447 # Look for profiles
448 for name in sorted_dir(root):
449 path = os.path.join(root, name)
450 canon = os.path.join(path, fname)
452 if os.path.isdir(path) and os.path.exists(canon) and os.path.isfile(canon):
453 subprofile = False
455 # Look for subprofiles
456 for subname in sorted_dir(path):
457 subpath = os.path.join(path, subname)
458 subcanon = os.path.join(subpath, fname)
460 if os.path.isdir(subpath) and os.path.exists(subcanon) and os.path.isfile(subcanon):
461 subprofile = True
462 options.append("%s (%s)" % (name, subname))
463 opt2path[cnt] = [name, subname]
464 cnt += 1
466 if not subprofile:
467 options.append(name)
468 opt2path[cnt] = [name]
469 cnt += 1
471 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
473 if button == 'cancel':
474 return None
476 return opt2path[value]
478 ## Read presets from a configuration profile.
480 # @param profile Profile to load from (a list of string components)
481 # @param config Output configuration
483 def read_presets(profile, config):
484 path = os.path.join(PRESETS_DIR, profile[0], MAKEFILE)
485 read_config(path, config)
487 if len(profile) > 1:
488 path = os.path.join(PRESETS_DIR, profile[0], profile[1], MAKEFILE)
489 read_config(path, config)
491 ## Parse profile name (relative OS path) into a list of components.
493 # @param profile_name Relative path (using OS separator)
494 # @return List of components
496 def parse_profile_name(profile_name):
497 profile = []
499 head, tail = os.path.split(profile_name)
500 if head != '':
501 profile.append(head)
503 profile.append(tail)
504 return profile
506 def main():
507 profile = None
508 config = {}
509 rules = []
511 # Parse rules file
512 parse_rules(RULES_FILE, rules)
514 # Input configuration file can be specified on command line
515 # otherwise configuration from previous run is used.
516 if len(sys.argv) >= 4:
517 profile = parse_profile_name(sys.argv[3])
518 read_presets(profile, config)
519 elif os.path.exists(MAKEFILE):
520 read_config(MAKEFILE, config)
522 # Default mode: check values and regenerate configuration files
523 if (len(sys.argv) >= 3) and (sys.argv[2] == 'default'):
524 if (infer_verify_choices(config, rules)):
525 preprocess_config(config, rules)
526 create_output(MAKEFILE, MACROS, config, rules)
527 return 0
529 # Hands-off mode: check values and regenerate configuration files,
530 # but no interactive fallback
531 if (len(sys.argv) >= 3) and (sys.argv[2] == 'hands-off'):
532 # We deliberately test sys.argv >= 4 because we do not want
533 # to read implicitly any possible previous run configuration
534 if len(sys.argv) < 4:
535 sys.stderr.write("Configuration error: No presets specified\n")
536 return 2
538 if (infer_verify_choices(config, rules)):
539 preprocess_config(config, rules)
540 create_output(MAKEFILE, MACROS, config, rules)
541 return 0
543 sys.stderr.write("Configuration error: The presets are ambiguous\n")
544 return 1
546 # Check mode: only check configuration
547 if (len(sys.argv) >= 3) and (sys.argv[2] == 'check'):
548 if infer_verify_choices(config, rules):
549 return 0
550 return 1
552 screen = xtui.screen_init()
553 try:
554 selname = None
555 position = None
556 while True:
558 # Cancel out all values which have to be deduced
559 for varname, vartype, name, choices, cond in rules:
560 if (vartype == 'y') and (varname in config) and (config[varname] == '*'):
561 config[varname] = None
563 options = []
564 opt2row = {}
565 cnt = 1
567 options.append(" --- Load preconfigured defaults ... ")
569 for rule in rules:
570 varname, vartype, name, choices, cond = rule
572 if cond and (not check_condition(cond, config, rules)):
573 continue
575 if varname == selname:
576 position = cnt
578 if not varname in config:
579 value = None
580 else:
581 value = config[varname]
583 if not validate_rule_value(rule, value):
584 value = None
586 default = get_default_rule(rule)
589 # If we don't have a value but we do have
590 # a default, use it.
592 if value == None and default != None:
593 value = default
594 config[varname] = default
596 option = get_rule_option(rule, value)
597 if option != None:
598 options.append(option)
599 else:
600 continue
602 opt2row[cnt] = (varname, vartype, name, choices)
604 cnt += 1
606 if (position != None) and (position >= len(options)):
607 position = None
609 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
611 if button == 'cancel':
612 return 'Configuration canceled'
614 if button == 'done':
615 if (infer_verify_choices(config, rules)):
616 break
617 else:
618 xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
619 continue
621 if value == 0:
622 profile = choose_profile(PRESETS_DIR, MAKEFILE, screen, config)
623 if profile != None:
624 read_presets(profile, config)
625 position = 1
626 continue
628 position = None
629 if not value in opt2row:
630 raise RuntimeError("Error selecting value: %s" % value)
632 (selname, seltype, name, choices) = opt2row[value]
634 if not selname in config:
635 value = None
636 else:
637 value = config[selname]
639 if seltype == 'choice':
640 config[selname] = subchoice(screen, name, choices, value)
641 elif (seltype == 'y/n') or (seltype == 'n/y'):
642 if config[selname] == 'y':
643 config[selname] = 'n'
644 else:
645 config[selname] = 'y'
646 finally:
647 xtui.screen_done(screen)
649 preprocess_config(config, rules)
650 create_output(MAKEFILE, MACROS, config, rules)
651 return 0
653 if __name__ == '__main__':
654 sys.exit(main())