source: mainline/tools/config.py@ aca97582

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

tools/config.py: Clean up argument handling

  • Property mode set to 100755
File size: 20.9 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
[f857e8b]42import random
[98376de]43
[aca97582]44ARGPOS_RULES = 1
45ARGPOS_CHOICE = 2
46ARGPOS_PRESET = 3
47
48RULES_FILE = sys.argv[ARGPOS_RULES]
[84266669]49MAKEFILE = 'Makefile.config'
50MACROS = 'config.h'
[62bb73e]51PRESETS_DIR = 'defaults'
[98376de]52
[3ef901d0]53class BinaryOp:
54 def __init__(self, operator, left, right):
55 assert operator in ('&', '|', '=', '!=')
56
57 self._operator = operator
58 self._left = left
59 self._right = right
60
61 def evaluate(self, config):
62 if self._operator == '&':
63 return self._left.evaluate(config) and \
64 self._right.evaluate(config)
65 if self._operator == '|':
66 return self._left.evaluate(config) or \
67 self._right.evaluate(config)
68
69 # '=' or '!='
70 if not self._left in config:
71 config_val = ''
72 else:
73 config_val = config[self._left]
74 if config_val == '*':
75 config_val = 'y'
76
77 if self._operator == '=':
78 return self._right == config_val
79 return self._right != config_val
80
81# Expression parser
82class CondParser:
83 TOKEN_EOE = 0
84 TOKEN_SPECIAL = 1
85 TOKEN_STRING = 2
86
87 def __init__(self, text):
88 self._text = text
89
90 def parse(self):
91 self._position = -1
92 self._next_char()
93 self._next_token()
94
95 res = self._parse_expr()
96 if self._token_type != self.TOKEN_EOE:
97 self._error("Expected end of expression")
98 return res
99
100 def _next_char(self):
101 self._position += 1
102 if self._position >= len(self._text):
103 self._char = None
104 else:
105 self._char = self._text[self._position]
106 self._is_special_char = self._char in \
107 ('&', '|', '=', '!', '(', ')')
108
109 def _error(self, msg):
110 raise RuntimeError("Error parsing expression: %s:\n%s\n%s^" %
111 (msg, self._text, " " * self._token_position))
112
113 def _next_token(self):
114 self._token_position = self._position
115
116 # End of expression
117 if self._char == None:
118 self._token = None
119 self._token_type = self.TOKEN_EOE
120 return
121
122 # '&', '|', '=', '!=', '(', ')'
123 if self._is_special_char:
124 self._token = self._char
125 self._next_char()
126 if self._token == '!':
127 if self._char != '=':
128 self._error("Expected '='")
129 self._token += self._char
130 self._next_char()
131 self._token_type = self.TOKEN_SPECIAL
132 return
133
134 # <var> or <val>
135 self._token = ''
136 self._token_type = self.TOKEN_STRING
137 while True:
138 self._token += self._char
139 self._next_char()
140 if self._is_special_char or self._char == None:
141 break
[a35b458]142
[3ef901d0]143 def _parse_expr(self):
144 """ <expr> ::= <or_expr> ('&' <or_expr>)* """
[a35b458]145
[3ef901d0]146 left = self._parse_or_expr()
147 while self._token == '&':
148 self._next_token()
149 left = BinaryOp('&', left, self._parse_or_expr())
150 return left
[a35b458]151
[3ef901d0]152 def _parse_or_expr(self):
153 """ <or_expr> ::= <factor> ('|' <factor>)* """
[98376de]154
[3ef901d0]155 left = self._parse_factor()
156 while self._token == '|':
157 self._next_token()
158 left = BinaryOp('|', left, self._parse_factor())
159 return left
[a35b458]160
[3ef901d0]161 def _parse_factor(self):
162 """ <factor> ::= <var> <cond> | '(' <expr> ')' """
[a35b458]163
[3ef901d0]164 if self._token == '(':
165 self._next_token()
166 res = self._parse_expr()
167 if self._token != ')':
168 self._error("Expected ')'")
169 self._next_token()
170 return res
[a35b458]171
[3ef901d0]172 if self._token_type == self.TOKEN_STRING:
173 var = self._token
174 self._next_token()
175 return self._parse_cond(var)
[a35b458]176
[3ef901d0]177 self._error("Expected '(' or <var>")
[a35b458]178
[3ef901d0]179 def _parse_cond(self, var):
180 """ <cond> ::= '=' <val> | '!=' <val> """
[a35b458]181
[3ef901d0]182 if self._token not in ('=', '!='):
183 self._error("Expected '=' or '!='")
[a35b458]184
[3ef901d0]185 oper = self._token
186 self._next_token()
[a35b458]187
[3ef901d0]188 if self._token_type != self.TOKEN_STRING:
189 self._error("Expected <val>")
[a35b458]190
[3ef901d0]191 val = self._token
192 self._next_token()
[98376de]193
[3ef901d0]194 return BinaryOp(oper, var, val)
[a35b458]195
[3ef901d0]196def read_config(fname, config):
197 "Read saved values from last configuration run or a preset file"
[a35b458]198
[3ef901d0]199 inf = open(fname, 'r')
[a35b458]200
[3ef901d0]201 for line in inf:
202 res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
203 if res:
204 config[res.group(1)] = res.group(2)
[a35b458]205
[3ef901d0]206 inf.close()
[98376de]207
[62bb73e]208def parse_rules(fname, rules):
209 "Parse rules file"
[a35b458]210
[28f4adb]211 inf = open(fname, 'r')
[a35b458]212
[9a0367f]213 name = ''
214 choices = []
[a35b458]215
[9a0367f]216 for line in inf:
[a35b458]217
[ba8de9c3]218 if line.startswith('!'):
[9a0367f]219 # Ask a question
220 res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
[a35b458]221
[ba8de9c3]222 if not res:
[9a0367f]223 raise RuntimeError("Weird line: %s" % line)
[a35b458]224
[9a0367f]225 cond = res.group(1)
[3ef901d0]226 if cond:
227 cond = CondParser(cond).parse()
[9a0367f]228 varname = res.group(2)
229 vartype = res.group(3)
[a35b458]230
[62bb73e]231 rules.append((varname, vartype, name, choices, cond))
[9a0367f]232 name = ''
233 choices = []
234 continue
[a35b458]235
[ba8de9c3]236 if line.startswith('@'):
[9a0367f]237 # Add new line into the 'choices' array
238 res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
[a35b458]239
[9a0367f]240 if not res:
241 raise RuntimeError("Bad line: %s" % line)
[a35b458]242
[9a0367f]243 choices.append((res.group(2), res.group(3)))
244 continue
[a35b458]245
[ba8de9c3]246 if line.startswith('%'):
[9a0367f]247 # Name of the option
248 name = line[1:].strip()
249 continue
[a35b458]250
[ba8de9c3]251 if line.startswith('#') or (line == '\n'):
[9a0367f]252 # Comment or empty line
253 continue
[a35b458]254
255
[9a0367f]256 raise RuntimeError("Unknown syntax: %s" % line)
[a35b458]257
[9a0367f]258 inf.close()
[98376de]259
[9a0367f]260def yes_no(default):
261 "Return '*' if yes, ' ' if no"
[a35b458]262
[ba8de9c3]263 if default == 'y':
[9a0367f]264 return '*'
[a35b458]265
[9a0367f]266 return ' '
[98376de]267
[27fb3d6]268def subchoice(screen, name, choices, default):
[9a0367f]269 "Return choice of choices"
[a35b458]270
[27fb3d6]271 maxkey = 0
272 for key, val in choices:
273 length = len(key)
274 if (length > maxkey):
275 maxkey = length
[a35b458]276
[9a0367f]277 options = []
[27fb3d6]278 position = None
279 cnt = 0
280 for key, val in choices:
[ba8de9c3]281 if (default) and (key == default):
[27fb3d6]282 position = cnt
[a35b458]283
[27fb3d6]284 options.append(" %-*s %s " % (maxkey, key, val))
285 cnt += 1
[a35b458]286
[27fb3d6]287 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
[a35b458]288
[ba8de9c3]289 if button == 'cancel':
[9a0367f]290 return None
[a35b458]291
[27fb3d6]292 return choices[value][0]
[98376de]293
[e4d540b]294## Infer and verify configuration values.
295#
[62bb73e]296# Augment @a config with values that can be inferred, purge invalid ones
[e4d540b]297# and verify that all variables have a value (previously specified or inferred).
298#
[d40ffbb]299# @param config Configuration to work on
300# @param rules Rules
[e4d540b]301#
[d40ffbb]302# @return True if configuration is complete and valid, False
303# otherwise.
[e4d540b]304#
[62bb73e]305def infer_verify_choices(config, rules):
[e4d540b]306 "Infer and verify configuration values."
[a35b458]307
[ba8de9c3]308 for rule in rules:
309 varname, vartype, name, choices, cond = rule
[a35b458]310
[3ef901d0]311 if cond and not cond.evaluate(config):
[9a0367f]312 continue
[a35b458]313
[ba8de9c3]314 if not varname in config:
[4756634]315 value = None
[e4d540b]316 else:
[4756634]317 value = config[varname]
[a35b458]318
[d40ffbb]319 if not validate_rule_value(rule, value):
[4756634]320 value = None
[a35b458]321
[d40ffbb]322 default = get_default_rule(rule)
[a35b458]323
[c0bd08d]324 #
325 # If we don't have a value but we do have
326 # a default, use it.
327 #
328 if value == None and default != None:
329 value = default
[4756634]330 config[varname] = default
[a35b458]331
[ba8de9c3]332 if not varname in config:
[9a0367f]333 return False
[a35b458]334
[9a0367f]335 return True
[98376de]336
[f857e8b]337## Fill the configuration with random (but valid) values.
338#
339# The random selection takes next rule and if the condition does
340# not violate existing configuration, random value of the variable
341# is selected.
342# This happens recursively as long as there are more rules.
343# If a conflict is found, we backtrack and try other settings of the
344# variable or ignoring the variable altogether.
345#
346# @param config Configuration to work on
347# @param rules Rules
348# @param start_index With which rule to start (initial call must specify 0 here).
[1b20da0]349# @return True if able to find a valid configuration
[f857e8b]350def random_choices(config, rules, start_index):
351 "Fill the configuration with random (but valid) values."
352 if start_index >= len(rules):
353 return True
[a35b458]354
[f857e8b]355 varname, vartype, name, choices, cond = rules[start_index]
356
[1b20da0]357 # First check that this rule would make sense
[3ef901d0]358 if cond and not cond.evaluate(config):
359 return random_choices(config, rules, start_index + 1)
[a35b458]360
[f857e8b]361 # Remember previous choices for backtracking
362 yes_no = 0
363 choices_indexes = range(0, len(choices))
364 random.shuffle(choices_indexes)
[a35b458]365
[f857e8b]366 # Remember current configuration value
367 old_value = None
368 try:
369 old_value = config[varname]
370 except KeyError:
371 old_value = None
[a35b458]372
[f857e8b]373 # For yes/no choices, we ran the loop at most 2 times, for select
374 # choices as many times as there are options.
375 try_counter = 0
376 while True:
377 if vartype == 'choice':
378 if try_counter >= len(choices_indexes):
379 break
380 value = choices[choices_indexes[try_counter]][0]
381 elif vartype == 'y' or vartype == 'n':
382 if try_counter > 0:
383 break
384 value = vartype
385 elif vartype == 'y/n' or vartype == 'n/y':
386 if try_counter == 0:
387 yes_no = random.randint(0, 1)
388 elif try_counter == 1:
389 yes_no = 1 - yes_no
390 else:
391 break
392 if yes_no == 0:
393 value = 'n'
394 else:
395 value = 'y'
396 else:
397 raise RuntimeError("Unknown variable type: %s" % vartype)
[a35b458]398
[f857e8b]399 config[varname] = value
[a35b458]400
[f857e8b]401 ok = random_choices(config, rules, start_index + 1)
402 if ok:
403 return True
[a35b458]404
[f857e8b]405 try_counter = try_counter + 1
[a35b458]406
[f857e8b]407 # Restore the old value and backtrack
408 # (need to delete to prevent "ghost" variables that do not exist under
409 # certain configurations)
410 config[varname] = old_value
411 if old_value is None:
412 del config[varname]
[a35b458]413
[f857e8b]414 return random_choices(config, rules, start_index + 1)
[a35b458]415
[f857e8b]416
[e4d540b]417## Get default value from a rule.
[d40ffbb]418def get_default_rule(rule):
[e4d540b]419 varname, vartype, name, choices, cond = rule
[a35b458]420
[e4d540b]421 default = None
[a35b458]422
[ba8de9c3]423 if vartype == 'choice':
[e4d540b]424 # If there is just one option, use it
[ba8de9c3]425 if len(choices) == 1:
[e4d540b]426 default = choices[0][0]
[ba8de9c3]427 elif vartype == 'y':
[e4d540b]428 default = '*'
[ba8de9c3]429 elif vartype == 'n':
[e4d540b]430 default = 'n'
[ba8de9c3]431 elif vartype == 'y/n':
[e4d540b]432 default = 'y'
[ba8de9c3]433 elif vartype == 'n/y':
[e4d540b]434 default = 'n'
435 else:
436 raise RuntimeError("Unknown variable type: %s" % vartype)
[a35b458]437
[e4d540b]438 return default
439
440## Get option from a rule.
441#
[d40ffbb]442# @param rule Rule for a variable
443# @param value Current value of the variable
[e4d540b]444#
445# @return Option (string) to ask or None which means not to ask.
446#
[d40ffbb]447def get_rule_option(rule, value):
[e4d540b]448 varname, vartype, name, choices, cond = rule
[a35b458]449
[e4d540b]450 option = None
[a35b458]451
[ba8de9c3]452 if vartype == 'choice':
[e4d540b]453 # If there is just one option, don't ask
[ba8de9c3]454 if len(choices) != 1:
[e4d540b]455 if (value == None):
456 option = "? %s --> " % name
457 else:
458 option = " %s [%s] --> " % (name, value)
[ba8de9c3]459 elif vartype == 'y':
[e4d540b]460 pass
[ba8de9c3]461 elif vartype == 'n':
[e4d540b]462 pass
[ba8de9c3]463 elif vartype == 'y/n':
[e4d540b]464 option = " <%s> %s " % (yes_no(value), name)
[ba8de9c3]465 elif vartype == 'n/y':
[e4d540b]466 option =" <%s> %s " % (yes_no(value), name)
467 else:
468 raise RuntimeError("Unknown variable type: %s" % vartype)
[a35b458]469
[e4d540b]470 return option
471
472## Check if variable value is valid.
473#
[d40ffbb]474# @param rule Rule for the variable
475# @param value Value of the variable
[e4d540b]476#
[d40ffbb]477# @return True if valid, False if not valid.
[e4d540b]478#
[d40ffbb]479def validate_rule_value(rule, value):
[e4d540b]480 varname, vartype, name, choices, cond = rule
[a35b458]481
[e4d540b]482 if value == None:
483 return True
[a35b458]484
[ba8de9c3]485 if vartype == 'choice':
486 if not value in [choice[0] for choice in choices]:
[e4d540b]487 return False
[ba8de9c3]488 elif vartype == 'y':
[e4d540b]489 if value != 'y':
490 return False
[ba8de9c3]491 elif vartype == 'n':
[e4d540b]492 if value != 'n':
493 return False
[ba8de9c3]494 elif vartype == 'y/n':
[e4d540b]495 if not value in ['y', 'n']:
496 return False
[ba8de9c3]497 elif vartype == 'n/y':
[e4d540b]498 if not value in ['y', 'n']:
499 return False
500 else:
501 raise RuntimeError("Unknown variable type: %s" % vartype)
[a35b458]502
[e4d540b]503 return True
504
[1f5c9c96]505def preprocess_config(config, rules):
506 "Preprocess configuration"
[a35b458]507
[1f5c9c96]508 varname_mode = 'CONFIG_BFB_MODE'
509 varname_width = 'CONFIG_BFB_WIDTH'
510 varname_height = 'CONFIG_BFB_HEIGHT'
[a35b458]511
[1f5c9c96]512 if varname_mode in config:
513 mode = config[varname_mode].partition('x')
[a35b458]514
[1f5c9c96]515 config[varname_width] = mode[0]
516 rules.append((varname_width, 'choice', 'Default framebuffer width', None, None))
[a35b458]517
[1f5c9c96]518 config[varname_height] = mode[2]
519 rules.append((varname_height, 'choice', 'Default framebuffer height', None, None))
520
[62bb73e]521def create_output(mkname, mcname, config, rules):
[9a0367f]522 "Create output configuration"
[a35b458]523
[476ac3b]524 varname_strip = 'CONFIG_STRIP_REVISION_INFO'
525 strip_rev_info = (varname_strip in config) and (config[varname_strip] == 'y')
[a35b458]526
[476ac3b]527 if strip_rev_info:
528 timestamp_unix = int(0)
529 else:
530 # TODO: Use commit timestamp instead of build time.
531 timestamp_unix = int(time.time())
[a35b458]532
[41e871f]533 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp_unix))
[a35b458]534
[fe12f9f4]535 sys.stderr.write("Fetching current revision identifier ... ")
[a35b458]536
[7b76744]537 try:
[0a0b3d8]538 version = subprocess.Popen(['git', 'log', '-1', '--pretty=%h'], stdout = subprocess.PIPE).communicate()[0].decode().strip()
[7b76744]539 sys.stderr.write("ok\n")
540 except:
[0a0b3d8]541 version = None
[7b76744]542 sys.stderr.write("failed\n")
[a35b458]543
[0a0b3d8]544 if (not strip_rev_info) and (version is not None):
545 revision = version
[5a8fbcb9]546 else:
547 revision = None
[a35b458]548
[28f4adb]549 outmk = open(mkname, 'w')
550 outmc = open(mcname, 'w')
[a35b458]551
[84266669]552 outmk.write('#########################################\n')
553 outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
[571239a]554 outmk.write('## Generated by: tools/config.py ##\n')
[84266669]555 outmk.write('#########################################\n\n')
[a35b458]556
[84266669]557 outmc.write('/***************************************\n')
558 outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
[571239a]559 outmc.write(' * Generated by: tools/config.py *\n')
[84266669]560 outmc.write(' ***************************************/\n\n')
[a35b458]561
[4e9aaf5]562 defs = 'CONFIG_DEFS ='
[a35b458]563
[62bb73e]564 for varname, vartype, name, choices, cond in rules:
[3ef901d0]565 if cond and not cond.evaluate(config):
[9a0367f]566 continue
[a35b458]567
[ba8de9c3]568 if not varname in config:
[4756634]569 value = ''
[9a0367f]570 else:
[4756634]571 value = config[varname]
572 if (value == '*'):
573 value = 'y'
[a35b458]574
[4756634]575 outmk.write('# %s\n%s = %s\n\n' % (name, varname, value))
[a35b458]576
[ba8de9c3]577 if vartype in ["y", "n", "y/n", "n/y"]:
578 if value == "y":
[84266669]579 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
[4e9aaf5]580 defs += ' -D%s' % varname
[84266669]581 else:
[4756634]582 outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, value, varname, value))
583 defs += ' -D%s=%s -D%s_%s' % (varname, value, varname, value)
[a35b458]584
[ba8de9c3]585 if revision is not None:
[5a8fbcb9]586 outmk.write('REVISION = %s\n' % revision)
587 outmc.write('#define REVISION %s\n' % revision)
[4e9aaf5]588 defs += ' "-DREVISION=%s"' % revision
[a35b458]589
[41e871f]590 outmk.write('TIMESTAMP_UNIX = %d\n' % timestamp_unix)
591 outmc.write('#define TIMESTAMP_UNIX %d\n' % timestamp_unix)
[093e956]592 defs += ' "-DTIMESTAMP_UNIX=%d"' % timestamp_unix
[a35b458]593
[5a8fbcb9]594 outmk.write('TIMESTAMP = %s\n' % timestamp)
[84266669]595 outmc.write('#define TIMESTAMP %s\n' % timestamp)
[093e956]596 defs += ' "-DTIMESTAMP=%s"' % timestamp
[a35b458]597
[093e956]598 outmk.write('%s\n' % defs)
[a35b458]599
[84266669]600 outmk.close()
601 outmc.close()
[98376de]602
[31fb9a0]603def sorted_dir(root):
604 list = os.listdir(root)
605 list.sort()
606 return list
607
[6ec0acd]608## Ask user to choose a configuration profile.
[62bb73e]609#
[d40ffbb]610def choose_profile(root, fname, screen, config):
[31fb9a0]611 options = []
612 opt2path = {}
613 cnt = 0
[a35b458]614
[31fb9a0]615 # Look for profiles
616 for name in sorted_dir(root):
617 path = os.path.join(root, name)
618 canon = os.path.join(path, fname)
[a35b458]619
[ba8de9c3]620 if os.path.isdir(path) and os.path.exists(canon) and os.path.isfile(canon):
[31fb9a0]621 subprofile = False
[a35b458]622
[31fb9a0]623 # Look for subprofiles
624 for subname in sorted_dir(path):
625 subpath = os.path.join(path, subname)
626 subcanon = os.path.join(subpath, fname)
[a35b458]627
[ba8de9c3]628 if os.path.isdir(subpath) and os.path.exists(subcanon) and os.path.isfile(subcanon):
[31fb9a0]629 subprofile = True
630 options.append("%s (%s)" % (name, subname))
[6ec0acd]631 opt2path[cnt] = [name, subname]
[31fb9a0]632 cnt += 1
[a35b458]633
[ba8de9c3]634 if not subprofile:
[31fb9a0]635 options.append(name)
[6ec0acd]636 opt2path[cnt] = [name]
[31fb9a0]637 cnt += 1
[a35b458]638
[31fb9a0]639 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
[a35b458]640
[ba8de9c3]641 if button == 'cancel':
[31fb9a0]642 return None
[a35b458]643
[6ec0acd]644 return opt2path[value]
645
646## Read presets from a configuration profile.
647#
[d40ffbb]648# @param profile Profile to load from (a list of string components)
649# @param config Output configuration
[6ec0acd]650#
[d40ffbb]651def read_presets(profile, config):
[6ec0acd]652 path = os.path.join(PRESETS_DIR, profile[0], MAKEFILE)
653 read_config(path, config)
[a35b458]654
[6ec0acd]655 if len(profile) > 1:
656 path = os.path.join(PRESETS_DIR, profile[0], profile[1], MAKEFILE)
657 read_config(path, config)
658
659## Parse profile name (relative OS path) into a list of components.
660#
[d40ffbb]661# @param profile_name Relative path (using OS separator)
662# @return List of components
[6ec0acd]663#
664def parse_profile_name(profile_name):
665 profile = []
[a35b458]666
[6ec0acd]667 head, tail = os.path.split(profile_name)
668 if head != '':
669 profile.append(head)
[a35b458]670
[6ec0acd]671 profile.append(tail)
672 return profile
[31fb9a0]673
[98376de]674def main():
[6ec0acd]675 profile = None
[62bb73e]676 config = {}
677 rules = []
[a35b458]678
[62bb73e]679 # Parse rules file
680 parse_rules(RULES_FILE, rules)
[a35b458]681
[aca97582]682 if len(sys.argv) > ARGPOS_CHOICE:
683 choice = sys.argv[ARGPOS_CHOICE]
684 else:
685 choice = None
686
687 if len(sys.argv) > ARGPOS_PRESET:
688 preset = sys.argv[ARGPOS_PRESET]
689 else:
690 preset = None
691
[421250e]692 # Input configuration file can be specified on command line
693 # otherwise configuration from previous run is used.
[aca97582]694 if preset is not None:
695 profile = parse_profile_name(preset)
[d40ffbb]696 read_presets(profile, config)
[6ec0acd]697 elif os.path.exists(MAKEFILE):
698 read_config(MAKEFILE, config)
[a35b458]699
[e3c3172]700 # Default mode: check values and regenerate configuration files
[aca97582]701 if choice == 'default':
[62bb73e]702 if (infer_verify_choices(config, rules)):
[1f5c9c96]703 preprocess_config(config, rules)
[62bb73e]704 create_output(MAKEFILE, MACROS, config, rules)
[9a0367f]705 return 0
[a35b458]706
[e3c3172]707 # Hands-off mode: check values and regenerate configuration files,
708 # but no interactive fallback
[aca97582]709 if choice == 'hands-off':
710 # We deliberately test this because we do not want
[e3c3172]711 # to read implicitly any possible previous run configuration
[aca97582]712 if preset is None:
[e3c3172]713 sys.stderr.write("Configuration error: No presets specified\n")
714 return 2
[a35b458]715
[e3c3172]716 if (infer_verify_choices(config, rules)):
[1f5c9c96]717 preprocess_config(config, rules)
[e3c3172]718 create_output(MAKEFILE, MACROS, config, rules)
719 return 0
[a35b458]720
[e3c3172]721 sys.stderr.write("Configuration error: The presets are ambiguous\n")
722 return 1
[a35b458]723
[62bb73e]724 # Check mode: only check configuration
[aca97582]725 if choice == 'check':
[ba8de9c3]726 if infer_verify_choices(config, rules):
[48c3d50]727 return 0
728 return 1
[a35b458]729
[f857e8b]730 # Random mode
[aca97582]731 if choice == 'random':
[f857e8b]732 ok = random_choices(config, rules, 0)
733 if not ok:
734 sys.stderr.write("Internal error: unable to generate random config.\n")
735 return 2
736 if not infer_verify_choices(config, rules):
737 sys.stderr.write("Internal error: random configuration not consistent.\n")
738 return 2
739 preprocess_config(config, rules)
740 create_output(MAKEFILE, MACROS, config, rules)
[a35b458]741
[1b20da0]742 return 0
[a35b458]743
[27fb3d6]744 screen = xtui.screen_init()
[9a0367f]745 try:
746 selname = None
[31fb9a0]747 position = None
[9a0367f]748 while True:
[a35b458]749
[62bb73e]750 # Cancel out all values which have to be deduced
751 for varname, vartype, name, choices, cond in rules:
[ba8de9c3]752 if (vartype == 'y') and (varname in config) and (config[varname] == '*'):
[62bb73e]753 config[varname] = None
[a35b458]754
[9a0367f]755 options = []
756 opt2row = {}
[31fb9a0]757 cnt = 1
[a35b458]758
[31fb9a0]759 options.append(" --- Load preconfigured defaults ... ")
[a35b458]760
[62bb73e]761 for rule in rules:
[e4d540b]762 varname, vartype, name, choices, cond = rule
[a35b458]763
[3ef901d0]764 if cond and not cond.evaluate(config):
[9a0367f]765 continue
[a35b458]766
[ba8de9c3]767 if varname == selname:
[9a0367f]768 position = cnt
[a35b458]769
[ba8de9c3]770 if not varname in config:
[4756634]771 value = None
[9a0367f]772 else:
[4756634]773 value = config[varname]
[a35b458]774
[d40ffbb]775 if not validate_rule_value(rule, value):
[4756634]776 value = None
[a35b458]777
[d40ffbb]778 default = get_default_rule(rule)
[a35b458]779
[c0bd08d]780 #
781 # If we don't have a value but we do have
782 # a default, use it.
783 #
784 if value == None and default != None:
785 value = default
786 config[varname] = default
[a35b458]787
[d40ffbb]788 option = get_rule_option(rule, value)
[e4d540b]789 if option != None:
790 options.append(option)
[8fe3f832]791 else:
792 continue
[a35b458]793
[27fb3d6]794 opt2row[cnt] = (varname, vartype, name, choices)
[a35b458]795
[9a0367f]796 cnt += 1
[a35b458]797
[28f4adb]798 if (position != None) and (position >= len(options)):
[31fb9a0]799 position = None
[a35b458]800
[27fb3d6]801 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
[a35b458]802
[ba8de9c3]803 if button == 'cancel':
[9a0367f]804 return 'Configuration canceled'
[a35b458]805
[ba8de9c3]806 if button == 'done':
[62bb73e]807 if (infer_verify_choices(config, rules)):
[6346efd]808 break
809 else:
810 xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
811 continue
[a35b458]812
[ba8de9c3]813 if value == 0:
[d40ffbb]814 profile = choose_profile(PRESETS_DIR, MAKEFILE, screen, config)
[6ec0acd]815 if profile != None:
[d40ffbb]816 read_presets(profile, config)
[31fb9a0]817 position = 1
818 continue
[a35b458]819
[31fb9a0]820 position = None
[ba8de9c3]821 if not value in opt2row:
[27fb3d6]822 raise RuntimeError("Error selecting value: %s" % value)
[a35b458]823
[27fb3d6]824 (selname, seltype, name, choices) = opt2row[value]
[a35b458]825
[ba8de9c3]826 if not selname in config:
[4756634]827 value = None
[27fb3d6]828 else:
[4756634]829 value = config[selname]
[a35b458]830
[ba8de9c3]831 if seltype == 'choice':
[4756634]832 config[selname] = subchoice(screen, name, choices, value)
[ba8de9c3]833 elif (seltype == 'y/n') or (seltype == 'n/y'):
834 if config[selname] == 'y':
[62bb73e]835 config[selname] = 'n'
[9a0367f]836 else:
[62bb73e]837 config[selname] = 'y'
[9a0367f]838 finally:
[27fb3d6]839 xtui.screen_done(screen)
[a35b458]840
[1f5c9c96]841 preprocess_config(config, rules)
[62bb73e]842 create_output(MAKEFILE, MACROS, config, rules)
[9a0367f]843 return 0
[98376de]844
845if __name__ == '__main__':
[43a10c4]846 sys.exit(main())
Note: See TracBrowser for help on using the repository browser.