source: mainline/tools/config.py@ 8fe3f832

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 8fe3f832 was 8fe3f832, checked in by Martin Decky <martin@…>, 15 years ago

fix interactive configuration which was broken in r736

  • Property mode set to 100755
File size: 14.6 KB
Line 
1#!/usr/bin/env python
2#
3# Copyright (c) 2006 Ondrej Palkovsky
4# Copyright (c) 2009 Martin Decky
5# Copyright (c) 2010 Jiri Svoboda
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#
31
32"""
33HelenOS configuration system
34"""
35
36import sys
37import os
38import re
39import time
40import subprocess
41import xtui
42
43RULES_FILE = sys.argv[1]
44MAKEFILE = 'Makefile.config'
45MACROS = 'config.h'
46PRESETS_DIR = 'defaults'
47
48def read_config(fname, config):
49 "Read saved values from last configuration run"
50
51 inf = open(fname, 'r')
52
53 for line in inf:
54 res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
55 if res:
56 config[res.group(1)] = res.group(2)
57
58 inf.close()
59
60def check_condition(text, config, rules):
61 "Check that the condition specified on input line is True (only CNF and DNF is supported)"
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
77 inside = check_inside(cond, config, ctype)
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
88
89def check_inside(text, config, ctype):
90 "Check for condition"
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
106 if not condname in config:
107 varval = ''
108 else:
109 varval = config[condname]
110 if (varval == '*'):
111 varval = 'y'
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
130
131def parse_rules(fname, rules):
132 "Parse rules file"
133
134 inf = open(fname, 'r')
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
152 rules.append((varname, vartype, name, choices, cond))
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()
180
181def yes_no(default):
182 "Return '*' if yes, ' ' if no"
183
184 if default == 'y':
185 return '*'
186
187 return ' '
188
189def subchoice(screen, name, choices, default):
190 "Return choice of choices"
191
192 maxkey = 0
193 for key, val in choices:
194 length = len(key)
195 if (length > maxkey):
196 maxkey = length
197
198 options = []
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
207
208 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
209
210 if button == 'cancel':
211 return None
212
213 return choices[value][0]
214
215## Infer and verify configuration values.
216#
217# Augment @a config with values that can be inferred, purge invalid ones
218# and verify that all variables have a value (previously specified or inferred).
219#
220# @param config Configuration to work on
221# @param rules Rules
222#
223# @return True if configuration is complete and valid, False
224# otherwise.
225#
226def infer_verify_choices(config, rules):
227 "Infer and verify configuration values."
228
229 for rule in rules:
230 varname, vartype, name, choices, cond = rule
231
232 if cond and (not check_condition(cond, config, rules)):
233 continue
234
235 if not varname in config:
236 value = None
237 else:
238 value = config[varname]
239
240 if not rule_value_is_valid(rule, value):
241 value = None
242
243 default = rule_get_default(rule)
244 if default != None:
245 config[varname] = default
246
247 if not varname in config:
248 return False
249
250 return True
251
252## Get default value from a rule.
253def rule_get_default(rule):
254 varname, vartype, name, choices, cond = rule
255
256 default = None
257
258 if vartype == 'choice':
259 # If there is just one option, use it
260 if len(choices) == 1:
261 default = choices[0][0]
262 elif vartype == 'y':
263 default = '*'
264 elif vartype == 'n':
265 default = 'n'
266 elif vartype == 'y/n':
267 default = 'y'
268 elif vartype == 'n/y':
269 default = 'n'
270 else:
271 raise RuntimeError("Unknown variable type: %s" % vartype)
272
273 return default
274
275## Get option from a rule.
276#
277# @param rule Rule for a variable
278# @param value Current value of the variable
279#
280# @return Option (string) to ask or None which means not to ask.
281#
282def rule_get_option(rule, value):
283 varname, vartype, name, choices, cond = rule
284
285 option = None
286
287 if vartype == 'choice':
288 # If there is just one option, don't ask
289 if len(choices) != 1:
290 if (value == None):
291 option = "? %s --> " % name
292 else:
293 option = " %s [%s] --> " % (name, value)
294 elif vartype == 'y':
295 pass
296 elif vartype == 'n':
297 pass
298 elif vartype == 'y/n':
299 option = " <%s> %s " % (yes_no(value), name)
300 elif vartype == 'n/y':
301 option =" <%s> %s " % (yes_no(value), name)
302 else:
303 raise RuntimeError("Unknown variable type: %s" % vartype)
304
305 return option
306
307## Check if variable value is valid.
308#
309# @param rule Rule for the variable
310# @param value Value of the variable
311#
312# @return True if valid, False if not valid.
313#
314def rule_value_is_valid(rule, value):
315 varname, vartype, name, choices, cond = rule
316
317 if value == None:
318 return True
319
320 if vartype == 'choice':
321 if not value in [choice[0] for choice in choices]:
322 return False
323 elif vartype == 'y':
324 if value != 'y':
325 return False
326 elif vartype == 'n':
327 if value != 'n':
328 return False
329 elif vartype == 'y/n':
330 if not value in ['y', 'n']:
331 return False
332 elif vartype == 'n/y':
333 if not value in ['y', 'n']:
334 return False
335 else:
336 raise RuntimeError("Unknown variable type: %s" % vartype)
337
338 return True
339
340def create_output(mkname, mcname, config, rules):
341 "Create output configuration"
342
343 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
344
345 sys.stderr.write("Fetching current revision identifier ... ")
346
347 try:
348 version = subprocess.Popen(['bzr', 'version-info', '--custom', '--template={clean}:{revno}:{revision_id}'], stdout = subprocess.PIPE).communicate()[0].decode().split(':')
349 sys.stderr.write("ok\n")
350 except:
351 version = [1, "unknown", "unknown"]
352 sys.stderr.write("failed\n")
353
354 if len(version) == 3:
355 revision = version[1]
356 if version[0] != 1:
357 revision += 'M'
358 revision += ' (%s)' % version[2]
359 else:
360 revision = None
361
362 outmk = open(mkname, 'w')
363 outmc = open(mcname, 'w')
364
365 outmk.write('#########################################\n')
366 outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
367 outmk.write('#########################################\n\n')
368
369 outmc.write('/***************************************\n')
370 outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
371 outmc.write(' ***************************************/\n\n')
372
373 defs = 'CONFIG_DEFS ='
374
375 for varname, vartype, name, choices, cond in rules:
376 if cond and (not check_condition(cond, config, rules)):
377 continue
378
379 if not varname in config:
380 value = ''
381 else:
382 value = config[varname]
383 if (value == '*'):
384 value = 'y'
385
386 outmk.write('# %s\n%s = %s\n\n' % (name, varname, value))
387
388 if vartype in ["y", "n", "y/n", "n/y"]:
389 if value == "y":
390 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
391 defs += ' -D%s' % varname
392 else:
393 outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, value, varname, value))
394 defs += ' -D%s=%s -D%s_%s' % (varname, value, varname, value)
395
396 if revision is not None:
397 outmk.write('REVISION = %s\n' % revision)
398 outmc.write('#define REVISION %s\n' % revision)
399 defs += ' "-DREVISION=%s"' % revision
400
401 outmk.write('TIMESTAMP = %s\n' % timestamp)
402 outmc.write('#define TIMESTAMP %s\n' % timestamp)
403 defs += ' "-DTIMESTAMP=%s"\n' % timestamp
404
405 outmk.write(defs)
406
407 outmk.close()
408 outmc.close()
409
410def sorted_dir(root):
411 list = os.listdir(root)
412 list.sort()
413 return list
414
415## Ask user to choose a configuration profile.
416#
417def profile_choose(root, fname, screen, config):
418 options = []
419 opt2path = {}
420 cnt = 0
421
422 # Look for profiles
423 for name in sorted_dir(root):
424 path = os.path.join(root, name)
425 canon = os.path.join(path, fname)
426
427 if os.path.isdir(path) and os.path.exists(canon) and os.path.isfile(canon):
428 subprofile = False
429
430 # Look for subprofiles
431 for subname in sorted_dir(path):
432 subpath = os.path.join(path, subname)
433 subcanon = os.path.join(subpath, fname)
434
435 if os.path.isdir(subpath) and os.path.exists(subcanon) and os.path.isfile(subcanon):
436 subprofile = True
437 options.append("%s (%s)" % (name, subname))
438 opt2path[cnt] = [name, subname]
439 cnt += 1
440
441 if not subprofile:
442 options.append(name)
443 opt2path[cnt] = [name]
444 cnt += 1
445
446 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
447
448 if button == 'cancel':
449 return None
450
451 return opt2path[value]
452
453## Read presets from a configuration profile.
454#
455# @param profile Profile to load from (a list of string components)
456# @param config Output configuration
457#
458def presets_read(profile, config):
459 path = os.path.join(PRESETS_DIR, profile[0], MAKEFILE)
460 read_config(path, config)
461
462 if len(profile) > 1:
463 path = os.path.join(PRESETS_DIR, profile[0], profile[1], MAKEFILE)
464 read_config(path, config)
465
466## Parse profile name (relative OS path) into a list of components.
467#
468# @param profile_name Relative path (using OS separator)
469# @return List of components
470#
471def parse_profile_name(profile_name):
472 profile = []
473
474 head, tail = os.path.split(profile_name)
475 if head != '':
476 profile.append(head)
477
478 profile.append(tail)
479 return profile
480
481def main():
482 profile = None
483 config = {}
484 rules = []
485
486 # Parse rules file
487 parse_rules(RULES_FILE, rules)
488
489 # Input configuration file can be specified on command line
490 # otherwise configuration from previous run is used.
491 if len(sys.argv) >= 4:
492 profile = parse_profile_name(sys.argv[3])
493 presets_read(profile, config)
494 elif os.path.exists(MAKEFILE):
495 read_config(MAKEFILE, config)
496
497 # Default mode: only check values and regenerate configuration files
498 if (len(sys.argv) >= 3) and (sys.argv[2] == 'default'):
499 if (infer_verify_choices(config, rules)):
500 create_output(MAKEFILE, MACROS, config, rules)
501 return 0
502
503 # Check mode: only check configuration
504 if (len(sys.argv) >= 3) and (sys.argv[2] == 'check'):
505 if infer_verify_choices(config, rules):
506 return 0
507 return 1
508
509 screen = xtui.screen_init()
510 try:
511 selname = None
512 position = None
513 while True:
514
515 # Cancel out all values which have to be deduced
516 for varname, vartype, name, choices, cond in rules:
517 if (vartype == 'y') and (varname in config) and (config[varname] == '*'):
518 config[varname] = None
519
520 options = []
521 opt2row = {}
522 cnt = 1
523
524 options.append(" --- Load preconfigured defaults ... ")
525
526 for rule in rules:
527 varname, vartype, name, choices, cond = rule
528
529 if cond and (not check_condition(cond, config, rules)):
530 continue
531
532 if varname == selname:
533 position = cnt
534
535 if not varname in config:
536 value = None
537 else:
538 value = config[varname]
539
540 if not rule_value_is_valid(rule, value):
541 value = None
542
543 default = rule_get_default(rule)
544 if default != None:
545 if value == None:
546 value = default
547 config[varname] = value
548
549 option = rule_get_option(rule, value)
550 if option != None:
551 options.append(option)
552 else:
553 continue
554
555 opt2row[cnt] = (varname, vartype, name, choices)
556
557 cnt += 1
558
559 if (position != None) and (position >= len(options)):
560 position = None
561
562 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
563
564 if button == 'cancel':
565 return 'Configuration canceled'
566
567 if button == 'done':
568 if (infer_verify_choices(config, rules)):
569 break
570 else:
571 xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
572 continue
573
574 if value == 0:
575 profile = profile_choose(PRESETS_DIR, MAKEFILE, screen, config)
576 if profile != None:
577 presets_read(profile, config)
578 position = 1
579 continue
580
581 position = None
582 if not value in opt2row:
583 raise RuntimeError("Error selecting value: %s" % value)
584
585 (selname, seltype, name, choices) = opt2row[value]
586
587 if not selname in config:
588 value = None
589 else:
590 value = config[selname]
591
592 if seltype == 'choice':
593 config[selname] = subchoice(screen, name, choices, value)
594 elif (seltype == 'y/n') or (seltype == 'n/y'):
595 if config[selname] == 'y':
596 config[selname] = 'n'
597 else:
598 config[selname] = 'y'
599 finally:
600 xtui.screen_done(screen)
601
602 create_output(MAKEFILE, MACROS, config, rules)
603 return 0
604
605if __name__ == '__main__':
606 sys.exit(main())
Note: See TracBrowser for help on using the repository browser.