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

4.104.115
Last change on this file since dc2ed4f was dc2ed4f, checked in by Chris Johns <chrisj@…>, on 08/09/10 at 04:50:30

2010-08-09 Chris Johns <chrisj@…>

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