source: rtems-source-builder/source-builder/sb/config.py @ 8992d20

5
Last change on this file since 8992d20 was 38fd56c, checked in by Chris Johns <chrisj@…>, on 09/27/18 at 21:27:57

sb: Monitor the build disk usage. Report the usage, total and various sizes

  • Track the size of a build of a package in a build set to determine the maximum amout of disk space used. This can be used as a guide to documenting how much space a user needs to set aside to build a specific set of tools.
  • The %clean stage of a build is now split into a separate script. I do not think this is an issue because I could not find any %clean sections in any build configs we have. In time support for the %clean section will be removed, the package builder cleans up.

Closes #3516

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