source: rtems-source-builder/source-builder/sb/config.py @ 3fac45e

4.11
Last change on this file since 3fac45e was 3fac45e, checked in by Chris Johns <chrisj@…>, on 03/17/16 at 07:27:47

bare: Update qemu package source hashes that are missing.

Add support for pkgconfig checks to fail when just downloading the
source so it is actually downloaded on hosts that may have a package
installed.

Updates #2657.

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