source: mainline/tools/config.py@ 4756634

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 4756634 was 4756634, checked in by Jiri Svoboda <jiri@…>, 15 years ago

More nomenclature cleanup.

  • Property mode set to 100755
File size: 14.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
[98376de]42
[62bb73e]43RULES_FILE = sys.argv[1]
[84266669]44MAKEFILE = 'Makefile.config'
45MACROS = 'config.h'
[62bb73e]46PRESETS_DIR = 'defaults'
[98376de]47
[62bb73e]48def read_config(fname, config):
[9a0367f]49 "Read saved values from last configuration run"
50
[28f4adb]51 inf = open(fname, 'r')
[9a0367f]52
53 for line in inf:
54 res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
55 if (res):
[62bb73e]56 config[res.group(1)] = res.group(2)
[9a0367f]57
58 inf.close()
[98376de]59
[62bb73e]60def check_condition(text, config, rules):
[81c8d54]61 "Check that the condition specified on input line is True (only CNF and DNF is supported)"
[9a0367f]62
63 ctype = 'cnf'
64
65 if ((')|' in text) or ('|(' in text)):
66 ctype = 'dnf'
67
68 if (ctype == 'cnf'):
69 conds = text.split('&')
70 else:
71 conds = text.split('|')
72
73 for cond in conds:
74 if (cond.startswith('(')) and (cond.endswith(')')):
75 cond = cond[1:-1]
76
[62bb73e]77 inside = check_inside(cond, config, ctype)
[9a0367f]78
79 if (ctype == 'cnf') and (not inside):
80 return False
81
82 if (ctype == 'dnf') and (inside):
83 return True
84
85 if (ctype == 'cnf'):
86 return True
87 return False
[98376de]88
[62bb73e]89def check_inside(text, config, ctype):
[81c8d54]90 "Check for condition"
[9a0367f]91
92 if (ctype == 'cnf'):
93 conds = text.split('|')
94 else:
95 conds = text.split('&')
96
97 for cond in conds:
98 res = re.match(r'^(.*?)(!?=)(.*)$', cond)
99 if (not res):
100 raise RuntimeError("Invalid condition: %s" % cond)
101
102 condname = res.group(1)
103 oper = res.group(2)
104 condval = res.group(3)
105
[62bb73e]106 if (not condname in config):
[9a0367f]107 varval = ''
108 else:
[62bb73e]109 varval = config[condname]
[7aef7ee]110 if (varval == '*'):
111 varval = 'y'
[9a0367f]112
113 if (ctype == 'cnf'):
114 if (oper == '=') and (condval == varval):
115 return True
116
117 if (oper == '!=') and (condval != varval):
118 return True
119 else:
120 if (oper == '=') and (condval != varval):
121 return False
122
123 if (oper == '!=') and (condval == varval):
124 return False
125
126 if (ctype == 'cnf'):
127 return False
128
129 return True
[98376de]130
[62bb73e]131def parse_rules(fname, rules):
132 "Parse rules file"
[9a0367f]133
[28f4adb]134 inf = open(fname, 'r')
[9a0367f]135
136 name = ''
137 choices = []
138
139 for line in inf:
140
141 if (line.startswith('!')):
142 # Ask a question
143 res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
144
145 if (not res):
146 raise RuntimeError("Weird line: %s" % line)
147
148 cond = res.group(1)
149 varname = res.group(2)
150 vartype = res.group(3)
151
[62bb73e]152 rules.append((varname, vartype, name, choices, cond))
[9a0367f]153 name = ''
154 choices = []
155 continue
156
157 if (line.startswith('@')):
158 # Add new line into the 'choices' array
159 res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
160
161 if not res:
162 raise RuntimeError("Bad line: %s" % line)
163
164 choices.append((res.group(2), res.group(3)))
165 continue
166
167 if (line.startswith('%')):
168 # Name of the option
169 name = line[1:].strip()
170 continue
171
172 if ((line.startswith('#')) or (line == '\n')):
173 # Comment or empty line
174 continue
175
176
177 raise RuntimeError("Unknown syntax: %s" % line)
178
179 inf.close()
[98376de]180
[9a0367f]181def yes_no(default):
182 "Return '*' if yes, ' ' if no"
183
184 if (default == 'y'):
185 return '*'
186
187 return ' '
[98376de]188
[27fb3d6]189def subchoice(screen, name, choices, default):
[9a0367f]190 "Return choice of choices"
191
[27fb3d6]192 maxkey = 0
193 for key, val in choices:
194 length = len(key)
195 if (length > maxkey):
196 maxkey = length
[9a0367f]197
198 options = []
[27fb3d6]199 position = None
200 cnt = 0
201 for key, val in choices:
202 if ((default) and (key == default)):
203 position = cnt
204
205 options.append(" %-*s %s " % (maxkey, key, val))
206 cnt += 1
[9a0367f]207
[27fb3d6]208 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
[9a0367f]209
[27fb3d6]210 if (button == 'cancel'):
[9a0367f]211 return None
212
[27fb3d6]213 return choices[value][0]
[98376de]214
[e4d540b]215## Infer and verify configuration values.
216#
[62bb73e]217# Augment @a config with values that can be inferred, purge invalid ones
[e4d540b]218# and verify that all variables have a value (previously specified or inferred).
219#
[62bb73e]220# @param config Configuration to work on
221# @param rules Rules
[e4d540b]222#
[62bb73e]223# @return True if configuration is complete and valid, False
224# otherwise.
[e4d540b]225#
[62bb73e]226def infer_verify_choices(config, rules):
[e4d540b]227 "Infer and verify configuration values."
[9a0367f]228
[62bb73e]229 for varname, vartype, name, choices, cond in rules:
230 if ((cond) and (not check_condition(cond, config, rules))):
[9a0367f]231 continue
232
[62bb73e]233 if (not varname in config):
[4756634]234 value = None
[e4d540b]235 else:
[4756634]236 value = config[varname]
[e4d540b]237
[4756634]238 if not rule_value_is_valid((varname, vartype, name, choices, cond), value):
239 value = None
[e4d540b]240
[4756634]241 default = rule_get_default((varname, vartype, name, choices, cond))
242 if default != None:
243 config[varname] = default
[e4d540b]244
[62bb73e]245 if (not varname in config):
[9a0367f]246 return False
247
248 return True
[98376de]249
[e4d540b]250## Get default value from a rule.
251def rule_get_default(rule):
252 varname, vartype, name, choices, cond = rule
253
254 default = None
255
256 if (vartype == 'choice'):
257 # If there is just one option, use it
258 if (len(choices) == 1):
259 default = choices[0][0]
260 elif (vartype == 'y'):
261 default = '*'
262 elif (vartype == 'n'):
263 default = 'n'
264 elif (vartype == 'y/n'):
265 default = 'y'
266 elif (vartype == 'n/y'):
267 default = 'n'
268 else:
269 raise RuntimeError("Unknown variable type: %s" % vartype)
270
271 return default
272
273## Get option from a rule.
274#
275# @param rule Rule for a variable
276# @param value Current value of the variable
277#
278# @return Option (string) to ask or None which means not to ask.
279#
280def rule_get_option(rule, value):
281 varname, vartype, name, choices, cond = rule
282
283 option = None
284
285 if (vartype == 'choice'):
286 # If there is just one option, don't ask
287 if (len(choices) != 1):
288 if (value == None):
289 option = "? %s --> " % name
290 else:
291 option = " %s [%s] --> " % (name, value)
292 elif (vartype == 'y'):
293 pass
294 elif (vartype == 'n'):
295 pass
296 elif (vartype == 'y/n'):
297 option = " <%s> %s " % (yes_no(value), name)
298 elif (vartype == 'n/y'):
299 option =" <%s> %s " % (yes_no(value), name)
300 else:
301 raise RuntimeError("Unknown variable type: %s" % vartype)
302
303 return option
304
305## Check if variable value is valid.
306#
307# @param rule Rule for the variable
308# @param value Value of the variable
309#
310# @return True if valid, False if not valid.
311#
312def rule_value_is_valid(rule, value):
313 varname, vartype, name, choices, cond = rule
314
315 if value == None:
316 return True
317
318 if (vartype == 'choice'):
319 if (not value in [choice[0] for choice in choices]):
320 return False
321 elif (vartype == 'y'):
322 if value != 'y':
323 return False
324 elif (vartype == 'n'):
325 if value != 'n':
326 return False
327 elif (vartype == 'y/n'):
328 if not value in ['y', 'n']:
329 return False
330 elif (vartype == 'n/y'):
331 if not value in ['y', 'n']:
332 return False
333 else:
334 raise RuntimeError("Unknown variable type: %s" % vartype)
335
336 return True
337
[62bb73e]338def create_output(mkname, mcname, config, rules):
[9a0367f]339 "Create output configuration"
340
[5a8fbcb9]341 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
[fe12f9f4]342
343 sys.stderr.write("Fetching current revision identifier ... ")
[7b76744]344
345 try:
[28f4adb]346 version = subprocess.Popen(['bzr', 'version-info', '--custom', '--template={clean}:{revno}:{revision_id}'], stdout = subprocess.PIPE).communicate()[0].decode().split(':')
[7b76744]347 sys.stderr.write("ok\n")
348 except:
349 version = [1, "unknown", "unknown"]
350 sys.stderr.write("failed\n")
[5a8fbcb9]351
352 if (len(version) == 3):
353 revision = version[1]
354 if (version[0] != 1):
355 revision += 'M'
356 revision += ' (%s)' % version[2]
357 else:
358 revision = None
[84266669]359
[28f4adb]360 outmk = open(mkname, 'w')
361 outmc = open(mcname, 'w')
[84266669]362
363 outmk.write('#########################################\n')
364 outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
365 outmk.write('#########################################\n\n')
[9a0367f]366
[84266669]367 outmc.write('/***************************************\n')
368 outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
369 outmc.write(' ***************************************/\n\n')
370
[4e9aaf5]371 defs = 'CONFIG_DEFS ='
[9a0367f]372
[62bb73e]373 for varname, vartype, name, choices, cond in rules:
374 if ((cond) and (not check_condition(cond, config, rules))):
[9a0367f]375 continue
376
[62bb73e]377 if (not varname in config):
[4756634]378 value = ''
[9a0367f]379 else:
[4756634]380 value = config[varname]
381 if (value == '*'):
382 value = 'y'
[9a0367f]383
[4756634]384 outmk.write('# %s\n%s = %s\n\n' % (name, varname, value))
[84266669]385
[04b29ca]386 if ((vartype == "y") or (vartype == "n") or (vartype == "y/n") or (vartype == "n/y")):
[4756634]387 if (value == "y"):
[84266669]388 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
[4e9aaf5]389 defs += ' -D%s' % varname
[84266669]390 else:
[4756634]391 outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, value, varname, value))
392 defs += ' -D%s=%s -D%s_%s' % (varname, value, varname, value)
[9a0367f]393
[5a8fbcb9]394 if (revision is not None):
395 outmk.write('REVISION = %s\n' % revision)
396 outmc.write('#define REVISION %s\n' % revision)
[4e9aaf5]397 defs += ' "-DREVISION=%s"' % revision
[84266669]398
[5a8fbcb9]399 outmk.write('TIMESTAMP = %s\n' % timestamp)
[84266669]400 outmc.write('#define TIMESTAMP %s\n' % timestamp)
[4e9aaf5]401 defs += ' "-DTIMESTAMP=%s"\n' % timestamp
402
403 outmk.write(defs)
[84266669]404
405 outmk.close()
406 outmc.close()
[98376de]407
[31fb9a0]408def sorted_dir(root):
409 list = os.listdir(root)
410 list.sort()
411 return list
412
[62bb73e]413## Choose a profile and load configuration presets.
414#
415def load_presets(root, fname, screen, config):
[31fb9a0]416 options = []
417 opt2path = {}
418 cnt = 0
419
420 # Look for profiles
421 for name in sorted_dir(root):
422 path = os.path.join(root, name)
423 canon = os.path.join(path, fname)
424
425 if ((os.path.isdir(path)) and (os.path.exists(canon)) and (os.path.isfile(canon))):
426 subprofile = False
427
428 # Look for subprofiles
429 for subname in sorted_dir(path):
430 subpath = os.path.join(path, subname)
431 subcanon = os.path.join(subpath, fname)
432
433 if ((os.path.isdir(subpath)) and (os.path.exists(subcanon)) and (os.path.isfile(subcanon))):
434 subprofile = True
435 options.append("%s (%s)" % (name, subname))
436 opt2path[cnt] = (canon, subcanon)
437 cnt += 1
438
439 if (not subprofile):
440 options.append(name)
441 opt2path[cnt] = (canon, None)
442 cnt += 1
443
444 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
445
446 if (button == 'cancel'):
447 return None
448
[62bb73e]449 read_config(opt2path[value][0], config)
[31fb9a0]450 if (opt2path[value][1] != None):
[62bb73e]451 read_config(opt2path[value][1], config)
[31fb9a0]452
[98376de]453def main():
[62bb73e]454 config = {}
455 rules = []
[9a0367f]456
[62bb73e]457 # Parse rules file
458 parse_rules(RULES_FILE, rules)
[9a0367f]459
[62bb73e]460 # Read configuration from previous run
[84266669]461 if os.path.exists(MAKEFILE):
[62bb73e]462 read_config(MAKEFILE, config)
[9a0367f]463
[62bb73e]464 # Default mode: only check values and regenerate configuration files
[9a0367f]465 if ((len(sys.argv) >= 3) and (sys.argv[2] == 'default')):
[62bb73e]466 if (infer_verify_choices(config, rules)):
467 create_output(MAKEFILE, MACROS, config, rules)
[9a0367f]468 return 0
469
[62bb73e]470 # Check mode: only check configuration
[48c3d50]471 if ((len(sys.argv) >= 3) and (sys.argv[2] == 'check')):
[62bb73e]472 if (infer_verify_choices(config, rules)):
[48c3d50]473 return 0
474 return 1
475
[27fb3d6]476 screen = xtui.screen_init()
[9a0367f]477 try:
478 selname = None
[31fb9a0]479 position = None
[9a0367f]480 while True:
481
[62bb73e]482 # Cancel out all values which have to be deduced
483 for varname, vartype, name, choices, cond in rules:
484 if ((vartype == 'y') and (varname in config) and (config[varname] == '*')):
485 config[varname] = None
[81c8d54]486
[9a0367f]487 options = []
488 opt2row = {}
[31fb9a0]489 cnt = 1
490
491 options.append(" --- Load preconfigured defaults ... ")
492
[62bb73e]493 for rule in rules:
[e4d540b]494 varname, vartype, name, choices, cond = rule
[9a0367f]495
[62bb73e]496 if ((cond) and (not check_condition(cond, config, rules))):
[9a0367f]497 continue
498
499 if (varname == selname):
500 position = cnt
501
[62bb73e]502 if (not varname in config):
[4756634]503 value = None
[9a0367f]504 else:
[4756634]505 value = config[varname]
[9a0367f]506
[4756634]507 if not rule_value_is_valid(rule, value):
508 value = None
[e4d540b]509
[4756634]510 default = rule_get_default(rule)
511 if default != None:
512 value = default
513 config[varname] = default
[e4d540b]514
[4756634]515 option = rule_get_option(rule, value)
[e4d540b]516 if option != None:
517 options.append(option)
[9a0367f]518
[27fb3d6]519 opt2row[cnt] = (varname, vartype, name, choices)
[9a0367f]520
521 cnt += 1
522
[28f4adb]523 if (position != None) and (position >= len(options)):
[31fb9a0]524 position = None
525
[27fb3d6]526 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
[9a0367f]527
[27fb3d6]528 if (button == 'cancel'):
[9a0367f]529 return 'Configuration canceled'
530
[6346efd]531 if (button == 'done'):
[62bb73e]532 if (infer_verify_choices(config, rules)):
[6346efd]533 break
534 else:
535 xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
536 continue
537
[31fb9a0]538 if (value == 0):
[62bb73e]539 load_presets(PRESETS_DIR, MAKEFILE, screen, config)
[31fb9a0]540 position = 1
541 continue
542
543 position = None
[28f4adb]544 if (not value in opt2row):
[27fb3d6]545 raise RuntimeError("Error selecting value: %s" % value)
546
547 (selname, seltype, name, choices) = opt2row[value]
[9a0367f]548
[62bb73e]549 if (not selname in config):
[4756634]550 value = None
[27fb3d6]551 else:
[4756634]552 value = config[selname]
[9a0367f]553
554 if (seltype == 'choice'):
[4756634]555 config[selname] = subchoice(screen, name, choices, value)
[9a0367f]556 elif ((seltype == 'y/n') or (seltype == 'n/y')):
[62bb73e]557 if (config[selname] == 'y'):
558 config[selname] = 'n'
[9a0367f]559 else:
[62bb73e]560 config[selname] = 'y'
[9a0367f]561 finally:
[27fb3d6]562 xtui.screen_done(screen)
[9a0367f]563
[62bb73e]564 create_output(MAKEFILE, MACROS, config, rules)
[9a0367f]565 return 0
[98376de]566
567if __name__ == '__main__':
[43a10c4]568 sys.exit(main())
Note: See TracBrowser for help on using the repository browser.