3 # Copyright (c) 2006 Ondrej Palkovsky
4 # Copyright (c) 2009 Martin Decky
5 # Copyright (c) 2010 Jiri Svoboda
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
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.
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.
33 HelenOS configuration system
43 RULES_FILE
= sys
.argv
[1]
44 MAKEFILE
= 'Makefile.config'
46 PRESETS_DIR
= 'defaults'
48 def read_config(fname
, config
):
49 "Read saved values from last configuration run or a preset file"
51 inf
= open(fname
, 'r')
54 res
= re
.match(r
'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line
)
56 config
[res
.group(1)] = res
.group(2)
60 def check_condition(text
, config
, rules
):
61 "Check that the condition specified on input line is True (only CNF and DNF is supported)"
65 if (')|' in text
) or ('|(' in text
):
69 conds
= text
.split('&')
71 conds
= text
.split('|')
74 if cond
.startswith('(') and cond
.endswith(')'):
77 inside
= check_inside(cond
, config
, ctype
)
79 if (ctype
== 'cnf') and (not inside
):
82 if (ctype
== 'dnf') and inside
:
90 def check_inside(text
, config
, ctype
):
94 conds
= text
.split('|')
96 conds
= text
.split('&')
99 res
= re
.match(r
'^(.*?)(!?=)(.*)$', cond
)
101 raise RuntimeError("Invalid condition: %s" % cond
)
103 condname
= res
.group(1)
105 condval
= res
.group(3)
107 if not condname
in config
:
110 varval
= config
[condname
]
115 if (oper
== '=') and (condval
== varval
):
118 if (oper
== '!=') and (condval
!= varval
):
121 if (oper
== '=') and (condval
!= varval
):
124 if (oper
== '!=') and (condval
== varval
):
132 def parse_rules(fname
, rules
):
135 inf
= open(fname
, 'r')
142 if line
.startswith('!'):
144 res
= re
.search(r
'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line
)
147 raise RuntimeError("Weird line: %s" % line
)
150 varname
= res
.group(2)
151 vartype
= res
.group(3)
153 rules
.append((varname
, vartype
, name
, choices
, cond
))
158 if line
.startswith('@'):
159 # Add new line into the 'choices' array
160 res
= re
.match(r
'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line
)
163 raise RuntimeError("Bad line: %s" % line
)
165 choices
.append((res
.group(2), res
.group(3)))
168 if line
.startswith('%'):
170 name
= line
[1:].strip()
173 if line
.startswith('#') or (line
== '\n'):
174 # Comment or empty line
178 raise RuntimeError("Unknown syntax: %s" % line
)
183 "Return '*' if yes, ' ' if no"
190 def subchoice(screen
, name
, choices
, default
):
191 "Return choice of choices"
194 for key
, val
in choices
:
196 if (length
> maxkey
):
202 for key
, val
in choices
:
203 if (default
) and (key
== default
):
206 options
.append(" %-*s %s " % (maxkey
, key
, val
))
209 (button
, value
) = xtui
.choice_window(screen
, name
, 'Choose value', options
, position
)
211 if button
== 'cancel':
214 return choices
[value
][0]
216 ## Infer and verify configuration values.
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).
221 # @param config Configuration to work on
224 # @return True if configuration is complete and valid, False
227 def infer_verify_choices(config
, rules
):
228 "Infer and verify configuration values."
231 varname
, vartype
, name
, choices
, cond
= rule
233 if cond
and (not check_condition(cond
, config
, rules
)):
236 if not varname
in config
:
239 value
= config
[varname
]
241 if not validate_rule_value(rule
, value
):
244 default
= get_default_rule(rule
)
247 # If we don't have a value but we do have
250 if value
== None and default
!= None:
252 config
[varname
] = default
254 if not varname
in config
:
259 ## Get default value from a rule.
260 def get_default_rule(rule
):
261 varname
, vartype
, name
, choices
, cond
= rule
265 if vartype
== 'choice':
266 # If there is just one option, use it
267 if len(choices
) == 1:
268 default
= choices
[0][0]
273 elif vartype
== 'y/n':
275 elif vartype
== 'n/y':
278 raise RuntimeError("Unknown variable type: %s" % vartype
)
282 ## Get option from a rule.
284 # @param rule Rule for a variable
285 # @param value Current value of the variable
287 # @return Option (string) to ask or None which means not to ask.
289 def get_rule_option(rule
, value
):
290 varname
, vartype
, name
, choices
, cond
= rule
294 if vartype
== 'choice':
295 # If there is just one option, don't ask
296 if len(choices
) != 1:
298 option
= "? %s --> " % name
300 option
= " %s [%s] --> " % (name
, value
)
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
)
310 raise RuntimeError("Unknown variable type: %s" % vartype
)
314 ## Check if variable value is valid.
316 # @param rule Rule for the variable
317 # @param value Value of the variable
319 # @return True if valid, False if not valid.
321 def validate_rule_value(rule
, value
):
322 varname
, vartype
, name
, choices
, cond
= rule
327 if vartype
== 'choice':
328 if not value
in [choice
[0] for choice
in choices
]:
336 elif vartype
== 'y/n':
337 if not value
in ['y', 'n']:
339 elif vartype
== 'n/y':
340 if not value
in ['y', 'n']:
343 raise RuntimeError("Unknown variable type: %s" % vartype
)
347 def preprocess_config(config
, rules
):
348 "Preprocess configuration"
350 varname_mode
= 'CONFIG_BFB_MODE'
351 varname_width
= 'CONFIG_BFB_WIDTH'
352 varname_height
= 'CONFIG_BFB_HEIGHT'
354 if varname_mode
in config
:
355 mode
= config
[varname_mode
].partition('x')
357 config
[varname_width
] = mode
[0]
358 rules
.append((varname_width
, 'choice', 'Default framebuffer width', None, None))
360 config
[varname_height
] = mode
[2]
361 rules
.append((varname_height
, 'choice', 'Default framebuffer height', None, None))
363 def create_output(mkname
, mcname
, config
, rules
):
364 "Create output configuration"
366 timestamp
= time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime())
368 sys
.stderr
.write("Fetching current revision identifier ... ")
371 version
= subprocess
.Popen(['bzr', 'version-info', '--custom', '--template={clean}:{revno}:{revision_id}'], stdout
= subprocess
.PIPE
).communicate()[0].decode().split(':')
372 sys
.stderr
.write("ok\n")
374 version
= [1, "unknown", "unknown"]
375 sys
.stderr
.write("failed\n")
377 if len(version
) == 3:
378 revision
= version
[1]
381 revision
+= ' (%s)' % version
[2]
385 outmk
= open(mkname
, 'w')
386 outmc
= open(mcname
, 'w')
388 outmk
.write('#########################################\n')
389 outmk
.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
390 outmk
.write('## Generated by: tools/config.py ##\n')
391 outmk
.write('#########################################\n\n')
393 outmc
.write('/***************************************\n')
394 outmc
.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
395 outmc
.write(' * Generated by: tools/config.py *\n')
396 outmc
.write(' ***************************************/\n\n')
398 defs
= 'CONFIG_DEFS ='
400 for varname
, vartype
, name
, choices
, cond
in rules
:
401 if cond
and (not check_condition(cond
, config
, rules
)):
404 if not varname
in config
:
407 value
= config
[varname
]
411 outmk
.write('# %s\n%s = %s\n\n' % (name
, varname
, value
))
413 if vartype
in ["y", "n", "y/n", "n/y"]:
415 outmc
.write('/* %s */\n#define %s\n\n' % (name
, varname
))
416 defs
+= ' -D%s' % varname
418 outmc
.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name
, varname
, value
, varname
, value
))
419 defs
+= ' -D%s=%s -D%s_%s' % (varname
, value
, varname
, value
)
421 if revision
is not None:
422 outmk
.write('REVISION = %s\n' % revision
)
423 outmc
.write('#define REVISION %s\n' % revision
)
424 defs
+= ' "-DREVISION=%s"' % revision
426 outmk
.write('TIMESTAMP = %s\n' % timestamp
)
427 outmc
.write('#define TIMESTAMP %s\n' % timestamp
)
428 defs
+= ' "-DTIMESTAMP=%s"\n' % timestamp
435 def sorted_dir(root
):
436 list = os
.listdir(root
)
440 ## Ask user to choose a configuration profile.
442 def choose_profile(root
, fname
, screen
, config
):
448 for name
in sorted_dir(root
):
449 path
= os
.path
.join(root
, name
)
450 canon
= os
.path
.join(path
, fname
)
452 if os
.path
.isdir(path
) and os
.path
.exists(canon
) and os
.path
.isfile(canon
):
455 # Look for subprofiles
456 for subname
in sorted_dir(path
):
457 subpath
= os
.path
.join(path
, subname
)
458 subcanon
= os
.path
.join(subpath
, fname
)
460 if os
.path
.isdir(subpath
) and os
.path
.exists(subcanon
) and os
.path
.isfile(subcanon
):
462 options
.append("%s (%s)" % (name
, subname
))
463 opt2path
[cnt
] = [name
, subname
]
468 opt2path
[cnt
] = [name
]
471 (button
, value
) = xtui
.choice_window(screen
, 'Load preconfigured defaults', 'Choose configuration profile', options
, None)
473 if button
== 'cancel':
476 return opt2path
[value
]
478 ## Read presets from a configuration profile.
480 # @param profile Profile to load from (a list of string components)
481 # @param config Output configuration
483 def read_presets(profile
, config
):
484 path
= os
.path
.join(PRESETS_DIR
, profile
[0], MAKEFILE
)
485 read_config(path
, config
)
488 path
= os
.path
.join(PRESETS_DIR
, profile
[0], profile
[1], MAKEFILE
)
489 read_config(path
, config
)
491 ## Parse profile name (relative OS path) into a list of components.
493 # @param profile_name Relative path (using OS separator)
494 # @return List of components
496 def parse_profile_name(profile_name
):
499 head
, tail
= os
.path
.split(profile_name
)
512 parse_rules(RULES_FILE
, rules
)
514 # Input configuration file can be specified on command line
515 # otherwise configuration from previous run is used.
516 if len(sys
.argv
) >= 4:
517 profile
= parse_profile_name(sys
.argv
[3])
518 read_presets(profile
, config
)
519 elif os
.path
.exists(MAKEFILE
):
520 read_config(MAKEFILE
, config
)
522 # Default mode: check values and regenerate configuration files
523 if (len(sys
.argv
) >= 3) and (sys
.argv
[2] == 'default'):
524 if (infer_verify_choices(config
, rules
)):
525 preprocess_config(config
, rules
)
526 create_output(MAKEFILE
, MACROS
, config
, rules
)
529 # Hands-off mode: check values and regenerate configuration files,
530 # but no interactive fallback
531 if (len(sys
.argv
) >= 3) and (sys
.argv
[2] == 'hands-off'):
532 # We deliberately test sys.argv >= 4 because we do not want
533 # to read implicitly any possible previous run configuration
534 if len(sys
.argv
) < 4:
535 sys
.stderr
.write("Configuration error: No presets specified\n")
538 if (infer_verify_choices(config
, rules
)):
539 preprocess_config(config
, rules
)
540 create_output(MAKEFILE
, MACROS
, config
, rules
)
543 sys
.stderr
.write("Configuration error: The presets are ambiguous\n")
546 # Check mode: only check configuration
547 if (len(sys
.argv
) >= 3) and (sys
.argv
[2] == 'check'):
548 if infer_verify_choices(config
, rules
):
552 screen
= xtui
.screen_init()
558 # Cancel out all values which have to be deduced
559 for varname
, vartype
, name
, choices
, cond
in rules
:
560 if (vartype
== 'y') and (varname
in config
) and (config
[varname
] == '*'):
561 config
[varname
] = None
567 options
.append(" --- Load preconfigured defaults ... ")
570 varname
, vartype
, name
, choices
, cond
= rule
572 if cond
and (not check_condition(cond
, config
, rules
)):
575 if varname
== selname
:
578 if not varname
in config
:
581 value
= config
[varname
]
583 if not validate_rule_value(rule
, value
):
586 default
= get_default_rule(rule
)
589 # If we don't have a value but we do have
592 if value
== None and default
!= None:
594 config
[varname
] = default
596 option
= get_rule_option(rule
, value
)
598 options
.append(option
)
602 opt2row
[cnt
] = (varname
, vartype
, name
, choices
)
606 if (position
!= None) and (position
>= len(options
)):
609 (button
, value
) = xtui
.choice_window(screen
, 'HelenOS configuration', 'Choose configuration option', options
, position
)
611 if button
== 'cancel':
612 return 'Configuration canceled'
615 if (infer_verify_choices(config
, rules
)):
618 xtui
.error_dialog(screen
, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
622 profile
= choose_profile(PRESETS_DIR
, MAKEFILE
, screen
, config
)
624 read_presets(profile
, config
)
629 if not value
in opt2row
:
630 raise RuntimeError("Error selecting value: %s" % value
)
632 (selname
, seltype
, name
, choices
) = opt2row
[value
]
634 if not selname
in config
:
637 value
= config
[selname
]
639 if seltype
== 'choice':
640 config
[selname
] = subchoice(screen
, name
, choices
, value
)
641 elif (seltype
== 'y/n') or (seltype
== 'n/y'):
642 if config
[selname
] == 'y':
643 config
[selname
] = 'n'
645 config
[selname
] = 'y'
647 xtui
.screen_done(screen
)
649 preprocess_config(config
, rules
)
650 create_output(MAKEFILE
, MACROS
, config
, rules
)
653 if __name__
== '__main__':