source: rtems-source-builder/source-builder/sb/pkgconfig.py @ 3a972f6

4.104.95
Last change on this file since 3a972f6 was 3a972f6, checked in by Chris Johns <chrisj@…>, on 03/07/16 at 00:56:02

sb: Update code base to support Python3 and Python2.

Fix Windows support to allow MSYS2 Python to be used.

Updates #2619.

  • Property mode set to 100755
File size: 19.6 KB
Line 
1#! /usr/bin/env python
2#
3# RTEMS Tools Project (http://www.rtems.org/)
4# Copyright 2014-2016 Chris Johns (chrisj@rtems.org)
5# All rights reserved.
6#
7# This file is part of the RTEMS Tools package in 'rtems-tools'.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions are met:
11#
12# 1. Redistributions of source code must retain the above copyright notice,
13# this list of conditions and the following disclaimer.
14#
15# 2. Redistributions in binary form must reproduce the above copyright notice,
16# this list of conditions and the following disclaimer in the documentation
17# and/or other materials provided with the distribution.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29# POSSIBILITY OF SUCH DAMAGE.
30#
31
32#
33# Pkg-config in python. It attempts to provide a few simple features
34# provided by the full pkg-config so packages can configure and build.
35#
36
37from __future__ import print_function
38
39import copy
40import os
41import os.path
42import re
43import shlex
44import sys
45
46import path
47
48def default_prefix(common = True):
49    paths = []
50    #
51    # We have two paths to work around an issue in MSYS2 and the
52    # conversion of Windows paths to shell paths.
53    #
54    if 'PKG_CONFIG_DEFAULT_PATH' in os.environ:
55        for p in os.environ['PKG_CONFIG_DEFAULT_PATH'].split(os.pathsep):
56            paths += [path.shell(p)]
57    if 'PKG_CONFIG_PATH' in os.environ:
58        for p in os.environ['PKG_CONFIG_PATH'].split(os.pathsep):
59            paths += [path.shell(p)]
60    if common:
61        defaults = ['/usr',
62                    '/usr/share',
63                    '/lib',
64                    '/lib64',
65                    '/usr/lib',
66                    '/usr/lib64',
67                    '/usr/local']
68        for d in defaults:
69            for cp in package.config_prefixes:
70                prefix = path.join(d, cp, 'pkgconfig')
71                if path.exists(prefix):
72                    paths += [prefix]
73    return paths
74
75class error(Exception):
76    def __init__(self, msg):
77        self.msg = msg
78
79    def __str__(self):
80        return self.msg
81
82class package(object):
83
84    node_types = ['requires', 'requires.private']
85    node_type_labels = { 'requires': 'r',
86                         'requires.private': 'rp',
87                         'failed': 'F' }
88    version_ops = ['=', '<', '>', '<=', '>=', '!=']
89    config_prefixes = ['lib', 'libdata']
90    get_recursion = ['cflags', 'libs']
91    no_dup_flags = ['-I', '-l', '-L']
92    dual_opts = ['-D', '-U', '-I', '-l', '-L']
93    lib_list_splitter = re.compile('[\s,]+')
94    loaded = {}
95
96    @staticmethod
97    def _copy(src, dst):
98        dst.name_ = src.name_
99        dst.file_ = src.file_
100        dst.defines = copy.copy(src.defines)
101        dst.fields = copy.copy(src.fields)
102        dst.nodes = copy.copy(src.nodes)
103
104    @staticmethod
105    def is_version(v):
106        for n in v.split('.'):
107            if not n.isdigit():
108                return False
109        return True
110
111    @staticmethod
112    def splitter(pkg_list):
113        pkgs = []
114        if type(pkg_list) == list:
115            pls = []
116            for p in pkg_list:
117                pls += package.lib_list_splitter.split(p)
118        else:
119            pls = package.lib_list_splitter.split(pkg_list)
120        i = 0
121        while i < len(pls):
122            pkg = [pls[i]]
123            i += 1
124            if i < len(pls):
125                op = None
126                if package.is_version(pls[i]):
127                    op = '>='
128                    ver = pls[i]
129                    i += 1
130                elif pls[i] in package.version_ops:
131                    op = pls[i]
132                    i += 1
133                    if i < len(pls):
134                        ver = pls[i]
135                        i += 1
136                else:
137                    op = '>='
138                    ver = '0'
139                pkg += [op, ver]
140            else:
141                pkg += ['>=', '0']
142            pkgs += [pkg]
143        return pkgs
144
145    @staticmethod
146    def check_versions(lhs, op, rhs):
147        if op not in package.version_ops:
148            raise error('bad operator: %s' % (op))
149        if not lhs or not rhs:
150            return False
151        slhs = lhs.split('.')
152        srhs = rhs.split('.')
153        ok = True
154        i = 0
155        while i < len(srhs):
156            try:
157                l = int(slhs[i])
158                r = int(srhs[i])
159            except:
160                return False
161            if op == '=':
162                if l != r:
163                    ok = False
164                    break
165            elif op == '<':
166                if l < r:
167                    break
168                if l >= r:
169                    ok = False
170                    break
171            elif op == '>':
172                if l > r:
173                    break
174                if l <= r:
175                    ok = False
176                    break
177            elif op == '<=':
178                if l < r:
179                    break
180                if l > r:
181                    ok = False
182                    break
183            elif op == '>=':
184                if l > r:
185                    break
186                if l < r:
187                    ok = False
188                    break
189            elif op == '!=':
190                if l != r:
191                    ok = True
192                    break
193                if l == r:
194                    ok = False
195            i += 1
196        return ok
197
198    @staticmethod
199    def dump_loaded():
200        for n in sorted(package.loaded):
201            print(package.loaded[n]._str())
202
203    def __init__(self, name = None, prefix = None,
204                 libs_scan = False, output = None, src = None):
205        self._clean()
206        self.name_ = name
207        self.libs_scan = libs_scan
208        self.output = output
209        self.src = src
210        self.prefix = None
211        self.paths = []
212        if prefix is None:
213            prefix = default_prefix()
214        if prefix:
215            self._log('prefix: %s' % (prefix))
216            if type(prefix) is str:
217                for p in prefix.split(os.pathsep):
218                    self.prefix += [path.shell(p)]
219            elif type(prefix) is list:
220                self.prefix = prefix
221            else:
222                raise error('invalid type of prefix: %s' % (type(prefix)))
223            for p in self.prefix:
224                if path.exists(p):
225                    self.paths += [p]
226            self._log('paths: %s' % (', '.join(self.paths)))
227        if 'sysroot' in self.defines:
228            self._log('sysroot: %s' % (self.defines['sysroot']))
229        if 'top_builddir' in self.defines:
230            self._log('top_builddir: %s' % (self.defines['top_builddir']))
231        if self.name_:
232            self.load(self.name_)
233
234    def __str__(self):
235        s = self._str()
236        for nt in package.node_types:
237            for n in sorted(self.nodes[nt]):
238                s += ' ' + \
239                     ' '.join(['%s%s' % (s, os.linesep) \
240                               for s in str(self.nodes[nt][n]).split(os.linesep)])
241        return s[:-1]
242
243    def _str(self):
244        if self.file_ or len(self.libraries):
245            e = 'E'
246        else:
247            e = '-'
248        s = '> %s %s (%s)%s' % (self.name_, e, self.file_, os.linesep)
249        for d in sorted(self.defines):
250            s += 'd: %s: %s%s' % (d, self.defines[d], os.linesep)
251        for f in sorted(self.fields):
252            s += 'f: %s: %s%s' % (f, self.fields[f], os.linesep)
253        for l in sorted(self.libraries):
254            s += 'l: %s%s' % (l, os.linesep)
255        for nt in package.node_types + ['failed']:
256            s += '%s: ' % (package.node_type_labels[nt])
257            if len(self.nodes[nt]):
258                txt = []
259                for n in sorted(self.nodes[nt]):
260                    if self.nodes[nt][n].exists():
261                        e = ''
262                    else:
263                        e = '*'
264                    txt += ['%s%s' % (n, e)]
265                s += '%s%s' % (', '.join(txt), os.linesep)
266            else:
267                s += 'none' + os.linesep
268        return s #[:-1]
269
270    def _clean(self):
271        self.name_ = None
272        self.file_ = None
273        self.defines = {}
274        self.fields = {}
275        self.nodes = { 'failed': {} }
276        for nt in package.node_types:
277            self.nodes[nt] = {}
278        self.libraries = []
279        if 'PKG_CONFIG_SYSROOT_DIR' in os.environ:
280            self.defines['sysroot'] = os.environ['PKG_CONFIG_SYSROOT_DIR']
281        if 'PKG_CONFIG_BUILD_TOP_DIR' in os.environ:
282            self.defines['top_builddir'] = os.environ['PKG_CONFIG_BUILD_TOP_DIR']
283
284    def _log(self, s):
285        if self.output:
286            self.output(s)
287
288    def _find_package(self, name):
289        if len(self.paths):
290            for p in self.paths:
291                pc = path.join(p, '%s.pc' % (name))
292                if path.isfile(pc):
293                    return pc;
294        return None
295
296    def _find_libraries(self, name):
297        libraries = []
298        if self.libs_scan:
299            for prefix in self.prefix:
300                prefix = path.join(prefix, 'lib')
301                if path.exists(prefix):
302                    for l in os.listdir(path.host(prefix)):
303                        if l.startswith(name + '.'):
304                            libraries += [path.join(prefix, l)]
305                            break
306        return libraries
307
308    def _filter_sysroot(self, s):
309        if 'sysroot' in self.defines:
310            sysroot = self.defines['sysroot']
311            offset = 0
312            while True:
313                dash = s[offset:].find('-')
314                if dash < 0:
315                    break
316                if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
317                    s = s[:offset + dash + 2] + sysroot + s[offset + dash + 2:]
318                offset += dash + 1
319        return s
320
321    def _filter_top_builddir(self, s):
322        if 'top_builddir' in self.defines:
323            top_builddir = self.defines['top_builddir']
324            if self.file_.startswith(top_builddir):
325                offset = 0
326                while True:
327                    dash = s[offset:].find('-')
328                    if dash < 0:
329                        break
330                    if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
331                        p = s[offset + dash + 2:]
332                        if not p.startswith(top_builddir):
333                            s = s[:offset + dash + 2] + top_builddir + p
334                    offset += dash + 1
335        return s
336
337    def _filter_duplicates(self, s):
338        clean = ''
339        present = {}
340        ss = shlex.split(s)
341        i = 0
342        while i < len(ss):
343            added = False
344            for do in package.dual_opts:
345                if ss[i].startswith(do):
346                    if ss[i] == do:
347                        i += 1
348                        if i == len(ss):
349                            clean += ' %s' % (do)
350                        else:
351                            key = '%s%s' % (do, ss[i])
352                            if key not in present:
353                                if ' ' in ss[i]:
354                                    clean += ' %s"%s"' % (do, ss[i])
355                                else:
356                                    clean += ' %s' % (key)
357                    else:
358                        key = ss[i]
359                        if key not in present:
360                            clean += ' %s' % (key)
361                    added = True
362                    present[key] = True
363                    break
364            if not added:
365                if ss[i] not in present:
366                    clean += ' %s' % (ss[i])
367                    present[ss[i]] = True
368            i += 1
369        return clean
370
371    def _filter(self, s):
372        s = self._filter_top_builddir(s)
373        s = self._filter_sysroot(s)
374        s = self._filter_duplicates(s)
375        return s.strip()
376
377    def _splitter(self, pkg_list):
378        pkgs = []
379        pls = pkg_list.split()
380        i = 0
381        while i < len(pls):
382            pkg = [pls[i]]
383            i += 1
384            if i < len(pls) and pls[i] in package.version_ops:
385                pkg += [pls[i]]
386                i += 1
387                if i < len(ls):
388                    pkg += [pls[i]]
389                    i += 1
390            pkgs += [pkg]
391        return pkgs
392
393    def name_from_file(self, file = None):
394        if file is None:
395            file = self.file_
396        if file is None:
397            return None
398        name = path.basename(file)
399        if name.endswith('.pc'):
400            name = name[:-3]
401        return name
402
403    def name(self):
404        return self.name_
405
406    def file(self):
407        return self.file_
408
409    def exists(self):
410        ok = False
411        if self.file_:
412            ok = True
413        if self.libraries:
414            ok = True
415        return ok
416
417    def load(self, name):
418        if name in package.loaded:
419            package._copy(package.loaded[name], self)
420            return
421        self._log('loading: %s' % (name))
422        if self.name_:
423            self._clean()
424        self.name_ = name
425        file = self._find_package(name)
426        if file:
427            self._log('load: %s (%s)' % (name, file))
428            if self.src:
429                self.src('==%s%s' % ('=' * 80, os.linesep))
430                self.src(' %s %s%s' % (file, '=' * (80 - len(file)), os.linesep))
431                self.src('==%s%s' % ('=' * 80, os.linesep))
432            f = open(path.host(file))
433            tm = False
434            for l in f.readlines():
435                if self.src:
436                    self.src(l)
437                l = l[:-1]
438                hash = l.find('#')
439                if hash >= 0:
440                    l = l[:hash]
441                if len(l):
442                    d = 0
443                    define = False
444                    eq = l.find('=')
445                    dd = l.find(':')
446                    if eq > 0 and dd > 0:
447                        if eq < dd:
448                            define = True
449                            d = eq
450                        else:
451                            define = False
452                            d = dd
453                    elif eq >= 0:
454                        define = True
455                        d = eq
456                    elif dd >= 0:
457                        define = False
458                        d = dd
459                    if d > 0:
460                        lhs = l[:d].lower()
461                        rhs = l[d + 1:]
462                        if tm:
463                            print(('define: ' + str(define) + ', lhs: ' + lhs + ', ' + rhs))
464                        if define:
465                            self.defines[lhs] = rhs
466                        else:
467                            self.fields[lhs] = rhs
468            self.file_ = file
469        else:
470            self.libraries = self._find_libraries(name)
471        for nt in package.node_types:
472            requires = self.get(nt, private = False)
473            if requires:
474                for r in package.splitter(requires):
475                    if r[0] not in self.nodes[nt]:
476                        if r[0] in package.loaded:
477                            pkg = package.loaded[r[0]]
478                        else:
479                            pkg = package(r[0], self.prefix, self.output)
480                        ver = pkg.get('version')
481                        self._log(' checking: %s (%s) %s %s' % (r[0], ver, r[1], r[2]))
482                        if ver and package.check_versions(ver, r[1], r[2]):
483                            self.nodes[nt][r[0]] = pkg
484                        else:
485                            self._log('failed: %s (%s %s %s)' % (r[0], ver, r[1], r[2]))
486                            self.nodes['failed'][r[0]] = pkg
487        if self.exists():
488            self._log('load: exists')
489            package.loaded[name] = self
490
491    def get(self, label, private = True):
492        self._log('get: %s (%s)' % (label, ','.join(self.fields)))
493        if label.lower() not in self.fields:
494            return None
495        s = ''
496        if self.file_:
497            mre = re.compile('\$\{[^\}]+\}')
498            s = self.fields[label.lower()]
499            expanded = True
500            tm = False
501            while expanded:
502                expanded = False
503                if tm:
504                    self._log('pc:get: "' + s + '"')
505                ms = mre.findall(s)
506                for m in ms:
507                    mn = m[2:-1]
508                    if mn.lower() in self.defines:
509                        s = s.replace(m, self.defines[mn.lower()])
510                        expanded = True
511            if label in package.get_recursion:
512                for nt in package.node_types:
513                    if 'private' not in nt or ('private' in nt and private):
514                        for n in self.nodes[nt]:
515                            r = self.nodes[nt][n].get(label, private = private)
516                            self._log('node: %s: %s' % (self.nodes[nt][n].name(), r))
517                            if r:
518                                s += ' ' + r
519        elif label == 'libs' and len(self.libraries):
520            s = '-l%s' % (self.name_[3:])
521        return self._filter(s)
522
523    def check(self, op, version):
524        self._log('checking: %s %s %s' % (self.name_, op, version))
525        ok = False
526        if self.file_:
527            pkgver = self.get('version')
528            if pkgver is None:
529                self._log('check: %s %s failed (no version)' % (op, version))
530                return False
531            ok = package.check_versions(pkgver, op, version)
532            if ok:
533                self._log('check: %s %s %s ok' % (pkgver, op, version))
534            else:
535                self._log('check: %s %s %s failed' % (pkgver, op, version))
536        else:
537            if len(self.libraries):
538                ok = True
539            else:
540                self._log('check: %s not found' % (self.name_))
541        return ok
542
543def check_package(libraries, args, output, src):
544    ec = 1
545    pkg = None
546    flags = { 'cflags': '',
547              'libs': '' }
548    output('libraries: %s' % (libraries))
549    libs = package.splitter(libraries)
550    for lib in libs:
551        output('pkg: %s' % (lib))
552        pkg = package(lib[0], prefix = args.prefix, output = output, src = src)
553        if args.dump:
554            output(pkg)
555        if pkg.exists():
556            if len(lib) == 1:
557                if args.exact_version:
558                    if pkg.check('=', args.exact_version):
559                        ec = 0
560                elif args.atleast_version:
561                    if pkg.check('>=', args.atleast_version):
562                        ec = 0
563                elif args.max_version:
564                    if pkg.check('<=', args.max_version):
565                        ec = 0
566                else:
567                    ec = 0
568            else:
569                if len(lib) != 3:
570                    raise error('invalid package check: %s' % (' '.join(lib)))
571                if pkg.check(lib[1], lib[2]):
572                    ec = 0
573            if ec == 0:
574                cflags = pkg.get('cflags')
575                if cflags:
576                    flags['cflags'] += cflags
577                libs = pkg.get('libs', private = False)
578                if libs:
579                    flags['libs'] += libs
580                break
581        if ec > 0:
582            break
583    return ec, pkg, flags
Note: See TracBrowser for help on using the repository browser.