source: mainline/tools/config.py@ e4d540b

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

Endow 'config.py default' with the same inference abilities as the interactive variant. Factor out vartype-specific code.

  • Property mode set to 100755
File size: 14.1 KB
Line 
1#!/usr/bin/env python
2#
3# Copyright (c) 2006 Ondrej Palkovsky
4# Copyright (c) 2009 Martin Decky
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10#
11# - Redistributions of source code must retain the above copyright
12# notice, this list of conditions and the following disclaimer.
13# - Redistributions in binary form must reproduce the above copyright
14# notice, this list of conditions and the following disclaimer in the
15# documentation and/or other materials provided with the distribution.
16# - The name of the author may not be used to endorse or promote products
17# derived from this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29#
30
31"""
32HelenOS configuration system
33"""
34
35import sys
36import os
37import re
38import time
39import subprocess
40import xtui
41
42INPUT = sys.argv[1]
43MAKEFILE = 'Makefile.config'
44MACROS = 'config.h'
45PRECONF = 'defaults'
46
47def read_defaults(fname, defaults):
48 "Read saved values from last configuration run"
49
50 inf = open(fname, 'r')
51
52 for line in inf:
53 res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
54 if (res):
55 defaults[res.group(1)] = res.group(2)
56
57 inf.close()
58
59def check_condition(text, defaults, ask_names):
60 "Check that the condition specified on input line is True (only CNF and DNF is supported)"
61
62 ctype = 'cnf'
63
64 if ((')|' in text) or ('|(' in text)):
65 ctype = 'dnf'
66
67 if (ctype == 'cnf'):
68 conds = text.split('&')
69 else:
70 conds = text.split('|')
71
72 for cond in conds:
73 if (cond.startswith('(')) and (cond.endswith(')')):
74 cond = cond[1:-1]
75
76 inside = check_inside(cond, defaults, ctype)
77
78 if (ctype == 'cnf') and (not inside):
79 return False
80
81 if (ctype == 'dnf') and (inside):
82 return True
83
84 if (ctype == 'cnf'):
85 return True
86 return False
87
88def check_inside(text, defaults, ctype):
89 "Check for condition"
90
91 if (ctype == 'cnf'):
92 conds = text.split('|')
93 else:
94 conds = text.split('&')
95
96 for cond in conds:
97 res = re.match(r'^(.*?)(!?=)(.*)$', cond)
98 if (not res):
99 raise RuntimeError("Invalid condition: %s" % cond)
100
101 condname = res.group(1)
102 oper = res.group(2)
103 condval = res.group(3)
104
105 if (not condname in defaults):
106 varval = ''
107 else:
108 varval = defaults[condname]
109 if (varval == '*'):
110 varval = 'y'
111
112 if (ctype == 'cnf'):
113 if (oper == '=') and (condval == varval):
114 return True
115
116 if (oper == '!=') and (condval != varval):
117 return True
118 else:
119 if (oper == '=') and (condval != varval):
120 return False
121
122 if (oper == '!=') and (condval == varval):
123 return False
124
125 if (ctype == 'cnf'):
126 return False
127
128 return True
129
130def parse_config(fname, ask_names):
131 "Parse configuration file"
132
133 inf = open(fname, 'r')
134
135 name = ''
136 choices = []
137
138 for line in inf:
139
140 if (line.startswith('!')):
141 # Ask a question
142 res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
143
144 if (not res):
145 raise RuntimeError("Weird line: %s" % line)
146
147 cond = res.group(1)
148 varname = res.group(2)
149 vartype = res.group(3)
150
151 ask_names.append((varname, vartype, name, choices, cond))
152 name = ''
153 choices = []
154 continue
155
156 if (line.startswith('@')):
157 # Add new line into the 'choices' array
158 res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
159
160 if not res:
161 raise RuntimeError("Bad line: %s" % line)
162
163 choices.append((res.group(2), res.group(3)))
164 continue
165
166 if (line.startswith('%')):
167 # Name of the option
168 name = line[1:].strip()
169 continue
170
171 if ((line.startswith('#')) or (line == '\n')):
172 # Comment or empty line
173 continue
174
175
176 raise RuntimeError("Unknown syntax: %s" % line)
177
178 inf.close()
179
180def yes_no(default):
181 "Return '*' if yes, ' ' if no"
182
183 if (default == 'y'):
184 return '*'
185
186 return ' '
187
188def subchoice(screen, name, choices, default):
189 "Return choice of choices"
190
191 maxkey = 0
192 for key, val in choices:
193 length = len(key)
194 if (length > maxkey):
195 maxkey = length
196
197 options = []
198 position = None
199 cnt = 0
200 for key, val in choices:
201 if ((default) and (key == default)):
202 position = cnt
203
204 options.append(" %-*s %s " % (maxkey, key, val))
205 cnt += 1
206
207 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
208
209 if (button == 'cancel'):
210 return None
211
212 return choices[value][0]
213
214## Infer and verify configuration values.
215#
216# Augment @a defaults with values that can be inferred, purge invalid ones
217# and verify that all variables have a value (previously specified or inferred).
218#
219# @param defaults Configuration to work on
220# @param ask_names Rules
221#
222# @return True if configuration is complete and valid, False
223# otherwise.
224#
225def infer_verify_choices(defaults, ask_names):
226 "Infer and verify configuration values."
227
228 for varname, vartype, name, choices, cond in ask_names:
229 if ((cond) and (not check_condition(cond, defaults, ask_names))):
230 continue
231
232 if (not varname in defaults):
233 default = None
234 else:
235 default = defaults[varname]
236
237 if not rule_value_is_valid((varname, vartype, name, choices, cond), default):
238 default = None
239
240 rdef = rule_get_default((varname, vartype, name, choices, cond))
241 if rdef != None:
242 default = rdef
243 defaults[varname] = rdef
244
245 if (not varname in defaults):
246 return False
247
248 return True
249
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
338def create_output(mkname, mcname, defaults, ask_names):
339 "Create output configuration"
340
341 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
342
343 sys.stderr.write("Fetching current revision identifier ... ")
344
345 try:
346 version = subprocess.Popen(['bzr', 'version-info', '--custom', '--template={clean}:{revno}:{revision_id}'], stdout = subprocess.PIPE).communicate()[0].decode().split(':')
347 sys.stderr.write("ok\n")
348 except:
349 version = [1, "unknown", "unknown"]
350 sys.stderr.write("failed\n")
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
359
360 outmk = open(mkname, 'w')
361 outmc = open(mcname, 'w')
362
363 outmk.write('#########################################\n')
364 outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
365 outmk.write('#########################################\n\n')
366
367 outmc.write('/***************************************\n')
368 outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
369 outmc.write(' ***************************************/\n\n')
370
371 defs = 'CONFIG_DEFS ='
372
373 for varname, vartype, name, choices, cond in ask_names:
374 if ((cond) and (not check_condition(cond, defaults, ask_names))):
375 continue
376
377 if (not varname in defaults):
378 default = ''
379 else:
380 default = defaults[varname]
381 if (default == '*'):
382 default = 'y'
383
384 outmk.write('# %s\n%s = %s\n\n' % (name, varname, default))
385
386 if ((vartype == "y") or (vartype == "n") or (vartype == "y/n") or (vartype == "n/y")):
387 if (default == "y"):
388 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
389 defs += ' -D%s' % varname
390 else:
391 outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, default, varname, default))
392 defs += ' -D%s=%s -D%s_%s' % (varname, default, varname, default)
393
394 if (revision is not None):
395 outmk.write('REVISION = %s\n' % revision)
396 outmc.write('#define REVISION %s\n' % revision)
397 defs += ' "-DREVISION=%s"' % revision
398
399 outmk.write('TIMESTAMP = %s\n' % timestamp)
400 outmc.write('#define TIMESTAMP %s\n' % timestamp)
401 defs += ' "-DTIMESTAMP=%s"\n' % timestamp
402
403 outmk.write(defs)
404
405 outmk.close()
406 outmc.close()
407
408def sorted_dir(root):
409 list = os.listdir(root)
410 list.sort()
411 return list
412
413def read_preconfigured(root, fname, screen, defaults):
414 options = []
415 opt2path = {}
416 cnt = 0
417
418 # Look for profiles
419 for name in sorted_dir(root):
420 path = os.path.join(root, name)
421 canon = os.path.join(path, fname)
422
423 if ((os.path.isdir(path)) and (os.path.exists(canon)) and (os.path.isfile(canon))):
424 subprofile = False
425
426 # Look for subprofiles
427 for subname in sorted_dir(path):
428 subpath = os.path.join(path, subname)
429 subcanon = os.path.join(subpath, fname)
430
431 if ((os.path.isdir(subpath)) and (os.path.exists(subcanon)) and (os.path.isfile(subcanon))):
432 subprofile = True
433 options.append("%s (%s)" % (name, subname))
434 opt2path[cnt] = (canon, subcanon)
435 cnt += 1
436
437 if (not subprofile):
438 options.append(name)
439 opt2path[cnt] = (canon, None)
440 cnt += 1
441
442 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
443
444 if (button == 'cancel'):
445 return None
446
447 read_defaults(opt2path[value][0], defaults)
448 if (opt2path[value][1] != None):
449 read_defaults(opt2path[value][1], defaults)
450
451def main():
452 defaults = {}
453 ask_names = []
454
455 # Parse configuration file
456 parse_config(INPUT, ask_names)
457
458 # Read defaults from previous run
459 if os.path.exists(MAKEFILE):
460 read_defaults(MAKEFILE, defaults)
461
462 # Default mode: only check defaults and regenerate configuration
463 if ((len(sys.argv) >= 3) and (sys.argv[2] == 'default')):
464 if (infer_verify_choices(defaults, ask_names)):
465 create_output(MAKEFILE, MACROS, defaults, ask_names)
466 return 0
467
468 # Check mode: only check defaults
469 if ((len(sys.argv) >= 3) and (sys.argv[2] == 'check')):
470 if (infer_verify_choices(defaults, ask_names)):
471 return 0
472 return 1
473
474 screen = xtui.screen_init()
475 try:
476 selname = None
477 position = None
478 while True:
479
480 # Cancel out all defaults which have to be deduced
481 for varname, vartype, name, choices, cond in ask_names:
482 if ((vartype == 'y') and (varname in defaults) and (defaults[varname] == '*')):
483 defaults[varname] = None
484
485 options = []
486 opt2row = {}
487 cnt = 1
488
489 options.append(" --- Load preconfigured defaults ... ")
490
491 for rule in ask_names:
492 varname, vartype, name, choices, cond = rule
493
494 if ((cond) and (not check_condition(cond, defaults, ask_names))):
495 continue
496
497 if (varname == selname):
498 position = cnt
499
500 if (not varname in defaults):
501 default = None
502 else:
503 default = defaults[varname]
504
505 if not rule_value_is_valid(rule, default):
506 default = None
507
508 rdef = rule_get_default(rule)
509 if rdef != None:
510 default = rdef
511 defaults[varname] = rdef
512
513 option = rule_get_option(rule, default)
514 if option != None:
515 options.append(option)
516
517 opt2row[cnt] = (varname, vartype, name, choices)
518
519 cnt += 1
520
521 if (position != None) and (position >= len(options)):
522 position = None
523
524 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
525
526 if (button == 'cancel'):
527 return 'Configuration canceled'
528
529 if (button == 'done'):
530 if (infer_verify_choices(defaults, ask_names)):
531 break
532 else:
533 xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
534 continue
535
536 if (value == 0):
537 read_preconfigured(PRECONF, MAKEFILE, screen, defaults)
538 position = 1
539 continue
540
541 position = None
542 if (not value in opt2row):
543 raise RuntimeError("Error selecting value: %s" % value)
544
545 (selname, seltype, name, choices) = opt2row[value]
546
547 if (not selname in defaults):
548 default = None
549 else:
550 default = defaults[selname]
551
552 if (seltype == 'choice'):
553 defaults[selname] = subchoice(screen, name, choices, default)
554 elif ((seltype == 'y/n') or (seltype == 'n/y')):
555 if (defaults[selname] == 'y'):
556 defaults[selname] = 'n'
557 else:
558 defaults[selname] = 'y'
559 finally:
560 xtui.screen_done(screen)
561
562 create_output(MAKEFILE, MACROS, defaults, ask_names)
563 return 0
564
565if __name__ == '__main__':
566 sys.exit(main())
Note: See TracBrowser for help on using the repository browser.