Chris Johns 7231f495e0 2010-08-09 Chris Johns <chrisj@rtems.org>
* specbuilder, specbuilder/sb-build, specbuilder/sb-crossgcc,
        specbuilder/sb-setup, specbuilder/sb-specdump,
        specbuilder/sb-status, specbuilder/specbuilder/.cvsignore,
        specbuilder/specbuilder/build.py,
        specbuilder/specbuilder/config.sub,
        specbuilder/specbuilder/crossgcc.py,
        specbuilder/specbuilder/darwin.py,
        specbuilder/specbuilder/defaults.py,
        specbuilder/specbuilder/error.py,
        specbuilder/specbuilder/execute.py,
        specbuilder/specbuilder/log.py, specbuilder/specbuilder/setup.py,
        specbuilder/specbuilder/spec.py,
        specbuilder/specbuilder/status.py: New.
2010-08-09 01:29:43 +00:00

761 lines
26 KiB
Python

#
# $Id$
#
# RTEMS Tools Project (http://www.rtems.org/)
# Copyright 2010 Chris Johns (chrisj@rtems.org)
# All rights reserved.
#
# This file is part of the RTEMS Tools package in 'rtems-tools'.
#
# RTEMS Tools is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RTEMS Tools is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with RTEMS Tools. If not, see <http://www.gnu.org/licenses/>.
#
#
# This code is based on what ever doco about spec files I could find and
# RTEMS project's spec files. It parses a spec file into Python data types
# that can be used by other software modules.
#
import os
import re
import sys
import defaults
import error
import execute
import log
debug = False
class package:
def __init__(self, name, arch):
self._name = name
self._arch = arch
self.directives = {}
self.infos = {}
def __str__(self):
def _dictlist(dl):
s = ''
dll = dl.keys()
dll.sort()
for d in dll:
s += ' ' + d + ':\n'
for l in dl[d]:
s += ' ' + l + '\n'
return s
s = '\npackage: ' + self._name + \
'\n directives:\n' + _dictlist(self.directives) + \
'\n infos:\n' + _dictlist(self.infos)
return s
def get_info(self, info):
if not info in self.infos:
raise error.general('no ' + info + ' in package "' + self.name + '"')
return self.info
def version(self):
return self.get_info('Version')
def extract_info(self, label):
infos = {}
for i in self.infos:
il = i.lower()
if il.startswith(label) and il[len(label):].isdigit():
infos[il] = self.infos[i]
return infos
def find_info(self, label):
for i in self.infos:
if i.lower() == label:
return self.infos[i]
return None
def find_directive(self, label):
for d in self.directives:
if d.lower() == label:
return self.directives[d]
return None
def name(self):
info = self.find_info('name')
if info:
return info[0]
return self._name
def version(self):
info = self.find_info('version')
if not info:
return None
return info[0]
def release(self):
info = self.find_info('release')
if not info:
return None
return info[0]
def buildarch(self):
info = self.find_info('buildarch')
if not info:
return self._arch
return info[0]
def sources(self):
return self.extract_info('source');
def patches(self):
return self.extract_info('patch')
def prep(self):
return self.find_directive('%prep')
def build(self):
return self.find_directive('%build')
def install(self):
return self.find_directive('%install')
def clean(self):
return self.find_directive('%clean')
def post(self):
return self.find_directive('%post')
def long_name(self):
buildarch = self.buildarch()
return '-'.join([self.name(), self.version(), self.release()]) + \
'.' + buildarch
class file:
"""Parse a spec file."""
_directive = [ '%description',
'%prep',
'%build',
'%install',
'%clean',
'%post',
'%preun',
'%files' ]
_ignore = [ re.compile('%setup'),
re.compile('%doc'),
re.compile('%dir'),
re.compile('%ghost'),
re.compile('%exclude'),
re.compile('%source[0-9]*'),
re.compile('%patch[0-9]*'),
re.compile('%__os_install_post') ]
def __init__(self, name, _defaults, opts):
self.opts = opts
self.specpath = 'not set'
self.wss = re.compile(r'\s+')
self.tags = re.compile(r':+')
self.sf = re.compile(r'%\([^\)]+\)')
self.default_defines = {}
for d in _defaults:
self.default_defines[self._label(d)] = _defaults[d]
for arg in self.opts.args:
if arg.startswith('--with-') or arg.startswith('--without-'):
label = arg[2:].lower().replace('-', '_')
self.default_defines[self._label(label)] = label
self.load(name)
def __str__(self):
def _dict(dd):
s = ''
ddl = dd.keys()
ddl.sort()
for d in ddl:
s += ' ' + d + ': ' + dd[d] + '\n'
return s
s = 'spec: %s' % (self.specpath) + \
'\n' + str(self.opts) + \
'\nlines parsed: %d' % (self.lc) + \
'\nname: ' + self.name + \
'\ndefines:\n' + _dict(self.defines)
for _package in self._packages:
s += str(self._packages[_package])
return s
def _output(self, text):
if not self.opts.quiet():
log.output(text)
def _warning(self, msg):
self._output('warning: ' + self.name + ':' + str(self.lc) + ': ' + msg)
def _error(self, msg):
print >> sys.stderr, \
'error: ' + self.name + ':' + str(self.lc) + ': ' + msg
self.in_error = True
def _label(self, name):
return '%{' + name.lower() + '}'
def _macro_split(self, s):
'''Split the string (s) up by macros. Only split on the
outter level. Nested levels will need to split with futher calls.'''
trace_me = False
macros = []
nesting = []
has_braces = False
c = 0
while c < len(s):
if trace_me:
print 'ms:', c, '"' + s[c:] + '"', has_braces, len(nesting), nesting
#
# We need to watch for shell type variables or the form '${var}' because
# they can upset the brace matching.
#
if s[c] == '%' or s[c] == '$':
start = s[c]
c += 1
if c == len(s):
continue
#
# Do we have '%%' or '%(' or '$%' or '$(' or not '${' ?
#
if s[c] == '%' or s[c] == '(' or (start == '$' and s[c] != '{'):
continue
elif not s[c].isspace():
#
# If this is a shell macro and we are at the outter
# level or is '$var' forget it and move on.
#
if start == '$' and (s[c] != '{' or len(nesting) == 0):
continue
if s[c] == '{':
this_has_braces = True
else:
this_has_braces = False
nesting.append((c - 1, has_braces))
has_braces = this_has_braces
elif len(nesting) > 0:
if s[c] == '}' or (s[c].isspace() and not has_braces):
#
# Can have '%{?test: something %more}' where the
# nested %more ends with the '}' which also ends
# the outter macro.
#
if not has_braces:
if s[c] == '}':
macro_start, has_braces = nesting[len(nesting) - 1]
nesting = nesting[:-1]
if len(nesting) == 0:
macros.append(s[macro_start:c].strip())
if len(nesting) > 0:
macro_start, has_braces = nesting[len(nesting) - 1]
nesting = nesting[:-1]
if len(nesting) == 0:
macros.append(s[macro_start:c + 1].strip())
c += 1
if trace_me:
print 'ms:', macros
return macros
def _shell(self, line):
sl = self.sf.findall(line)
if len(sl):
e = execute.capture_execution()
for s in sl:
exit_code, proc, output = e.shell(s[2:-1])
if exit_code == 0:
line = line.replace(s, output)
else:
raise error.general('shell macro failed: ' + s + ': ' + output)
return line
def _expand(self, s):
expanded = True
while expanded:
expanded = False
ms = self._macro_split(s)
for m in ms:
mn = m
#
# A macro can be '%{macro}' or '%macro'. Turn the later into
# the former.
#
show_warning = True
if mn[1] != '{':
for r in self._ignore:
if r.match(mn) is not None:
mn = None
break
else:
mn = self._label(mn[1:])
show_warning = False
elif m.startswith('%{expand'):
colon = m.find(':')
if colon < 8:
self._warning('malformed expand macro, no colon found')
else:
e = self._expand(m[colon + 1:-1].strip())
s = s.replace(m, e)
expanded = True
mn = None
elif m.startswith('%{with '):
#
# Change the ' ' to '_' because the macros have no spaces.
#
n = self._label('with_' + m[7:-1].strip())
if n in self.defines:
s = s.replace(m, '1')
else:
s = s.replace(m, '0')
expanded = True
mn = None
elif m.startswith('%{echo'):
mn = None
elif m.startswith('%{defined'):
n = self._label(m[9:-1].strip())
if n in self.defines:
s = s.replace(m, '1')
else:
s = s.replace(m, '0')
expanded = True
mn = None
elif m.startswith('%{?') or m.startswith('%{!?'):
if m[2] == '!':
start = 4
else:
start = 3
colon = m[start:].find(':')
if colon < 0:
if not m.endswith('}'):
self._warning("malform conditional macro'" + m)
mn = None
else:
mn = self._label(m[start:-1])
else:
mn = self._label(m[start:start + colon])
if mn:
if m.startswith('%{?'):
if mn in self.defines:
if colon >= 0:
s = s.replace(m, m[start + colon + 1:-1])
expanded = True
mn = None
else:
mn = '%{nil}'
else:
if mn not in self.defines:
if colon >= 0:
s = s.replace(m, m[start + colon + 1:-1])
expanded = True
mn = None
else:
mn = '%{nil}'
if mn:
if mn.lower() in self.defines:
s = s.replace(m, self.defines[mn.lower()])
expanded = True
elif show_warning:
self._error("macro '" + mn + "' not found")
return self._shell(s)
def _define(self, spec, ls):
if len(ls) <= 1:
self._warning('invalid macro definition')
else:
d = self._label(ls[1])
if d not in self.defines:
if len(ls) == 2:
self.defines[d] = '1'
else:
self.defines[d] = ls[2].strip()
else:
if self.opts.warn_all():
self._warning("macro '" + d + "' already defined")
def _undefine(self, spec, ls):
if len(ls) <= 1:
self._warning('invalid macro definition')
else:
mn = self._label(ls[1])
if mn in self.defines:
self._error("macro '" + mn + "' not defined")
del self.defines[mn]
def _ifs(self, spec, ls, label, iftrue, isvalid):
text = []
in_iftrue = True
while True:
if isvalid and \
((iftrue and in_iftrue) or (not iftrue and not in_iftrue)):
this_isvalid = True
else:
this_isvalid = False
r = self._parse(spec, roc = True, isvalid = this_isvalid)
if r[0] == 'control':
if r[1] == '%end':
self._error(label + ' without %endif')
if r[1] == '%endif':
return text
if r[1] == '%else':
in_iftrue = False
elif r[0] == 'data':
if this_isvalid:
text.extend(r[1])
def _if(self, spec, ls, isvalid):
global debug
def add(x, y):
return x + ' ' + str(y)
def check_bool(value):
if value.isdigit():
if int(value) == 0:
istrue = False
else:
istrue = True
else:
istrue = None
return istrue
istrue = False
if isvalid:
if len(ls) == 2:
s = ls[1]
else:
s = (ls[1] + ' ' + ls[2])
ifls = s.split()
if len(ifls) == 1:
istrue = check_bool(ifls[0])
if istrue == None:
self._error('invalid if bool value: ' + reduce(add, ls, ''))
istrue = False
elif len(ifls) == 2:
if ifls[0] == '!':
istrue = check_bool(ifls[1])
if istrue == None:
self._error('invalid if bool value: ' + reduce(add, ls, ''))
istrue = False
else:
istrue = not istrue
else:
self._error('invalid if bool operator: ' + reduce(add, ls, ''))
elif len(ifls) == 3:
if ifls[1] == '==':
if ifls[0] == ifls[2]:
istrue = True
else:
istrue = False
elif ifls[1] == '!=' or ifls[1] == '=!':
if ifls[0] != ifls[2]:
istrue = True
else:
istrue = False
elif ifls[1] == '>':
if ifls[0] > ifls[2]:
istrue = True
else:
istrue = False
elif ifls[1] == '>=' or ifls[1] == '=>':
if ifls[0] >= ifls[2]:
istrue = True
else:
istrue = False
elif ifls[1] == '<=' or ifls[1] == '=<':
if ifls[0] <= ifls[2]:
istrue = True
else:
istrue = False
elif ifls[1] == '<':
if ifls[0] < ifls[2]:
istrue = True
else:
istrue = False
else:
self._error('invalid %if operator: ' + reduce(add, ls, ''))
else:
self._error('malformed if: ' + reduce(add, ls, ''))
if debug:
print '_if: ', ifls, istrue
return self._ifs(spec, ls, '%if', istrue, isvalid)
def _ifos(self, spec, ls, isvalid):
isos = False
if isvalid:
os = self.define('_os')
if ls[0].find(os) >= 0 or ls[1].find(os) >= 0:
isos = True
else:
isos = False
return self._ifs(spec, ls, '%ifos', isos, isvalid)
def _ifarch(self, spec, positive, ls, isvalid):
isarch = False
if isvalid:
arch = self.define('_arch')
if ls[0].find(arch) >= 0 or ls[1].find(arch) >= 0:
isarch = True
else:
isarch = False
if not positive:
isarch = not isarch
return self._ifs(spec, ls, '%ifarch', isarch, isvalid)
def _parse(self, spec, roc = False, isvalid = True):
# roc = return on control
global debug
def _clean(line):
line = line[0:-1]
b = line.find('#')
if b >= 0:
line = line[1:b]
return line.strip()
#
# Need to add code to count matching '{' and '}' and if they
# do not match get the next line and add to the string until
# they match. This closes an opening '{' that is on another
# line.
#
for l in spec:
self.lc += 1
l = _clean(l)
if len(l) == 0:
continue
if debug:
print '%03d: %d %s' % (self.lc, isvalid, l)
if isvalid:
l = self._expand(l)
if len(l) == 0:
continue
if l[0] == '%':
ls = self.wss.split(l, 2)
if ls[0] == '%package':
if isvalid:
if ls[1] == '-n':
name = ls[2]
else:
name = self.name + '-' + ls[1]
return ('package', name)
elif ls[0] == '%define' or ls[0] == '%global':
if isvalid:
self._define(spec, ls)
elif ls[0] == '%undefine':
if isvalid:
self._undefine(spec, ls)
elif ls[0] == '%if':
d = self._if(spec, ls, isvalid)
if len(d):
return ('data', d)
elif ls[0] == '%ifos':
d = self._ifos(spec, ls, isvalid)
if len(d):
return ('data', d)
elif ls[0] == '%ifarch':
d = self._ifarch(spec, True, ls, isvalid)
if len(d):
return ('data', d)
elif ls[0] == '%ifnarch':
d = self._ifarch(spec, False, ls, isvalid)
if len(d):
return ('data', d)
elif ls[0] == '%endif':
if roc:
return ('control', '%endif')
self._warning("unexpected '" + ls[0] + "'")
elif ls[0] == '%else':
if roc:
return ('control', '%else')
self._warning("unexpected '" + ls[0] + "'")
elif ls[0].startswith('%defattr'):
return ('data', [l])
elif ls[0] == '%bcond_with':
if isvalid:
#
# Check is already defined. Would be by the command line or
# even a host specific default.
#
if self._label('with_' + ls[1]) not in self.defines:
self._define(spec, (ls[0], 'without_' + ls[1]))
elif ls[0] == '%bcond_without':
if isvalid:
if self._label('without_' + ls[1]) not in self.defines:
self._define(spec, (ls[0], 'with_' + ls[1]))
else:
for r in self._ignore:
if r.match(ls[0]) is not None:
return ('data', [l])
if isvalid:
for d in self._directive:
if ls[0].strip() == d:
if len(ls) == 1:
package = 'main'
elif len(ls) == 2:
package = ls[1].strip()
else:
if ls[1].strip() != '-n':
self._warning("unknown directive option: '" + ls[1] + "'")
package = ls[2].strip()
return ('directive', ls[0].strip(), package)
self._warning("unknown directive: '" + ls[0] + "'")
return ('data', [l])
else:
return ('data', [l])
return ('control', '%end')
def _set_package(self, _package):
if self.package == 'main' and \
self._packages[self.package].name() != None:
if self._packages[self.package].name() == _package:
return
if _package not in self._packages:
self._packages[_package] = package(_package,
self.define('%{_arch}'))
self.package = _package
def _directive_extend(self, dir, data):
if dir not in self._packages[self.package].directives:
self._packages[self.package].directives[dir] = []
for i in range(0, len(data)):
data[i] = data[i].strip()
self._packages[self.package].directives[dir].extend(data)
def _info_append(self, info, data):
if info not in self._packages[self.package].infos:
self._packages[self.package].infos[info] = []
self._packages[self.package].infos[info].append(data)
def load(self, name):
global debug
self.in_error = False
self.name = name
self.lc = 0
self.defines = self.default_defines
self.conditionals = {}
self._packages = {}
self.package = 'main'
self._packages[self.package] = package(self.package,
self.define('%{_arch}'))
self.specpath = os.path.join(self.abspath('_specdir'), name)
if not os.path.exists(self.specpath):
raise error.general('no spec file found: ' + self.specpath)
try:
spec = open(self.specpath, 'r')
except IOError, err:
raise error.general('error opening spec file: ' + self.specpath)
try:
dir = None
data = []
while True:
r = self._parse(spec)
if r[0] == 'package':
self._set_package(r[1])
dir = None
elif r[0] == 'control':
if r[1] == '%end':
break
self._warning("unexpected '" + r[1] + "'")
elif r[0] == 'directive':
self._set_package(r[2])
if dir and dir != r[1]:
self._directive_extend(dir, data)
dir = r[1]
data = []
elif r[0] == 'data':
for l in r[1]:
l = self._expand(l)
if not dir:
ls = self.tags.split(l, 1)
if debug:
print '_tag: ', l, ls
if len(ls) > 1:
i = ls[0]
self._info_append(i, ls[1].strip())
# It seems like the info's also appear as
# defines or can be accessed via macros.
if ls[0][len(ls[0]) - 1] == ':':
ls[0] = ls[0][:-1]
ls[0] = ls[0].lower()
self._define(None, ('', ls[0], ls[1]))
else:
self._warning("invalid format: '" + l[:-1] + "'")
else:
data.append(l)
else:
self._error("invalid parse state: '" + r[0] + "'")
self._directive_extend(dir, data)
finally:
spec.close()
def define(self, name):
if name.lower() in self.defines:
d = self.defines[name.lower()]
else:
n = self._label(name)
if n in self.defines:
d = self.defines[n]
else:
raise error.general('macro "' + name + '" not found')
return self._expand(d)
def expand(self, line):
return self._expand(line)
def directive(self, _package, name):
if _package not in self._packages:
raise error.general('package "' + _package + '" not found')
if name not in self._packages[_package].directives:
raise error.general('directive "' + name + \
'" not found in package "' + _package + '"')
return self._packages[_package].directives[name]
def abspath(self, path):
return os.path.abspath(self.define(path))
def packages(self):
return self._packages
def run():
import sys
try:
opts, _defaults = defaults.load(sys.argv)
for spec_file in opts.spec_files():
s = file(spec_file, _defaults = _defaults, opts = opts)
print s
del s
except error.general, gerr:
print gerr
sys.exit(1)
except error.internal, ierr:
print ierr
sys.exit(1)
except KeyboardInterrupt:
print 'user terminated'
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
run()