source: mainline/tools/config.py@ dcc4cb31

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

Display masked entries in config

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