source: mainline/tools/config.py@ 093e956

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

Fix invalid newline in Makefile.config

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