sb: Add pkg-config support.

Add a pkg-config look alike command so packages that use pkg-config can
build if pkg-config is not present on a host.

Add support to query package config from configuration scripts.
This commit is contained in:
Chris Johns 2014-02-11 10:18:35 +11:00
parent 56e8d80055
commit c4fefdeb55
5 changed files with 763 additions and 357 deletions

View File

@ -1,356 +0,0 @@
#! /usr/bin/env python
#
# RTEMS Tools Project (http://www.rtems.org/)
# Copyright 2014 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.
#
# Pkg-config as a tool is a good idea how-ever the implementation creates a
# range of problems. If it was implemented with all parts included it would be
# portable and I suspect useful to others on platforms other than Linux and
# Unix equivs that come with packaging systems..
#
# Note: This is used on platforms where pkg-config is not easy to get
# working.
#
import argparse
import os
import os.path
import re
import sys
#
# Make trace true to get a file of what happens and what is being asked.
#
trace = True
logfile = 'pc.log'
out = None
pkg_aliases = {
# Use to add package name aliases eg
# 'package': ['pkg_alias'],
}
def log(s, lf = True):
global trace, logfile, out
if trace:
if out is None:
if logfile:
out = open(logfile, 'a')
else:
out = sys.stdout
if lf:
print >> out, s
else:
print >> out, s,
def default_prefix():
paths = ['/usr', '/usr/local']
if 'PKG_CONFIG_PATH' in os.environ:
paths += os.environ['PKG_CONFIG_PATH'].split(':')
return paths
class error(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class package:
def __init__(self, file = None):
self.defines = {}
self.fields = {}
if file:
self.load(file)
def __str__(self):
s = ''
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)
return s
def _sysroot(self, s):
if 'PKG_CONFIG_SYSROOT_DIR' in os.environ:
sysroot = os.environ['PKG_CONFIG_SYSROOT_DIR']
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 load(self, file):
f = open(file)
tm = False
for l in f.readlines():
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
def get(self, label):
if label.lower() not in self.fields:
None
mre = re.compile('\$\{[^\}]+\}')
s = self.fields[label.lower()]
expanded = True
tm = False
while expanded:
expanded = False
if tm:
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
return self._sysroot(s)
def _check_package(lib_check, args, pc_paths):
ec = 1
pkg = None
ls = lib_check.split()
lib = ls[0]
if len(pc_paths):
for path in pc_paths:
pc = os.path.join(path, '%s.pc' % (lib))
if os.path.isfile(pc):
pkg = package(file = pc)
if args.dump:
log(pkg)
ec = 0
break
if ec > 0:
versions = []
for prefix in args.prefix:
prefix = os.path.join(prefix, 'lib')
if os.path.exists(prefix):
for l in os.listdir(prefix):
if l.startswith(lib + '.'):
versions += [l]
break
if len(versions) > 0:
if len(ls) > 1:
ec = 0
elif args.exists:
ec = 0
elif args.exact_version:
ec = 0
elif args.atleast_version:
ec = 0
elif args.atleast_version:
ec = 0
return ec, pkg
def run(argv):
class version_action(argparse.Action):
def __call__(self, parser, namespace, values, option_string = None):
parts = values.strip().split('.')
for p in parts:
if not p.isdigit():
raise error('invalid version value: %s' % (values))
setattr(namespace, self.dest, '.'.join(parts))
ec = 0
opts = argparse.ArgumentParser(prog = 'pkg-config',
description = 'Package Configuration.')
opts.add_argument('libraries', metavar='lib', type = str, nargs = '+',
help = 'a library')
opts.add_argument('--modversion', dest = 'modversion', action = 'store', default = None,
help = 'Requests that the version information of the libraries.')
opts.add_argument('--print-errors', dest = 'print_errors', action = 'store_true',
default = False,
help = 'Print any errors.')
opts.add_argument('--short-errors', dest = 'short_errors', action = 'store_true',
default = False,
help = 'Make error messages short.')
opts.add_argument('--silence-errors', dest = 'silence_errors', action = 'store_true',
default = False,
help = 'Do not print any errors.')
opts.add_argument('--errors-to-stdout', dest = 'errors_to_stdout', action = 'store_true',
default = False,
help = 'Print errors to stdout rather than stderr.')
opts.add_argument('--cflags', dest = 'cflags', action = 'store_true',
default = False,
help = 'This prints pre-processor and compile flags required to' \
' compile the package(s)')
opts.add_argument('--libs', dest = 'libs', action = 'store_true',
default = False,
help = 'This option is identical to "--cflags", only it prints the' \
' link flags.')
opts.add_argument('--libs-only-L', dest = 'libs_only_L', action = 'store_true',
default = False,
help = 'This prints the -L/-R part of "--libs".')
opts.add_argument('--libs-only-l', dest = 'libs_only_l', action = 'store_true',
default = False,
help = 'This prints the -l part of "--libs".')
opts.add_argument('--variable', dest = 'variable', action = 'store',
default = None,
help = 'This returns the value of a variable.')
opts.add_argument('--define-variable', dest = 'define_variable', action = 'store',
default = None,
help = 'This sets a global value for a variable')
opts.add_argument('--uninstalled', dest = 'uninstalled', action = 'store_true',
default = False,
help = 'Ignored')
opts.add_argument('--atleast-pkgconfig-version', dest = 'atleast_pkgconfig_version', action = 'store_true',
default = False,
help = 'Check the version of package config. Always ok.')
opts.add_argument('--exists', dest = 'exists', action = 'store_true',
default = False,
help = 'Test if a library is present')
opts.add_argument('--atleast-version', dest = 'atleast_version', action = version_action,
default = None,
help = 'The package is at least this version.')
opts.add_argument('--exact-version', dest = 'exact_version', action = version_action,
default = None,
help = 'The package is the exact version.')
opts.add_argument('--max-version', dest = 'max_version', action = version_action,
default = None,
help = 'The package is no later than this version.')
opts.add_argument('--msvc-syntax', dest = 'msvc_syntax', action = 'store_true',
default = False,
help = 'Ignored')
opts.add_argument('--dont-define-prefix', dest = 'dont_define_prefix', action = 'store_true',
default = False,
help = 'Ignored')
opts.add_argument('--prefix-variable', dest = 'prefix', action = 'store',
default = default_prefix(),
help = 'Define the prefix.')
opts.add_argument('--static', dest = 'static', action = 'store_true',
default = False,
help = 'Output libraries suitable for static linking')
opts.add_argument('--dump', dest = 'dump', action = 'store_true',
default = False,
help = 'Dump the package if one is found.')
args = opts.parse_args(argv[1:])
if (args.exists and (args.exact_version or args.max_version)) or \
(args.exact_version and (args.exists or args.max_version)) or \
(args.max_version and (args.exists or args.exact_version)):
raise error('only one of --exists, --exact-version, or --max-version')
pc_paths = []
if args.prefix:
for prefix in args.prefix:
prefix = os.path.join(prefix, 'lib', 'pkgconfig')
if os.path.exists(prefix):
pc_paths += [prefix]
if len(pc_paths):
log('pc_paths = %s' % (', '.join(pc_paths)))
else:
log('pc_paths = None')
exists = False
ec = 1
if args.atleast_pkgconfig_version:
ec = 0
else:
for lib in args.libraries:
ec, pkg = _check_package(lib, args, pc_paths)
if ec > 0:
for pa in pkg_aliases:
if lib.startswith(pa):
for a in pkg_aliases[pa]:
ec, pkg = _check_package(a, args, pc_paths)
if ec == 0:
break
if ec == 0:
if pkg:
if args.cflags:
cflags = pkg.get('cflags')
print cflags
log('cflags: %s' % (cflags))
if args.libs:
libs = pkg.get('libs')
print libs
log('libs: %s' % (libs))
return ec
try:
log('-' * 40)
log('pkg-config', lf = False)
for a in sys.argv[1:]:
log(' "%s"' % (a), lf = False)
log('')
ec = run(sys.argv)
log('ec = %d' % (ec))
except ImportError:
print >> sys.stderr, "incorrect package config installation"
sys.exit(1)
except error, e:
print >> sys.stderr, 'error: %s' % (e)
sys.exit(1)
sys.exit(ec)

View File

@ -58,7 +58,7 @@ optflags: none, convert, '-O2 -pipe'
optincludes: none, convert, ''
# Extra path a platform can override.
_extra_path: none, none, ''
_extra_path: none, none, '%{_sbdir}'
_ld_library_path: none, none, 'LD_LIBRARY_PATH'
# Paths

244
source-builder/pkg-config Executable file
View File

@ -0,0 +1,244 @@
#! /usr/bin/env python
#
# RTEMS Tools Project (http://www.rtems.org/)
# Copyright 2014 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.
#
import argparse
import os
import sys
base = os.path.dirname(sys.argv[0])
sys.path.insert(0, base + '/sb')
try:
import pkgconfig
except ImportError:
print >> sys.stderr, "Incorrect Source Builder installation"
sys.exit(1)
#
# Make trace true to get a file of what happens and what is being asked.
#
trace = True
trace_stdout = False
logfile = 'pkg-config.log'
out = None
#
# Write all the package source parsed to a single file.
#
trace_src = True
if trace_src:
src = open('pkg-src.txt', 'w')
def log(s, lf = True):
global trace, logfile, out
if trace:
if out is None:
if logfile:
out = open(logfile, 'a')
else:
out = sys.stdout
if lf:
if out != sys.stdout and trace_stdout:
print s
print >> out, s
else:
if out != sys.stdout and trace_stdout:
print s,
print >> out, s,
def _check_package(lib_check, args):
ec = 1
pkg = None
flags = { 'cflags': '',
'libs': '' }
libs = pkgconfig.package.splitter(lib_check)
for lib in libs:
log('pkg: %s' % (lib))
pkg = pkgconfig.package(lib[0], prefix = args.prefix, output = log, src = src)
if args.dump:
log(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:
flags['cflags'] += pkg.get('cflags')
flags['libs'] += pkg.get('libs', private = False)
break
if ec > 0:
break
return ec, pkg, flags
def run(argv):
class version_action(argparse.Action):
def __call__(self, parser, namespace, values, option_string = None):
parts = values[0].strip().split('.')
for p in parts:
if not p.isdigit():
raise error('invalid version value: %s' % (values))
setattr(namespace, self.dest, '.'.join(parts))
ec = 0
opts = argparse.ArgumentParser(prog = 'pkg-config', description = 'Package Configuration.')
opts.add_argument('libraries', metavar='lib', type = str, help = 'a library', nargs = '*')
opts.add_argument('--modversion', dest = 'modversion', action = 'store', default = None,
help = 'Requests that the version information of the libraries.')
opts.add_argument('--print-errors', dest = 'print_errors', action = 'store_true',
default = False,
help = 'Print any errors.')
opts.add_argument('--short-errors', dest = 'short_errors', action = 'store_true',
default = False,
help = 'Make error messages short.')
opts.add_argument('--silence-errors', dest = 'silence_errors', action = 'store_true',
default = False,
help = 'Do not print any errors.')
opts.add_argument('--errors-to-stdout', dest = 'errors_to_stdout', action = 'store_true',
default = False,
help = 'Print errors to stdout rather than stderr.')
opts.add_argument('--cflags', dest = 'cflags', action = 'store_true',
default = False,
help = 'This prints pre-processor and compile flags required to' \
' compile the package(s)')
opts.add_argument('--libs', dest = 'libs', action = 'store_true',
default = False,
help = 'This option is identical to "--cflags", only it prints the' \
' link flags.')
opts.add_argument('--libs-only-L', dest = 'libs_only_L', action = 'store_true',
default = False,
help = 'This prints the -L/-R part of "--libs".')
opts.add_argument('--libs-only-l', dest = 'libs_only_l', action = 'store_true',
default = False,
help = 'This prints the -l part of "--libs".')
opts.add_argument('--variable', dest = 'variable', action = 'store',
nargs = 1, default = None,
help = 'This returns the value of a variable.')
opts.add_argument('--define-variable', dest = 'define_variable', action = 'store',
nargs = 1, default = None,
help = 'This sets a global value for a variable')
opts.add_argument('--uninstalled', dest = 'uninstalled', action = 'store_true',
default = False,
help = 'Ignored')
opts.add_argument('--atleast-pkgconfig-version', dest = 'atleast_pkgconfig_version',
action = 'store', nargs = 1, default = None,
help = 'Check the version of package config. Always ok.')
opts.add_argument('--exists', dest = 'exists', action = 'store_true',
default = False,
help = 'Test if a library is present')
opts.add_argument('--atleast-version', dest = 'atleast_version',
action = version_action, nargs = 1, default = None,
help = 'The package is at least this version.')
opts.add_argument('--exact-version', dest = 'exact_version', action = version_action,
nargs = 1, default = None,
help = 'The package is the exact version.')
opts.add_argument('--max-version', dest = 'max_version', action = version_action,
nargs = 1, default = None,
help = 'The package is no later than this version.')
opts.add_argument('--msvc-syntax', dest = 'msvc_syntax', action = 'store_true',
default = False,
help = 'Ignored')
opts.add_argument('--dont-define-prefix', dest = 'dont_define_prefix', action = 'store_true',
default = False,
help = 'Ignored')
opts.add_argument('--prefix-variable', dest = 'prefix', action = 'store',
nargs = 1, default = pkgconfig.default_prefix(),
help = 'Define the prefix.')
opts.add_argument('--static', dest = 'static', action = 'store_true',
default = False,
help = 'Output libraries suitable for static linking')
opts.add_argument('--dump', dest = 'dump', action = 'store_true',
default = False,
help = 'Dump the package if one is found.')
args = opts.parse_args(argv[1:])
if (args.exists and (args.exact_version or args.max_version)) or \
(args.exact_version and (args.exists or args.max_version)) or \
(args.max_version and (args.exists or args.exact_version)):
raise error('only one of --exists, --exact-version, or --max-version')
exists = False
ec = 1
if args.atleast_pkgconfig_version:
ec = 0
else:
for lib in args.libraries:
ec, pkg, flags = _check_package(lib, args)
if ec == 0:
if args.cflags:
if len(flags['cflags']):
print flags['cflags']
log('cflags: %s' % (flags['cflags']))
else:
log('cflags: empty')
if args.libs:
if len(flags['libs']):
print flags['libs']
log('libs: %s' % (flags['libs']))
else:
log('libs: empty')
#pkgconfig.package.dump_loaded()
return ec
try:
log('-' * 40)
log('pkg-config', lf = False)
for a in sys.argv[1:]:
log(' "%s"' % (a), lf = False)
log('')
ec = run(sys.argv)
log('ec = %d' % (ec))
except ImportError:
print >> sys.stderr, "incorrect package config installation"
sys.exit(1)
except pkgconfig.error, e:
print >> sys.stderr, 'error: %s' % (e)
sys.exit(1)
sys.exit(ec)

View File

@ -36,6 +36,7 @@ try:
import log
import options
import path
import pkgconfig
except KeyboardInterrupt:
print 'user terminated'
sys.exit(1)
@ -367,6 +368,26 @@ class file:
raise error.general('shell macro failed: %s:%d: %s' % (s, exit_code, output))
return line
def _pkgconfig(self, test):
ts = test.split()
ok = False
pkg = pkgconfig.package(ts[0], output = log.output)
if len(ts) > 1 and len(ts) != 3:
self._error('malformed pkgconfig check')
else:
op = '>='
ver = '0'
if len(ts) == 3:
op = ts[1]
ver = self.macros.expand(ts[2])
try:
ok = pkg.check(op, ver)
except pkgconfig.error, pe:
self._error('pkgconfig: %s' % (pe))
except:
raise error.interal('pkgconfig failure')
return ok
def _expand(self, s):
expand_count = 0
expanded = True
@ -429,6 +450,13 @@ class file:
s = s.replace(m, '0')
expanded = True
mn = None
elif m.startswith('%{pkgconfig'):
if self._pkgconfig(m[11:-1].strip()):
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

490
source-builder/sb/pkgconfig.py Executable file
View File

@ -0,0 +1,490 @@
#! /usr/bin/env python
#
# RTEMS Tools Project (http://www.rtems.org/)
# Copyright 2014 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.
#
import argparse
import os
import os.path
import re
import shlex
import sys
def default_prefix():
paths = ['/usr', '/usr/local']
if 'PKG_CONFIG_PATH' in os.environ:
paths += os.environ['PKG_CONFIG_PATH'].split(':')
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 is_version(v):
for n in v.split('.'):
if not n.isdigit():
return False
return True
@staticmethod
def splitter(pkg_list):
pkgs = []
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, output = None, src = None):
self._clean()
self.name_ = name
self.output = output
self.src = src
self.prefix = None
self.paths = []
if prefix is None:
prefix = default_prefix()
if prefix:
if type(prefix) is str:
self.prefix = prefix.split(os.pathsep)
elif type(prefix) is list:
self.prefix = prefix
else:
raise error('invalid type of prefix')
for p in self.prefix:
for d in package.config_prefixes:
prefix = os.path.join(p, d, 'pkgconfig')
if os.path.exists(prefix):
self.paths += [prefix]
self._log('paths: %s' % (', '.join(self.paths)))
if 'PKG_CONFIG_SYSROOT_DIR' in os.environ:
self.defines['sysroot'] = os.environ['PKG_CONFIG_SYSROOT_DIR']
self._log('sysroot: %s' % (self.defines['sysroot']))
if 'PKG_CONFIG_BUILD_TOP_DIR' in os.environ:
self.defines['top_builddir'] = os.environ['PKG_CONFIG_BUILD_TOP_DIR']
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 = []
def _log(self, s):
if self.output:
self.output(s)
def _find_package(self, name):
if len(self.paths):
for path in self.paths:
pc = os.path.join(path, '%s.pc' % (name))
if os.path.isfile(pc):
return pc;
return None
def _find_libraries(self, name):
libraries = []
for prefix in self.prefix:
prefix = os.path.join(prefix, 'lib')
if os.path.exists(prefix):
for l in os.listdir(prefix):
if l.startswith(name + '.'):
libraries += [os.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) and not s.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':
s = s[:offset + dash + 2] + top_builddir + s[offset + dash + 2:]
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 = os.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:
raise error('package already loaded: %s' % (name))
if self.name_:
self._clean()
self.name_ = name
file = self._find_package(name)
self._log('load: %s (%s)' % (name, file))
if file:
if self.src:
self.src.writelines('==%s%s' % ('=' * 80, os.linesep))
self.src.writelines(' %s %s%s' % (file, '=' * (80 - len(file)), os.linesep))
self.src.writelines('==%s%s' % ('=' * 80, os.linesep))
f = open(file)
tm = False
for l in f.readlines():
if self.src:
self.src.writelines(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():
package.loaded[name] = self
def get(self, label, private = True):
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