source: rtems-tools/specbuilder/specbuilder/spec.py @ a86cd82

4.104.11
Last change on this file since a86cd82 was a86cd82, checked in by Chris Johns <chrisj@…>, on Jun 9, 2011 at 5:59:32 AM

2011-06-09 Chris Johns <chrisj@…>

  • specbuilder/specbuilder/build.py, specbuilder/specbuilder/crossgcc.py, specbuilder/specbuilder/defaults.py, specbuilder/specbuilder/linux.py, specbuilder/specbuilder/spec.py: Add CentOS support for older Pythons. Add options to build the tools with specific flags.
  • Property mode set to 100644
File size: 26.9 KB
Line 
1#
2# $Id$
3#
4# RTEMS Tools Project (http://www.rtems.org/)
5# Copyright 2010 Chris Johns (chrisj@rtems.org)
6# All rights reserved.
7#
8# This file is part of the RTEMS Tools package in 'rtems-tools'.
9#
10# RTEMS Tools is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# RTEMS Tools is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with RTEMS Tools.  If not, see <http://www.gnu.org/licenses/>.
22#
23
24#
25# This code is based on what ever doco about spec files I could find and
26# RTEMS project's spec files. It parses a spec file into Python data types
27# that can be used by other software modules.
28#
29
30import os
31import re
32import sys
33
34import defaults
35import error
36import execute
37import log
38
39debug = False
40
41class package:
42
43    def __init__(self, name, arch):
44        self._name = name
45        self._arch = arch
46        self.directives = {}
47        self.infos = {}
48
49    def __str__(self):
50
51        def _dictlist(dl):
52            s = ''
53            dll = dl.keys()
54            dll.sort()
55            for d in dll:
56                s += '  ' + d + ':\n'
57                for l in dl[d]:
58                    s += '    ' + l + '\n'
59            return s
60
61        s = '\npackage: ' + self._name + \
62            '\n directives:\n' + _dictlist(self.directives) + \
63            '\n infos:\n' + _dictlist(self.infos)
64       
65        return s
66           
67    def get_info(self, info):
68        if not info in self.infos:
69            raise error.general('no ' + info + ' in package "' + self.name + '"')
70        return self.info
71
72    def version(self):
73        return self.get_info('Version')
74
75    def extract_info(self, label):
76        infos = {}
77        for i in self.infos:
78            il = i.lower()
79            if il.startswith(label):
80                if il == label:
81                    il = label + '0'
82                elif not il[len(label):].isdigit():
83                    continue
84                infos[il] = self.infos[i]
85        return infos
86
87    def find_info(self, label):
88        for i in self.infos:
89            if i.lower() == label:
90                return self.infos[i]
91        return None
92
93    def find_directive(self, label):
94        for d in self.directives:
95            if d.lower() == label:
96                return self.directives[d]
97        return None
98
99    def name(self):
100        info = self.find_info('name')
101        if info:
102            return info[0]
103        return self._name
104
105    def version(self):
106        info = self.find_info('version')
107        if not info:
108            return None
109        return info[0]
110
111    def release(self):
112        info = self.find_info('release')
113        if not info:
114            return None
115        return info[0]
116
117    def buildarch(self):
118        info = self.find_info('buildarch')
119        if not info:
120            return self._arch
121        return info[0]
122
123    def sources(self):
124        return self.extract_info('source');
125
126    def patches(self):
127        return self.extract_info('patch')
128
129    def prep(self):
130        return self.find_directive('%prep')
131       
132    def build(self):
133        return self.find_directive('%build')
134
135    def install(self):
136        return self.find_directive('%install')
137
138    def clean(self):
139        return self.find_directive('%clean')
140       
141    def post(self):
142        return self.find_directive('%post')
143
144    def long_name(self):
145        buildarch = self.buildarch()
146        return '-'.join([self.name(), self.version(), self.release()]) + \
147                            '.' + buildarch
148
149class file:
150    """Parse a spec file."""
151
152    _directive = [ '%description',
153                   '%changelog',
154                   '%prep',
155                   '%build',
156                   '%check',
157                   '%install',
158                   '%clean',
159                   '%post',
160                   '%preun',
161                   '%files' ]
162
163    _ignore = [ re.compile('%setup'),
164                re.compile('%configure'),
165                re.compile('%doc'),
166                re.compile('%dir'),
167                re.compile('%ghost'),
168                re.compile('%exclude'),
169                re.compile('%source[0-9]*'),
170                re.compile('%patch[0-9]*'),
171                re.compile('%__os_install_post') ]
172
173    def __init__(self, name, _defaults, opts):
174        self.opts = opts
175        self.specpath = 'not set'
176        self.wss = re.compile(r'\s+')
177        self.tags = re.compile(r':+')
178        self.sf = re.compile(r'%\([^\)]+\)')
179        self.default_defines = {}
180        for d in _defaults:
181            self.default_defines[self._label(d)] = _defaults[d]
182        for arg in self.opts.args:
183            if arg.startswith('--with-') or arg.startswith('--without-'):
184                label = arg[2:].lower().replace('-', '_')
185                self.default_defines[self._label(label)] = label
186        self.load(name)
187
188    def __str__(self):
189
190        def _dict(dd):
191            s = ''
192            ddl = dd.keys()
193            ddl.sort()
194            for d in ddl:
195                s += '  ' + d + ': ' + dd[d] + '\n'
196            return s
197
198        s = 'spec: %s' % (self.specpath) + \
199            '\n' + str(self.opts) + \
200            '\nlines parsed: %d' % (self.lc) + \
201            '\nname: ' + self.name + \
202            '\ndefines:\n' + _dict(self.defines)
203        for _package in self._packages:
204            s += str(self._packages[_package])
205        return s
206
207    def _output(self, text):
208        if not self.opts.quiet():
209            log.output(text)
210
211    def _warning(self, msg):
212        self._output('warning: ' + self.name + ':' + str(self.lc) + ': ' + msg)
213
214    def _error(self, msg):
215        print >> sys.stderr, \
216            'error: ' + self.name + ':' + str(self.lc) + ': ' + msg
217        self.in_error = True
218
219    def _label(self, name):
220        return '%{' + name.lower() + '}'
221
222    def _macro_split(self, s):
223        '''Split the string (s) up by macros. Only split on the
224           outter level. Nested levels will need to split with futher calls.'''
225        trace_me = False
226        macros = []
227        nesting = []
228        has_braces = False
229        c = 0
230        while c < len(s):
231            if trace_me:
232                print 'ms:', c, '"' + s[c:] + '"', has_braces, len(nesting), nesting
233            #
234            # We need to watch for shell type variables or the form '${var}' because
235            # they can upset the brace matching.
236            #
237            if s[c] == '%' or s[c] == '$':
238                start = s[c]
239                c += 1
240                if c == len(s):
241                    continue
242                #
243                # Do we have '%%' or '%(' or '$%' or '$(' or not '${' ?
244                #
245                if s[c] == '%' or s[c] == '(' or (start == '$' and s[c] != '{'):
246                    continue
247                elif not s[c].isspace():
248                    #
249                    # If this is a shell macro and we are at the outter
250                    # level or is '$var' forget it and move on.
251                    #
252                    if start == '$' and (s[c] != '{' or len(nesting) == 0):
253                        continue
254                    if s[c] == '{':
255                        this_has_braces = True
256                    else:
257                        this_has_braces = False
258                    nesting.append((c - 1, has_braces))
259                    has_braces = this_has_braces
260            elif len(nesting) > 0:
261                if s[c] == '}' or (s[c].isspace() and not has_braces):
262                    #
263                    # Can have '%{?test: something %more}' where the
264                    # nested %more ends with the '}' which also ends
265                    # the outter macro.
266                    #
267                    if not has_braces:
268                        if s[c] == '}':
269                            macro_start, has_braces = nesting[len(nesting) - 1]
270                            nesting = nesting[:-1]
271                            if len(nesting) == 0:
272                                macros.append(s[macro_start:c].strip())
273                    if len(nesting) > 0:
274                        macro_start, has_braces = nesting[len(nesting) - 1]
275                        nesting = nesting[:-1]
276                        if len(nesting) == 0:
277                            macros.append(s[macro_start:c + 1].strip())
278            c += 1
279        if trace_me:
280            print 'ms:', macros
281        return macros
282                   
283    def _shell(self, line):
284        sl = self.sf.findall(line)
285        if len(sl):
286            e = execute.capture_execution()
287            for s in sl:
288                exit_code, proc, output = e.shell(s[2:-1])
289                if exit_code == 0:
290                    line = line.replace(s, output)
291                else:
292                    raise error.general('shell macro failed: ' + s + ': ' + output)
293        return line
294
295    def _expand(self, s):
296        expanded = True
297        while expanded:
298            expanded = False
299            ms = self._macro_split(s)
300            for m in ms:
301                mn = m
302                #
303                # A macro can be '%{macro}' or '%macro'. Turn the later into
304                # the former.
305                #
306                show_warning = True
307                if mn[1] != '{':
308                    for r in self._ignore:
309                        if r.match(mn) is not None:
310                            mn = None
311                            break
312                    else:
313                        mn = self._label(mn[1:])
314                        show_warning = False
315                elif m.startswith('%{expand'):
316                    colon = m.find(':')
317                    if colon < 8:
318                        self._warning('malformed expand macro, no colon found')
319                    else:
320                        e = self._expand(m[colon + 1:-1].strip())
321                        s = s.replace(m, e)
322                        expanded = True
323                        mn = None
324                elif m.startswith('%{with '):
325                    #
326                    # Change the ' ' to '_' because the macros have no spaces.
327                    #
328                    n = self._label('with_' + m[7:-1].strip())
329                    if n in self.defines:
330                        s = s.replace(m, '1')
331                    else:
332                        s = s.replace(m, '0')
333                    expanded = True
334                    mn = None
335                elif m.startswith('%{echo'):
336                    mn = None
337                elif m.startswith('%{defined'):
338                    n = self._label(m[9:-1].strip())
339                    if n in self.defines:
340                        s = s.replace(m, '1')
341                    else:
342                        s = s.replace(m, '0')
343                    expanded = True
344                    mn = None
345                elif m.startswith('%{?') or m.startswith('%{!?'):
346                    if m[2] == '!':
347                        start = 4
348                    else:
349                        start = 3
350                    colon = m[start:].find(':')
351                    if colon < 0:
352                        if not m.endswith('}'):
353                            self._warning("malform conditional macro'" + m)
354                            mn = None
355                        else:
356                            mn = self._label(m[start:-1])
357                    else:
358                        mn = self._label(m[start:start + colon])
359                    if mn:
360                        if m.startswith('%{?'):
361                            if mn in self.defines:
362                                if colon >= 0:
363                                    s = s.replace(m, m[start + colon + 1:-1])
364                                    expanded = True
365                                    mn = None
366                            else:
367                                mn = '%{nil}'
368                        else:
369                            if mn not in self.defines:
370                                if colon >= 0:
371                                    s = s.replace(m, m[start + colon + 1:-1])
372                                    expanded = True
373                                    mn = None
374                            else:
375                                mn = '%{nil}'
376                if mn:
377                    if mn.lower() in self.defines:
378                        s = s.replace(m, self.defines[mn.lower()])
379                        expanded = True
380                    elif show_warning:
381                        self._error("macro '" + mn + "' not found")
382        return self._shell(s)
383
384    def _define(self, spec, ls):
385        if len(ls) <= 1:
386            self._warning('invalid macro definition')
387        else:
388            d = self._label(ls[1])
389            if d not in self.defines:
390                if len(ls) == 2: 
391                    self.defines[d] = '1'
392                else:
393                    self.defines[d] = ls[2].strip()
394            else:
395                if self.opts.warn_all():
396                    self._warning("macro '" + d + "' already defined")
397
398    def _undefine(self, spec, ls):
399        if len(ls) <= 1:
400            self._warning('invalid macro definition')
401        else:
402            mn = self._label(ls[1])
403            if mn in self.defines:
404                self._error("macro '" + mn + "' not defined")
405            del self.defines[mn]
406
407    def _ifs(self, spec, ls, label, iftrue, isvalid):
408        text = []
409        in_iftrue = True
410        while True:
411            if isvalid and \
412                    ((iftrue and in_iftrue) or (not iftrue and not in_iftrue)):
413                this_isvalid = True
414            else:
415                this_isvalid = False
416            r = self._parse(spec, roc = True, isvalid = this_isvalid)
417            if r[0] == 'control':
418                if r[1] == '%end':
419                    self._error(label + ' without %endif')
420                if r[1] == '%endif':
421                    return text
422                if r[1] == '%else':
423                    in_iftrue = False
424            elif r[0] == 'data':
425                if this_isvalid:
426                    text.extend(r[1])
427
428    def _if(self, spec, ls, isvalid):
429       
430        global debug
431
432        def add(x, y):
433            return x + ' ' + str(y)
434
435        def check_bool(value):
436            if value.isdigit():
437                if int(value) == 0:
438                    istrue = False
439                else:
440                    istrue = True
441            else:
442                istrue = None
443            return istrue
444
445        istrue = False
446        if isvalid:
447            if len(ls) == 2:
448                s = ls[1]
449            else:
450                s = (ls[1] + ' ' + ls[2])
451            ifls = s.split()
452            if len(ifls) == 1:
453                istrue = check_bool(ifls[0])
454                if istrue == None:
455                    self._error('invalid if bool value: ' + reduce(add, ls, ''))
456                    istrue = False
457            elif len(ifls) == 2:
458                if ifls[0] == '!':
459                    istrue = check_bool(ifls[1])
460                    if istrue == None:
461                        self._error('invalid if bool value: ' + reduce(add, ls, ''))
462                        istrue = False
463                    else:
464                        istrue = not istrue
465                else:
466                    self._error('invalid if bool operator: ' + reduce(add, ls, ''))
467            elif len(ifls) == 3:
468                if ifls[1] == '==':
469                    if ifls[0] == ifls[2]:
470                        istrue = True
471                    else:
472                        istrue = False
473                elif ifls[1] == '!=' or ifls[1] == '=!':
474                    if ifls[0] != ifls[2]:
475                        istrue = True
476                    else:
477                        istrue = False
478                elif ifls[1] == '>':
479                    if ifls[0] > ifls[2]:
480                        istrue = True
481                    else:
482                        istrue = False
483                elif ifls[1] == '>=' or ifls[1] == '=>':
484                    if ifls[0] >= ifls[2]:
485                        istrue = True
486                    else:
487                        istrue = False
488                elif ifls[1] == '<=' or ifls[1] == '=<':
489                    if ifls[0] <= ifls[2]:
490                        istrue = True
491                    else:
492                        istrue = False
493                elif ifls[1] == '<':
494                    if ifls[0] < ifls[2]:
495                        istrue = True
496                    else:
497                        istrue = False
498                else:
499                    self._error('invalid %if operator: ' + reduce(add, ls, ''))
500            else:
501                self._error('malformed if: ' + reduce(add, ls, ''))
502            if debug:
503                print '_if:  ', ifls, istrue
504        return self._ifs(spec, ls, '%if', istrue, isvalid)
505           
506    def _ifos(self, spec, ls, isvalid):
507        isos = False
508        if isvalid:
509            os = self.define('_os')
510            if ls[0].find(os) >= 0 or ls[1].find(os) >= 0:
511                isos = True
512            else:
513                isos = False
514        return self._ifs(spec, ls, '%ifos', isos, isvalid)
515
516    def _ifarch(self, spec, positive, ls, isvalid):
517        isarch = False
518        if isvalid:
519            arch = self.define('_arch')
520            if ls[0].find(arch) >= 0 or ls[1].find(arch) >= 0:
521                isarch = True
522            else:
523                isarch = False
524        if not positive:
525            isarch = not isarch
526        return self._ifs(spec, ls, '%ifarch', isarch, isvalid)
527
528    def _parse(self, spec, roc = False, isvalid = True):
529        # roc = return on control
530
531        global debug
532
533        def _clean(line):
534            line = line[0:-1]
535            b = line.find('#')
536            if b >= 0:
537                line = line[1:b]
538            return line.strip()
539
540        #
541        # Need to add code to count matching '{' and '}' and if they
542        # do not match get the next line and add to the string until
543        # they match. This closes an opening '{' that is on another
544        # line.
545        #
546
547        for l in spec:
548            self.lc += 1
549            l = _clean(l)
550            if len(l) == 0:
551                continue
552            if debug:
553                print '%03d: %d %s' % (self.lc, isvalid, l)
554            if isvalid:
555                l = self._expand(l)
556            if len(l) == 0:
557                continue
558            if l[0] == '%':
559                ls = self.wss.split(l, 2)
560                if ls[0] == '%package':
561                    if isvalid:
562                        if ls[1] == '-n':
563                            name = ls[2]
564                        else:
565                            name = self.name + '-' + ls[1]
566                        return ('package', name)
567                elif ls[0] == '%define' or ls[0] == '%global':
568                    if isvalid:
569                        self._define(spec, ls)
570                elif ls[0] == '%undefine':
571                    if isvalid:
572                        self._undefine(spec, ls)
573                elif ls[0] == '%if':
574                    d = self._if(spec, ls, isvalid)
575                    if len(d):
576                        return ('data', d)
577                elif ls[0] == '%ifos':
578                    d = self._ifos(spec, ls, isvalid)
579                    if len(d):
580                        return ('data', d)
581                elif ls[0] == '%ifarch':
582                    d = self._ifarch(spec, True, ls, isvalid)
583                    if len(d):
584                        return ('data', d)
585                elif ls[0] == '%ifnarch':
586                    d = self._ifarch(spec, False, ls, isvalid)
587                    if len(d):
588                        return ('data', d)
589                elif ls[0] == '%endif':
590                    if roc:
591                        return ('control', '%endif')
592                    self._warning("unexpected '" + ls[0] + "'")
593                elif ls[0] == '%else':
594                    if roc:
595                        return ('control', '%else')
596                    self._warning("unexpected '" + ls[0] + "'")
597                elif ls[0].startswith('%defattr'):
598                    return ('data', [l])
599                elif ls[0] == '%bcond_with':
600                    if isvalid:
601                        #
602                        # Check is already defined. Would be by the command line or
603                        # even a host specific default.
604                        #
605                        if self._label('with_' + ls[1]) not in self.defines:
606                            self._define(spec, (ls[0], 'without_' + ls[1]))
607                elif ls[0] == '%bcond_without':
608                    if isvalid:
609                        if self._label('without_' + ls[1]) not in self.defines:
610                            self._define(spec, (ls[0], 'with_' + ls[1]))
611                else:
612                    for r in self._ignore:
613                        if r.match(ls[0]) is not None:
614                            return ('data', [l])
615                    if isvalid:
616                        for d in self._directive:
617                            if ls[0].strip() == d:
618                                return ('directive', ls[0].strip(), ls[1:])
619                        self._warning("unknown directive: '" + ls[0] + "'")
620                        return ('data', [l])
621            else:
622                return ('data', [l])
623        return ('control', '%end')
624
625    def _set_package(self, _package):
626        if self.package == 'main' and \
627                self._packages[self.package].name() != None:
628            if self._packages[self.package].name() == _package:
629                return
630        if _package not in self._packages:
631            self._packages[_package] = package(_package, 
632                                               self.define('%{_arch}'))
633        self.package = _package
634
635    def _directive_extend(self, dir, data):
636        if dir not in self._packages[self.package].directives:
637            self._packages[self.package].directives[dir] = []
638        for i in range(0, len(data)):
639            data[i] = data[i].strip()
640        self._packages[self.package].directives[dir].extend(data)
641
642    def _info_append(self, info, data):
643        if info not in self._packages[self.package].infos:
644            self._packages[self.package].infos[info] = []
645        self._packages[self.package].infos[info].append(data)
646
647    def load(self, name):
648
649        global debug
650
651        self.in_error = False
652        self.name = name
653        self.lc = 0
654        self.defines = self.default_defines
655        self.conditionals = {}
656        self._packages = {}
657        self.package = 'main'
658        self._packages[self.package] = package(self.package, 
659                                               self.define('%{_arch}'))
660        self.specpath = os.path.join(self.abspath('_specdir'), name)
661        if not os.path.exists(self.specpath):
662            raise error.general('no spec file found: ' + self.specpath)
663        try:
664            spec = open(self.specpath, 'r')
665        except IOError, err:
666            raise error.general('error opening spec file: ' + self.specpath)
667        try:
668            dir = None
669            data = []
670            while True:
671                r = self._parse(spec)
672                if r[0] == 'package':
673                    self._set_package(r[1])
674                    dir = None
675                elif r[0] == 'control':
676                    if r[1] == '%end':
677                        break
678                    self._warning("unexpected '" + r[1] + "'")
679                elif r[0] == 'directive':
680                    new_data = []
681                    if r[1] == '%description':
682                        new_data = [' '.join(r[2])]
683                    else:
684                        if len(r[2]) == 0:
685                            _package = 'main'
686                        elif len(r[2]) == 1:
687                            _package = r[2][0]
688                        else:
689                            if r[2][0].strip() != '-n':
690                                self._warning("unknown directive option: '" + ' '.join(r[2]) + "'")
691                            _package = r[2][1].strip()
692                        self._set_package(_package)
693                    if dir and dir != r[1]:
694                        self._directive_extend(dir, data)
695                    dir = r[1]
696                    data = new_data
697                elif r[0] == 'data':
698                    for l in r[1]:
699                        l = self._expand(l)
700                        if not dir:
701                            ls = self.tags.split(l, 1)
702                            if debug:
703                                print '_tag: ', l, ls
704                            if len(ls) > 1:
705                                i = ls[0]
706                                self._info_append(i, ls[1].strip())
707                                # It seems like the info's also appear as
708                                # defines or can be accessed via macros.
709                                if ls[0][len(ls[0]) - 1] == ':':
710                                    ls[0] = ls[0][:-1]
711                                ls[0] = ls[0].lower()
712                                self._define(None, ('', ls[0], ls[1]))
713                            else:
714                                self._warning("invalid format: '" + l[:-1] + "'")
715                        else:
716                            data.append(l)
717                else:
718                    self._error("invalid parse state: '" + r[0] + "'")
719            self._directive_extend(dir, data)
720        except:
721            spec.close()
722            raise
723        spec.close()
724
725    def define(self, name):
726        if name.lower() in self.defines:
727            d = self.defines[name.lower()]
728        else:
729            n = self._label(name)
730            if n in self.defines:
731                d = self.defines[n]
732            else:
733                raise error.general('macro "' + name + '" not found')
734        return self._expand(d)
735
736    def expand(self, line):
737        return self._expand(line)
738
739    def directive(self, _package, name):
740        if _package not in self._packages:
741            raise error.general('package "' + _package + '" not found')
742        if name not in self._packages[_package].directives:
743            raise error.general('directive "' + name + \
744                                    '" not found in package "' + _package + '"')
745        return self._packages[_package].directives[name]
746
747    def abspath(self, path):
748        return os.path.abspath(self.define(path))
749
750    def packages(self):
751        return self._packages
752
753def run():
754    import sys
755    try:
756        opts, _defaults = defaults.load(sys.argv)
757        for spec_file in opts.spec_files():
758            s = file(spec_file, _defaults = _defaults, opts = opts)
759            print s
760            del s
761    except error.general, gerr:
762        print gerr
763        sys.exit(1)
764    except error.internal, ierr:
765        print ierr
766        sys.exit(1)
767    except KeyboardInterrupt:
768        print 'user terminated'
769        sys.exit(1)
770    sys.exit(0)
771
772if __name__ == "__main__":
773    run()
Note: See TracBrowser for help on using the repository browser.