source: mainline/tools/config.py@ 860a7bb

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 860a7bb was 41e871f, checked in by Martin Sucha <sucha14@…>, 12 years ago

Add unix timestamp into defines

  • Property mode set to 100755
File size: 16.4 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 or a preset file"
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
88 return False
89
90def check_inside(text, config, ctype):
91 "Check for condition"
92
93 if ctype == 'cnf':
94 conds = text.split('|')
95 else:
96 conds = text.split('&')
97
98 for cond in conds:
99 res = re.match(r'^(.*?)(!?=)(.*)$', cond)
100 if not res:
101 raise RuntimeError("Invalid condition: %s" % cond)
102
103 condname = res.group(1)
104 oper = res.group(2)
105 condval = res.group(3)
106
107 if not condname in config:
108 varval = ''
109 else:
110 varval = config[condname]
111 if (varval == '*'):
112 varval = 'y'
113
114 if ctype == 'cnf':
115 if (oper == '=') and (condval == varval):
116 return True
117
118 if (oper == '!=') and (condval != varval):
119 return True
120 else:
121 if (oper == '=') and (condval != varval):
122 return False
123
124 if (oper == '!=') and (condval == varval):
125 return False
126
127 if ctype == 'cnf':
128 return False
129
130 return True
131
132def parse_rules(fname, rules):
133 "Parse rules file"
134
135 inf = open(fname, 'r')
136
137 name = ''
138 choices = []
139
140 for line in inf:
141
142 if line.startswith('!'):
143 # Ask a question
144 res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
145
146 if not res:
147 raise RuntimeError("Weird line: %s" % line)
148
149 cond = res.group(1)
150 varname = res.group(2)
151 vartype = res.group(3)
152
153 rules.append((varname, vartype, name, choices, cond))
154 name = ''
155 choices = []
156 continue
157
158 if line.startswith('@'):
159 # Add new line into the 'choices' array
160 res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
161
162 if not res:
163 raise RuntimeError("Bad line: %s" % line)
164
165 choices.append((res.group(2), res.group(3)))
166 continue
167
168 if line.startswith('%'):
169 # Name of the option
170 name = line[1:].strip()
171 continue
172
173 if line.startswith('#') or (line == '\n'):
174 # Comment or empty line
175 continue
176
177
178 raise RuntimeError("Unknown syntax: %s" % line)
179
180 inf.close()
181
182def yes_no(default):
183 "Return '*' if yes, ' ' if no"
184
185 if default == 'y':
186 return '*'
187
188 return ' '
189
190def subchoice(screen, name, choices, default):
191 "Return choice of choices"
192
193 maxkey = 0
194 for key, val in choices:
195 length = len(key)
196 if (length > maxkey):
197 maxkey = length
198
199 options = []
200 position = None
201 cnt = 0
202 for key, val in choices:
203 if (default) and (key == default):
204 position = cnt
205
206 options.append(" %-*s %s " % (maxkey, key, val))
207 cnt += 1
208
209 (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
210
211 if button == 'cancel':
212 return None
213
214 return choices[value][0]
215
216## Infer and verify configuration values.
217#
218# Augment @a config with values that can be inferred, purge invalid ones
219# and verify that all variables have a value (previously specified or inferred).
220#
221# @param config Configuration to work on
222# @param rules Rules
223#
224# @return True if configuration is complete and valid, False
225# otherwise.
226#
227def infer_verify_choices(config, rules):
228 "Infer and verify configuration values."
229
230 for rule in rules:
231 varname, vartype, name, choices, cond = rule
232
233 if cond and (not check_condition(cond, config, rules)):
234 continue
235
236 if not varname in config:
237 value = None
238 else:
239 value = config[varname]
240
241 if not validate_rule_value(rule, value):
242 value = None
243
244 default = get_default_rule(rule)
245
246 #
247 # If we don't have a value but we do have
248 # a default, use it.
249 #
250 if value == None and default != None:
251 value = default
252 config[varname] = default
253
254 if not varname in config:
255 return False
256
257 return True
258
259## Get default value from a rule.
260def get_default_rule(rule):
261 varname, vartype, name, choices, cond = rule
262
263 default = None
264
265 if vartype == 'choice':
266 # If there is just one option, use it
267 if len(choices) == 1:
268 default = choices[0][0]
269 elif vartype == 'y':
270 default = '*'
271 elif vartype == 'n':
272 default = 'n'
273 elif vartype == 'y/n':
274 default = 'y'
275 elif vartype == 'n/y':
276 default = 'n'
277 else:
278 raise RuntimeError("Unknown variable type: %s" % vartype)
279
280 return default
281
282## Get option from a rule.
283#
284# @param rule Rule for a variable
285# @param value Current value of the variable
286#
287# @return Option (string) to ask or None which means not to ask.
288#
289def get_rule_option(rule, value):
290 varname, vartype, name, choices, cond = rule
291
292 option = None
293
294 if vartype == 'choice':
295 # If there is just one option, don't ask
296 if len(choices) != 1:
297 if (value == None):
298 option = "? %s --> " % name
299 else:
300 option = " %s [%s] --> " % (name, value)
301 elif vartype == 'y':
302 pass
303 elif vartype == 'n':
304 pass
305 elif vartype == 'y/n':
306 option = " <%s> %s " % (yes_no(value), name)
307 elif vartype == 'n/y':
308 option =" <%s> %s " % (yes_no(value), name)
309 else:
310 raise RuntimeError("Unknown variable type: %s" % vartype)
311
312 return option
313
314## Check if variable value is valid.
315#
316# @param rule Rule for the variable
317# @param value Value of the variable
318#
319# @return True if valid, False if not valid.
320#
321def validate_rule_value(rule, value):
322 varname, vartype, name, choices, cond = rule
323
324 if value == None:
325 return True
326
327 if vartype == 'choice':
328 if not value in [choice[0] for choice in choices]:
329 return False
330 elif vartype == 'y':
331 if value != 'y':
332 return False
333 elif vartype == 'n':
334 if value != 'n':
335 return False
336 elif vartype == 'y/n':
337 if not value in ['y', 'n']:
338 return False
339 elif vartype == 'n/y':
340 if not value in ['y', 'n']:
341 return False
342 else:
343 raise RuntimeError("Unknown variable type: %s" % vartype)
344
345 return True
346
347def preprocess_config(config, rules):
348 "Preprocess configuration"
349
350 varname_mode = 'CONFIG_BFB_MODE'
351 varname_width = 'CONFIG_BFB_WIDTH'
352 varname_height = 'CONFIG_BFB_HEIGHT'
353
354 if varname_mode in config:
355 mode = config[varname_mode].partition('x')
356
357 config[varname_width] = mode[0]
358 rules.append((varname_width, 'choice', 'Default framebuffer width', None, None))
359
360 config[varname_height] = mode[2]
361 rules.append((varname_height, 'choice', 'Default framebuffer height', None, None))
362
363def create_output(mkname, mcname, config, rules):
364 "Create output configuration"
365
366 timestamp_unix = int(time.time())
367 timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp_unix))
368
369 sys.stderr.write("Fetching current revision identifier ... ")
370
371 try:
372 version = subprocess.Popen(['bzr', 'version-info', '--custom', '--template={clean}:{revno}:{revision_id}'], stdout = subprocess.PIPE).communicate()[0].decode().split(':')
373 sys.stderr.write("ok\n")
374 except:
375 version = [1, "unknown", "unknown"]
376 sys.stderr.write("failed\n")
377
378 if len(version) == 3:
379 revision = version[1]
380 if version[0] != 1:
381 revision += 'M'
382 revision += ' (%s)' % version[2]
383 else:
384 revision = None
385
386 outmk = open(mkname, 'w')
387 outmc = open(mcname, 'w')
388
389 outmk.write('#########################################\n')
390 outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
391 outmk.write('## Generated by: tools/config.py ##\n')
392 outmk.write('#########################################\n\n')
393
394 outmc.write('/***************************************\n')
395 outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
396 outmc.write(' * Generated by: tools/config.py *\n')
397 outmc.write(' ***************************************/\n\n')
398
399 defs = 'CONFIG_DEFS ='
400
401 for varname, vartype, name, choices, cond in rules:
402 if cond and (not check_condition(cond, config, rules)):
403 continue
404
405 if not varname in config:
406 value = ''
407 else:
408 value = config[varname]
409 if (value == '*'):
410 value = 'y'
411
412 outmk.write('# %s\n%s = %s\n\n' % (name, varname, value))
413
414 if vartype in ["y", "n", "y/n", "n/y"]:
415 if value == "y":
416 outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
417 defs += ' -D%s' % varname
418 else:
419 outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, value, varname, value))
420 defs += ' -D%s=%s -D%s_%s' % (varname, value, varname, value)
421
422 if revision is not None:
423 outmk.write('REVISION = %s\n' % revision)
424 outmc.write('#define REVISION %s\n' % revision)
425 defs += ' "-DREVISION=%s"' % revision
426
427 outmk.write('TIMESTAMP_UNIX = %d\n' % timestamp_unix)
428 outmc.write('#define TIMESTAMP_UNIX %d\n' % timestamp_unix)
429 defs += ' "-DTIMESTAMP_UNIX=%d"\n' % timestamp_unix
430
431 outmk.write('TIMESTAMP = %s\n' % timestamp)
432 outmc.write('#define TIMESTAMP %s\n' % timestamp)
433 defs += ' "-DTIMESTAMP=%s"\n' % timestamp
434
435 outmk.write(defs)
436
437 outmk.close()
438 outmc.close()
439
440def sorted_dir(root):
441 list = os.listdir(root)
442 list.sort()
443 return list
444
445## Ask user to choose a configuration profile.
446#
447def choose_profile(root, fname, screen, config):
448 options = []
449 opt2path = {}
450 cnt = 0
451
452 # Look for profiles
453 for name in sorted_dir(root):
454 path = os.path.join(root, name)
455 canon = os.path.join(path, fname)
456
457 if os.path.isdir(path) and os.path.exists(canon) and os.path.isfile(canon):
458 subprofile = False
459
460 # Look for subprofiles
461 for subname in sorted_dir(path):
462 subpath = os.path.join(path, subname)
463 subcanon = os.path.join(subpath, fname)
464
465 if os.path.isdir(subpath) and os.path.exists(subcanon) and os.path.isfile(subcanon):
466 subprofile = True
467 options.append("%s (%s)" % (name, subname))
468 opt2path[cnt] = [name, subname]
469 cnt += 1
470
471 if not subprofile:
472 options.append(name)
473 opt2path[cnt] = [name]
474 cnt += 1
475
476 (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
477
478 if button == 'cancel':
479 return None
480
481 return opt2path[value]
482
483## Read presets from a configuration profile.
484#
485# @param profile Profile to load from (a list of string components)
486# @param config Output configuration
487#
488def read_presets(profile, config):
489 path = os.path.join(PRESETS_DIR, profile[0], MAKEFILE)
490 read_config(path, config)
491
492 if len(profile) > 1:
493 path = os.path.join(PRESETS_DIR, profile[0], profile[1], MAKEFILE)
494 read_config(path, config)
495
496## Parse profile name (relative OS path) into a list of components.
497#
498# @param profile_name Relative path (using OS separator)
499# @return List of components
500#
501def parse_profile_name(profile_name):
502 profile = []
503
504 head, tail = os.path.split(profile_name)
505 if head != '':
506 profile.append(head)
507
508 profile.append(tail)
509 return profile
510
511def main():
512 profile = None
513 config = {}
514 rules = []
515
516 # Parse rules file
517 parse_rules(RULES_FILE, rules)
518
519 # Input configuration file can be specified on command line
520 # otherwise configuration from previous run is used.
521 if len(sys.argv) >= 4:
522 profile = parse_profile_name(sys.argv[3])
523 read_presets(profile, config)
524 elif os.path.exists(MAKEFILE):
525 read_config(MAKEFILE, config)
526
527 # Default mode: check values and regenerate configuration files
528 if (len(sys.argv) >= 3) and (sys.argv[2] == 'default'):
529 if (infer_verify_choices(config, rules)):
530 preprocess_config(config, rules)
531 create_output(MAKEFILE, MACROS, config, rules)
532 return 0
533
534 # Hands-off mode: check values and regenerate configuration files,
535 # but no interactive fallback
536 if (len(sys.argv) >= 3) and (sys.argv[2] == 'hands-off'):
537 # We deliberately test sys.argv >= 4 because we do not want
538 # to read implicitly any possible previous run configuration
539 if len(sys.argv) < 4:
540 sys.stderr.write("Configuration error: No presets specified\n")
541 return 2
542
543 if (infer_verify_choices(config, rules)):
544 preprocess_config(config, rules)
545 create_output(MAKEFILE, MACROS, config, rules)
546 return 0
547
548 sys.stderr.write("Configuration error: The presets are ambiguous\n")
549 return 1
550
551 # Check mode: only check configuration
552 if (len(sys.argv) >= 3) and (sys.argv[2] == 'check'):
553 if infer_verify_choices(config, rules):
554 return 0
555 return 1
556
557 screen = xtui.screen_init()
558 try:
559 selname = None
560 position = None
561 while True:
562
563 # Cancel out all values which have to be deduced
564 for varname, vartype, name, choices, cond in rules:
565 if (vartype == 'y') and (varname in config) and (config[varname] == '*'):
566 config[varname] = None
567
568 options = []
569 opt2row = {}
570 cnt = 1
571
572 options.append(" --- Load preconfigured defaults ... ")
573
574 for rule in rules:
575 varname, vartype, name, choices, cond = rule
576
577 if cond and (not check_condition(cond, config, rules)):
578 continue
579
580 if varname == selname:
581 position = cnt
582
583 if not varname in config:
584 value = None
585 else:
586 value = config[varname]
587
588 if not validate_rule_value(rule, value):
589 value = None
590
591 default = get_default_rule(rule)
592
593 #
594 # If we don't have a value but we do have
595 # a default, use it.
596 #
597 if value == None and default != None:
598 value = default
599 config[varname] = default
600
601 option = get_rule_option(rule, value)
602 if option != None:
603 options.append(option)
604 else:
605 continue
606
607 opt2row[cnt] = (varname, vartype, name, choices)
608
609 cnt += 1
610
611 if (position != None) and (position >= len(options)):
612 position = None
613
614 (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
615
616 if button == 'cancel':
617 return 'Configuration canceled'
618
619 if button == 'done':
620 if (infer_verify_choices(config, rules)):
621 break
622 else:
623 xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
624 continue
625
626 if value == 0:
627 profile = choose_profile(PRESETS_DIR, MAKEFILE, screen, config)
628 if profile != None:
629 read_presets(profile, config)
630 position = 1
631 continue
632
633 position = None
634 if not value in opt2row:
635 raise RuntimeError("Error selecting value: %s" % value)
636
637 (selname, seltype, name, choices) = opt2row[value]
638
639 if not selname in config:
640 value = None
641 else:
642 value = config[selname]
643
644 if seltype == 'choice':
645 config[selname] = subchoice(screen, name, choices, value)
646 elif (seltype == 'y/n') or (seltype == 'n/y'):
647 if config[selname] == 'y':
648 config[selname] = 'n'
649 else:
650 config[selname] = 'y'
651 finally:
652 xtui.screen_done(screen)
653
654 preprocess_config(config, rules)
655 create_output(MAKEFILE, MACROS, config, rules)
656 return 0
657
658if __name__ == '__main__':
659 sys.exit(main())
Note: See TracBrowser for help on using the repository browser.