source: rtems-source-builder/source-builder/sb/config.py @ 9f4ed7e

4.10
Last change on this file since 9f4ed7e was 9f4ed7e, checked in by Chris Johns <chrisj@…>, on 02/08/18 at 05:13:40

rtems: Do not build the RTEMS kernel by default in releases.

Close #3292

  • Property mode set to 100644
File size: 49.5 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 = [ '%include',
225                   '%description',
226                   '%prep',
227                   '%build',
228                   '%clean',
229                   '%install',
230                   '%testing' ]
231
232    _ignore = [ re.compile('%setup'),
233                re.compile('%configure'),
234                re.compile('%source'),
235                re.compile('%patch'),
236                re.compile('%hash'),
237                re.compile('%select'),
238                re.compile('%disable') ]
239
240    def __init__(self, name, opts, macros = None):
241        log.trace('config: %s: initialising' % (name))
242        self.opts = opts
243        self.init_name = name
244        self.wss = re.compile(r'\s+')
245        self.tags = re.compile(r':+')
246        self.sf = re.compile(r'%\([^\)]+\)')
247        self.set_macros(macros)
248        self._reset(name)
249        self.load(name)
250
251    def __str__(self):
252
253        def _dict(dd):
254            s = ''
255            ddl = list(dd.keys())
256            ddl.sort()
257            for d in ddl:
258                s += '  ' + d + ': ' + dd[d] + '\n'
259            return s
260
261        s = 'config: %s' % ('.'.join(self.configpath)) + \
262            '\n' + str(self.opts) + \
263            '\nlines parsed: %d' % (self.lc) + \
264            '\nname: ' + self.name + \
265            '\nmacros:\n' + str(self.macros)
266        for _package in self._packages:
267            s += str(self._packages[_package])
268        return s
269
270    def _reset(self, name):
271        self.name = name
272        self.load_depth = 0
273        self.configpath = []
274        self._includes = []
275        self._packages = {}
276        self.in_error = False
277        self.lc = 0
278        self.if_depth = 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        # Hack to by pass pkgconfig checks when just wanting to download the
424        # source.
425        if self.macros['_dry_run'] == '1' and \
426           ('with_download' in self.macros and self.macros['with_download'] == '1'):
427            return '0'
428        ok = False
429        if type(test) == str:
430            test = test.split()
431        if not self._cross_compile() or self.pkgconfig_crosscompile:
432            try:
433                pkg = pkgconfig.package(test[0],
434                                        prefix = self.pkgconfig_prefix,
435                                        output = self._output,
436                                        src = log.trace)
437                if len(test) != 1 and len(test) != 3:
438                    self._error('malformed check: %s' % (' '.join(test)))
439                else:
440                    op = '>='
441                    ver = '0'
442                    if len(test) == 3:
443                        op = test[1]
444                        ver = self.macros.expand(test[2])
445                    ok = pkg.check(op, ver)
446            except pkgconfig.error as pe:
447                self._error('pkgconfig: check: %s' % (pe))
448            except:
449                raise error.internal('pkgconfig failure')
450        if ok:
451            return '1'
452        return '0'
453
454    def _pkgconfig_flags(self, package, flags):
455        pkg_flags = None
456        if not self._cross_compile() or self.pkgconfig_crosscompile:
457            try:
458                pkg = pkgconfig.package(package,
459                                        prefix = self.pkgconfig_prefix,
460                                        output = self._output,
461                                        src = log.trace)
462                pkg_flags = pkg.get(flags)
463                if pkg_flags and self.pkgconfig_filter_flags:
464                    fflags = []
465                    for f in pkg_flags.split():
466                        if not f.startswith('-W'):
467                            fflags += [f]
468                    pkg_flags = ' '.join(fflags)
469                log.trace('pkgconfig: %s: %s' % (flags, pkg_flags))
470            except pkgconfig.error as pe:
471                self._error('pkgconfig: %s: %s' % (flags, pe))
472            except:
473                raise error.internal('pkgconfig failure')
474        if pkg_flags is None:
475            pkg_flags = ''
476        return pkg_flags
477
478    def _pkgconfig(self, pcl):
479        ok = False
480        ps = ''
481        if pcl[0] == 'check':
482            ps = self._pkgconfig_check(pcl[1:])
483        elif pcl[0] == 'prefix':
484            if len(pcl) == 2:
485                self.pkgconfig_prefix = pcl[1]
486            else:
487                self._error('prefix error: %s' % (' '.join(pcl)))
488        elif pcl[0] == 'crosscompile':
489            ok = True
490            if len(pcl) == 2:
491                if pcl[1].lower() == 'yes':
492                    self.pkgconfig_crosscompile = True
493                elif pcl[1].lower() == 'no':
494                    self.pkgconfig_crosscompile = False
495                else:
496                    ok = False
497            else:
498                ok = False
499            if not ok:
500                self._error('crosscompile error: %s' % (' '.join(pcl)))
501        elif pcl[0] == 'filter-flags':
502            ok = True
503            if len(pcl) == 2:
504                if pcl[1].lower() == 'yes':
505                    self.pkgconfig_filter_flags = True
506                elif pcl[1].lower() == 'no':
507                    self.pkgconfig_filter_flags = False
508                else:
509                    ok = False
510            else:
511                ok = False
512            if not ok:
513                self._error('crosscompile error: %s' % (' '.join(pcl)))
514        elif pcl[0] in ['ccflags', 'cflags', 'ldflags', 'libs']:
515            ps = self._pkgconfig_flags(pcl[1], pcl[0])
516        else:
517            self._error('pkgconfig error: %s' % (' '.join(pcl)))
518        return ps
519
520    def _expand(self, s):
521        expand_count = 0
522        expanded = True
523        while expanded:
524            expand_count += 1
525            if expand_count > 500:
526                raise error.general('macro expand looping: %s' % (s))
527            expanded = False
528            ms = self._macro_split(s)
529            for m in ms:
530                mn = m
531                #
532                # A macro can be '%{macro}' or '%macro'. Turn the later into
533                # the former.
534                #
535                show_warning = True
536                if mn[1] != '{':
537                    for r in self._ignore:
538                        if r.match(mn) is not None:
539                            mn = None
540                            break
541                    else:
542                        mn = self._label(mn[1:])
543                        show_warning = False
544                elif m.startswith('%{expand'):
545                    colon = m.find(':')
546                    if colon < 8:
547                        log.warning('malformed expand macro, no colon found')
548                    else:
549                        e = self._expand(m[colon + 1:-1].strip())
550                        s = s.replace(m, self._label(e))
551                        expanded = True
552                        mn = None
553                elif m.startswith('%{with '):
554                    #
555                    # Change the ' ' to '_' because the macros have no spaces.
556                    #
557                    n = self._label('with_' + m[7:-1].strip())
558                    if n in self.macros:
559                        s = s.replace(m, '1')
560                    else:
561                        s = s.replace(m, '0')
562                    expanded = True
563                    mn = None
564                elif m.startswith('%{echo'):
565                    if not m.endswith('}'):
566                        log.warning("malformed conditional macro '%s'" % (m))
567                        mn = None
568                    else:
569                        e = self._expand(m[6:-1].strip())
570                        log.notice('%s' % (self._name_line_msg(e)))
571                        s = ''
572                        expanded = True
573                        mn = None
574                elif m.startswith('%{defined'):
575                    n = self._label(m[9:-1].strip())
576                    if n in self.macros:
577                        s = s.replace(m, '1')
578                    else:
579                        s = s.replace(m, '0')
580                    expanded = True
581                    mn = None
582                elif m.startswith('%{!defined'):
583                    n = self._label(m[10:-1].strip())
584                    if n in self.macros:
585                        s = s.replace(m, '0')
586                    else:
587                        s = s.replace(m, '1')
588                    expanded = True
589                    mn = None
590                elif m.startswith('%{path '):
591                    pl = m[7:-1].strip().split()
592                    ok = False
593                    if len(pl) == 2:
594                        ok = True
595                        epl = []
596                        for p in pl[1:]:
597                            epl += [self._expand(p)]
598                        p = ' '.join(epl)
599                        if pl[0].lower() == 'prepend':
600                            if len(self.macros['_pathprepend']):
601                                self.macros['_pathprepend'] = \
602                                    '%s:%s' % (p, self.macros['_pathprepend'])
603                            else:
604                                self.macros['_pathprepend'] = p
605                        elif pl[0].lower() == 'postpend':
606                            if len(self.macros['_pathprepend']):
607                                self.macros['_pathprepend'] = \
608                                    '%s:%s' % (self.macros['_pathprepend'], p)
609                            else:
610                                self.macros['_pathprepend'] = p
611                        else:
612                            ok = False
613                    if ok:
614                        s = s.replace(m, '')
615                    else:
616                        self._error('path error: %s' % (' '.join(pl)))
617                    mn = None
618                elif m.startswith('%{pkgconfig '):
619                    pcl = m[11:-1].strip().split()
620                    if len(pcl):
621                        epcl = []
622                        for pc in pcl:
623                            epcl += [self._expand(pc)]
624                        ps = self._pkgconfig(epcl)
625                        s = s.replace(m, ps)
626                        expanded = True
627                    else:
628                        self._error('pkgconfig error: %s' % (m[11:-1].strip()))
629                    mn = None
630                elif m.startswith('%{?') or m.startswith('%{!?'):
631                    if m[2] == '!':
632                        start = 4
633                    else:
634                        start = 3
635                    colon = m[start:].find(':')
636                    if colon < 0:
637                        if not m.endswith('}'):
638                            log.warning("malformed conditional macro '%s'" % (m))
639                            mn = None
640                        else:
641                            mn = self._label(m[start:-1])
642                    else:
643                        mn = self._label(m[start:start + colon])
644                    if mn:
645                        if m.startswith('%{?'):
646                            istrue = False
647                            if mn in self.macros:
648                                # If defined and 0 or '' then it is false.
649                                istrue = _check_bool(self.macros[mn])
650                                if istrue is None:
651                                    istrue = _check_nil(self.macros[mn])
652                            if colon >= 0 and istrue:
653                                s = s.replace(m, m[start + colon + 1:-1])
654                                expanded = True
655                                mn = None
656                            elif not istrue:
657                                mn = '%{nil}'
658                        else:
659                            isfalse = True
660                            if mn in self.macros:
661                                istrue = _check_bool(self.macros[mn])
662                                if istrue is None or istrue == True:
663                                    isfalse = False
664                            if colon >= 0 and isfalse:
665                                s = s.replace(m, m[start + colon + 1:-1])
666                                expanded = True
667                                mn = None
668                            else:
669                                mn = '%{nil}'
670                if mn:
671                    if mn.lower() in self.macros:
672                        s = s.replace(m, self.macros[mn.lower()])
673                        expanded = True
674                    elif show_warning:
675                        self._error("macro '%s' not found" % (mn))
676        return self._shell(s)
677
678    def _disable(self, config, ls):
679        if len(ls) != 2:
680            log.warning('invalid disable statement')
681        else:
682            if ls[1] == 'select':
683                self.macros.lock_read_map()
684                log.trace('config: %s: %3d: _disable_select: %s' % (self.name, self.lc,
685                                                                     ls[1]))
686            else:
687                log.warning('invalid disable statement: %s' % (ls[1]))
688
689    def _select(self, config, ls):
690        if len(ls) != 2:
691            log.warning('invalid select statement')
692        else:
693            r = self.macros.set_read_map(ls[1])
694            log.trace('config: %s: %3d: _select: %s %s %r' % \
695                          (self.name, self.lc,
696                           r, ls[1], self.macros.maps()))
697
698    def _sources(self, ls):
699        return sources.process(ls[0][1:], ls[1:], self.macros, self._error)
700
701    def _hash(self, ls):
702        return sources.hash(ls[1:], self.macros, self._error)
703
704    def _define(self, config, ls):
705        if len(ls) <= 1:
706            log.warning('invalid macro definition')
707        else:
708            d = self._label(ls[1])
709            if self.disable_macro_reassign:
710                if (d not in self.macros) or \
711                        (d in self.macros and len(self.macros[d]) == 0):
712                    if len(ls) == 2:
713                        self.macros[d] = '1'
714                    else:
715                        self.macros[d] = ' '.join([f.strip() for f in ls[2:]])
716                else:
717                    log.warning("macro '%s' already defined" % (d))
718            else:
719                if len(ls) == 2:
720                    self.macros[d] = '1'
721                else:
722                    self.macros[d] = ' '.join([f.strip() for f in ls[2:]])
723
724    def _undefine(self, config, ls):
725        if len(ls) <= 1:
726            log.warning('invalid macro definition')
727        else:
728            mn = self._label(ls[1])
729            if mn in self.macros:
730                del self.macros[mn]
731
732    def _ifs(self, config, ls, label, iftrue, isvalid, dir, info):
733        log.trace('config: %s: %3d: _ifs[%i]: dir=%s %i %r' % \
734                  (self.name, self.lc, self.if_depth, str(dir), len(ls), ls))
735        in_dir = dir
736        in_iftrue = True
737        data = []
738        while True:
739            if isvalid and \
740                    ((iftrue and in_iftrue) or (not iftrue and not in_iftrue)):
741                this_isvalid = True
742            else:
743                this_isvalid = False
744            r = self._parse(config, dir, info, roc = True, isvalid = this_isvalid)
745            if r[0] == 'package':
746                if this_isvalid:
747                    dir, info, data = self._process_package(r, dir, info, data)
748            elif r[0] == 'control':
749                if r[1] == '%end':
750                    self._error(label + ' without %endif')
751                    raise error.general('terminating build')
752                if r[1] == '%endif':
753                    log.trace('config: %s: %3d: _ifs[%i]: %%endif: dir=%s %s %s %r' % \
754                              (self.name, self.lc, self.if_depth,
755                               str(dir), r[1], this_isvalid, data))
756                    if in_dir is None:
757                        if dir is not None:
758                            dir, info, data = self._process_directive(r, dir, info, data)
759                    else:
760                        if in_dir != dir:
761                            self._error('directives cannot change scope across if statements')
762
763                    return data
764                if r[1] == '%else':
765                    in_iftrue = False
766            elif r[0] == 'directive':
767                if this_isvalid:
768                    if r[1] == '%include':
769                        self.load(r[2][0])
770                        continue
771                    dir, info, data = self._process_directive(r, dir, info, data)
772            elif r[0] == 'data':
773                if this_isvalid:
774                    dir, info, data = self._process_data(r, dir, info, data)
775        # @note is a directive extend missing
776
777    def _if(self, config, ls, isvalid, dir, info, invert = False):
778
779        def add(x, y):
780            return x + ' ' + str(y)
781
782        if len(ls) == 1:
783            self._error('invalid if expression: ' + reduce(add, ls, ''))
784
785        cistrue = True # compound istrue
786        sls = reduce(add, ls[1:], '').split()
787        cls = sls
788
789        log.trace('config: %s: %3d: _if[%i]: %s' % (self.name, self.lc,
790                                                    self.if_depth, sls))
791
792        self.if_depth += 1
793
794        while len(cls) > 0 and isvalid:
795
796            join_op = 'none'
797
798            if cls[0] == '||' or cls[0] == '&&':
799                if cls[0] == '||':
800                    join_op = 'or'
801                elif cls[0] == '&&':
802                    join_op = 'and'
803                cls = cls[1:]
804                log.trace('config: %s: %3d: _if[%i]: joining: %s' % (self.name, self.lc,
805                                                                     self.if_depth,
806                                                                     join_op))
807            ori = 0
808            andi = 0
809            i = len(cls)
810            if '||' in cls:
811                ori = cls.index('||')
812                log.trace('config: %s: %3d: _if[%i}: OR found at %i' % (self.name, self.lc,
813                                                                        self.if_depth,
814                                                                        ori))
815            if '&&' in cls:
816                andi = cls.index('&&')
817                log.trace('config: %s: %3d: _if[%i]: AND found at %i' % (self.name, self.lc,
818                                                                         self.if_depth,
819                                                                         andi))
820            if ori > 0 or andi > 0:
821                if ori == 0:
822                    i = andi
823                elif andi == 0:
824                    i = ori
825                elif ori < andi:
826                    i = andi
827                else:
828                    i = andi
829                log.trace('config: %s: %3d: _if[%i]: next OP found at %i' % (self.name, self.lc,
830                                                                             self.if_depth,
831                i))
832            ls = cls[:i]
833            if len(ls) == 0:
834                self._error('invalid if expression: ' + reduce(add, sls, ''))
835            cls = cls[i:]
836
837            istrue = False
838
839            s = ' '.join(ls)
840            ifls = ls
841
842            if len(ifls) == 1:
843                #
844                # Check if '%if %{x} == %{nil}' has both parts as nothing
845                # which means '%if ==' is always True and '%if !=' is always false.
846                #
847                if ifls[0] == '==':
848                    istrue = True
849                elif ifls[0] == '!=':
850                    istrue = False
851                else:
852                    istrue = _check_bool(ifls[0])
853                    if istrue == None:
854                        self._error('invalid if bool value: ' + reduce(add, ls, ''))
855                        istrue = False
856            elif len(ifls) == 2:
857                if ifls[0] == '!':
858                    istrue = _check_bool(ifls[1])
859                    if istrue == None:
860                        self._error('invalid if bool value: ' + reduce(add, ls, ''))
861                        istrue = False
862                    else:
863                        istrue = not istrue
864                else:
865                    #
866                    # Check is something is being checked against empty,
867                    #   ie '%if %{x} == %{nil}'
868                    # The logic is 'something == nothing' is False and
869                    # 'something != nothing' is True.
870                    #
871                    if ifls[1] == '==':
872                        istrue = False
873                    elif  ifls[1] == '!=':
874                        istrue = True
875                    else:
876                        self._error('invalid if bool operator: ' + reduce(add, ls, ''))
877            else:
878                if len(ifls) > 3:
879                    for op in ['==', '!=', '>=', '=>', '=<', '<=', '>', '<']:
880                        ops = s.split(op)
881                        if len(ops) == 2:
882                            ifls = (ops[0], op, ops[1])
883                            break
884                if len(ifls) != 3:
885                     self._error('malformed if: ' + reduce(add, ls, ''))
886                if ifls[1] == '==':
887                    if ifls[0] == ifls[2]:
888                        istrue = True
889                    else:
890                        istrue = False
891                elif ifls[1] == '!=' or ifls[1] == '=!':
892                    if ifls[0] != ifls[2]:
893                        istrue = True
894                    else:
895                        istrue = False
896                elif ifls[1] == '>':
897                    if ifls[0] > ifls[2]:
898                        istrue = True
899                    else:
900                        istrue = False
901                elif ifls[1] == '>=' or ifls[1] == '=>':
902                    if ifls[0] >= ifls[2]:
903                        istrue = True
904                    else:
905                        istrue = False
906                elif ifls[1] == '<=' or ifls[1] == '=<':
907                    if ifls[0] <= ifls[2]:
908                        istrue = True
909                    else:
910                        istrue = False
911                elif ifls[1] == '<':
912                    if ifls[0] < ifls[2]:
913                        istrue = True
914                    else:
915                        istrue = False
916                else:
917                    self._error('invalid %if operator: ' + reduce(add, ls, ''))
918
919            if join_op == 'or':
920                if istrue:
921                    cistrue = True
922            elif join_op == 'and':
923                if not istrue:
924                    cistrue = False
925            else:
926                cistrue = istrue
927
928            log.trace('config: %s: %3d: _if[%i]:  %s %s %s %s' % (self.name, self.lc,
929                                                                  self.if_depth,
930                                                                  ifls, str(cistrue),
931                                                                  join_op, str(istrue)))
932
933        if invert:
934            cistrue = not cistrue
935
936        ifs_return = self._ifs(config, ls, '%if', cistrue, isvalid, dir, info)
937
938        self.if_depth -= 1
939
940        log.trace('config: %s: %3d: _if[%i]: %r' % (self.name, self.lc,
941                                                    self.if_depth, ifs_return))
942
943        return ifs_return
944
945    def _ifos(self, config, ls, isvalid, dir, info):
946        isos = False
947        if isvalid:
948            os = self.define('_os')
949            for l in ls:
950                if l in os:
951                    isos = True
952                    break
953        return self._ifs(config, ls, '%ifos', isos, isvalid, dir, info)
954
955    def _ifarch(self, config, positive, ls, isvalid, dir, info):
956        isarch = False
957        if isvalid:
958            arch = self.define('_arch')
959            for l in ls:
960                if l in arch:
961                    isarch = True
962                    break
963        if not positive:
964            isarch = not isarch
965        return self._ifs(config, ls, '%ifarch', isarch, isvalid, dir, info)
966
967    def _parse(self, config, dir, info, roc = False, isvalid = True):
968        # roc = return on control
969
970        def _clean(line):
971            line = line[0:-1]
972            b = line.find('#')
973            if b >= 0:
974                line = line[1:b]
975            return line.strip()
976
977        #
978        # Need to add code to count matching '{' and '}' and if they
979        # do not match get the next line and add to the string until
980        # they match. This closes an opening '{' that is on another
981        # line.
982        #
983        for l in config:
984            self.lc += 1
985            l = _clean(l)
986            if len(l) == 0:
987                continue
988            log.trace('config: %s: %0d: %s %s' % \
989                          (self.name, self.lc, str(isvalid), l))
990            lo = l
991            if isvalid:
992                l = self._expand(l)
993            if len(l) == 0:
994                continue
995            if l[0] == '%':
996                ls = self.wss.split(l, 2)
997                los = self.wss.split(lo, 2)
998                if ls[0] == '%package':
999                    if isvalid:
1000                        if ls[1] == '-n':
1001                            name = ls[2]
1002                        else:
1003                            name = self.name + '-' + ls[1]
1004                        return ('package', name)
1005                elif ls[0] == '%disable':
1006                    if isvalid:
1007                        self._disable(config, ls)
1008                elif ls[0] == '%select':
1009                    if isvalid:
1010                        self._select(config, ls)
1011                elif ls[0] == '%source' or ls[0] == '%patch':
1012                    if isvalid:
1013                        d = self._sources(ls)
1014                        if d is not None:
1015                            return ('data', d)
1016                elif ls[0] == '%hash':
1017                    if isvalid:
1018                        d = self._hash(ls)
1019                        if d is not None:
1020                            return ('data', d)
1021                elif ls[0] == '%patch':
1022                    if isvalid:
1023                        self._select(config, ls)
1024                elif ls[0] == '%error':
1025                    if isvalid:
1026                        return ('data', ['%%error %s' % (self._name_line_msg(l[7:]))])
1027                elif ls[0] == '%log':
1028                    if isvalid:
1029                        return ('data', ['%%log %s' % (self._name_line_msg(l[4:]))])
1030                elif ls[0] == '%warning':
1031                    if isvalid:
1032                        return ('data', ['%%warning %s' % (self._name_line_msg(l[9:]))])
1033                elif ls[0] == '%define' or ls[0] == '%global':
1034                    if isvalid:
1035                        self._define(config, ls)
1036                elif ls[0] == '%undefine':
1037                    if isvalid:
1038                        self._undefine(config, ls)
1039                elif ls[0] == '%if':
1040                    d = self._if(config, ls, isvalid, dir, info)
1041                    if len(d):
1042                        log.trace('config: %s: %3d: %%if: %s' % (self.name, self.lc, d))
1043                        return ('data', d)
1044                elif ls[0] == '%ifn':
1045                    d = self._if(config, ls, isvalid, dir, info, True)
1046                    if len(d):
1047                        log.trace('config: %s: %3d: %%ifn: %s' % (self.name, self.lc, d))
1048                        return ('data', d)
1049                elif ls[0] == '%ifos':
1050                    d = self._ifos(config, ls, isvalid, dir, info)
1051                    if len(d):
1052                        return ('data', d)
1053                elif ls[0] == '%ifarch':
1054                    d = self._ifarch(config, True, ls, isvalid, dir, info)
1055                    if len(d):
1056                        return ('data', d)
1057                elif ls[0] == '%ifnarch':
1058                    d = self._ifarch(config, False, ls, isvalid, dir, info)
1059                    if len(d):
1060                        return ('data', d)
1061                elif ls[0] == '%endif':
1062                    if roc:
1063                        return ('control', '%endif', '%endif')
1064                    log.warning("unexpected '" + ls[0] + "'")
1065                elif ls[0] == '%else':
1066                    if roc:
1067                        return ('control', '%else', '%else')
1068                    log.warning("unexpected '" + ls[0] + "'")
1069                elif ls[0].startswith('%defattr'):
1070                    return ('data', [l])
1071                elif ls[0] == '%bcond_with':
1072                    if isvalid:
1073                        #
1074                        # Check if already defined. Would be by the command line or
1075                        # even a host specific default.
1076                        #
1077                        if self._label('with_' + ls[1]) not in self.macros:
1078                            self._define(config, (ls[0], 'without_' + ls[1]))
1079                elif ls[0] == '%bcond_without':
1080                    if isvalid:
1081                        if self._label('without_' + ls[1]) not in self.macros:
1082                            self._define(config, (ls[0], 'with_' + ls[1]))
1083                else:
1084                    for r in self._ignore:
1085                        if r.match(ls[0]) is not None:
1086                            return ('data', [l])
1087                    if isvalid:
1088                        for d in self._directive:
1089                            if ls[0].strip() == d:
1090                                log.trace('config: %s: %0d: _parse: directive: %s' % \
1091                                          (self.name, self.lc, ls[0].strip()))
1092                                return ('directive', ls[0].strip(), ls[1:])
1093                        log.warning("unknown directive: '" + ls[0] + "'")
1094                        return ('data', [lo])
1095            else:
1096                return ('data', [lo])
1097        return ('control', '%end', '%end')
1098
1099    def _process_package(self, results, directive, info, data):
1100        self._set_package(results[1])
1101        directive = None
1102        return (directive, info, data)
1103
1104    def _process_directive(self, results, directive, info, data):
1105        new_data = []
1106        if results[1] == '%description':
1107            new_data = [' '.join(results[2])]
1108            if len(results[2]) == 0:
1109                _package = 'main'
1110            elif len(results[2]) == 1:
1111                _package = results[2][0]
1112            else:
1113                if results[2][0].strip() != '-n':
1114                    log.warning("unknown directive option: '%s'" % (' '.join(results[2])))
1115                _package = results[2][1].strip()
1116            self._set_package(_package)
1117        if directive and directive != results[1]:
1118            self._directive_extend(directive, data)
1119        directive = results[1]
1120        data = new_data
1121        return (directive, info, data)
1122
1123    def _process_data(self, results, directive, info, data):
1124        log.trace('config: %s: %3d: _process_data: result=#%r# directive=#%s# info=#%r# data=#%r#' % \
1125                  (self.name, self.lc, results, directive, info, data))
1126        new_data = []
1127        for l in results[1]:
1128            if l.startswith('%error'):
1129                l = self._expand(l)
1130                raise error.general('config error: %s' % (l[7:]))
1131            elif l.startswith('%log'):
1132                l = self._expand(l)
1133                log.output(l[4:])
1134            elif l.startswith('%warning'):
1135                l = self._expand(l)
1136                log.warning(l[9:])
1137            if not directive:
1138                l = self._expand(l)
1139                ls = self.tags.split(l, 1)
1140                log.trace('config: %s: %3d: _tag: %s %s' % (self.name, self.lc, l, ls))
1141                if len(ls) > 1:
1142                    info = ls[0].lower()
1143                    if info[-1] == ':':
1144                        info = info[:-1]
1145                    info_data = ls[1].strip()
1146                else:
1147                    info_data = ls[0].strip()
1148                if info is not None:
1149                    self._info_append(info, info_data)
1150                else:
1151                    log.warning("invalid format: '%s'" % (info_data[:-1]))
1152            else:
1153                l = self._expand(l)
1154                log.trace('config: %s: %3d: _data: %s %s' % (self.name, self.lc, l, new_data))
1155                new_data.append(l)
1156        return (directive, info, data + new_data)
1157
1158    def _set_package(self, _package):
1159        if self.package == 'main' and \
1160                self._packages[self.package].name() != None:
1161            if self._packages[self.package].name() == _package:
1162                return
1163        if _package not in self._packages:
1164            self._packages[_package] = package(_package,
1165                                               self.define('%{_arch}'),
1166                                               self)
1167        self.package = _package
1168
1169    def _directive_extend(self, dir, data):
1170        log.trace('config: %s: %3d: _directive_extend: %s: %r' % (self.name, self.lc, dir, data))
1171        self._packages[self.package].directive_extend(dir, data)
1172
1173    def _info_append(self, info, data):
1174        self._packages[self.package].info_append(info, data)
1175
1176    def set_macros(self, macros):
1177        if macros is None:
1178            self.macros = opts.defaults
1179        else:
1180            self.macros = macros
1181
1182    def load(self, name):
1183
1184        def common_end(left, right):
1185            end = ''
1186            while len(left) and len(right):
1187                if left[-1] != right[-1]:
1188                    return end
1189                end = left[-1] + end
1190                left = left[:-1]
1191                right = right[:-1]
1192            return end
1193
1194        if self.load_depth == 0:
1195            self._reset(name)
1196            self._packages[self.package] = package(self.package,
1197                                                   self.define('%{_arch}'),
1198                                                   self)
1199
1200        self.load_depth += 1
1201
1202        save_name = self.name
1203        save_lc = self.lc
1204
1205        #
1206        # Locate the config file. Expand any macros then add the
1207        # extension. Check if the file exists, therefore directly
1208        # referenced. If not see if the file contains ':' or the path
1209        # separator. If it does split the path else use the standard config dir
1210        # path in the defaults.
1211        #
1212
1213        exname = self.expand(name)
1214
1215        #
1216        # Macro could add an extension.
1217        #
1218        if exname.endswith('.cfg'):
1219            configname = exname
1220        else:
1221            configname = '%s.cfg' % (exname)
1222            name = '%s.cfg' % (name)
1223
1224        if ':' in configname:
1225            cfgname = path.basename(configname)
1226        else:
1227            cfgname = common_end(configname, name)
1228
1229        if not path.exists(configname):
1230            if ':' in configname:
1231                configdirs = path.dirname(configname).split(':')
1232            else:
1233                configdirs = self.define('_configdir').split(':')
1234            for cp in configdirs:
1235                configname = path.join(path.abspath(cp), cfgname)
1236                if path.exists(configname):
1237                    break
1238                configname = None
1239            if configname is None:
1240                raise error.general('no config file found: %s' % (cfgname))
1241
1242        try:
1243            log.trace('config: %s: _open: %s' % (self.name, path.host(configname)))
1244            config = open(path.host(configname), 'r')
1245        except IOError as err:
1246            raise error.general('error opening config file: %s' % (path.host(configname)))
1247
1248        self.configpath += [configname]
1249        self._includes += [configname]
1250
1251        self.name = self._relative_path(configname)
1252        self.lc = 0
1253
1254        try:
1255            dir = None
1256            info = None
1257            data = []
1258            while True:
1259                r = self._parse(config, dir, info)
1260                if r[0] == 'package':
1261                    dir, info, data = self._process_package(r, dir, info, data)
1262                elif r[0] == 'control':
1263                    if r[1] == '%end':
1264                        break
1265                    log.warning("unexpected '%s'" % (r[1]))
1266                elif r[0] == 'directive':
1267                    if r[1] == '%include':
1268                        self.load(r[2][0])
1269                        continue
1270                    dir, info, data = self._process_directive(r, dir, info, data)
1271                elif r[0] == 'data':
1272                    dir, info, data = self._process_data(r, dir, info, data)
1273                else:
1274                    self._error("%d: invalid parse state: '%s" % (self.lc, r[0]))
1275            if dir is not None:
1276                self._directive_extend(dir, data)
1277        except:
1278            config.close()
1279            raise
1280
1281        config.close()
1282
1283        self.name = save_name
1284        self.lc = save_lc
1285
1286        self.load_depth -= 1
1287
1288    def defined(self, name):
1289        return name in self.macros
1290
1291    def define(self, name):
1292        if name in self.macros:
1293            d = self.macros[name]
1294        else:
1295            n = self._label(name)
1296            if n in self.macros:
1297                d = self.macros[n]
1298            else:
1299                raise error.general('%d: macro "%s" not found' % (self.lc, name))
1300        return self._expand(d)
1301
1302    def set_define(self, name, value):
1303        self.macros[name] = value
1304
1305    def expand(self, line):
1306        if type(line) == list:
1307            el = []
1308            for l in line:
1309                el += [self._expand(l)]
1310            return el
1311        return self._expand(line)
1312
1313    def macro(self, name):
1314        if name in self.macros:
1315            return self.macros[name]
1316        raise error.general('macro "%s" not found' % (name))
1317
1318    def directive(self, _package, name):
1319        if _package not in self._packages:
1320            raise error.general('package "' + _package + '" not found')
1321        if name not in self._packages[_package].directives:
1322            raise error.general('directive "' + name + \
1323                                    '" not found in package "' + _package + '"')
1324        return self._packages[_package].directives[name]
1325
1326    def abspath(self, rpath):
1327        return path.abspath(self.define(rpath))
1328
1329    def packages(self):
1330        return self._packages
1331
1332    def includes(self):
1333        return self._includes
1334
1335    def file_name(self):
1336        return self.name
1337
1338def run():
1339    import sys
1340    try:
1341        #
1342        # Run where defaults.mc is located
1343        #
1344        opts = options.load(sys.argv, defaults = 'defaults.mc')
1345        log.trace('config: count %d' % (len(opts.config_files())))
1346        for config_file in opts.config_files():
1347            s = open(config_file, opts)
1348            print(s)
1349            del s
1350    except error.general as gerr:
1351        print(gerr)
1352        sys.exit(1)
1353    except error.internal as ierr:
1354        print(ierr)
1355        sys.exit(1)
1356    except KeyboardInterrupt:
1357        log.notice('abort: user terminated')
1358        sys.exit(1)
1359    sys.exit(0)
1360
1361if __name__ == "__main__":
1362    run()
Note: See TracBrowser for help on using the repository browser.