source: mainline/tools/config.py@ 4f080f2a

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 4f080f2a was a35b458, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 7 years ago

style: Remove trailing whitespace on _all_ lines, including empty ones, for particular file types.

Command used: tools/srepl '\s\+$' '' -- *.c *.h *.py *.sh *.s *.S *.ag

Currently, whitespace on empty lines is very inconsistent.
There are two basic choices: Either remove the whitespace, or keep empty lines
indented to the level of surrounding code. The former is AFAICT more common,
and also much easier to do automatically.

Alternatively, we could write script for automatic indentation, and use that
instead. However, if such a script exists, it's possible to use the indented
style locally, by having the editor apply relevant conversions on load/save,
without affecting remote repository. IMO, it makes more sense to adopt
the simpler rule.

  • Property mode set to 100755
File size: 19.0 KB
Line 
1#!/usr/bin/env python
2#
3# Copyright (c) 2006 Ondrej Palkovsky
4# Copyright (c) 2009 Martin Decky
5# Copyright (c) 2010 Jiri Svoboda
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#
31
32"""
33HelenOS configuration system
34"""
35
36import sys
37import os
38import re
39import time
40import subprocess
41import xtui
42import random
43
44RULES_FILE = sys.argv[1]
45MAKEFILE = 'Makefile.config'
46MACROS = 'config.h'
47PRESETS_DIR = 'defaults'
48
49def read_config(fname, config):
50 "Read saved values from last configuration run or a preset file"
51
52 inf = open(fname, 'r')
53
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)
58
59 inf.close()
60
61def check_condition(text, config, rules):
62 "Check that the condition specified on input line is True (only CNF and DNF is supported)"
63
64 ctype = 'cnf'
65
66 if (')|' in text) or ('|(' in text):
67 ctype = 'dnf'
68
69 if ctype == 'cnf':
70 conds = text.split('&')
71 else:
72 conds = text.split('|')
73
74 for cond in conds:
75 if cond.startswith('(') and cond.endswith(')'):
76 cond = cond[1:-1]
77
78 inside = check_inside(cond, config, ctype)
79
80 if (ctype == 'cnf') and (not inside):
81 return False
82
83 if (ctype == 'dnf') and inside:
84 return True
85
86 if ctype == 'cnf':
87 return True
88
89 return False
90
91def check_inside(text, config, ctype):
92 "Check for condition"
93
94 if ctype == 'cnf':
95 conds = text.split('|')
96 else:
97 conds = text.split('&')
98
99 for cond in conds:
100 res = re.match(r'^(.*?)(!?=)(.*)$', cond)
101 if not res:
102 raise RuntimeError("Invalid condition: %s" % cond)
103
104 condname = res.group(1)
105 oper = res.group(2)
106 condval = res.group(3)
107
108 if not condname in config:
109 varval = ''
110 else:
111 varval = config[condname]
112 if (varval == '*'):
113 varval = 'y'
114
115 if ctype == 'cnf':
116 if (oper == '=') and (condval == varval):
117 return True
118
119 if (oper == '!=') and (condval != varval):
120 return True
121 else:
122 if (oper == '=') and (condval != varval):
123 return False
124
125 if (oper == '!=') and (condval == varval):
126 return False
127
128 if ctype == 'cnf':
129 return False
130
131 return True
132
133def parse_rules(fname, rules):
134 "Parse rules file"
135
136 inf = open(fname, 'r')
137
138 name = ''
139 choices = []
140
141 for line in inf:
142
143 if line.startswith('!'):
144 # Ask a question
145 res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
146
147 if not res:
148 raise RuntimeError("Weird line: %s" % line)
149
150 cond = res.group(1)
151 varname = res.group(2)
152 vartype = res.group(3)
153
154 rules.append((varname, vartype, name, choices, cond))
155 name = ''
156 choices = []
157 continue
158
159 if line.startswith('@'):
160 # Add new line into the 'choices' array
161 res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
162
163 if not res:
164 raise RuntimeError("Bad line: %s" % line)
165
166 choices.append((res.group(2), res.group(3)))
167 continue
168
169 if line.startswith('%'):
170 # Name of the option
171 name = line[1:].strip()
172 continue
173
174 if line.startswith('#') or (line == '\n'):
175 # Comment or empty line
176 continue
177
178
179 raise RuntimeError("Unknown syntax: %s" % line)
180
181 inf.close()
182
183def yes_no(default):
184 "Return '*' if yes, ' ' if no"
185
186 if default == 'y':
187 return '*'
188
189 return ' '
190
191def subchoice(screen, name, choices, default):
192 "Return choice of choices"
193
194 maxkey = 0
195 for key, val in choices:
196 length = len(key)
197 if (length > maxkey):
198 maxkey = length
199
200 options = []
201 position = None
202 cnt = 0
203 for key, val in choices:
204 if (default) and (key == default):
205 position = cnt
206
207 options.append(" %-*s %s " % (maxkey, key, val))
208 cnt += 1
209
210 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
211
212 if button == 'cancel':
213 return None
214
215 return choices[value][0]
216
217## Infer and verify configuration values.
218#
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).
221#
222# @param config Configuration to work on
223# @param rules Rules
224#
225# @return True if configuration is complete and valid, False
226# otherwise.
227#
228def infer_verify_choices(config, rules):
229 "Infer and verify configuration values."
230
231 for rule in rules:
232 varname, vartype, name, choices, cond = rule
233
234 if cond and (not check_condition(cond, config, rules)):
235 continue
236
237 if not varname in config:
238 value = None
239 else:
240 value = config[varname]
241
242 if not validate_rule_value(rule, value):
243 value = None
244
245 default = get_default_rule(rule)
246
247 #
248 # If we don't have a value but we do have
249 # a default, use it.
250 #
251 if value == None and default != None:
252 value = default
253 config[varname] = default
254
255 if not varname in config:
256 return False
257
258 return True
259
260## Fill the configuration with random (but valid) values.
261#
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.
268#
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
273def random_choices(config, rules, start_index):
274 "Fill the configuration with random (but valid) values."
275 if start_index >= len(rules):
276 return True
277
278 varname, vartype, name, choices, cond = rules[start_index]
279
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)
284
285 # Remember previous choices for backtracking
286 yes_no = 0
287 choices_indexes = range(0, len(choices))
288 random.shuffle(choices_indexes)
289
290 # Remember current configuration value
291 old_value = None
292 try:
293 old_value = config[varname]
294 except KeyError:
295 old_value = None
296
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)
322
323 config[varname] = value
324
325 ok = random_choices(config, rules, start_index + 1)
326 if ok:
327 return True
328
329 try_counter = try_counter + 1
330
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]
337
338 return random_choices(config, rules, start_index + 1)
339
340
341## Get default value from a rule.
342def get_default_rule(rule):
343 varname, vartype, name, choices, cond = rule
344
345 default = None
346
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)
361
362 return default
363
364## Get option from a rule.
365#
366# @param rule Rule for a variable
367# @param value Current value of the variable
368#
369# @return Option (string) to ask or None which means not to ask.
370#
371def get_rule_option(rule, value):
372 varname, vartype, name, choices, cond = rule
373
374 option = None
375
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)
393
394 return option
395
396## Check if variable value is valid.
397#
398# @param rule Rule for the variable
399# @param value Value of the variable
400#
401# @return True if valid, False if not valid.
402#
403def validate_rule_value(rule, value):
404 varname, vartype, name, choices, cond = rule
405
406 if value == None:
407 return True
408
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)
426
427 return True
428
429def preprocess_config(config, rules):
430 "Preprocess configuration"
431
432 varname_mode = 'CONFIG_BFB_MODE'
433 varname_width = 'CONFIG_BFB_WIDTH'
434 varname_height = 'CONFIG_BFB_HEIGHT'
435
436 if varname_mode in config:
437 mode = config[varname_mode].partition('x')
438
439 config[varname_width] = mode[0]
440 rules.append((varname_width, 'choice', 'Default framebuffer width', None, None))
441
442 config[varname_height] = mode[2]
443 rules.append((varname_height, 'choice', 'Default framebuffer height', None, None))
444
445def create_output(mkname, mcname, config, rules):
446 "Create output configuration"
447
448 varname_strip = 'CONFIG_STRIP_REVISION_INFO'
449 strip_rev_info = (varname_strip in config) and (config[varname_strip] == 'y')
450
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())
456
457 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp_unix))
458
459 sys.stderr.write("Fetching current revision identifier ... ")
460
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")
467
468 if (not strip_rev_info) and (version is not None):
469 revision = version
470 else:
471 revision = None
472
473 outmk = open(mkname, 'w')
474 outmc = open(mcname, 'w')
475
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')
480
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')
485
486 defs = 'CONFIG_DEFS ='
487
488 for varname, vartype, name, choices, cond in rules:
489 if cond and (not check_condition(cond, config, rules)):
490 continue
491
492 if not varname in config:
493 value = ''
494 else:
495 value = config[varname]
496 if (value == '*'):
497 value = 'y'
498
499 outmk.write('# %s\n%s = %s\n\n' % (name, varname, value))
500
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)
508
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
513
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
517
518 outmk.write('TIMESTAMP = %s\n' % timestamp)
519 outmc.write('#define TIMESTAMP %s\n' % timestamp)
520 defs += ' "-DTIMESTAMP=%s"\n' % timestamp
521
522 outmk.write(defs)
523
524 outmk.close()
525 outmc.close()
526
527def sorted_dir(root):
528 list = os.listdir(root)
529 list.sort()
530 return list
531
532## Ask user to choose a configuration profile.
533#
534def choose_profile(root, fname, screen, config):
535 options = []
536 opt2path = {}
537 cnt = 0
538
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)
543
544 if os.path.isdir(path) and os.path.exists(canon) and os.path.isfile(canon):
545 subprofile = False
546
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)
551
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
557
558 if not subprofile:
559 options.append(name)
560 opt2path[cnt] = [name]
561 cnt += 1
562
563 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
564
565 if button == 'cancel':
566 return None
567
568 return opt2path[value]
569
570## Read presets from a configuration profile.
571#
572# @param profile Profile to load from (a list of string components)
573# @param config Output configuration
574#
575def read_presets(profile, config):
576 path = os.path.join(PRESETS_DIR, profile[0], MAKEFILE)
577 read_config(path, config)
578
579 if len(profile) > 1:
580 path = os.path.join(PRESETS_DIR, profile[0], profile[1], MAKEFILE)
581 read_config(path, config)
582
583## Parse profile name (relative OS path) into a list of components.
584#
585# @param profile_name Relative path (using OS separator)
586# @return List of components
587#
588def parse_profile_name(profile_name):
589 profile = []
590
591 head, tail = os.path.split(profile_name)
592 if head != '':
593 profile.append(head)
594
595 profile.append(tail)
596 return profile
597
598def main():
599 profile = None
600 config = {}
601 rules = []
602
603 # Parse rules file
604 parse_rules(RULES_FILE, rules)
605
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)
613
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
620
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
629
630 if (infer_verify_choices(config, rules)):
631 preprocess_config(config, rules)
632 create_output(MAKEFILE, MACROS, config, rules)
633 return 0
634
635 sys.stderr.write("Configuration error: The presets are ambiguous\n")
636 return 1
637
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
643
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)
655
656 return 0
657
658 screen = xtui.screen_init()
659 try:
660 selname = None
661 position = None
662 while True:
663
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
668
669 options = []
670 opt2row = {}
671 cnt = 1
672
673 options.append(" --- Load preconfigured defaults ... ")
674
675 for rule in rules:
676 varname, vartype, name, choices, cond = rule
677
678 if cond and (not check_condition(cond, config, rules)):
679 continue
680
681 if varname == selname:
682 position = cnt
683
684 if not varname in config:
685 value = None
686 else:
687 value = config[varname]
688
689 if not validate_rule_value(rule, value):
690 value = None
691
692 default = get_default_rule(rule)
693
694 #
695 # If we don't have a value but we do have
696 # a default, use it.
697 #
698 if value == None and default != None:
699 value = default
700 config[varname] = default
701
702 option = get_rule_option(rule, value)
703 if option != None:
704 options.append(option)
705 else:
706 continue
707
708 opt2row[cnt] = (varname, vartype, name, choices)
709
710 cnt += 1
711
712 if (position != None) and (position >= len(options)):
713 position = None
714
715 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
716
717 if button == 'cancel':
718 return 'Configuration canceled'
719
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
726
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
733
734 position = None
735 if not value in opt2row:
736 raise RuntimeError("Error selecting value: %s" % value)
737
738 (selname, seltype, name, choices) = opt2row[value]
739
740 if not selname in config:
741 value = None
742 else:
743 value = config[selname]
744
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)
754
755 preprocess_config(config, rules)
756 create_output(MAKEFILE, MACROS, config, rules)
757 return 0
758
759if __name__ == '__main__':
760 sys.exit(main())
Note: See TracBrowser for help on using the repository browser.