source: rtems-source-builder/source-builder/sb/config.py @ 175ce0b

5
Last change on this file since 175ce0b was 175ce0b, checked in by Chris Johns <chrisj@…>, on 03/02/20 at 03:45:29

sb/config: Expanded nested shell commands

Updates #3893

  • Property mode set to 100644
File size: 54.3 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2010-2018 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    istrue = None
53    if value.isdigit():
54        if int(value) == 0:
55            istrue = False
56        else:
57            istrue = True
58    else:
59        if type(value) is str and len(value) == 2 and value[0] == '!':
60            istrue = _check_bool(value[1])
61            if type(istrue) is bool:
62                istrue = not istrue
63    return istrue
64
65def _check_nil(value):
66    if len(value):
67        istrue = True
68    else:
69        istrue = False
70    return istrue
71
72class package:
73
74    def __init__(self, name, arch, config):
75        self._name = name
76        self._arch = arch
77        self.config = config
78        self.directives = {}
79        self.infos = {}
80        self.sizes = {}
81
82    def __str__(self):
83
84        def _dictlist(dl):
85            s = ''
86            dll = list(dl.keys())
87            dll.sort()
88            for d in dll:
89                if d:
90                    s += '  ' + d + ':\n'
91                    for l in dl[d]:
92                        s += '    ' + l + '\n'
93            return s
94
95        s = '\npackage: ' + self._name + \
96            '\n directives:\n' + _dictlist(self.directives) + \
97            '\n infos:\n' + _dictlist(self.infos)
98
99        return s
100
101    def _macro_override(self, info, macro):
102        '''See if a macro overrides this setting.'''
103        overridden = self.config.macros.overridden(macro)
104        if overridden:
105            return self.config.macros.expand(macro)
106        return info
107
108    def directive_extend(self, dir, data):
109        if dir not in self.directives:
110            self.directives[dir] = []
111        for i in range(0, len(data)):
112            data[i] = data[i].strip()
113        self.directives[dir].extend(data)
114        self.config.macros[dir] = '\n'.join(self.directives[dir])
115
116    def info_append(self, info, data):
117        if info not in self.infos:
118            self.infos[info] = []
119        self.infos[info].append(data)
120        self.config.macros[info] = '\n'.join(self.infos[info])
121
122    def get_info(self, info, expand = True):
123        if info in self.config.macros:
124            _info = self.config.macros[info].split('\n')
125            if expand:
126                return self.config.expand(_info)
127            else:
128                return _info
129        return None
130
131    def extract_info(self, label, expand = True):
132        ll = label.lower()
133        infos = {}
134        keys = self.config.macros.find('%s.*' % (ll))
135        for k in keys:
136            if k == ll:
137                k = '%s0' % (ll)
138            elif not k[len(ll):].isdigit():
139                continue
140            infos[k] = [self.config.expand(self.config.macros[k])]
141        return infos
142
143    def _find_macro(self, label, expand = True):
144        if label in self.config.macros:
145            macro = self.config.macros[label].split('\n')
146            if expand:
147                return self.config.expand(macro)
148            else:
149                return macro
150        return None
151
152    def find_info(self, label, expand = True):
153        return self._find_macro(label, expand)
154
155    def find_directive(self, label, expand = True):
156        return self._find_macro(label, expand)
157
158    def name(self):
159        info = self.find_info('name')
160        if info:
161            n = info[0]
162        else:
163            n = self._name
164        return self._macro_override(n, 'name')
165
166    def summary(self):
167        info = self.find_info('summary')
168        if info:
169            return info[0]
170        return ''
171
172    def url(self):
173        info = self.find_info('url')
174        if info:
175            return info[0]
176        return ''
177
178    def version(self):
179        info = self.find_info('version')
180        if not info:
181            return None
182        return info[0]
183
184    def release(self):
185        info = self.find_info('release')
186        if not info:
187            return None
188        return info[0]
189
190    def buildarch(self):
191        info = self.find_info('buildarch')
192        if not info:
193            return self._arch
194        return info[0]
195
196    def sources(self):
197        return self.extract_info('source')
198
199    def patches(self):
200        return self.extract_info('patch')
201
202    def prep(self):
203        return self.find_directive('%prep')
204
205    def build(self):
206        return self.find_directive('%build')
207
208    def install(self):
209        return self.find_directive('%install')
210
211    def clean(self):
212        return self.find_directive('%clean')
213
214    def include(self):
215        return self.find_directive('%include')
216
217    def testing(self):
218        return self.find_directive('%testing')
219
220    def long_name(self):
221        return self.name()
222
223    def disabled(self):
224        return len(self.name()) == 0
225
226    def set_size(self, what, path_):
227        if what not in self.sizes:
228            self.sizes[what] = 0
229        self.sizes[what] += path.get_size(path_)
230
231    def get_size(self, what):
232        if what in self.sizes:
233            return self.sizes[what]
234        return 0
235
236class file:
237    """Parse a config file."""
238
239    _directive = [ '%include',
240                   '%description',
241                   '%prep',
242                   '%build',
243                   '%clean',
244                   '%install',
245                   '%testing' ]
246
247    _ignore = [ re.compile('%setup'),
248                re.compile('%configure'),
249                re.compile('%source'),
250                re.compile('%patch'),
251                re.compile('%hash'),
252                re.compile('%select'),
253                re.compile('%disable') ]
254
255    def __init__(self, name, opts, macros = None):
256        log.trace('config: %s: initialising' % (name))
257        self.opts = opts
258        self.init_name = name
259        self.wss = re.compile(r'\s+')
260        self.tags = re.compile(r':+')
261        self.sf = re.compile(r'%\([^\)]+\)')
262        self.set_macros(macros)
263        self._reset(name)
264        self.load(name)
265
266    def __str__(self):
267
268        def _dict(dd):
269            s = ''
270            ddl = list(dd.keys())
271            ddl.sort()
272            for d in ddl:
273                s += '  ' + d + ': ' + dd[d] + '\n'
274            return s
275
276        s = 'config: %s' % ('.'.join(self.configpath)) + \
277            '\n' + str(self.opts) + \
278            '\nlines parsed: %d' % (self.lc) + \
279            '\nname: ' + self.name + \
280            '\nmacros:\n' + str(self.macros)
281        for _package in self._packages:
282            s += str(self._packages[_package])
283        return s
284
285    def _reset(self, name):
286        self.name = name
287        self.load_depth = 0
288        self.configpath = []
289        self._includes = []
290        self._packages = {}
291        self.in_error = False
292        self.lc = 0
293        self.if_depth = 0
294        self.conditionals = {}
295        self._packages = {}
296        self.package = 'main'
297        self.disable_macro_reassign = False
298        self.pkgconfig_prefix = None
299        self.pkgconfig_crosscompile = False
300        self.pkgconfig_filter_flags = False
301        for arg in self.opts.args:
302            if arg.startswith('--with-') or arg.startswith('--without-'):
303                if '=' in arg:
304                    label, value = arg.split('=', 1)
305                else:
306                    label = arg
307                    value = None
308                label = label[2:].lower().replace('-', '_')
309                if value:
310                    self.macros.define(label, value)
311                else:
312                    self.macros.define(label)
313
314    def _relative_path(self, p):
315        sbdir = None
316        if '_sbdir' in self.macros:
317            sbdir = path.dirname(self.expand('%{_sbdir}'))
318            if p.startswith(sbdir):
319                p = p[len(sbdir) + 1:]
320        return p
321
322    def _name_line_msg(self,  msg):
323        return '%s:%d: %s' % (path.basename(self.name), self.lc,  msg)
324
325    def _output(self, text):
326        if not self.opts.quiet():
327            log.output(text)
328
329    def _error(self, msg):
330        if not self.opts.dry_run():
331            if self.opts.keep_going():
332                err = 'error: %s' % (self._name_line_msg(msg))
333                log.stderr(err)
334                log.output(err)
335                self.in_error = True
336                log.stderr('warning: switched to dry run due to errors')
337                self.opts.set_dry_run()
338        raise error.general(self._name_line_msg(msg))
339
340    def _label(self, name):
341        if name.startswith('%{') and name[-1] is '}':
342            return name
343        return '%{' + name.lower() + '}'
344
345    def _cross_compile(self):
346        _host = self.expand('%{_host}')
347        _build = self.expand('%{_build}')
348        return _host != _build
349
350    def _candian_cross_compile(self):
351        _host = self.expand('%{_host}')
352        _build = self.expand('%{_build}')
353        _target = self.expand('%{_target}')
354        _alloc_cxc = self.defined('%{allow_cxc}')
355        return _alloc_cxc and _host != _build and _host != _target
356
357    def _macro_split(self, s):
358        '''Split the string (s) up by macros. Only split on the
359           outter level. Nested levels will need to split with futher calls.'''
360        trace_me = False
361        if trace_me:
362            print('------------------------------------------------------')
363        macros = []
364        nesting = []
365        has_braces = False
366        c = 0
367        while c < len(s):
368            if trace_me:
369                print('ms:', c, '"' + s[c:] + '"', has_braces, len(nesting), nesting)
370            #
371            # We need to watch for shell type variables or the form '${var}' because
372            # they can upset the brace matching.
373            #
374            if s[c] == '%' or s[c] == '$':
375                start = s[c]
376                c += 1
377                if c == len(s):
378                    continue
379                #
380                # Do we have '%%' or '%(' or '$%' or '$(' or not '${' ?
381                #
382                if s[c] == '%' or s[c] == '(' or (start == '$' and s[c] != '{'):
383                    continue
384                elif not s[c].isspace():
385                    #
386                    # If this is a shell macro and we are at the outter
387                    # level or is '$var' forget it and move on.
388                    #
389                    if start == '$' and (s[c] != '{' or len(nesting) == 0):
390                        continue
391                    if s[c] == '{':
392                        this_has_braces = True
393                    else:
394                        this_has_braces = False
395                    nesting.append((c - 1, has_braces))
396                    has_braces = this_has_braces
397            elif len(nesting) > 0:
398                if s[c] == '}' or (s[c].isspace() and not has_braces):
399                    #
400                    # Can have '%{?test: something %more}' where the
401                    # nested %more ends with the '}' which also ends
402                    # the outter macro.
403                    #
404                    if not has_braces:
405                        if s[c] == '}':
406                            macro_start, has_braces = nesting[len(nesting) - 1]
407                            nesting = nesting[:-1]
408                            if len(nesting) == 0:
409                                macros.append(s[macro_start:c].strip())
410                    if len(nesting) > 0:
411                        macro_start, has_braces = nesting[len(nesting) - 1]
412                        nesting = nesting[:-1]
413                        if len(nesting) == 0:
414                            macros.append(s[macro_start:c + 1].strip())
415            c += 1
416        if trace_me:
417            print('ms:', macros)
418        if trace_me:
419            print('-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
420        return macros
421
422    def _shell(self, line, nesting = 0):
423        #
424        # Parse the line and handle nesting '()' pairs. If on Windows
425        # handle embedded '"' (double quotes) as the command is run as
426        # a double quoted string.
427        #
428        def _exec(shell_macro):
429            output = ''
430            if len(shell_macro) > 3:
431                e = execute.capture_execution()
432                if options.host_windows:
433                    shell_cmd = ''.join([c if c != '"' else '\\' + c for c in shell_macro[2:-1]])
434                    cmd = '%s -c "%s"' % (self.macros.expand('%{__sh}'), shell_cmd)
435                else:
436                    cmd = shell_macro[2:-1]
437                exit_code, proc, output = e.shell(cmd)
438                log.trace('shell-output: %d %s' % (exit_code, output))
439                if exit_code != 0:
440                    raise error.general('shell macro failed: %s: %d: %s' % (cmd,
441                                                                            exit_code,
442                                                                            output))
443            return output
444
445        if nesting > 200:
446            raise error.general('shell macro failed: too many nesting levels')
447
448        updating = True
449        while updating:
450            updating = False
451            pos = line.find('%(')
452            if pos >= 0:
453                braces = 0
454                for p in range(pos + 2, len(line)):
455                    if line[p] == '(':
456                        braces += 1
457                    elif line[p] == ')':
458                        if braces > 0:
459                            braces -= 1
460                        else:
461                            shell_cmd = '%(' + self._shell(line[pos + 2:p], nesting + 1) + ')'
462                            line = line[:pos] + _exec(shell_cmd) + line[p + 1:]
463                            updating = True
464                            break
465
466        return line
467
468    def _pkgconfig_check(self, test):
469        # Hack to by pass pkgconfig checks when just wanting to download the
470        # source.
471        if self.macros['_dry_run'] == '1' and \
472           ('with_download' in self.macros and self.macros['with_download'] == '1'):
473            return '0'
474        ok = False
475        if type(test) == str:
476            test = test.split()
477        if not self._cross_compile() or self.pkgconfig_crosscompile:
478            try:
479                pkg = pkgconfig.package(test[0],
480                                        prefix = self.pkgconfig_prefix,
481                                        output = self._output,
482                                        src = log.trace)
483                if len(test) != 1 and len(test) != 3:
484                    self._error('malformed check: %s' % (' '.join(test)))
485                else:
486                    op = '>='
487                    ver = '0'
488                    if len(test) == 3:
489                        op = test[1]
490                        ver = self.macros.expand(test[2])
491                    ok = pkg.check(op, ver)
492            except pkgconfig.error as pe:
493                self._error('pkgconfig: check: %s' % (pe))
494            except:
495                raise error.internal('pkgconfig failure')
496        if ok:
497            return '1'
498        return '0'
499
500    def _pkgconfig_flags(self, package, flags):
501        pkg_flags = None
502        if not self._cross_compile() or self.pkgconfig_crosscompile:
503            try:
504                pkg = pkgconfig.package(package,
505                                        prefix = self.pkgconfig_prefix,
506                                        output = self._output,
507                                        src = log.trace)
508                pkg_flags = pkg.get(flags)
509                if pkg_flags and self.pkgconfig_filter_flags:
510                    fflags = []
511                    for f in pkg_flags.split():
512                        if not f.startswith('-W'):
513                            fflags += [f]
514                    pkg_flags = ' '.join(fflags)
515                log.trace('pkgconfig: %s:  %s' % (flags, pkg_flags))
516            except pkgconfig.error as pe:
517                self._error('pkgconfig: %s:  %s' % (flags, pe))
518            except:
519                raise error.internal('pkgconfig failure')
520        if pkg_flags is None:
521            pkg_flags = ''
522        return pkg_flags
523
524    def _pkgconfig(self, pcl):
525        ok = False
526        ps = ''
527        if pcl[0] == 'check':
528            ps = self._pkgconfig_check(pcl[1:])
529        elif pcl[0] == 'prefix':
530            if len(pcl) == 2:
531                self.pkgconfig_prefix = pcl[1]
532            else:
533                self._error('prefix error: %s' % (' '.join(pcl)))
534        elif pcl[0] == 'crosscompile':
535            ok = True
536            if len(pcl) == 2:
537                if pcl[1].lower() == 'yes':
538                    self.pkgconfig_crosscompile = True
539                elif pcl[1].lower() == 'no':
540                    self.pkgconfig_crosscompile = False
541                else:
542                    ok = False
543            else:
544                ok = False
545            if not ok:
546                self._error('crosscompile error: %s' % (' '.join(pcl)))
547        elif pcl[0] == 'filter-flags':
548            ok = True
549            if len(pcl) == 2:
550                if pcl[1].lower() == 'yes':
551                    self.pkgconfig_filter_flags = True
552                elif pcl[1].lower() == 'no':
553                    self.pkgconfig_filter_flags = False
554                else:
555                    ok = False
556            else:
557                ok = False
558            if not ok:
559                self._error('crosscompile error: %s' % (' '.join(pcl)))
560        elif pcl[0] in ['ccflags', 'cflags', 'ldflags', 'libs']:
561            ps = self._pkgconfig_flags(pcl[1], pcl[0])
562        else:
563            self._error('pkgconfig error: %s' % (' '.join(pcl)))
564        return ps
565
566    def _expand(self, s):
567        expand_count = 0
568        expanded = True
569        while expanded:
570            expand_count += 1
571            if expand_count > 500:
572                raise error.general('macro expand looping: %s' % (s))
573            expanded = False
574            ms = self._macro_split(s)
575            for m in ms:
576                mn = m
577                #
578                # A macro can be '%{macro}' or '%macro'. Turn the later into
579                # the former.
580                #
581                show_warning = True
582                if mn[1] != '{':
583                    for r in self._ignore:
584                        if r.match(mn) is not None:
585                            mn = None
586                            break
587                    else:
588                        mn = self._label(mn[1:])
589                        show_warning = False
590                elif m.startswith('%{expand'):
591                    colon = m.find(':')
592                    if colon < 8:
593                        log.warning(self._name_line_msg('malformed expand macro, no colon found'))
594                    else:
595                        e = self._expand(m[colon + 1:-1].strip())
596                        s = s.replace(m, self._label(e))
597                        expanded = True
598                        mn = None
599                elif m.startswith('%{with '):
600                    #
601                    # Change the ' ' to '_' because the macros have no spaces.
602                    #
603                    n = self._label('with_' + m[7:-1].strip())
604                    if n in self.macros:
605                        s = s.replace(m, '1')
606                    else:
607                        s = s.replace(m, '0')
608                    expanded = True
609                    mn = None
610                elif m.startswith('%{echo'):
611                    if not m.endswith('}'):
612                        log.warning(self._name_line_msg("malformed conditional macro '%s'" % (m)))
613                        mn = None
614                    else:
615                        e = self._expand(m[6:-1].strip())
616                        log.notice('%s' % (self._name_line_msg(e)))
617                        s = ''
618                        expanded = True
619                        mn = None
620                elif m.startswith('%{defined'):
621                    n = self._label(m[9:-1].strip())
622                    if n in self.macros:
623                        s = s.replace(m, '1')
624                    else:
625                        s = s.replace(m, '0')
626                    expanded = True
627                    mn = None
628                elif m.startswith('%{!defined'):
629                    n = self._label(m[10:-1].strip())
630                    if n in self.macros:
631                        s = s.replace(m, '0')
632                    else:
633                        s = s.replace(m, '1')
634                    expanded = True
635                    mn = None
636                elif m.startswith('%{triplet'):
637                    triplet = m[len('%{triplet'):-1].strip().split()
638                    ok = False
639                    if len(triplet) == 2:
640                        macro = self._expand(triplet[0])
641                        value = self._expand(triplet[1])
642                        vorig = value
643                        arch_value = ''
644                        vendor_value = ''
645                        os_value = ''
646                        dash = value.find('-')
647                        if dash >= 0:
648                            arch_value = value[:dash]
649                            value = value[dash + 1:]
650                        dash = value.find('-')
651                        if dash >= 0:
652                            vendor_value = value[:dash]
653                            value = value[dash + 1:]
654                        if len(value):
655                            os_value = value
656                        self.macros[macro] = vorig
657                        self.macros[macro + '_cpu'] = arch_value
658                        self.macros[macro + '_arch'] = arch_value
659                        self.macros[macro + '_vendor'] = vendor_value
660                        self.macros[macro + '_os'] = os_value
661                        ok = True
662                    if ok:
663                        s = s.replace(m, '')
664                    else:
665                        self._error('triplet error: %s' % (' '.join(triplet)))
666                    mn = None
667                elif m.startswith('%{path '):
668                    pl = m[7:-1].strip().split()
669                    ok = False
670                    if len(pl) == 2:
671                        ok = True
672                        epl = []
673                        for p in pl[1:]:
674                            epl += [self._expand(p)]
675                        p = ' '.join(epl)
676                        if pl[0].lower() == 'prepend':
677                            if len(self.macros['_pathprepend']):
678                                self.macros['_pathprepend'] = \
679                                    '%s:%s' % (p, self.macros['_pathprepend'])
680                            else:
681                                self.macros['_pathprepend'] = p
682                        elif pl[0].lower() == 'postpend':
683                            if len(self.macros['_pathprepend']):
684                                self.macros['_pathprepend'] = \
685                                    '%s:%s' % (self.macros['_pathprepend'], p)
686                            else:
687                                self.macros['_pathprepend'] = p
688                        else:
689                            ok = False
690                    if ok:
691                        s = s.replace(m, '')
692                    else:
693                        self._error('path error: %s' % (' '.join(pl)))
694                    mn = None
695                elif m.startswith('%{pkgconfig '):
696                    pcl = m[11:-1].strip().split()
697                    if len(pcl):
698                        epcl = []
699                        for pc in pcl:
700                            epcl += [self._expand(pc)]
701                        ps = self._pkgconfig(epcl)
702                        s = s.replace(m, ps)
703                        expanded = True
704                    else:
705                        self._error('pkgconfig error: %s' % (m[11:-1].strip()))
706                    mn = None
707                elif m.startswith('%{?') or m.startswith('%{!?'):
708                    if m[2] == '!':
709                        start = 4
710                    else:
711                        start = 3
712                    colon = m[start:].find(':')
713                    if colon < 0:
714                        if not m.endswith('}'):
715                            log.warning(self._name_line_msg("malformed conditional macro '%s'" % (m)))
716                            mn = None
717                        else:
718                            mn = self._label(m[start:-1])
719                    else:
720                        mn = self._label(m[start:start + colon])
721                    if mn:
722                        if m.startswith('%{?'):
723                            istrue = False
724                            if mn in self.macros:
725                                # If defined and 0 or '' then it is false.
726                                istrue = _check_bool(self.macros[mn])
727                                if istrue is None:
728                                    istrue = _check_nil(self.macros[mn])
729                            if colon >= 0 and istrue:
730                                s = s.replace(m, m[start + colon + 1:-1])
731                                expanded = True
732                                mn = None
733                            elif not istrue:
734                                mn = '%{nil}'
735                        else:
736                            isfalse = True
737                            if mn in self.macros:
738                                istrue = _check_bool(self.macros[mn])
739                                if istrue is None or istrue == True:
740                                    isfalse = False
741                            if colon >= 0 and isfalse:
742                                s = s.replace(m, m[start + colon + 1:-1])
743                                expanded = True
744                                mn = None
745                            else:
746                                mn = '%{nil}'
747                if mn:
748                    if mn.lower() in self.macros:
749                        s = s.replace(m, self.macros[mn.lower()])
750                        expanded = True
751                    elif show_warning:
752                        self._error("macro '%s' not found" % (mn))
753        return self._shell(s)
754
755    def _disable(self, config, ls):
756        if len(ls) != 2:
757            log.warning(self._name_line_msg('invalid disable statement'))
758        else:
759            if ls[1] == 'select':
760                self.macros.lock_read_map()
761                log.trace('config: %s: %3d:  _disable_select: %s' % (self.name, self.lc,
762                                                                     ls[1]))
763            else:
764                log.warning(self._name_line_msg('invalid disable statement: %s' % (ls[1])))
765
766    def _select(self, config, ls):
767        if len(ls) != 2:
768            log.warning(self._name_line_msg('invalid select statement'))
769        else:
770            r = self.macros.set_read_map(ls[1])
771            log.trace('config: %s: %3d:  _select: %s %s %r' % \
772                          (self.name, self.lc,
773                           r, ls[1], self.macros.maps()))
774
775    def _sources(self, ls):
776        return sources.process(ls[0][1:], ls[1:], self.macros, self._error)
777
778    def _hash(self, ls):
779        return sources.hash(ls[1:], self.macros, self._error)
780
781    def _define(self, config, ls):
782        if len(ls) <= 1:
783            log.warning(self._name_line_msg('invalid macro definition'))
784        else:
785            d = self._label(ls[1])
786            if self.disable_macro_reassign:
787                if (d not in self.macros) or \
788                        (d in self.macros and len(self.macros[d]) == 0):
789                    if len(ls) == 2:
790                        self.macros[d] = '1'
791                    else:
792                        self.macros[d] = ' '.join([f.strip() for f in ls[2:]])
793                else:
794                    log.warning(self._name_line_msg("macro '%s' already defined" % (d)))
795            else:
796                if len(ls) == 2:
797                    self.macros[d] = '1'
798                else:
799                    self.macros[d] = ' '.join([f.strip() for f in ls[2:]])
800
801    def _undefine(self, config, ls):
802        if len(ls) <= 1:
803            log.warning(self._name_line_msg('invalid macro definition'))
804        else:
805            mn = self._label(ls[1])
806            if mn in self.macros:
807                del self.macros[mn]
808
809    def _ifs(self, config, ls, label, iftrue, isvalid, dir, info):
810        log.trace('config: %s: %3d:  _ifs[%i]: dir=%s %i %r' % \
811                  (self.name, self.lc, self.if_depth, str(dir), len(ls), ls))
812        in_dir = dir
813        in_iftrue = True
814        data = []
815        while True:
816            if isvalid and \
817                    ((iftrue and in_iftrue) or (not iftrue and not in_iftrue)):
818                this_isvalid = True
819            else:
820                this_isvalid = False
821            r = self._parse(config, dir, info, roc = True, isvalid = this_isvalid)
822            if r[0] == 'package':
823                if this_isvalid:
824                    dir, info, data = self._process_package(r, dir, info, data)
825            elif r[0] == 'control':
826                if r[1] == '%end':
827                    self._error(label + ' without %endif')
828                    raise error.general('terminating build')
829                if r[1] == '%endif':
830                    log.trace('config: %s: %3d:  _ifs[%i]: %%endif: dir=%s %s %s %r' % \
831                              (self.name, self.lc, self.if_depth,
832                               str(dir), r[1], this_isvalid, data))
833                    if in_dir is None:
834                        if dir is not None:
835                            dir, info, data = self._process_directive(r, dir, info, data)
836                    else:
837                        if in_dir != dir:
838                            self._error('directives cannot change scope across if statements')
839
840                    return data
841                if r[1] == '%else':
842                    in_iftrue = False
843            elif r[0] == 'directive':
844                if this_isvalid:
845                    if r[1] == '%include':
846                        self.load(r[2][0])
847                        continue
848                    dir, info, data = self._process_directive(r, dir, info, data)
849            elif r[0] == 'data':
850                if this_isvalid:
851                    dir, info, data = self._process_data(r, dir, info, data)
852        # @note is a directive extend missing
853
854    def _if(self, config, ls, isvalid, dir, info, invert = False):
855
856        def add(x, y):
857            return x + ' ' + str(y)
858
859        if len(ls) == 1:
860            self._error('invalid if expression: ' + reduce(add, ls, ''))
861
862        cistrue = True # compound istrue
863        sls = reduce(add, ls[1:], '').split()
864        cls = sls
865
866        log.trace('config: %s: %3d:  _if[%i]: %s' % (self.name, self.lc,
867                                                    self.if_depth, sls))
868
869        self.if_depth += 1
870
871        while len(cls) > 0 and isvalid:
872
873            join_op = 'none'
874
875            if cls[0] == '||' or cls[0] == '&&':
876                if cls[0] == '||':
877                    join_op = 'or'
878                elif cls[0] == '&&':
879                    join_op = 'and'
880                cls = cls[1:]
881                log.trace('config: %s: %3d:  _if[%i]: joining: %s' % (self.name, self.lc,
882                                                                      self.if_depth,
883                                                                      join_op))
884            ori = 0
885            andi = 0
886            i = len(cls)
887            if '||' in cls:
888                ori = cls.index('||')
889                log.trace('config: %s: %3d:  _if[%i}: OR found at %i' % (self.name, self.lc,
890                                                                         self.if_depth,
891                                                                         ori))
892            if '&&' in cls:
893                andi = cls.index('&&')
894                log.trace('config: %s: %3d:  _if[%i]: AND found at %i' % (self.name, self.lc,
895                                                                          self.if_depth,
896                                                                          andi))
897            if ori > 0 or andi > 0:
898                if ori == 0:
899                    i = andi
900                elif andi == 0:
901                    i = ori
902                elif ori < andi:
903                    i = andi
904                else:
905                    i = andi
906                log.trace('config: %s: %3d:  _if[%i]: next OP found at %i' % (self.name, self.lc,
907                                                                              self.if_depth,
908                                                                              i))
909            ls = cls[:i]
910            if len(ls) == 0:
911                self._error('invalid if expression: ' + reduce(add, sls, ''))
912            cls = cls[i:]
913
914            istrue = False
915
916            s = ' '.join(ls)
917            ifls = ls
918
919            if len(ifls) == 1:
920                #
921                # Check if '%if %{x} == %{nil}' has both parts as nothing
922                # which means '%if ==' is always True and '%if !=' is always false.
923                #
924                if ifls[0] == '==':
925                    istrue = True
926                elif ifls[0] == '!=':
927                    istrue = False
928                else:
929                    istrue = _check_bool(ifls[0])
930                    if istrue == None:
931                        self._error('invalid if bool value: ' + reduce(add, ls, ''))
932                        istrue = False
933            elif len(ifls) == 2:
934                if ifls[0] == '!':
935                    istrue = _check_bool(ifls[1])
936                    if istrue == None:
937                        self._error('invalid if bool value: ' + reduce(add, ls, ''))
938                        istrue = False
939                    else:
940                        istrue = not istrue
941                else:
942                    #
943                    # Check is something is being checked against empty,
944                    #   ie '%if %{x} == %{nil}'
945                    # The logic is 'something == nothing' is False and
946                    # 'something != nothing' is True.
947                    #
948                    if ifls[1] == '==':
949                        istrue = False
950                    elif  ifls[1] == '!=':
951                        istrue = True
952                    else:
953                        self._error('invalid if bool operator: ' + reduce(add, ls, ''))
954            else:
955                if len(ifls) > 3:
956                    for op in ['==', '!=', '>=', '=>', '=<', '<=', '>', '<']:
957                        ops = s.split(op)
958                        if len(ops) == 2:
959                            ifls = (ops[0], op, ops[1])
960                            break
961                if len(ifls) != 3:
962                     self._error('malformed if: ' + reduce(add, ls, ''))
963                if ifls[1] == '==':
964                    if ifls[0] == ifls[2]:
965                        istrue = True
966                    else:
967                        istrue = False
968                elif ifls[1] == '!=' or ifls[1] == '=!':
969                    if ifls[0] != ifls[2]:
970                        istrue = True
971                    else:
972                        istrue = False
973                elif ifls[1] == '>':
974                    if ifls[0] > ifls[2]:
975                        istrue = True
976                    else:
977                        istrue = False
978                elif ifls[1] == '>=' or ifls[1] == '=>':
979                    if ifls[0] >= ifls[2]:
980                        istrue = True
981                    else:
982                        istrue = False
983                elif ifls[1] == '<=' or ifls[1] == '=<':
984                    if ifls[0] <= ifls[2]:
985                        istrue = True
986                    else:
987                        istrue = False
988                elif ifls[1] == '<':
989                    if ifls[0] < ifls[2]:
990                        istrue = True
991                    else:
992                        istrue = False
993                else:
994                    self._error('invalid %if operator: ' + reduce(add, ls, ''))
995
996            if join_op == 'or':
997                if istrue:
998                    cistrue = True
999            elif join_op == 'and':
1000                if not istrue:
1001                    cistrue = False
1002            else:
1003                cistrue = istrue
1004
1005            log.trace('config: %s: %3d:  _if[%i]:  %s %s %s %s' % (self.name, self.lc,
1006                                                                   self.if_depth,
1007                                                                   ifls, str(cistrue),
1008                                                                   join_op, str(istrue)))
1009
1010        if invert:
1011            cistrue = not cistrue
1012
1013        ifs_return = self._ifs(config, ls, '%if', cistrue, isvalid, dir, info)
1014
1015        self.if_depth -= 1
1016
1017        log.trace('config: %s: %3d:  _if[%i]: %r' % (self.name, self.lc,
1018                                                     self.if_depth, ifs_return))
1019
1020        return ifs_return
1021
1022    def _ifos(self, config, ls, isvalid, dir, info):
1023        isos = False
1024        if isvalid:
1025            os = self.define('_os')
1026            ls = ' '.join(ls).split()
1027            for l in ls[1:]:
1028                if l in os:
1029                    isos = True
1030                    break
1031        return self._ifs(config, ls, '%ifos', isos, isvalid, dir, info)
1032
1033    def _ifnos(self, config, ls, isvalid, dir, info):
1034        isnos = True
1035        if isvalid:
1036            os = self.define('_os')
1037            ls = ' '.join(ls).split()
1038            for l in ls[1:]:
1039                if l in os:
1040                    isnos = False
1041                    break
1042        return self._ifs(config, ls, '%ifnos', isnos, isvalid, dir, info)
1043
1044    def _ifarch(self, config, positive, ls, isvalid, dir, info):
1045        isarch = False
1046        if isvalid:
1047            arch = self.define('_arch')
1048            ls = ' '.join(ls).split()
1049            for l in ls[1:]:
1050                if l in arch:
1051                    isarch = True
1052                    break
1053        if not positive:
1054            isarch = not isarch
1055        return self._ifs(config, ls, '%ifarch', isarch, isvalid, dir, info)
1056
1057    def _parse(self, config, dir, info, roc = False, isvalid = True):
1058        # roc = return on control
1059
1060        def _clean(line):
1061            line = line[0:-1]
1062            b = line.find('#')
1063            if b >= 0:
1064                line = line[1:b] + ('\\' if line[-1] == '\\' else '')
1065            return line.strip()
1066
1067        def _clean_and_pack(line, last_line):
1068            leading_ws = ' ' if len(line) > 0 and line[0].isspace() else ''
1069            line = _clean(line)
1070            if len(last_line) > 0:
1071                line = last_line + leading_ws + line
1072            return line
1073
1074        #
1075        # Need to add code to count matching '{' and '}' and if they
1076        # do not match get the next line and add to the string until
1077        # they match. This closes an opening '{' that is on another
1078        # line.
1079        #
1080        ll = ''
1081        for l in config:
1082            self.lc += 1
1083            l = _clean_and_pack(l, ll)
1084            if len(l) == 0:
1085                continue
1086            if l[-1] == '\\':
1087                ll = l[0:-1]
1088                continue
1089            ll = ''
1090            if isvalid:
1091                indicator = '>'
1092            else:
1093                indicator = ' '
1094            log.trace('config: %s: %3d:%s%s [%s]' % \
1095                          (self.name, self.lc, indicator, l, str(isvalid)))
1096            lo = l
1097            if isvalid:
1098                l = self._expand(l)
1099            if len(l) == 0:
1100                continue
1101            if l[0] == '%':
1102                ls = self.wss.split(l, 2)
1103                los = self.wss.split(lo, 2)
1104                if ls[0] == '%package':
1105                    if isvalid:
1106                        if ls[1] == '-n':
1107                            name = ls[2]
1108                        else:
1109                            name = self.name + '-' + ls[1]
1110                        return ('package', name)
1111                elif ls[0] == '%disable':
1112                    if isvalid:
1113                        self._disable(config, ls)
1114                elif ls[0] == '%select':
1115                    if isvalid:
1116                        self._select(config, ls)
1117                elif ls[0] == '%source' or ls[0] == '%patch':
1118                    if isvalid:
1119                        d = self._sources(ls)
1120                        if d is not None:
1121                            return ('data', d)
1122                elif ls[0] == '%hash':
1123                    if isvalid:
1124                        d = self._hash(ls)
1125                        if d is not None:
1126                            return ('data', d)
1127                elif ls[0] == '%patch':
1128                    if isvalid:
1129                        self._select(config, ls)
1130                elif ls[0] == '%error':
1131                    if isvalid:
1132                        return ('data', ['%%error %s' % (self._name_line_msg(l[7:]))])
1133                elif ls[0] == '%log':
1134                    if isvalid:
1135                        return ('data', ['%%log %s' % (self._name_line_msg(l[4:]))])
1136                elif ls[0] == '%warning':
1137                    if isvalid:
1138                        return ('data', ['%%warning %s' % (self._name_line_msg(l[9:]))])
1139                elif ls[0] == '%define' or ls[0] == '%global':
1140                    if isvalid:
1141                        self._define(config, ls)
1142                elif ls[0] == '%undefine':
1143                    if isvalid:
1144                        self._undefine(config, ls)
1145                elif ls[0] == '%if':
1146                    d = self._if(config, ls, isvalid, dir, info)
1147                    if len(d):
1148                        log.trace('config: %s: %3d:  %%if: %s' % (self.name, self.lc, d))
1149                        return ('data', d)
1150                elif ls[0] == '%ifn':
1151                    d = self._if(config, ls, isvalid, dir, info, True)
1152                    if len(d):
1153                        log.trace('config: %s: %3d:  %%ifn: %s' % (self.name, self.lc, d))
1154                        return ('data', d)
1155                elif ls[0] == '%ifos':
1156                    d = self._ifos(config, ls, isvalid, dir, info)
1157                    if len(d):
1158                        return ('data', d)
1159                elif ls[0] == '%ifnos':
1160                    d = self._ifnos(config, ls, isvalid, dir, info)
1161                    if len(d):
1162                        return ('data', d)
1163                elif ls[0] == '%ifarch':
1164                    d = self._ifarch(config, True, ls, isvalid, dir, info)
1165                    if len(d):
1166                        return ('data', d)
1167                elif ls[0] == '%ifnarch':
1168                    d = self._ifarch(config, False, ls, isvalid, dir, info)
1169                    if len(d):
1170                        return ('data', d)
1171                elif ls[0] == '%endif':
1172                    if roc:
1173                        return ('control', '%endif', '%endif')
1174                    log.warning(self._name_line_msg("unexpected '" + ls[0] + "'"))
1175                elif ls[0] == '%else':
1176                    if roc:
1177                        return ('control', '%else', '%else')
1178                    log.warning(self._name_line_msg("unexpected '" + ls[0] + "'"))
1179                elif ls[0].startswith('%defattr'):
1180                    return ('data', [l])
1181                elif ls[0] == '%bcond_with':
1182                    if isvalid:
1183                        #
1184                        # Check if already defined. Would be by the command line or
1185                        # even a host specific default.
1186                        #
1187                        if self._label('with_' + ls[1]) not in self.macros:
1188                            self._define(config, (ls[0], 'without_' + ls[1]))
1189                elif ls[0] == '%bcond_without':
1190                    if isvalid:
1191                        if self._label('without_' + ls[1]) not in self.macros:
1192                            self._define(config, (ls[0], 'with_' + ls[1]))
1193                else:
1194                    for r in self._ignore:
1195                        if r.match(ls[0]) is not None:
1196                            return ('data', [l])
1197                    if isvalid:
1198                        for d in self._directive:
1199                            if ls[0].strip() == d:
1200                                log.trace('config: %s: %3d:  _parse: directive: %s' % \
1201                                          (self.name, self.lc, ls[0].strip()))
1202                                return ('directive', ls[0].strip(), ls[1:])
1203                        log.warning(self._name_line_msg("unknown directive: '" + ls[0] + "'"))
1204                        return ('data', [lo])
1205            else:
1206                return ('data', [lo])
1207        return ('control', '%end', '%end')
1208
1209    def _process_package(self, results, directive, info, data):
1210        self._set_package(results[1])
1211        directive = None
1212        return (directive, info, data)
1213
1214    def _process_directive(self, results, directive, info, data):
1215        new_data = []
1216        if results[1] == '%description':
1217            new_data = [' '.join(results[2])]
1218            if len(results[2]) == 0:
1219                _package = 'main'
1220            elif len(results[2]) == 1:
1221                _package = results[2][0]
1222            else:
1223                if results[2][0].strip() != '-n':
1224                    log.warning(self._name_line_msg("unknown directive option: '%s'" % (' '.join(results[2]))))
1225                _package = results[2][1].strip()
1226            self._set_package(_package)
1227        if directive and directive != results[1]:
1228            self._directive_extend(directive, data)
1229        directive = results[1]
1230        data = new_data
1231        return (directive, info, data)
1232
1233    def _process_data(self, results, directive, info, data):
1234        log.trace('config: %s: %3d:  _process_data: result=#%r# directive=#%s# info=#%r# data=#%r#' % \
1235                  (self.name, self.lc, results, directive, info, data))
1236        new_data = []
1237        for l in results[1]:
1238            if l.startswith('%error'):
1239                l = self._expand(l)
1240                raise error.general('config error: %s' % (l[7:]))
1241            elif l.startswith('%log'):
1242                l = self._expand(l)
1243                log.output(l[4:])
1244            elif l.startswith('%warning'):
1245                l = self._expand(l)
1246                log.warning(self._name_line_msg(l[9:]))
1247            if not directive:
1248                l = self._expand(l)
1249                ls = self.tags.split(l, 1)
1250                log.trace('config: %s: %3d:  _tag: %s %s' % (self.name, self.lc, l, ls))
1251                if len(ls) > 1:
1252                    info = ls[0].lower()
1253                    if info[-1] == ':':
1254                        info = info[:-1]
1255                    info_data = ls[1].strip()
1256                else:
1257                    info_data = ls[0].strip()
1258                if info is not None:
1259                    self._info_append(info, info_data)
1260                else:
1261                    log.warning(self._name_line_msg("invalid format: '%s'" % (info_data[:-1])))
1262            else:
1263                l = self._expand(l)
1264                log.trace('config: %s: %3d:  _data: %s %s' % (self.name, self.lc, l, new_data))
1265                new_data.append(l)
1266        return (directive, info, data + new_data)
1267
1268    def _set_package(self, _package):
1269        if self.package == 'main' and \
1270                self._packages[self.package].name() != None:
1271            if self._packages[self.package].name() == _package:
1272                return
1273        if _package not in self._packages:
1274            self._packages[_package] = package(_package,
1275                                               self.define('%{_arch}'),
1276                                               self)
1277        self.package = _package
1278
1279    def _directive_extend(self, dir, data):
1280        log.trace('config: %s: %3d:  _directive_extend: %s: %r' % (self.name, self.lc, dir, data))
1281        self._packages[self.package].directive_extend(dir, data)
1282
1283    def _info_append(self, info, data):
1284        self._packages[self.package].info_append(info, data)
1285
1286    def set_macros(self, macros):
1287        if macros is None:
1288            self.macros = opts.defaults
1289        else:
1290            self.macros = macros
1291
1292    def load(self, name):
1293
1294        def common_end(left, right):
1295            end = ''
1296            while len(left) and len(right):
1297                if left[-1] != right[-1]:
1298                    return end
1299                end = left[-1] + end
1300                left = left[:-1]
1301                right = right[:-1]
1302            return end
1303
1304        if self.load_depth == 0:
1305            self._reset(name)
1306            self._packages[self.package] = package(self.package,
1307                                                   self.define('%{_arch}'),
1308                                                   self)
1309
1310        self.load_depth += 1
1311
1312        save_name = self.name
1313        save_lc = self.lc
1314
1315        #
1316        # Locate the config file. Expand any macros then add the
1317        # extension. Check if the file exists, therefore directly
1318        # referenced. If not see if the file contains ':' or the path
1319        # separator. If it does split the path else use the standard config dir
1320        # path in the defaults.
1321        #
1322
1323        exname = self.expand(name)
1324
1325        #
1326        # Macro could add an extension.
1327        #
1328        if exname.endswith('.cfg'):
1329            configname = exname
1330        else:
1331            configname = '%s.cfg' % (exname)
1332            name = '%s.cfg' % (name)
1333
1334        if ':' in configname:
1335            cfgname = path.basename(configname)
1336        else:
1337            cfgname = common_end(configname, name)
1338
1339        if not path.exists(configname):
1340            if ':' in configname:
1341                configdirs = path.dirname(configname).split(':')
1342            else:
1343                configdirs = self.define('_configdir').split(':')
1344            for cp in configdirs:
1345                configname = path.join(path.abspath(cp), cfgname)
1346                if path.exists(configname):
1347                    break
1348                configname = None
1349            if configname is None:
1350                raise error.general('no config file found: %s' % (cfgname))
1351
1352        try:
1353            log.trace('config: %s:  _open: %s' % (self.name, path.host(configname)))
1354            config = open(path.host(configname), 'r')
1355        except IOError as err:
1356            raise error.general('error opening config file: %s' % (path.host(configname)))
1357
1358        self.configpath += [configname]
1359        self._includes += [configname]
1360
1361        self.name = self._relative_path(configname)
1362        self.lc = 0
1363
1364        try:
1365            dir = None
1366            info = None
1367            data = []
1368            while True:
1369                r = self._parse(config, dir, info)
1370                if r[0] == 'package':
1371                    dir, info, data = self._process_package(r, dir, info, data)
1372                elif r[0] == 'control':
1373                    if r[1] == '%end':
1374                        break
1375                    log.warning(self._name_line_msg("unexpected '%s'" % (r[1])))
1376                elif r[0] == 'directive':
1377                    if r[1] == '%include':
1378                        self.load(r[2][0])
1379                        continue
1380                    dir, info, data = self._process_directive(r, dir, info, data)
1381                elif r[0] == 'data':
1382                    dir, info, data = self._process_data(r, dir, info, data)
1383                else:
1384                    self._error("%d: invalid parse state: '%s" % (self.lc, r[0]))
1385            if dir is not None:
1386                self._directive_extend(dir, data)
1387        except:
1388            config.close()
1389            raise
1390
1391        config.close()
1392
1393        self.name = save_name
1394        self.lc = save_lc
1395
1396        self.load_depth -= 1
1397
1398    def defined(self, name):
1399        return name in self.macros
1400
1401    def define(self, name):
1402        if name in self.macros:
1403            d = self.macros[name]
1404        else:
1405            n = self._label(name)
1406            if n in self.macros:
1407                d = self.macros[n]
1408            else:
1409                raise error.general('%d: macro "%s" not found' % (self.lc, name))
1410        return self._expand(d)
1411
1412    def set_define(self, name, value):
1413        self.macros[name] = value
1414
1415    def expand(self, line):
1416        if type(line) == list:
1417            el = []
1418            for l in line:
1419                el += [self._expand(l)]
1420            return el
1421        return self._expand(line)
1422
1423    def macro(self, name):
1424        if name in self.macros:
1425            return self.macros[name]
1426        raise error.general('macro "%s" not found' % (name))
1427
1428    def directive(self, _package, name):
1429        if _package not in self._packages:
1430            raise error.general('package "' + _package + '" not found')
1431        if name not in self._packages[_package].directives:
1432            raise error.general('directive "' + name + \
1433                                    '" not found in package "' + _package + '"')
1434        return self._packages[_package].directives[name]
1435
1436    def abspath(self, rpath):
1437        return path.abspath(self.define(rpath))
1438
1439    def packages(self):
1440        return self._packages
1441
1442    def includes(self):
1443        return self._includes
1444
1445    def file_name(self):
1446        return self.name
1447
1448def run():
1449    import sys
1450    try:
1451        #
1452        # Run where defaults.mc is located
1453        #
1454        opts = options.load(sys.argv, defaults = 'defaults.mc')
1455        log.trace('config: count %d' % (len(opts.config_files())))
1456        for config_file in opts.config_files():
1457            s = open(config_file, opts)
1458            print(s)
1459            del s
1460    except error.general as gerr:
1461        print(gerr)
1462        sys.exit(1)
1463    except error.internal as ierr:
1464        print(ierr)
1465        sys.exit(1)
1466    except KeyboardInterrupt:
1467        log.notice('abort: user terminated')
1468        sys.exit(1)
1469    sys.exit(0)
1470
1471if __name__ == "__main__":
1472    run()
Note: See TracBrowser for help on using the repository browser.