Chris Johns 0e22c3c7ff sb: Support --dry-run --with-download for 3rd party RTEMS BSP packages.
The building of 3rd party packages for an RTEMS BSP requires a valid
BSP so the standard method to download the source for releasing does
not work. This change adds support to allow this. The RTEMS BSP support
will not generate an error is no BSP or tools are provided or found.

The change addis logic operators to the %if statement so you can '||'
to 'or' and '&&' to 'and' logic expressions.

A new %log directive has been added to clean up the messages.

A new %{!define ...} has been added to aid checking within logic
expressions.

All command line --with/--without now appear as macros.

Add version.version to get just the RTEMS major and minor version.

Some pkg-config issues have been resolved.

Closes #2655.
2016-03-17 16:39:57 +11:00

585 lines
20 KiB
Python
Executable File

#! /usr/bin/env python
#
# RTEMS Tools Project (http://www.rtems.org/)
# Copyright 2014-2016 Chris Johns (chrisj@rtems.org)
# All rights reserved.
#
# This file is part of the RTEMS Tools package in 'rtems-tools'.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# Pkg-config in python. It attempts to provide a few simple features
# provided by the full pkg-config so packages can configure and build.
#
from __future__ import print_function
import copy
import os
import os.path
import re
import shlex
import sys
import path
def default_prefix(common = True):
paths = []
#
# We have two paths to work around an issue in MSYS2 and the
# conversion of Windows paths to shell paths.
#
if 'PKG_CONFIG_DEFAULT_PATH' in os.environ:
for p in os.environ['PKG_CONFIG_DEFAULT_PATH'].split(os.pathsep):
paths += [path.shell(p)]
if 'PKG_CONFIG_PATH' in os.environ:
for p in os.environ['PKG_CONFIG_PATH'].split(os.pathsep):
paths += [path.shell(p)]
if common:
defaults = ['/usr',
'/usr/share',
'/lib',
'/lib64',
'/usr/lib',
'/usr/lib64',
'/usr/local']
for d in defaults:
for cp in package.config_prefixes:
prefix = path.join(d, cp, 'pkgconfig')
if path.exists(prefix):
paths += [prefix]
return paths
class error(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class package(object):
node_types = ['requires', 'requires.private']
node_type_labels = { 'requires': 'r',
'requires.private': 'rp',
'failed': 'F' }
version_ops = ['=', '<', '>', '<=', '>=', '!=']
config_prefixes = ['lib', 'libdata']
get_recursion = ['cflags', 'libs']
no_dup_flags = ['-I', '-l', '-L']
dual_opts = ['-D', '-U', '-I', '-l', '-L']
lib_list_splitter = re.compile('[\s,]+')
loaded = {}
@staticmethod
def _copy(src, dst):
dst.name_ = src.name_
dst.file_ = src.file_
dst.defines = copy.copy(src.defines)
dst.fields = copy.copy(src.fields)
dst.nodes = copy.copy(src.nodes)
@staticmethod
def is_version(v):
for n in v.split('.'):
if not n.isdigit():
return False
return True
@staticmethod
def splitter(pkg_list):
pkgs = []
if type(pkg_list) == list:
pls = []
for p in pkg_list:
pls += package.lib_list_splitter.split(p)
else:
pls = package.lib_list_splitter.split(pkg_list)
i = 0
while i < len(pls):
pkg = [pls[i]]
i += 1
if i < len(pls):
op = None
if package.is_version(pls[i]):
op = '>='
ver = pls[i]
i += 1
elif pls[i] in package.version_ops:
op = pls[i]
i += 1
if i < len(pls):
ver = pls[i]
i += 1
else:
op = '>='
ver = '0'
pkg += [op, ver]
else:
pkg += ['>=', '0']
pkgs += [pkg]
return pkgs
@staticmethod
def check_versions(lhs, op, rhs):
if op not in package.version_ops:
raise error('bad operator: %s' % (op))
if not lhs or not rhs:
return False
slhs = lhs.split('.')
srhs = rhs.split('.')
ok = True
i = 0
while i < len(srhs):
try:
l = int(slhs[i])
r = int(srhs[i])
except:
return False
if op == '=':
if l != r:
ok = False
break
elif op == '<':
if l < r:
break
if l >= r:
ok = False
break
elif op == '>':
if l > r:
break
if l <= r:
ok = False
break
elif op == '<=':
if l < r:
break
if l > r:
ok = False
break
elif op == '>=':
if l > r:
break
if l < r:
ok = False
break
elif op == '!=':
if l != r:
ok = True
break
if l == r:
ok = False
i += 1
return ok
@staticmethod
def dump_loaded():
for n in sorted(package.loaded):
print(package.loaded[n]._str())
def __init__(self, name = None, prefix = None,
libs_scan = False, output = None, src = None):
self._clean()
self.name_ = name
self.libs_scan = libs_scan
self.output = output
self.src = src
self.prefix = None
self.paths = []
if prefix is None:
prefix = default_prefix()
if prefix:
self._log('prefix: %s' % (prefix))
if type(prefix) is str:
self.prefix = []
for p in prefix.split(os.pathsep):
self.prefix += [path.shell(p)]
elif type(prefix) is list:
self.prefix = prefix
else:
raise error('invalid type of prefix: %s' % (type(prefix)))
for p in self.prefix:
if path.exists(p):
self.paths += [p]
self._log('paths: %s' % (', '.join(self.paths)))
if 'sysroot' in self.defines:
self._log('sysroot: %s' % (self.defines['sysroot']))
if 'top_builddir' in self.defines:
self._log('top_builddir: %s' % (self.defines['top_builddir']))
if self.name_:
self.load(self.name_)
def __str__(self):
s = self._str()
for nt in package.node_types:
for n in sorted(self.nodes[nt]):
s += ' ' + \
' '.join(['%s%s' % (s, os.linesep) \
for s in str(self.nodes[nt][n]).split(os.linesep)])
return s[:-1]
def _str(self):
if self.file_ or len(self.libraries):
e = 'E'
else:
e = '-'
s = '> %s %s (%s)%s' % (self.name_, e, self.file_, os.linesep)
for d in sorted(self.defines):
s += 'd: %s: %s%s' % (d, self.defines[d], os.linesep)
for f in sorted(self.fields):
s += 'f: %s: %s%s' % (f, self.fields[f], os.linesep)
for l in sorted(self.libraries):
s += 'l: %s%s' % (l, os.linesep)
for nt in package.node_types + ['failed']:
s += '%s: ' % (package.node_type_labels[nt])
if len(self.nodes[nt]):
txt = []
for n in sorted(self.nodes[nt]):
if self.nodes[nt][n].exists():
e = ''
else:
e = '*'
txt += ['%s%s' % (n, e)]
s += '%s%s' % (', '.join(txt), os.linesep)
else:
s += 'none' + os.linesep
return s #[:-1]
def _clean(self):
self.name_ = None
self.file_ = None
self.defines = {}
self.fields = {}
self.nodes = { 'failed': {} }
for nt in package.node_types:
self.nodes[nt] = {}
self.libraries = []
if 'PKG_CONFIG_SYSROOT_DIR' in os.environ:
self.defines['sysroot'] = os.environ['PKG_CONFIG_SYSROOT_DIR']
if 'PKG_CONFIG_BUILD_TOP_DIR' in os.environ:
self.defines['top_builddir'] = os.environ['PKG_CONFIG_BUILD_TOP_DIR']
def _log(self, s):
if self.output:
self.output(s)
def _find_package(self, name):
if len(self.paths):
for p in self.paths:
pc = path.join(p, '%s.pc' % (name))
if path.isfile(pc):
return pc;
return None
def _find_libraries(self, name):
libraries = []
if self.libs_scan:
for prefix in self.prefix:
prefix = path.join(prefix, 'lib')
if path.exists(prefix):
for l in os.listdir(path.host(prefix)):
if l.startswith(name + '.'):
libraries += [path.join(prefix, l)]
break
return libraries
def _filter_sysroot(self, s):
if 'sysroot' in self.defines:
sysroot = self.defines['sysroot']
offset = 0
while True:
dash = s[offset:].find('-')
if dash < 0:
break
if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
s = s[:offset + dash + 2] + sysroot + s[offset + dash + 2:]
offset += dash + 1
return s
def _filter_top_builddir(self, s):
if 'top_builddir' in self.defines:
top_builddir = self.defines['top_builddir']
if self.file_.startswith(top_builddir):
offset = 0
while True:
dash = s[offset:].find('-')
if dash < 0:
break
if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
p = s[offset + dash + 2:]
if not p.startswith(top_builddir):
s = s[:offset + dash + 2] + top_builddir + p
offset += dash + 1
return s
def _filter_duplicates(self, s):
clean = ''
present = {}
ss = shlex.split(s)
i = 0
while i < len(ss):
added = False
for do in package.dual_opts:
if ss[i].startswith(do):
if ss[i] == do:
i += 1
if i == len(ss):
clean += ' %s' % (do)
else:
key = '%s%s' % (do, ss[i])
if key not in present:
if ' ' in ss[i]:
clean += ' %s"%s"' % (do, ss[i])
else:
clean += ' %s' % (key)
else:
key = ss[i]
if key not in present:
clean += ' %s' % (key)
added = True
present[key] = True
break
if not added:
if ss[i] not in present:
clean += ' %s' % (ss[i])
present[ss[i]] = True
i += 1
return clean
def _filter(self, s):
s = self._filter_top_builddir(s)
s = self._filter_sysroot(s)
s = self._filter_duplicates(s)
return s.strip()
def _splitter(self, pkg_list):
pkgs = []
pls = pkg_list.split()
i = 0
while i < len(pls):
pkg = [pls[i]]
i += 1
if i < len(pls) and pls[i] in package.version_ops:
pkg += [pls[i]]
i += 1
if i < len(ls):
pkg += [pls[i]]
i += 1
pkgs += [pkg]
return pkgs
def name_from_file(self, file = None):
if file is None:
file = self.file_
if file is None:
return None
name = path.basename(file)
if name.endswith('.pc'):
name = name[:-3]
return name
def name(self):
return self.name_
def file(self):
return self.file_
def exists(self):
ok = False
if self.file_:
ok = True
if self.libraries:
ok = True
return ok
def load(self, name):
if name in package.loaded:
package._copy(package.loaded[name], self)
return
self._log('loading: %s' % (name))
if self.name_:
self._clean()
self.name_ = name
file = self._find_package(name)
if file:
self._log('load: %s (%s)' % (name, file))
if self.src:
self.src('==%s%s' % ('=' * 80, os.linesep))
self.src(' %s %s%s' % (file, '=' * (80 - len(file)), os.linesep))
self.src('==%s%s' % ('=' * 80, os.linesep))
f = open(path.host(file))
tm = False
for l in f.readlines():
if self.src:
self.src(l)
l = l[:-1]
hash = l.find('#')
if hash >= 0:
l = l[:hash]
if len(l):
d = 0
define = False
eq = l.find('=')
dd = l.find(':')
if eq > 0 and dd > 0:
if eq < dd:
define = True
d = eq
else:
define = False
d = dd
elif eq >= 0:
define = True
d = eq
elif dd >= 0:
define = False
d = dd
if d > 0:
lhs = l[:d].lower()
rhs = l[d + 1:]
if tm:
print(('define: ' + str(define) + ', lhs: ' + lhs + ', ' + rhs))
if define:
self.defines[lhs] = rhs
else:
self.fields[lhs] = rhs
self.file_ = file
else:
self.libraries = self._find_libraries(name)
for nt in package.node_types:
requires = self.get(nt, private = False)
if requires:
for r in package.splitter(requires):
if r[0] not in self.nodes[nt]:
if r[0] in package.loaded:
pkg = package.loaded[r[0]]
else:
pkg = package(r[0], self.prefix, self.output)
ver = pkg.get('version')
self._log(' checking: %s (%s) %s %s' % (r[0], ver, r[1], r[2]))
if ver and package.check_versions(ver, r[1], r[2]):
self.nodes[nt][r[0]] = pkg
else:
self._log('failed: %s (%s %s %s)' % (r[0], ver, r[1], r[2]))
self.nodes['failed'][r[0]] = pkg
if self.exists():
self._log('load: exists')
package.loaded[name] = self
def get(self, label, private = True):
self._log('get: %s (%s)' % (label, ','.join(self.fields)))
if label.lower() not in self.fields:
return None
s = ''
if self.file_:
mre = re.compile('\$\{[^\}]+\}')
s = self.fields[label.lower()]
expanded = True
tm = False
while expanded:
expanded = False
if tm:
self._log('pc:get: "' + s + '"')
ms = mre.findall(s)
for m in ms:
mn = m[2:-1]
if mn.lower() in self.defines:
s = s.replace(m, self.defines[mn.lower()])
expanded = True
if label in package.get_recursion:
for nt in package.node_types:
if 'private' not in nt or ('private' in nt and private):
for n in self.nodes[nt]:
r = self.nodes[nt][n].get(label, private = private)
self._log('node: %s: %s' % (self.nodes[nt][n].name(), r))
if r:
s += ' ' + r
elif label == 'libs' and len(self.libraries):
s = '-l%s' % (self.name_[3:])
return self._filter(s)
def check(self, op, version):
self._log('checking: %s %s %s' % (self.name_, op, version))
ok = False
if self.file_:
pkgver = self.get('version')
if pkgver is None:
self._log('check: %s %s failed (no version)' % (op, version))
return False
ok = package.check_versions(pkgver, op, version)
if ok:
self._log('check: %s %s %s ok' % (pkgver, op, version))
else:
self._log('check: %s %s %s failed' % (pkgver, op, version))
else:
if len(self.libraries):
ok = True
else:
self._log('check: %s not found' % (self.name_))
return ok
def check_package(libraries, args, output, src):
ec = 1
pkg = None
flags = { 'cflags': '',
'libs': '' }
output('libraries: %s' % (libraries))
libs = package.splitter(libraries)
for lib in libs:
output('pkg: %s' % (lib))
pkg = package(lib[0], prefix = args.prefix, output = output, src = src)
if args.dump:
output(pkg)
if pkg.exists():
if len(lib) == 1:
if args.exact_version:
if pkg.check('=', args.exact_version):
ec = 0
elif args.atleast_version:
if pkg.check('>=', args.atleast_version):
ec = 0
elif args.max_version:
if pkg.check('<=', args.max_version):
ec = 0
else:
ec = 0
else:
if len(lib) != 3:
raise error('invalid package check: %s' % (' '.join(lib)))
if pkg.check(lib[1], lib[2]):
ec = 0
if ec == 0:
cflags = pkg.get('cflags')
if cflags:
flags['cflags'] += cflags
libs = pkg.get('libs', private = False)
if libs:
flags['libs'] += libs
break
if ec > 0:
break
return ec, pkg, flags