source: rtems-source-builder/source-builder/sb/config.py @ f88fcf3

4.11
Last change on this file since f88fcf3 was f88fcf3, checked in by Chris Johns <chrisj@…>, on Mar 7, 2016 at 12:56:02 AM

sb: Update code base to support Python3 and Python2.

Fix Windows support to allow MSYS2 Python to be used.

Updates #2619.

  • Property mode set to 100644
File size: 44.7 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2010-2016 Chris Johns (chrisj@rtems.org)
4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# Permission to use, copy, modify, and/or distribute this software for any
9# purpose with or without fee is hereby granted, provided that the above
10# copyright notice and this permission notice appear in all copies.
11#
12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#
21# This code is based on a tool I wrote to parse RPM spec files in the RTEMS
22# project. This is now a configuration file format that has moved away from the
23# spec file format to support the specific needs of cross-compiling GCC. This
24# module parses a configuration file into Python data types that can be used by
25# other software modules.
26#
27
28from __future__ import print_function
29
30import copy
31from functools import reduce
32import os
33import re
34import sys
35
36try:
37    import error
38    import execute
39    import log
40    import options
41    import path
42    import pkgconfig
43    import sources
44except KeyboardInterrupt:
45    print('user terminated', file = sys.stderr)
46    sys.exit(1)
47except:
48    print('error: unknown application load error', file = sys.stderr)
49    sys.exit(1)
50
51def _check_bool(value):
52    if value.isdigit():
53        if int(value) == 0:
54            istrue = False
55        else:
56            istrue = True
57    else:
58        istrue = None
59    return istrue
60
61def _check_nil(value):
62    if len(value):
63        istrue = True
64    else:
65        istrue = False
66    return istrue
67
68class package:
69
70    def __init__(self, name, arch, config):
71        self._name = name
72        self._arch = arch
73        self.config = config
74        self.directives = {}
75        self.infos = {}
76
77    def __str__(self):
78
79        def _dictlist(dl):
80            s = ''
81            dll = list(dl.keys())
82            dll.sort()
83            for d in dll:
84                if d:
85                    s += '  ' + d + ':\n'
86                    for l in dl[d]:
87                        s += '    ' + l + '\n'
88            return s
89
90        s = '\npackage: ' + self._name + \
91            '\n directives:\n' + _dictlist(self.directives) + \
92            '\n infos:\n' + _dictlist(self.infos)
93
94        return s
95
96    def _macro_override(self, info, macro):
97        '''See if a macro overrides this setting.'''
98        overridden = self.config.macros.overridden(macro)
99        if overridden:
100            return self.config.macros.expand(macro)
101        return info
102
103    def directive_extend(self, dir, data):
104        if dir not in self.directives:
105            self.directives[dir] = []
106        for i in range(0, len(data)):
107            data[i] = data[i].strip()
108        self.directives[dir].extend(data)
109        self.config.macros[dir] = '\n'.join(self.directives[dir])
110
111    def info_append(self, info, data):
112        if info not in self.infos:
113            self.infos[info] = []
114        self.infos[info].append(data)
115        self.config.macros[info] = '\n'.join(self.infos[info])
116
117    def get_info(self, info, expand = True):
118        if info in self.config.macros:
119            _info = self.config.macros[info].split('\n')
120            if expand:
121                return self.config.expand(_info)
122            else:
123                return _info
124        return None
125
126    def extract_info(self, label, expand = True):
127        ll = label.lower()
128        infos = {}
129        keys = self.config.macros.find('%s.*' % (ll))
130        for k in keys:
131            if k == ll:
132                k = '%s0' % (ll)
133            elif not k[len(ll):].isdigit():
134                continue
135            infos[k] = [self.config.expand(self.config.macros[k])]
136        return infos
137
138    def _find_macro(self, label, expand = True):
139        if label in self.config.macros:
140            macro = self.config.macros[label].split('\n')
141            if expand:
142                return self.config.expand(macro)
143            else:
144                return macro
145        return None
146
147    def find_info(self, label, expand = True):
148        return self._find_macro(label, expand)
149
150    def find_directive(self, label, expand = True):
151        return self._find_macro(label, expand)
152
153    def name(self):
154        info = self.find_info('name')
155        if info:
156            n = info[0]
157        else:
158            n = self._name
159        return self._macro_override(n, 'name')
160
161    def summary(self):
162        info = self.find_info('summary')
163        if info:
164            return info[0]
165        return ''
166
167    def url(self):
168        info = self.find_info('url')
169        if info:
170            return info[0]
171        return ''
172
173    def version(self):
174        info = self.find_info('version')
175        if not info:
176            return None
177        return info[0]
178
179    def release(self):
180        info = self.find_info('release')
181        if not info:
182            return None
183        return info[0]
184
185    def buildarch(self):
186        info = self.find_info('buildarch')
187        if not info:
188            return self._arch
189        return info[0]
190
191    def sources(self):
192        return self.extract_info('source')
193
194    def patches(self):
195        return self.extract_info('patch')
196
197    def prep(self):
198        return self.find_directive('%prep')
199
200    def build(self):
201        return self.find_directive('%build')
202
203    def install(self):
204        return self.find_directive('%install')
205
206    def clean(self):
207        return self.find_directive('%clean')
208
209    def include(self):
210        return self.find_directive('%include')
211
212    def testing(self):
213        return self.find_directive('%testing')
214
215    def long_name(self):
216        return self.name()
217
218    def disabled(self):
219        return len(self.name()) == 0
220
221class file:
222    """Parse a config file."""
223
224    _directive = [ '%description',
225                   '%prep',
226                   '%build',
227                   '%clean',
228                   '%install',
229                   '%include',
230                   '%install',
231                   '%testing' ]
232
233    _ignore = [ re.compile('%setup'),
234                re.compile('%configure'),
235                re.compile('%source'),
236                re.compile('%patch'),
237                re.compile('%hash'),
238                re.compile('%select'),
239                re.compile('%disable') ]
240
241    def __init__(self, name, opts, macros = None):
242        log.trace('config: %s: initialising' % (name))
243        self.opts = opts
244        self.init_name = name
245        self.wss = re.compile(r'\s+')
246        self.tags = re.compile(r':+')
247        self.sf = re.compile(r'%\([^\)]+\)')
248        self.set_macros(macros)
249        self._reset(name)
250        self.load(name)
251
252    def __str__(self):
253
254        def _dict(dd):
255            s = ''
256            ddl = list(dd.keys())
257            ddl.sort()
258            for d in ddl:
259                s += '  ' + d + ': ' + dd[d] + '\n'
260            return s
261
262        s = 'config: %s' % ('.'.join(self.configpath)) + \
263            '\n' + str(self.opts) + \
264            '\nlines parsed: %d' % (self.lc) + \
265            '\nname: ' + self.name + \
266            '\nmacros:\n' + str(self.macros)
267        for _package in self._packages:
268            s += str(self._packages[_package])
269        return s
270
271    def _reset(self, name):
272        self.name = name
273        self.load_depth = 0
274        self.configpath = []
275        self._includes = []
276        self._packages = {}
277        self.in_error = False
278        self.lc = 0
279        self.conditionals = {}
280        self._packages = {}
281        self.package = 'main'
282        self.disable_macro_reassign = False
283        self.pkgconfig_prefix = None
284        self.pkgconfig_crosscompile = False
285        self.pkgconfig_filter_flags = False
286        for arg in self.opts.args:
287            if arg.startswith('--with-') or arg.startswith('--without-'):
288                if '=' in arg:
289                    label, value = arg.split('=', 1)
290                else:
291                    label = arg
292                    value = None
293                label = label[2:].lower().replace('-', '_')
294                if value:
295                    self.macros.define(label, value)
296                else:
297                    self.macros.define(label)
298
299    def _relative_path(self, p):
300        sbdir = None
301        if '_sbdir' in self.macros:
302            sbdir = path.dirname(self.expand('%{_sbdir}'))
303            if p.startswith(sbdir):
304                p = p[len(sbdir) + 1:]
305        return p
306
307    def _name_line_msg(self,  msg):
308        return '%s:%d: %s' % (path.basename(self.name), self.lc,  msg)
309
310    def _output(self, text):
311        if not self.opts.quiet():
312            log.output(text)
313
314    def _error(self, msg):
315        err = 'error: %s' % (self._name_line_msg(msg))
316        log.stderr(err)
317        log.output(err)
318        self.in_error = True
319        if not self.opts.dry_run():
320            log.stderr('warning: switched to dry run due to errors')
321            self.opts.set_dry_run()
322
323    def _label(self, name):
324        if name.startswith('%{') and name[-1] is '}':
325            return name
326        return '%{' + name.lower() + '}'
327
328    def _cross_compile(self):
329        _host = self.expand('%{_host}')
330        _build = self.expand('%{_build}')
331        return _host != _build
332
333    def _candian_cross_compile(self):
334        _host = self.expand('%{_host}')
335        _build = self.expand('%{_build}')
336        _target = self.expand('%{_target}')
337        _alloc_cxc = self.defined('%{allow_cxc}')
338        return _alloc_cxc and _host != _build and _host != _target
339
340    def _macro_split(self, s):
341        '''Split the string (s) up by macros. Only split on the
342           outter level. Nested levels will need to split with futher calls.'''
343        trace_me = False
344        if trace_me:
345            print('------------------------------------------------------')
346        macros = []
347        nesting = []
348        has_braces = False
349        c = 0
350        while c < len(s):
351            if trace_me:
352                print('ms:', c, '"' + s[c:] + '"', has_braces, len(nesting), nesting)
353            #
354            # We need to watch for shell type variables or the form '${var}' because
355            # they can upset the brace matching.
356            #
357            if s[c] == '%' or s[c] == '$':
358                start = s[c]
359                c += 1
360                if c == len(s):
361                    continue
362                #
363                # Do we have '%%' or '%(' or '$%' or '$(' or not '${' ?
364                #
365                if s[c] == '%' or s[c] == '(' or (start == '$' and s[c] != '{'):
366                    continue
367                elif not s[c].isspace():
368                    #
369                    # If this is a shell macro and we are at the outter
370                    # level or is '$var' forget it and move on.
371                    #
372                    if start == '$' and (s[c] != '{' or len(nesting) == 0):
373                        continue
374                    if s[c] == '{':
375                        this_has_braces = True
376                    else:
377                        this_has_braces = False
378                    nesting.append((c - 1, has_braces))
379                    has_braces = this_has_braces
380            elif len(nesting) > 0:
381                if s[c] == '}' or (s[c].isspace() and not has_braces):
382                    #
383                    # Can have '%{?test: something %more}' where the
384                    # nested %more ends with the '}' which also ends
385                    # the outter macro.
386                    #
387                    if not has_braces:
388                        if s[c] == '}':
389                            macro_start, has_braces = nesting[len(nesting) - 1]
390                            nesting = nesting[:-1]
391                            if len(nesting) == 0:
392                                macros.append(s[macro_start:c].strip())
393                    if len(nesting) > 0:
394                        macro_start, has_braces = nesting[len(nesting) - 1]
395                        nesting = nesting[:-1]
396                        if len(nesting) == 0:
397                            macros.append(s[macro_start:c + 1].strip())
398            c += 1
399        if trace_me:
400            print('ms:', macros)
401        if trace_me:
402            print('-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
403        return macros
404
405    def _shell(self, line):
406        sl = self.sf.findall(line)
407        if len(sl):
408            e = execute.capture_execution()
409            for s in sl:
410                if options.host_windows:
411                    cmd = '%s -c "%s"' % (self.macros.expand('%{__sh}'), s[2:-1])
412                else:
413                    cmd = s[2:-1]
414                exit_code, proc, output = e.shell(cmd)
415                log.trace('shell-output: %d %s' % (exit_code, output))
416                if exit_code == 0:
417                    line = line.replace(s, output)
418                else:
419                    raise error.general('shell macro failed: %s:%d: %s' % (s, exit_code, output))
420        return line
421
422    def _pkgconfig_check(self, test):
423        ok = False
424        if type(test) == str:
425            test = test.split()
426        if not self._cross_compile() or self.pkgconfig_crosscompile:
427            try:
428                pkg = pkgconfig.package(test[0],
429                                        prefix = self.pkgconfig_prefix,
430                                        output = self._output,
431                                        src = log.trace)
432                if len(test) != 1 and len(test) != 3:
433                    self._error('malformed check: %s' % (' '.join(test)))
434                else:
435                    op = '>='
436                    ver = '0'
437                    if len(test) == 3:
438                        op = test[1]
439                        ver = self.macros.expand(test[2])
440                    ok = pkg.check(op, ver)
441            except pkgconfig.error as pe:
442                self._error('pkgconfig: check: %s' % (pe))
443            except:
444                raise error.internal('pkgconfig failure')
445        if ok:
446            return '1'
447        return '0'
448
449    def _pkgconfig_flags(self, package, flags):
450        pkg_flags = None
451        if not self._cross_compile() or self.pkgconfig_crosscompile:
452            try:
453                pkg = pkgconfig.package(package,
454                                        prefix = self.pkgconfig_prefix,
455                                        output = self._output,
456                                        src = log.trace)
457                pkg_flags = pkg.get(flags)
458                if pkg_flags and self.pkgconfig_filter_flags:
459                    fflags = []
460                    for f in pkg_flags.split():
461                        if not f.startswith('-f') and not f.startswith('-W'):
462                            fflags += [f]
463                    pkg_flags = ' '.join(fflags)
464                log.trace('pkgconfig: %s: %s' % (flags, pkg_flags))
465            except pkgconfig.error as pe:
466                self._error('pkgconfig: %s: %s' % (flags, pe))
467            except:
468                raise error.internal('pkgconfig failure')
469        if pkg_flags is None:
470            pkg_flags = ''
471        return pkg_flags
472
473    def _pkgconfig(self, pcl):
474        ok = False
475        ps = ''
476        if pcl[0] == 'check':
477            ps = self._pkgconfig_check(pcl[1:])
478        elif pcl[0] == 'prefix':
479            if len(pcl) == 2:
480                self.pkgconfig_prefix = pcl[1]
481            else:
482                self._error('prefix error: %s' % (' '.join(pcl)))
483        elif pcl[0] == 'crosscompile':
484            ok = True
485            if len(pcl) == 2:
486                if pcl[1].lower() == 'yes':
487                    self.pkgconfig_crosscompile = True
488                elif pcl[1].lower() == 'no':
489                    self.pkgconfig_crosscompile = False
490                else:
491                    ok = False
492            else:
493                ok = False
494            if not ok:
495                self._error('crosscompile error: %s' % (' '.join(pcl)))
496        elif pcl[0] == 'filter-flags':
497            ok = True
498            if len(pcl) == 2:
499                if pcl[1].lower() == 'yes':
500                    self.pkgconfig_filter_flags = True
501                elif pcl[1].lower() == 'no':
502                    self.pkgconfig_filter_flags = False
503                else:
504                    ok = False
505            else:
506                ok = False
507            if not ok:
508                self._error('crosscompile error: %s' % (' '.join(pcl)))
509        elif pcl[0] in ['ccflags', 'cflags', 'ldflags', 'libs']:
510            ps = self._pkgconfig_flags(pcl[1], pcl[0])
511        else:
512            self._error('pkgconfig error: %s' % (' '.join(pcl)))
513        return ps
514
515    def _expand(self, s):
516        expand_count = 0
517        expanded = True
518        while expanded:
519            expand_count += 1
520            if expand_count > 500:
521                raise error.general('macro expand looping: %s' % (s))
522            expanded = False
523            ms = self._macro_split(s)
524            for m in ms:
525                mn = m
526                #
527                # A macro can be '%{macro}' or '%macro'. Turn the later into
528                # the former.
529                #
530                show_warning = True
531                if mn[1] != '{':
532                    for r in self._ignore:
533                        if r.match(mn) is not None:
534                            mn = None
535                            break
536                    else:
537                        mn = self._label(mn[1:])
538                        show_warning = False
539                elif m.startswith('%{expand'):
540                    colon = m.find(':')
541                    if colon < 8:
542                        log.warning('malformed expand macro, no colon found')
543                    else:
544                        e = self._expand(m[colon + 1:-1].strip())
545                        s = s.replace(m, self._label(e))
546                        expanded = True
547                        mn = None
548                elif m.startswith('%{with '):
549                    #
550                    # Change the ' ' to '_' because the macros have no spaces.
551                    #
552                    n = self._label('with_' + m[7:-1].strip())
553                    if n in self.macros:
554                        s = s.replace(m, '1')
555                    else:
556                        s = s.replace(m, '0')
557                    expanded = True
558                    mn = None
559                elif m.startswith('%{echo'):
560                    if not m.endswith('}'):
561                        log.warning("malformed conditional macro '%s'" % (m))
562                        mn = None
563                    else:
564                        e = self._expand(m[6:-1].strip())
565                        log.notice('%s' % (self._name_line_msg(e)))
566                        s = ''
567                        expanded = True
568                        mn = None
569                elif m.startswith('%{defined'):
570                    n = self._label(m[9:-1].strip())
571                    if n in self.macros:
572                        s = s.replace(m, '1')
573                    else:
574                        s = s.replace(m, '0')
575                    expanded = True
576                    mn = None
577                elif m.startswith('%{path '):
578                    pl = m[7:-1].strip().split()
579                    ok = False
580                    if len(pl) == 2:
581                        ok = True
582                        epl = []
583                        for p in pl[1:]:
584                            epl += [self._expand(p)]
585                        p = ' '.join(epl)
586                        if pl[0].lower() == 'prepend':
587                            if len(self.macros['_pathprepend']):
588                                self.macros['_pathprepend'] = \
589                                    '%s:%s' % (p, self.macros['_pathprepend'])
590                            else:
591                                self.macros['_pathprepend'] = p
592                        elif pl[0].lower() == 'postpend':
593                            if len(self.macros['_pathprepend']):
594                                self.macros['_pathprepend'] = \
595                                    '%s:%s' % (self.macros['_pathprepend'], p)
596                            else:
597                                self.macros['_pathprepend'] = p
598                        else:
599                            ok = False
600                    if ok:
601                        s = s.replace(m, '')
602                    else:
603                        self._error('path error: %s' % (' '.join(pl)))
604                    mn = None
605                elif m.startswith('%{pkgconfig '):
606                    pcl = m[11:-1].strip().split()
607                    if len(pcl):
608                        epcl = []
609                        for pc in pcl:
610                            epcl += [self._expand(pc)]
611                        ps = self._pkgconfig(epcl)
612                        s = s.replace(m, ps)
613                        expanded = True
614                    else:
615                        self._error('pkgconfig error: %s' % (m[11:-1].strip()))
616                    mn = None
617                elif m.startswith('%{?') or m.startswith('%{!?'):
618                    if m[2] == '!':
619                        start = 4
620                    else:
621                        start = 3
622                    colon = m[start:].find(':')
623                    if colon < 0:
624                        if not m.endswith('}'):
625                            log.warning("malformed conditional macro '%s'" % (m))
626                            mn = None
627                        else:
628                            mn = self._label(m[start:-1])
629                    else:
630                        mn = self._label(m[start:start + colon])
631                    if mn:
632                        if m.startswith('%{?'):
633                            istrue = False
634                            if mn in self.macros:
635                                # If defined and 0 or '' then it is false.
636                                istrue = _check_bool(self.macros[mn])
637                                if istrue is None:
638                                    istrue = _check_nil(self.macros[mn])
639                            if colon >= 0 and istrue:
640                                s = s.replace(m, m[start + colon + 1:-1])
641                                expanded = True
642                                mn = None
643                            elif not istrue:
644                                mn = '%{nil}'
645                        else:
646                            isfalse = True
647                            if mn in self.macros:
648                                istrue = _check_bool(self.macros[mn])
649                                if istrue is None or istrue == True:
650                                    isfalse = False
651                            if colon >= 0 and isfalse:
652                                s = s.replace(m, m[start + colon + 1:-1])
653                                expanded = True
654                                mn = None
655                            else:
656                                mn = '%{nil}'
657                if mn:
658                    if mn.lower() in self.macros:
659                        s = s.replace(m, self.macros[mn.lower()])
660                        expanded = True
661                    elif show_warning:
662                        self._error("macro '%s' not found" % (mn))
663        return self._shell(s)
664
665    def _disable(self, config, ls):
666        if len(ls) != 2:
667            log.warning('invalid disable statement')
668        else:
669            if ls[1] == 'select':
670                self.macros.lock_read_map()
671                log.trace('config: %s: _disable_select: %s' % (self.name, ls[1]))
672            else:
673                log.warning('invalid disable statement: %s' % (ls[1]))
674
675    def _select(self, config, ls):
676        if len(ls) != 2:
677            log.warning('invalid select statement')
678        else:
679            r = self.macros.set_read_map(ls[1])
680            log.trace('config: %s: _select: %s %s %r' % \
681                          (self.name, r, ls[1], self.macros.maps()))
682
683    def _sources(self, ls):
684        return sources.process(ls[0][1:], ls[1:], self.macros, self._error)
685
686    def _hash(self, ls):
687        return sources.hash(ls[1:], self.macros, self._error)
688
689    def _define(self, config, ls):
690        if len(ls) <= 1:
691            log.warning('invalid macro definition')
692        else:
693            d = self._label(ls[1])
694            if self.disable_macro_reassign:
695                if (d not in self.macros) or \
696                        (d in self.macros and len(self.macros[d]) == 0):
697                    if len(ls) == 2:
698                        self.macros[d] = '1'
699                    else:
700                        self.macros[d] = ' '.join([f.strip() for f in ls[2:]])
701                else:
702                    log.warning("macro '%s' already defined" % (d))
703            else:
704                if len(ls) == 2:
705                    self.macros[d] = '1'
706                else:
707                    self.macros[d] = ' '.join([f.strip() for f in ls[2:]])
708
709    def _undefine(self, config, ls):
710        if len(ls) <= 1:
711            log.warning('invalid macro definition')
712        else:
713            mn = self._label(ls[1])
714            if mn in self.macros:
715                del self.macros[mn]
716
717    def _ifs(self, config, ls, label, iftrue, isvalid, dir, info):
718        in_iftrue = True
719        data = []
720        while True:
721            if isvalid and \
722                    ((iftrue and in_iftrue) or (not iftrue and not in_iftrue)):
723                this_isvalid = True
724            else:
725                this_isvalid = False
726            r = self._parse(config, dir, info, roc = True, isvalid = this_isvalid)
727            if r[0] == 'package':
728                if this_isvalid:
729                    dir, info, data = self._process_package(r, dir, info, data)
730            elif r[0] == 'control':
731                if r[1] == '%end':
732                    self._error(label + ' without %endif')
733                    raise error.general('terminating build')
734                if r[1] == '%endif':
735                    log.trace('config: %s: _ifs: %s %s' % (self.name, r[1], this_isvalid))
736                    return data
737                if r[1] == '%else':
738                    in_iftrue = False
739            elif r[0] == 'directive':
740                if this_isvalid:
741                    if r[1] == '%include':
742                        self.load(r[2][0])
743                        continue
744                    dir, info, data = self._process_directive(r, dir, info, data)
745            elif r[0] == 'data':
746                if this_isvalid:
747                    dir, info, data = self._process_data(r, dir, info, data)
748        # @note is a directive extend missing
749
750    def _if(self, config, ls, isvalid, dir, info, invert = False):
751
752        def add(x, y):
753            return x + ' ' + str(y)
754
755        istrue = False
756        if isvalid:
757            if len(ls) == 2:
758                s = ls[1]
759            else:
760                s = (ls[1] + ' ' + ls[2])
761            ifls = s.split()
762            if len(ifls) == 1:
763                #
764                # Check if '%if %{x} == %{nil}' has both parts as nothing
765                # which means '%if ==' is always True and '%if !=' is always false.
766                #
767                if ifls[0] == '==':
768                    istrue = True
769                elif ifls[0] == '!=':
770                    istrue = False
771                else:
772                    istrue = _check_bool(ifls[0])
773                    if istrue == None:
774                        self._error('invalid if bool value: ' + reduce(add, ls, ''))
775                        istrue = False
776            elif len(ifls) == 2:
777                if ifls[0] == '!':
778                    istrue = _check_bool(ifls[1])
779                    if istrue == None:
780                        self._error('invalid if bool value: ' + reduce(add, ls, ''))
781                        istrue = False
782                    else:
783                        istrue = not istrue
784                else:
785                    #
786                    # Check is something is being checked against empty,
787                    #   ie '%if %{x} == %{nil}'
788                    # The logic is 'something == nothing' is False and
789                    # 'something != nothing' is True.
790                    #
791                    if ifls[1] == '==':
792                        istrue = False
793                    elif  ifls[1] == '!=':
794                        istrue = True
795                    else:
796                        self._error('invalid if bool operator: ' + reduce(add, ls, ''))
797            else:
798                if len(ifls) > 3:
799                    for op in ['==', '!=', '>=', '=>', '=<', '<=', '>', '<']:
800                        ops = s.split(op)
801                        if len(ops) == 2:
802                            ifls = (ops[0], op, ops[1])
803                            break
804                if len(ifls) != 3:
805                     self._error('malformed if: ' + reduce(add, ls, ''))
806                if ifls[1] == '==':
807                    if ifls[0] == ifls[2]:
808                        istrue = True
809                    else:
810                        istrue = False
811                elif ifls[1] == '!=' or ifls[1] == '=!':
812                    if ifls[0] != ifls[2]:
813                        istrue = True
814                    else:
815                        istrue = False
816                elif ifls[1] == '>':
817                    if ifls[0] > ifls[2]:
818                        istrue = True
819                    else:
820                        istrue = False
821                elif ifls[1] == '>=' or ifls[1] == '=>':
822                    if ifls[0] >= ifls[2]:
823                        istrue = True
824                    else:
825                        istrue = False
826                elif ifls[1] == '<=' or ifls[1] == '=<':
827                    if ifls[0] <= ifls[2]:
828                        istrue = True
829                    else:
830                        istrue = False
831                elif ifls[1] == '<':
832                    if ifls[0] < ifls[2]:
833                        istrue = True
834                    else:
835                        istrue = False
836                else:
837                    self._error('invalid %if operator: ' + reduce(add, ls, ''))
838            if invert:
839                istrue = not istrue
840            log.trace('config: %s: _if:  %s %s' % (self.name, ifls, str(istrue)))
841        return self._ifs(config, ls, '%if', istrue, isvalid, dir, info)
842
843    def _ifos(self, config, ls, isvalid, dir, info):
844        isos = False
845        if isvalid:
846            os = self.define('_os')
847            for l in ls:
848                if l in os:
849                    isos = True
850                    break
851        return self._ifs(config, ls, '%ifos', isos, isvalid, dir, info)
852
853    def _ifarch(self, config, positive, ls, isvalid, dir, info):
854        isarch = False
855        if isvalid:
856            arch = self.define('_arch')
857            for l in ls:
858                if l in arch:
859                    isarch = True
860                    break
861        if not positive:
862            isarch = not isarch
863        return self._ifs(config, ls, '%ifarch', isarch, isvalid, dir, info)
864
865    def _parse(self, config, dir, info, roc = False, isvalid = True):
866        # roc = return on control
867
868        def _clean(line):
869            line = line[0:-1]
870            b = line.find('#')
871            if b >= 0:
872                line = line[1:b]
873            return line.strip()
874
875        #
876        # Need to add code to count matching '{' and '}' and if they
877        # do not match get the next line and add to the string until
878        # they match. This closes an opening '{' that is on another
879        # line.
880        #
881        for l in config:
882            self.lc += 1
883            l = _clean(l)
884            if len(l) == 0:
885                continue
886            log.trace('config: %s: %03d: %s %s' % \
887                          (self.name, self.lc, str(isvalid), l))
888            lo = l
889            if isvalid:
890                l = self._expand(l)
891            if len(l) == 0:
892                continue
893            if l[0] == '%':
894                ls = self.wss.split(l, 2)
895                los = self.wss.split(lo, 2)
896                if ls[0] == '%package':
897                    if isvalid:
898                        if ls[1] == '-n':
899                            name = ls[2]
900                        else:
901                            name = self.name + '-' + ls[1]
902                        return ('package', name)
903                elif ls[0] == '%disable':
904                    if isvalid:
905                        self._disable(config, ls)
906                elif ls[0] == '%select':
907                    if isvalid:
908                        self._select(config, ls)
909                elif ls[0] == '%source' or ls[0] == '%patch':
910                    if isvalid:
911                        d = self._sources(ls)
912                        if d is not None:
913                            return ('data', d)
914                elif ls[0] == '%hash':
915                    if isvalid:
916                        d = self._hash(ls)
917                        if d is not None:
918                            return ('data', d)
919                elif ls[0] == '%patch':
920                    if isvalid:
921                        self._select(config, ls)
922                elif ls[0] == '%error':
923                    if isvalid:
924                        return ('data', ['%%error %s' % (self._name_line_msg(l[7:]))])
925                elif ls[0] == '%warning':
926                    if isvalid:
927                        return ('data', ['%%warning %s' % (self._name_line_msg(l[9:]))])
928                elif ls[0] == '%define' or ls[0] == '%global':
929                    if isvalid:
930                        self._define(config, ls)
931                elif ls[0] == '%undefine':
932                    if isvalid:
933                        self._undefine(config, ls)
934                elif ls[0] == '%if':
935                    d = self._if(config, ls, isvalid, dir, info)
936                    if len(d):
937                        log.trace('config: %s: %%if: %s' % (self.name, d))
938                        return ('data', d)
939                elif ls[0] == '%ifn':
940                    d = self._if(config, ls, isvalid, dir, info, True)
941                    if len(d):
942                        log.trace('config: %s: %%ifn: %s' % (self.name, d))
943                        return ('data', d)
944                elif ls[0] == '%ifos':
945                    d = self._ifos(config, ls, isvalid, dir, info)
946                    if len(d):
947                        return ('data', d)
948                elif ls[0] == '%ifarch':
949                    d = self._ifarch(config, True, ls, isvalid, dir, info)
950                    if len(d):
951                        return ('data', d)
952                elif ls[0] == '%ifnarch':
953                    d = self._ifarch(config, False, ls, isvalid, dir, info)
954                    if len(d):
955                        return ('data', d)
956                elif ls[0] == '%endif':
957                    if roc:
958                        return ('control', '%endif', '%endif')
959                    log.warning("unexpected '" + ls[0] + "'")
960                elif ls[0] == '%else':
961                    if roc:
962                        return ('control', '%else', '%else')
963                    log.warning("unexpected '" + ls[0] + "'")
964                elif ls[0].startswith('%defattr'):
965                    return ('data', [l])
966                elif ls[0] == '%bcond_with':
967                    if isvalid:
968                        #
969                        # Check if already defined. Would be by the command line or
970                        # even a host specific default.
971                        #
972                        if self._label('with_' + ls[1]) not in self.macros:
973                            self._define(config, (ls[0], 'without_' + ls[1]))
974                elif ls[0] == '%bcond_without':
975                    if isvalid:
976                        if self._label('without_' + ls[1]) not in self.macros:
977                            self._define(config, (ls[0], 'with_' + ls[1]))
978                else:
979                    for r in self._ignore:
980                        if r.match(ls[0]) is not None:
981                            return ('data', [l])
982                    if isvalid:
983                        for d in self._directive:
984                            if ls[0].strip() == d:
985                                return ('directive', ls[0].strip(), ls[1:])
986                        log.warning("unknown directive: '" + ls[0] + "'")
987                        return ('data', [lo])
988            else:
989                return ('data', [lo])
990        return ('control', '%end', '%end')
991
992    def _process_package(self, results, directive, info, data):
993        self._set_package(results[1])
994        directive = None
995        return (directive, info, data)
996
997    def _process_directive(self, results, directive, info, data):
998        new_data = []
999        if results[1] == '%description':
1000            new_data = [' '.join(results[2])]
1001            if len(results[2]) == 0:
1002                _package = 'main'
1003            elif len(results[2]) == 1:
1004                _package = results[2][0]
1005            else:
1006                if results[2][0].strip() != '-n':
1007                    log.warning("unknown directive option: '%s'" % (' '.join(results[2])))
1008                _package = results[2][1].strip()
1009            self._set_package(_package)
1010        if directive and directive != results[1]:
1011            self._directive_extend(directive, data)
1012        directive = results[1]
1013        data = new_data
1014        return (directive, info, data)
1015
1016    def _process_data(self, results, directive, info, data):
1017        new_data = []
1018        for l in results[1]:
1019            if l.startswith('%error'):
1020                l = self._expand(l)
1021                raise error.general('config error: %s' % (l[7:]))
1022            elif l.startswith('%warning'):
1023                l = self._expand(l)
1024                log.stderr('warning: %s' % (l[9:]))
1025                log.warning(l[9:])
1026            if not directive:
1027                l = self._expand(l)
1028                ls = self.tags.split(l, 1)
1029                log.trace('config: %s: _tag: %s %s' % (self.name, l, ls))
1030                if len(ls) > 1:
1031                    info = ls[0].lower()
1032                    if info[-1] == ':':
1033                        info = info[:-1]
1034                    info_data = ls[1].strip()
1035                else:
1036                    info_data = ls[0].strip()
1037                if info is not None:
1038                    self._info_append(info, info_data)
1039                else:
1040                    log.warning("invalid format: '%s'" % (info_data[:-1]))
1041            else:
1042                l = self._expand(l)
1043                log.trace('config: %s: _data: %s %s' % (self.name, l, new_data))
1044                new_data.append(l)
1045        return (directive, info, data + new_data)
1046
1047    def _set_package(self, _package):
1048        if self.package == 'main' and \
1049                self._packages[self.package].name() != None:
1050            if self._packages[self.package].name() == _package:
1051                return
1052        if _package not in self._packages:
1053            self._packages[_package] = package(_package,
1054                                               self.define('%{_arch}'),
1055                                               self)
1056        self.package = _package
1057
1058    def _directive_extend(self, dir, data):
1059        self._packages[self.package].directive_extend(dir, data)
1060
1061    def _info_append(self, info, data):
1062        self._packages[self.package].info_append(info, data)
1063
1064    def set_macros(self, macros):
1065        if macros is None:
1066            self.macros = opts.defaults
1067        else:
1068            self.macros = macros
1069
1070    def load(self, name):
1071
1072        def common_end(left, right):
1073            end = ''
1074            while len(left) and len(right):
1075                if left[-1] != right[-1]:
1076                    return end
1077                end = left[-1] + end
1078                left = left[:-1]
1079                right = right[:-1]
1080            return end
1081
1082        if self.load_depth == 0:
1083            self._reset(name)
1084            self._packages[self.package] = package(self.package,
1085                                                   self.define('%{_arch}'),
1086                                                   self)
1087
1088        self.load_depth += 1
1089
1090        save_name = self.name
1091        save_lc = self.lc
1092
1093        #
1094        # Locate the config file. Expand any macros then add the
1095        # extension. Check if the file exists, therefore directly
1096        # referenced. If not see if the file contains ':' or the path
1097        # separator. If it does split the path else use the standard config dir
1098        # path in the defaults.
1099        #
1100
1101        exname = self.expand(name)
1102
1103        #
1104        # Macro could add an extension.
1105        #
1106        if exname.endswith('.cfg'):
1107            configname = exname
1108        else:
1109            configname = '%s.cfg' % (exname)
1110            name = '%s.cfg' % (name)
1111
1112        if ':' in configname:
1113            cfgname = path.basename(configname)
1114        else:
1115            cfgname = common_end(configname, name)
1116
1117        if not path.exists(configname):
1118            if ':' in configname:
1119                configdirs = path.dirname(configname).split(':')
1120            else:
1121                configdirs = self.define('_configdir').split(':')
1122            for cp in configdirs:
1123                configname = path.join(path.abspath(cp), cfgname)
1124                if path.exists(configname):
1125                    break
1126                configname = None
1127            if configname is None:
1128                raise error.general('no config file found: %s' % (cfgname))
1129
1130        try:
1131            log.trace('config: %s: _open: %s' % (self.name, path.host(configname)))
1132            config = open(path.host(configname), 'r')
1133        except IOError as err:
1134            raise error.general('error opening config file: %s' % (path.host(configname)))
1135
1136        self.configpath += [configname]
1137        self._includes += [configname]
1138
1139        self.name = self._relative_path(configname)
1140        self.lc = 0
1141
1142        try:
1143            dir = None
1144            info = None
1145            data = []
1146            while True:
1147                r = self._parse(config, dir, info)
1148                if r[0] == 'package':
1149                    dir, info, data = self._process_package(r, dir, info, data)
1150                elif r[0] == 'control':
1151                    if r[1] == '%end':
1152                        break
1153                    log.warning("unexpected '%s'" % (r[1]))
1154                elif r[0] == 'directive':
1155                    if r[1] == '%include':
1156                        self.load(r[2][0])
1157                        continue
1158                    dir, info, data = self._process_directive(r, dir, info, data)
1159                elif r[0] == 'data':
1160                    dir, info, data = self._process_data(r, dir, info, data)
1161                else:
1162                    self._error("%d: invalid parse state: '%s" % (self.lc, r[0]))
1163            if dir is not None:
1164                self._directive_extend(dir, data)
1165        except:
1166            config.close()
1167            raise
1168
1169        config.close()
1170
1171        self.name = save_name
1172        self.lc = save_lc
1173
1174        self.load_depth -= 1
1175
1176    def defined(self, name):
1177        return name in self.macros
1178
1179    def define(self, name):
1180        if name in self.macros:
1181            d = self.macros[name]
1182        else:
1183            n = self._label(name)
1184            if n in self.macros:
1185                d = self.macros[n]
1186            else:
1187                raise error.general('%d: macro "%s" not found' % (self.lc, name))
1188        return self._expand(d)
1189
1190    def set_define(self, name, value):
1191        self.macros[name] = value
1192
1193    def expand(self, line):
1194        if type(line) == list:
1195            el = []
1196            for l in line:
1197                el += [self._expand(l)]
1198            return el
1199        return self._expand(line)
1200
1201    def macro(self, name):
1202        if name in self.macros:
1203            return self.macros[name]
1204        raise error.general('macro "%s" not found' % (name))
1205
1206    def directive(self, _package, name):
1207        if _package not in self._packages:
1208            raise error.general('package "' + _package + '" not found')
1209        if name not in self._packages[_package].directives:
1210            raise error.general('directive "' + name + \
1211                                    '" not found in package "' + _package + '"')
1212        return self._packages[_package].directives[name]
1213
1214    def abspath(self, rpath):
1215        return path.abspath(self.define(rpath))
1216
1217    def packages(self):
1218        return self._packages
1219
1220    def includes(self):
1221        return self._includes
1222
1223    def file_name(self):
1224        return self.name
1225
1226def run():
1227    import sys
1228    try:
1229        #
1230        # Run where defaults.mc is located
1231        #
1232        opts = options.load(sys.argv, defaults = 'defaults.mc')
1233        log.trace('config: count %d' % (len(opts.config_files())))
1234        for config_file in opts.config_files():
1235            s = open(config_file, opts)
1236            print(s)
1237            del s
1238    except error.general as gerr:
1239        print(gerr)
1240        sys.exit(1)
1241    except error.internal as ierr:
1242        print(ierr)
1243        sys.exit(1)
1244    except KeyboardInterrupt:
1245        log.notice('abort: user terminated')
1246        sys.exit(1)
1247    sys.exit(0)
1248
1249if __name__ == "__main__":
1250    run()
Note: See TracBrowser for help on using the repository browser.