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
RevLine 
[98376de]1#!/usr/bin/env python
[44882c8]2#
[5a55ae6]3# Copyright (c) 2006 Ondrej Palkovsky
[9a0367f]4# Copyright (c) 2009 Martin Decky
[44882c8]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#
[3c80f2b]30
[98376de]31"""
[9a0367f]32HelenOS configuration system
[98376de]33"""
[3c80f2b]34
[98376de]35import sys
36import os
37import re
[5a8fbcb9]38import time
39import subprocess
[27fb3d6]40import xtui
[98376de]41
[41f7564]42INPUT = sys.argv[1]
[84266669]43MAKEFILE = 'Makefile.config'
44MACROS = 'config.h'
[31fb9a0]45PRECONF = 'defaults'
[98376de]46
[9a0367f]47def read_defaults(fname, defaults):
48 "Read saved values from last configuration run"
49
[28f4adb]50 inf = open(fname, 'r')
[9a0367f]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()
[98376de]58
[9a0367f]59def check_condition(text, defaults, ask_names):
[81c8d54]60 "Check that the condition specified on input line is True (only CNF and DNF is supported)"
[9a0367f]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
[98376de]87
[9a0367f]88def check_inside(text, defaults, ctype):
[81c8d54]89 "Check for condition"
[9a0367f]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
[28f4adb]105 if (not condname in defaults):
[9a0367f]106 varval = ''
107 else:
108 varval = defaults[condname]
[7aef7ee]109 if (varval == '*'):
110 varval = 'y'
[9a0367f]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
[98376de]129
[9a0367f]130def parse_config(fname, ask_names):
131 "Parse configuration file"
132
[28f4adb]133 inf = open(fname, 'r')
[9a0367f]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()
[98376de]179
[9a0367f]180def yes_no(default):
181 "Return '*' if yes, ' ' if no"
182
183 if (default == 'y'):
184 return '*'
185
186 return ' '
[98376de]187
[27fb3d6]188def subchoice(screen, name, choices, default):
[9a0367f]189 "Return choice of choices"
190
[27fb3d6]191 maxkey = 0
192 for key, val in choices:
193 length = len(key)
194 if (length > maxkey):
195 maxkey = length
[9a0367f]196
197 options = []
[27fb3d6]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
[9a0367f]206
[27fb3d6]207 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
[9a0367f]208
[27fb3d6]209 if (button == 'cancel'):
[9a0367f]210 return None
211
[27fb3d6]212 return choices[value][0]
[98376de]213
[e4d540b]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."
[9a0367f]227
[27fb3d6]228 for varname, vartype, name, choices, cond in ask_names:
[9a0367f]229 if ((cond) and (not check_condition(cond, defaults, ask_names))):
230 continue
231
[e4d540b]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
[28f4adb]245 if (not varname in defaults):
[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
[4e9aaf5]338def create_output(mkname, mcname, defaults, ask_names):
[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
[27fb3d6]373 for varname, vartype, name, choices, cond in ask_names:
[9a0367f]374 if ((cond) and (not check_condition(cond, defaults, ask_names))):
375 continue
376
[28f4adb]377 if (not varname in defaults):
[9a0367f]378 default = ''
379 else:
380 default = defaults[varname]
[7aef7ee]381 if (default == '*'):
382 default = 'y'
[9a0367f]383
[84266669]384 outmk.write('# %s\n%s = %s\n\n' % (name, varname, default))
385
[04b29ca]386 if ((vartype == "y") or (vartype == "n") or (vartype == "y/n") or (vartype == "n/y")):
[84266669]387 if (default == "y"):
388 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
[4e9aaf5]389 defs += ' -D%s' % varname
[84266669]390 else:
[06da55b]391 outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, default, varname, default))
[4e9aaf5]392 defs += ' -D%s=%s -D%s_%s' % (varname, default, varname, default)
[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
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
[98376de]451def main():
[9a0367f]452 defaults = {}
453 ask_names = []
454
455 # Parse configuration file
456 parse_config(INPUT, ask_names)
457
458 # Read defaults from previous run
[84266669]459 if os.path.exists(MAKEFILE):
460 read_defaults(MAKEFILE, defaults)
[9a0367f]461
462 # Default mode: only check defaults and regenerate configuration
463 if ((len(sys.argv) >= 3) and (sys.argv[2] == 'default')):
[e4d540b]464 if (infer_verify_choices(defaults, ask_names)):
[4e9aaf5]465 create_output(MAKEFILE, MACROS, defaults, ask_names)
[9a0367f]466 return 0
467
[48c3d50]468 # Check mode: only check defaults
469 if ((len(sys.argv) >= 3) and (sys.argv[2] == 'check')):
[e4d540b]470 if (infer_verify_choices(defaults, ask_names)):
[48c3d50]471 return 0
472 return 1
473
[27fb3d6]474 screen = xtui.screen_init()
[9a0367f]475 try:
476 selname = None
[31fb9a0]477 position = None
[9a0367f]478 while True:
479
[81c8d54]480 # Cancel out all defaults which have to be deduced
481 for varname, vartype, name, choices, cond in ask_names:
[28f4adb]482 if ((vartype == 'y') and (varname in defaults) and (defaults[varname] == '*')):
[81c8d54]483 defaults[varname] = None
484
[9a0367f]485 options = []
486 opt2row = {}
[31fb9a0]487 cnt = 1
488
489 options.append(" --- Load preconfigured defaults ... ")
490
[e4d540b]491 for rule in ask_names:
492 varname, vartype, name, choices, cond = rule
[9a0367f]493
494 if ((cond) and (not check_condition(cond, defaults, ask_names))):
495 continue
496
497 if (varname == selname):
498 position = cnt
499
[28f4adb]500 if (not varname in defaults):
[9a0367f]501 default = None
502 else:
503 default = defaults[varname]
504
[e4d540b]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)
[9a0367f]516
[27fb3d6]517 opt2row[cnt] = (varname, vartype, name, choices)
[9a0367f]518
519 cnt += 1
520
[28f4adb]521 if (position != None) and (position >= len(options)):
[31fb9a0]522 position = None
523
[27fb3d6]524 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
[9a0367f]525
[27fb3d6]526 if (button == 'cancel'):
[9a0367f]527 return 'Configuration canceled'
528
[6346efd]529 if (button == 'done'):
[e4d540b]530 if (infer_verify_choices(defaults, ask_names)):
[6346efd]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
[31fb9a0]536 if (value == 0):
537 read_preconfigured(PRECONF, MAKEFILE, screen, defaults)
538 position = 1
539 continue
540
541 position = None
[28f4adb]542 if (not value in opt2row):
[27fb3d6]543 raise RuntimeError("Error selecting value: %s" % value)
544
545 (selname, seltype, name, choices) = opt2row[value]
[9a0367f]546
[28f4adb]547 if (not selname in defaults):
[e4d540b]548 default = None
[27fb3d6]549 else:
550 default = defaults[selname]
[9a0367f]551
552 if (seltype == 'choice'):
[27fb3d6]553 defaults[selname] = subchoice(screen, name, choices, default)
[9a0367f]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:
[27fb3d6]560 xtui.screen_done(screen)
[9a0367f]561
[4e9aaf5]562 create_output(MAKEFILE, MACROS, defaults, ask_names)
[9a0367f]563 return 0
[98376de]564
565if __name__ == '__main__':
[43a10c4]566 sys.exit(main())
Note: See TracBrowser for help on using the repository browser.