source: mainline/tools/config.py@ af7223b

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

Fix revision fetching in out of tree config.py

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