virtio-net: Setup DMA buffers
[helenos.git] / tools / config.py
blob4ac1c92ceebc42ce51bd637102d1a32205e1efd9
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
42 import random
44 RULES_FILE = sys.argv[1]
45 MAKEFILE = 'Makefile.config'
46 MACROS = 'config.h'
47 PRESETS_DIR = 'defaults'
49 def read_config(fname, config):
50 "Read saved values from last configuration run or a preset file"
52 inf = open(fname, 'r')
54 for line in inf:
55 res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
56 if res:
57 config[res.group(1)] = res.group(2)
59 inf.close()
61 def check_condition(text, config, rules):
62 "Check that the condition specified on input line is True (only CNF and DNF is supported)"
64 ctype = 'cnf'
66 if (')|' in text) or ('|(' in text):
67 ctype = 'dnf'
69 if ctype == 'cnf':
70 conds = text.split('&')
71 else:
72 conds = text.split('|')
74 for cond in conds:
75 if cond.startswith('(') and cond.endswith(')'):
76 cond = cond[1:-1]
78 inside = check_inside(cond, config, ctype)
80 if (ctype == 'cnf') and (not inside):
81 return False
83 if (ctype == 'dnf') and inside:
84 return True
86 if ctype == 'cnf':
87 return True
89 return False
91 def check_inside(text, config, ctype):
92 "Check for condition"
94 if ctype == 'cnf':
95 conds = text.split('|')
96 else:
97 conds = text.split('&')
99 for cond in conds:
100 res = re.match(r'^(.*?)(!?=)(.*)$', cond)
101 if not res:
102 raise RuntimeError("Invalid condition: %s" % cond)
104 condname = res.group(1)
105 oper = res.group(2)
106 condval = res.group(3)
108 if not condname in config:
109 varval = ''
110 else:
111 varval = config[condname]
112 if (varval == '*'):
113 varval = 'y'
115 if ctype == 'cnf':
116 if (oper == '=') and (condval == varval):
117 return True
119 if (oper == '!=') and (condval != varval):
120 return True
121 else:
122 if (oper == '=') and (condval != varval):
123 return False
125 if (oper == '!=') and (condval == varval):
126 return False
128 if ctype == 'cnf':
129 return False
131 return True
133 def parse_rules(fname, rules):
134 "Parse rules file"
136 inf = open(fname, 'r')
138 name = ''
139 choices = []
141 for line in inf:
143 if line.startswith('!'):
144 # Ask a question
145 res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
147 if not res:
148 raise RuntimeError("Weird line: %s" % line)
150 cond = res.group(1)
151 varname = res.group(2)
152 vartype = res.group(3)
154 rules.append((varname, vartype, name, choices, cond))
155 name = ''
156 choices = []
157 continue
159 if line.startswith('@'):
160 # Add new line into the 'choices' array
161 res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
163 if not res:
164 raise RuntimeError("Bad line: %s" % line)
166 choices.append((res.group(2), res.group(3)))
167 continue
169 if line.startswith('%'):
170 # Name of the option
171 name = line[1:].strip()
172 continue
174 if line.startswith('#') or (line == '\n'):
175 # Comment or empty line
176 continue
179 raise RuntimeError("Unknown syntax: %s" % line)
181 inf.close()
183 def yes_no(default):
184 "Return '*' if yes, ' ' if no"
186 if default == 'y':
187 return '*'
189 return ' '
191 def subchoice(screen, name, choices, default):
192 "Return choice of choices"
194 maxkey = 0
195 for key, val in choices:
196 length = len(key)
197 if (length > maxkey):
198 maxkey = length
200 options = []
201 position = None
202 cnt = 0
203 for key, val in choices:
204 if (default) and (key == default):
205 position = cnt
207 options.append(" %-*s %s " % (maxkey, key, val))
208 cnt += 1
210 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
212 if button == 'cancel':
213 return None
215 return choices[value][0]
217 ## Infer and verify configuration values.
219 # Augment @a config with values that can be inferred, purge invalid ones
220 # and verify that all variables have a value (previously specified or inferred).
222 # @param config Configuration to work on
223 # @param rules Rules
225 # @return True if configuration is complete and valid, False
226 # otherwise.
228 def infer_verify_choices(config, rules):
229 "Infer and verify configuration values."
231 for rule in rules:
232 varname, vartype, name, choices, cond = rule
234 if cond and (not check_condition(cond, config, rules)):
235 continue
237 if not varname in config:
238 value = None
239 else:
240 value = config[varname]
242 if not validate_rule_value(rule, value):
243 value = None
245 default = get_default_rule(rule)
248 # If we don't have a value but we do have
249 # a default, use it.
251 if value == None and default != None:
252 value = default
253 config[varname] = default
255 if not varname in config:
256 return False
258 return True
260 ## Fill the configuration with random (but valid) values.
262 # The random selection takes next rule and if the condition does
263 # not violate existing configuration, random value of the variable
264 # is selected.
265 # This happens recursively as long as there are more rules.
266 # If a conflict is found, we backtrack and try other settings of the
267 # variable or ignoring the variable altogether.
269 # @param config Configuration to work on
270 # @param rules Rules
271 # @param start_index With which rule to start (initial call must specify 0 here).
272 # @return True if able to find a valid configuration
273 def random_choices(config, rules, start_index):
274 "Fill the configuration with random (but valid) values."
275 if start_index >= len(rules):
276 return True
278 varname, vartype, name, choices, cond = rules[start_index]
280 # First check that this rule would make sense
281 if cond:
282 if not check_condition(cond, config, rules):
283 return random_choices(config, rules, start_index + 1)
285 # Remember previous choices for backtracking
286 yes_no = 0
287 choices_indexes = range(0, len(choices))
288 random.shuffle(choices_indexes)
290 # Remember current configuration value
291 old_value = None
292 try:
293 old_value = config[varname]
294 except KeyError:
295 old_value = None
297 # For yes/no choices, we ran the loop at most 2 times, for select
298 # choices as many times as there are options.
299 try_counter = 0
300 while True:
301 if vartype == 'choice':
302 if try_counter >= len(choices_indexes):
303 break
304 value = choices[choices_indexes[try_counter]][0]
305 elif vartype == 'y' or vartype == 'n':
306 if try_counter > 0:
307 break
308 value = vartype
309 elif vartype == 'y/n' or vartype == 'n/y':
310 if try_counter == 0:
311 yes_no = random.randint(0, 1)
312 elif try_counter == 1:
313 yes_no = 1 - yes_no
314 else:
315 break
316 if yes_no == 0:
317 value = 'n'
318 else:
319 value = 'y'
320 else:
321 raise RuntimeError("Unknown variable type: %s" % vartype)
323 config[varname] = value
325 ok = random_choices(config, rules, start_index + 1)
326 if ok:
327 return True
329 try_counter = try_counter + 1
331 # Restore the old value and backtrack
332 # (need to delete to prevent "ghost" variables that do not exist under
333 # certain configurations)
334 config[varname] = old_value
335 if old_value is None:
336 del config[varname]
338 return random_choices(config, rules, start_index + 1)
341 ## Get default value from a rule.
342 def get_default_rule(rule):
343 varname, vartype, name, choices, cond = rule
345 default = None
347 if vartype == 'choice':
348 # If there is just one option, use it
349 if len(choices) == 1:
350 default = choices[0][0]
351 elif vartype == 'y':
352 default = '*'
353 elif vartype == 'n':
354 default = 'n'
355 elif vartype == 'y/n':
356 default = 'y'
357 elif vartype == 'n/y':
358 default = 'n'
359 else:
360 raise RuntimeError("Unknown variable type: %s" % vartype)
362 return default
364 ## Get option from a rule.
366 # @param rule Rule for a variable
367 # @param value Current value of the variable
369 # @return Option (string) to ask or None which means not to ask.
371 def get_rule_option(rule, value):
372 varname, vartype, name, choices, cond = rule
374 option = None
376 if vartype == 'choice':
377 # If there is just one option, don't ask
378 if len(choices) != 1:
379 if (value == None):
380 option = "? %s --> " % name
381 else:
382 option = " %s [%s] --> " % (name, value)
383 elif vartype == 'y':
384 pass
385 elif vartype == 'n':
386 pass
387 elif vartype == 'y/n':
388 option = " <%s> %s " % (yes_no(value), name)
389 elif vartype == 'n/y':
390 option =" <%s> %s " % (yes_no(value), name)
391 else:
392 raise RuntimeError("Unknown variable type: %s" % vartype)
394 return option
396 ## Check if variable value is valid.
398 # @param rule Rule for the variable
399 # @param value Value of the variable
401 # @return True if valid, False if not valid.
403 def validate_rule_value(rule, value):
404 varname, vartype, name, choices, cond = rule
406 if value == None:
407 return True
409 if vartype == 'choice':
410 if not value in [choice[0] for choice in choices]:
411 return False
412 elif vartype == 'y':
413 if value != 'y':
414 return False
415 elif vartype == 'n':
416 if value != 'n':
417 return False
418 elif vartype == 'y/n':
419 if not value in ['y', 'n']:
420 return False
421 elif vartype == 'n/y':
422 if not value in ['y', 'n']:
423 return False
424 else:
425 raise RuntimeError("Unknown variable type: %s" % vartype)
427 return True
429 def preprocess_config(config, rules):
430 "Preprocess configuration"
432 varname_mode = 'CONFIG_BFB_MODE'
433 varname_width = 'CONFIG_BFB_WIDTH'
434 varname_height = 'CONFIG_BFB_HEIGHT'
436 if varname_mode in config:
437 mode = config[varname_mode].partition('x')
439 config[varname_width] = mode[0]
440 rules.append((varname_width, 'choice', 'Default framebuffer width', None, None))
442 config[varname_height] = mode[2]
443 rules.append((varname_height, 'choice', 'Default framebuffer height', None, None))
445 def create_output(mkname, mcname, config, rules):
446 "Create output configuration"
448 varname_strip = 'CONFIG_STRIP_REVISION_INFO'
449 strip_rev_info = (varname_strip in config) and (config[varname_strip] == 'y')
451 if strip_rev_info:
452 timestamp_unix = int(0)
453 else:
454 # TODO: Use commit timestamp instead of build time.
455 timestamp_unix = int(time.time())
457 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp_unix))
459 sys.stderr.write("Fetching current revision identifier ... ")
461 try:
462 version = subprocess.Popen(['git', 'log', '-1', '--pretty=%h'], stdout = subprocess.PIPE).communicate()[0].decode().strip()
463 sys.stderr.write("ok\n")
464 except:
465 version = None
466 sys.stderr.write("failed\n")
468 if (not strip_rev_info) and (version is not None):
469 revision = version
470 else:
471 revision = None
473 outmk = open(mkname, 'w')
474 outmc = open(mcname, 'w')
476 outmk.write('#########################################\n')
477 outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
478 outmk.write('## Generated by: tools/config.py ##\n')
479 outmk.write('#########################################\n\n')
481 outmc.write('/***************************************\n')
482 outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
483 outmc.write(' * Generated by: tools/config.py *\n')
484 outmc.write(' ***************************************/\n\n')
486 defs = 'CONFIG_DEFS ='
488 for varname, vartype, name, choices, cond in rules:
489 if cond and (not check_condition(cond, config, rules)):
490 continue
492 if not varname in config:
493 value = ''
494 else:
495 value = config[varname]
496 if (value == '*'):
497 value = 'y'
499 outmk.write('# %s\n%s = %s\n\n' % (name, varname, value))
501 if vartype in ["y", "n", "y/n", "n/y"]:
502 if value == "y":
503 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
504 defs += ' -D%s' % varname
505 else:
506 outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, value, varname, value))
507 defs += ' -D%s=%s -D%s_%s' % (varname, value, varname, value)
509 if revision is not None:
510 outmk.write('REVISION = %s\n' % revision)
511 outmc.write('#define REVISION %s\n' % revision)
512 defs += ' "-DREVISION=%s"' % revision
514 outmk.write('TIMESTAMP_UNIX = %d\n' % timestamp_unix)
515 outmc.write('#define TIMESTAMP_UNIX %d\n' % timestamp_unix)
516 defs += ' "-DTIMESTAMP_UNIX=%d"\n' % timestamp_unix
518 outmk.write('TIMESTAMP = %s\n' % timestamp)
519 outmc.write('#define TIMESTAMP %s\n' % timestamp)
520 defs += ' "-DTIMESTAMP=%s"\n' % timestamp
522 outmk.write(defs)
524 outmk.close()
525 outmc.close()
527 def sorted_dir(root):
528 list = os.listdir(root)
529 list.sort()
530 return list
532 ## Ask user to choose a configuration profile.
534 def choose_profile(root, fname, screen, config):
535 options = []
536 opt2path = {}
537 cnt = 0
539 # Look for profiles
540 for name in sorted_dir(root):
541 path = os.path.join(root, name)
542 canon = os.path.join(path, fname)
544 if os.path.isdir(path) and os.path.exists(canon) and os.path.isfile(canon):
545 subprofile = False
547 # Look for subprofiles
548 for subname in sorted_dir(path):
549 subpath = os.path.join(path, subname)
550 subcanon = os.path.join(subpath, fname)
552 if os.path.isdir(subpath) and os.path.exists(subcanon) and os.path.isfile(subcanon):
553 subprofile = True
554 options.append("%s (%s)" % (name, subname))
555 opt2path[cnt] = [name, subname]
556 cnt += 1
558 if not subprofile:
559 options.append(name)
560 opt2path[cnt] = [name]
561 cnt += 1
563 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
565 if button == 'cancel':
566 return None
568 return opt2path[value]
570 ## Read presets from a configuration profile.
572 # @param profile Profile to load from (a list of string components)
573 # @param config Output configuration
575 def read_presets(profile, config):
576 path = os.path.join(PRESETS_DIR, profile[0], MAKEFILE)
577 read_config(path, config)
579 if len(profile) > 1:
580 path = os.path.join(PRESETS_DIR, profile[0], profile[1], MAKEFILE)
581 read_config(path, config)
583 ## Parse profile name (relative OS path) into a list of components.
585 # @param profile_name Relative path (using OS separator)
586 # @return List of components
588 def parse_profile_name(profile_name):
589 profile = []
591 head, tail = os.path.split(profile_name)
592 if head != '':
593 profile.append(head)
595 profile.append(tail)
596 return profile
598 def main():
599 profile = None
600 config = {}
601 rules = []
603 # Parse rules file
604 parse_rules(RULES_FILE, rules)
606 # Input configuration file can be specified on command line
607 # otherwise configuration from previous run is used.
608 if len(sys.argv) >= 4:
609 profile = parse_profile_name(sys.argv[3])
610 read_presets(profile, config)
611 elif os.path.exists(MAKEFILE):
612 read_config(MAKEFILE, config)
614 # Default mode: check values and regenerate configuration files
615 if (len(sys.argv) >= 3) and (sys.argv[2] == 'default'):
616 if (infer_verify_choices(config, rules)):
617 preprocess_config(config, rules)
618 create_output(MAKEFILE, MACROS, config, rules)
619 return 0
621 # Hands-off mode: check values and regenerate configuration files,
622 # but no interactive fallback
623 if (len(sys.argv) >= 3) and (sys.argv[2] == 'hands-off'):
624 # We deliberately test sys.argv >= 4 because we do not want
625 # to read implicitly any possible previous run configuration
626 if len(sys.argv) < 4:
627 sys.stderr.write("Configuration error: No presets specified\n")
628 return 2
630 if (infer_verify_choices(config, rules)):
631 preprocess_config(config, rules)
632 create_output(MAKEFILE, MACROS, config, rules)
633 return 0
635 sys.stderr.write("Configuration error: The presets are ambiguous\n")
636 return 1
638 # Check mode: only check configuration
639 if (len(sys.argv) >= 3) and (sys.argv[2] == 'check'):
640 if infer_verify_choices(config, rules):
641 return 0
642 return 1
644 # Random mode
645 if (len(sys.argv) == 3) and (sys.argv[2] == 'random'):
646 ok = random_choices(config, rules, 0)
647 if not ok:
648 sys.stderr.write("Internal error: unable to generate random config.\n")
649 return 2
650 if not infer_verify_choices(config, rules):
651 sys.stderr.write("Internal error: random configuration not consistent.\n")
652 return 2
653 preprocess_config(config, rules)
654 create_output(MAKEFILE, MACROS, config, rules)
656 return 0
658 screen = xtui.screen_init()
659 try:
660 selname = None
661 position = None
662 while True:
664 # Cancel out all values which have to be deduced
665 for varname, vartype, name, choices, cond in rules:
666 if (vartype == 'y') and (varname in config) and (config[varname] == '*'):
667 config[varname] = None
669 options = []
670 opt2row = {}
671 cnt = 1
673 options.append(" --- Load preconfigured defaults ... ")
675 for rule in rules:
676 varname, vartype, name, choices, cond = rule
678 if cond and (not check_condition(cond, config, rules)):
679 continue
681 if varname == selname:
682 position = cnt
684 if not varname in config:
685 value = None
686 else:
687 value = config[varname]
689 if not validate_rule_value(rule, value):
690 value = None
692 default = get_default_rule(rule)
695 # If we don't have a value but we do have
696 # a default, use it.
698 if value == None and default != None:
699 value = default
700 config[varname] = default
702 option = get_rule_option(rule, value)
703 if option != None:
704 options.append(option)
705 else:
706 continue
708 opt2row[cnt] = (varname, vartype, name, choices)
710 cnt += 1
712 if (position != None) and (position >= len(options)):
713 position = None
715 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
717 if button == 'cancel':
718 return 'Configuration canceled'
720 if button == 'done':
721 if (infer_verify_choices(config, rules)):
722 break
723 else:
724 xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
725 continue
727 if value == 0:
728 profile = choose_profile(PRESETS_DIR, MAKEFILE, screen, config)
729 if profile != None:
730 read_presets(profile, config)
731 position = 1
732 continue
734 position = None
735 if not value in opt2row:
736 raise RuntimeError("Error selecting value: %s" % value)
738 (selname, seltype, name, choices) = opt2row[value]
740 if not selname in config:
741 value = None
742 else:
743 value = config[selname]
745 if seltype == 'choice':
746 config[selname] = subchoice(screen, name, choices, value)
747 elif (seltype == 'y/n') or (seltype == 'n/y'):
748 if config[selname] == 'y':
749 config[selname] = 'n'
750 else:
751 config[selname] = 'y'
752 finally:
753 xtui.screen_done(screen)
755 preprocess_config(config, rules)
756 create_output(MAKEFILE, MACROS, config, rules)
757 return 0
759 if __name__ == '__main__':
760 sys.exit(main())