Subversion Repositories HelenOS

Rev

Rev 3042 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1802 decky 1
#!/usr/bin/env python
2925 svoboda 2
#
3042 svoboda 3
# Copyright (c) 2006 Ondrej Palkovsky
4377 svoboda 4
# Copyright (c) 2009 Martin Decky
2925 svoboda 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
#
1802 decky 30
"""
4377 svoboda 31
HelenOS configuration system
1802 decky 32
"""
33
import sys
34
import os
35
import re
36
import commands
4377 svoboda 37
import xtui
1802 decky 38
 
39
INPUT = sys.argv[1]
4377 svoboda 40
MAKEFILE = 'Makefile.config'
41
MACROS = 'config.h'
42
DEFS = 'config.defs'
43
PRECONF = 'defaults'
1802 decky 44
 
4377 svoboda 45
def read_defaults(fname, defaults):
46
    "Read saved values from last configuration run"
47
 
48
    inf = file(fname, 'r')
49
 
50
    for line in inf:
51
        res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
52
        if (res):
53
            defaults[res.group(1)] = res.group(2)
54
 
55
    inf.close()
1802 decky 56
 
4377 svoboda 57
def check_condition(text, defaults, ask_names):
58
    "Check that the condition specified on input line is True (only CNF and DNF is supported)"
59
 
60
    ctype = 'cnf'
61
 
62
    if ((')|' in text) or ('|(' in text)):
63
        ctype = 'dnf'
64
 
65
    if (ctype == 'cnf'):
66
        conds = text.split('&')
67
    else:
68
        conds = text.split('|')
69
 
70
    for cond in conds:
71
        if (cond.startswith('(')) and (cond.endswith(')')):
72
            cond = cond[1:-1]
73
 
74
        inside = check_inside(cond, defaults, ctype)
75
 
76
        if (ctype == 'cnf') and (not inside):
77
            return False
78
 
79
        if (ctype == 'dnf') and (inside):
80
            return True
81
 
82
    if (ctype == 'cnf'):
83
        return True
84
    return False
1802 decky 85
 
4377 svoboda 86
def check_inside(text, defaults, ctype):
87
    "Check for condition"
88
 
89
    if (ctype == 'cnf'):
90
        conds = text.split('|')
91
    else:
92
        conds = text.split('&')
93
 
94
    for cond in conds:
95
        res = re.match(r'^(.*?)(!?=)(.*)$', cond)
96
        if (not res):
97
            raise RuntimeError("Invalid condition: %s" % cond)
98
 
99
        condname = res.group(1)
100
        oper = res.group(2)
101
        condval = res.group(3)
102
 
103
        if (not defaults.has_key(condname)):
104
            varval = ''
105
        else:
106
            varval = defaults[condname]
107
            if (varval == '*'):
108
                varval = 'y'
109
 
110
        if (ctype == 'cnf'):
111
            if (oper == '=') and (condval == varval):
112
                return True
113
 
114
            if (oper == '!=') and (condval != varval):
115
                return True
116
        else:
117
            if (oper == '=') and (condval != varval):
118
                return False
119
 
120
            if (oper == '!=') and (condval == varval):
121
                return False
122
 
123
    if (ctype == 'cnf'):
124
        return False
125
 
126
    return True
1802 decky 127
 
4377 svoboda 128
def parse_config(fname, ask_names):
129
    "Parse configuration file"
130
 
131
    inf = file(fname, 'r')
132
 
133
    name = ''
134
    choices = []
135
 
136
    for line in inf:
137
 
138
        if (line.startswith('!')):
139
            # Ask a question
140
            res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
141
 
142
            if (not res):
143
                raise RuntimeError("Weird line: %s" % line)
144
 
145
            cond = res.group(1)
146
            varname = res.group(2)
147
            vartype = res.group(3)
148
 
149
            ask_names.append((varname, vartype, name, choices, cond))
150
            name = ''
151
            choices = []
152
            continue
153
 
154
        if (line.startswith('@')):
155
            # Add new line into the 'choices' array
156
            res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
157
 
158
            if not res:
159
                raise RuntimeError("Bad line: %s" % line)
160
 
161
            choices.append((res.group(2), res.group(3)))
162
            continue
163
 
164
        if (line.startswith('%')):
165
            # Name of the option
166
            name = line[1:].strip()
167
            continue
168
 
169
        if ((line.startswith('#')) or (line == '\n')):
170
            # Comment or empty line
171
            continue
172
 
173
 
174
        raise RuntimeError("Unknown syntax: %s" % line)
175
 
176
    inf.close()
1802 decky 177
 
4377 svoboda 178
def yes_no(default):
179
    "Return '*' if yes, ' ' if no"
180
 
181
    if (default == 'y'):
182
        return '*'
183
 
184
    return ' '
1802 decky 185
 
4377 svoboda 186
def subchoice(screen, name, choices, default):
187
    "Return choice of choices"
188
 
189
    maxkey = 0
190
    for key, val in choices:
191
        length = len(key)
192
        if (length > maxkey):
193
            maxkey = length
194
 
195
    options = []
196
    position = None
197
    cnt = 0
198
    for key, val in choices:
199
        if ((default) and (key == default)):
200
            position = cnt
201
 
202
        options.append(" %-*s  %s " % (maxkey, key, val))
203
        cnt += 1
204
 
205
    (button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
206
 
207
    if (button == 'cancel'):
208
        return None
209
 
210
    return choices[value][0]
1802 decky 211
 
4377 svoboda 212
def check_choices(defaults, ask_names):
213
    "Check whether all accessible variables have a default"
214
 
215
    for varname, vartype, name, choices, cond in ask_names:
216
        if ((cond) and (not check_condition(cond, defaults, ask_names))):
217
            continue
218
 
219
        if (not defaults.has_key(varname)):
220
            return False
221
 
222
    return True
1802 decky 223
 
4377 svoboda 224
def create_output(mkname, mcname, dfname, defaults, ask_names):
225
    "Create output configuration"
226
 
227
    revision = commands.getoutput('svnversion . 2> /dev/null')
228
    timestamp = commands.getoutput('date "+%Y-%m-%d %H:%M:%S"')
229
 
230
    outmk = file(mkname, 'w')
231
    outmc = file(mcname, 'w')
232
    outdf = file(dfname, 'w')
233
 
234
    outmk.write('#########################################\n')
235
    outmk.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
236
    outmk.write('#########################################\n\n')
237
 
238
    outmc.write('/***************************************\n')
239
    outmc.write(' * AUTO-GENERATED FILE, DO NOT EDIT!!! *\n')
240
    outmc.write(' ***************************************/\n\n')
241
 
242
    outdf.write('#########################################\n')
243
    outdf.write('## AUTO-GENERATED FILE, DO NOT EDIT!!! ##\n')
244
    outdf.write('#########################################\n\n')
245
    outdf.write('CONFIG_DEFS =')
246
 
247
    for varname, vartype, name, choices, cond in ask_names:
248
        if ((cond) and (not check_condition(cond, defaults, ask_names))):
249
            continue
250
 
251
        if (not defaults.has_key(varname)):
252
            default = ''
253
        else:
254
            default = defaults[varname]
255
            if (default == '*'):
256
                default = 'y'
257
 
258
        outmk.write('# %s\n%s = %s\n\n' % (name, varname, default))
259
 
260
        if ((vartype == "y") or (vartype == "n") or (vartype == "y/n") or (vartype == "n/y")):
261
            if (default == "y"):
262
                outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
263
                outdf.write(' -D%s' % varname)
264
        else:
265
            outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, default, varname, default))
266
            outdf.write(' -D%s=%s -D%s_%s' % (varname, default, varname, default))
267
 
268
    outmk.write('REVISION = %s\n' % revision)
269
    outmk.write('TIMESTAMP = %s\n' % timestamp)
270
 
271
    outmc.write('#define REVISION %s\n' % revision)
272
    outmc.write('#define TIMESTAMP %s\n' % timestamp)
273
 
274
    outdf.write(' "-DREVISION=%s" "-DTIMESTAMP=%s"\n' % (revision, timestamp))
275
 
276
    outmk.close()
277
    outmc.close()
278
    outdf.close()
1802 decky 279
 
4377 svoboda 280
def sorted_dir(root):
281
    list = os.listdir(root)
282
    list.sort()
283
    return list
1802 decky 284
 
4377 svoboda 285
def read_preconfigured(root, fname, screen, defaults):
286
    options = []
287
    opt2path = {}
288
    cnt = 0
289
 
290
    # Look for profiles
291
    for name in sorted_dir(root):
292
        path = os.path.join(root, name)
293
        canon = os.path.join(path, fname)
294
 
295
        if ((os.path.isdir(path)) and (os.path.exists(canon)) and (os.path.isfile(canon))):
296
            subprofile = False
297
 
298
            # Look for subprofiles
299
            for subname in sorted_dir(path):
300
                subpath = os.path.join(path, subname)
301
                subcanon = os.path.join(subpath, fname)
302
 
303
                if ((os.path.isdir(subpath)) and (os.path.exists(subcanon)) and (os.path.isfile(subcanon))):
304
                    subprofile = True
305
                    options.append("%s (%s)" % (name, subname))
306
                    opt2path[cnt] = (canon, subcanon)
307
                    cnt += 1
308
 
309
            if (not subprofile):
310
                options.append(name)
311
                opt2path[cnt] = (canon, None)
312
                cnt += 1
313
 
314
    (button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
315
 
316
    if (button == 'cancel'):
317
        return None
318
 
319
    read_defaults(opt2path[value][0], defaults)
320
    if (opt2path[value][1] != None):
321
        read_defaults(opt2path[value][1], defaults)
1802 decky 322
 
323
def main():
4377 svoboda 324
    defaults = {}
325
    ask_names = []
1802 decky 326
 
4377 svoboda 327
    # Parse configuration file
328
    parse_config(INPUT, ask_names)
1802 decky 329
 
4377 svoboda 330
    # Read defaults from previous run
331
    if os.path.exists(MAKEFILE):
332
        read_defaults(MAKEFILE, defaults)
1802 decky 333
 
4377 svoboda 334
    # Default mode: only check defaults and regenerate configuration
335
    if ((len(sys.argv) >= 3) and (sys.argv[2] == 'default')):
336
        if (check_choices(defaults, ask_names)):
337
            create_output(MAKEFILE, MACROS, DEFS, defaults, ask_names)
338
            return 0
339
 
340
    # Check mode: only check defaults
341
    if ((len(sys.argv) >= 3) and (sys.argv[2] == 'check')):
342
        if (check_choices(defaults, ask_names)):
343
            return 0
344
        return 1
345
 
346
    screen = xtui.screen_init()
347
    try:
348
        selname = None
349
        position = None
350
        while True:
351
 
352
            # Cancel out all defaults which have to be deduced
353
            for varname, vartype, name, choices, cond in ask_names:
354
                if ((vartype == 'y') and (defaults.has_key(varname)) and (defaults[varname] == '*')):
355
                    defaults[varname] = None
356
 
357
            options = []
358
            opt2row = {}
359
            cnt = 1
360
 
361
            options.append("  --- Load preconfigured defaults ... ")
362
 
363
            for varname, vartype, name, choices, cond in ask_names:
364
 
365
                if ((cond) and (not check_condition(cond, defaults, ask_names))):
366
                    continue
367
 
368
                if (varname == selname):
369
                    position = cnt
370
 
371
                if (not defaults.has_key(varname)):
372
                    default = None
373
                else:
374
                    default = defaults[varname]
375
 
376
                if (vartype == 'choice'):
377
                    # Check if the default is an acceptable value
378
                    if ((default) and (not default in [choice[0] for choice in choices])):
379
                        default = None
380
                        defaults.pop(varname)
381
 
382
                    # If there is just one option, use it
383
                    if (len(choices) == 1):
384
                        defaults[varname] = choices[0][0]
385
                        continue
386
 
387
                    if (default == None):
388
                        options.append("?     %s --> " % name)
389
                    else:
390
                        options.append("      %s [%s] --> " % (name, default))
391
                elif (vartype == 'y'):
392
                    defaults[varname] = '*'
393
                    continue
394
                elif (vartype == 'n'):
395
                    defaults[varname] = 'n'
396
                    continue
397
                elif (vartype == 'y/n'):
398
                    if (default == None):
399
                        default = 'y'
400
                        defaults[varname] = default
401
                    options.append("  <%s> %s " % (yes_no(default), name))
402
                elif (vartype == 'n/y'):
403
                    if (default == None):
404
                        default = 'n'
405
                        defaults[varname] = default
406
                    options.append("  <%s> %s " % (yes_no(default), name))
407
                else:
408
                    raise RuntimeError("Unknown variable type: %s" % vartype)
409
 
410
                opt2row[cnt] = (varname, vartype, name, choices)
411
 
412
                cnt += 1
413
 
414
            if (position >= options):
415
                position = None
416
 
417
            (button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
418
 
419
            if (button == 'cancel'):
420
                return 'Configuration canceled'
421
 
422
            if (button == 'done'):
423
                if (check_choices(defaults, ask_names)):
424
                    break
425
                else:
426
                    xtui.error_dialog(screen, 'Error', 'Some options have still undefined values. These options are marked with the "?" sign.')
427
                    continue
428
 
429
            if (value == 0):
430
                read_preconfigured(PRECONF, MAKEFILE, screen, defaults)
431
                position = 1
432
                continue
433
 
434
            position = None
435
            if (not opt2row.has_key(value)):
436
                raise RuntimeError("Error selecting value: %s" % value)
437
 
438
            (selname, seltype, name, choices) = opt2row[value]
439
 
440
            if (not defaults.has_key(selname)):
441
                    default = None
442
            else:
443
                default = defaults[selname]
444
 
445
            if (seltype == 'choice'):
446
                defaults[selname] = subchoice(screen, name, choices, default)
447
            elif ((seltype == 'y/n') or (seltype == 'n/y')):
448
                if (defaults[selname] == 'y'):
449
                    defaults[selname] = 'n'
450
                else:
451
                    defaults[selname] = 'y'
452
    finally:
453
        xtui.screen_done(screen)
454
 
455
    create_output(MAKEFILE, MACROS, DEFS, defaults, ask_names)
456
    return 0
1802 decky 457
 
458
if __name__ == '__main__':
4377 svoboda 459
    sys.exit(main())