source: rtems-source-builder/source-builder/sb/pkgconfig.py @ 9e49d20

5
Last change on this file since 9e49d20 was 9e49d20, checked in by Chris Johns <chrisj@…>, on Mar 3, 2020 at 10:56:01 PM

sb/pkgconfig: Fix python2 issue with caching changes

Closes #3893

  • Property mode set to 100755
File size: 19.8 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_prefixes = None
95    loaded = {}
96
97    @staticmethod
98    def _copy(src, dst):
99        dst.name_ = src.name_
100        dst.file_ = src.file_
101        dst.defines = copy.copy(src.defines)
102        dst.fields = copy.copy(src.fields)
103        dst.nodes = copy.copy(src.nodes)
104
105    @staticmethod
106    def is_version(v):
107        for n in v.split('.'):
108            if not n.isdigit():
109                return False
110        return True
111
112    @staticmethod
113    def splitter(pkg_list):
114        pkgs = []
115        if type(pkg_list) == list:
116            pls = []
117            for p in pkg_list:
118                pls += package.lib_list_splitter.split(p)
119        else:
120            pls = package.lib_list_splitter.split(pkg_list)
121        i = 0
122        while i < len(pls):
123            pkg = [pls[i]]
124            i += 1
125            if i < len(pls):
126                op = None
127                if package.is_version(pls[i]):
128                    op = '>='
129                    ver = pls[i]
130                    i += 1
131                elif pls[i] in package.version_ops:
132                    op = pls[i]
133                    i += 1
134                    if i < len(pls):
135                        ver = pls[i]
136                        i += 1
137                else:
138                    op = '>='
139                    ver = '0'
140                pkg += [op, ver]
141            else:
142                pkg += ['>=', '0']
143            pkgs += [pkg]
144        return pkgs
145
146    @staticmethod
147    def check_versions(lhs, op, rhs):
148        if op not in package.version_ops:
149            raise error('bad operator: %s' % (op))
150        if not lhs or not rhs:
151            return False
152        slhs = lhs.split('.')
153        srhs = rhs.split('.')
154        ok = True
155        i = 0
156        while i < len(srhs):
157            try:
158                l = int(slhs[i])
159                r = int(srhs[i])
160            except:
161                return False
162            if op == '=':
163                if l != r:
164                    ok = False
165                    break
166            elif op == '<':
167                if l < r:
168                    break
169                if l >= r:
170                    ok = False
171                    break
172            elif op == '>':
173                if l > r:
174                    break
175                if l <= r:
176                    ok = False
177                    break
178            elif op == '<=':
179                if l < r:
180                    break
181                if l > r:
182                    ok = False
183                    break
184            elif op == '>=':
185                if l > r:
186                    break
187                if l < r:
188                    ok = False
189                    break
190            elif op == '!=':
191                if l != r:
192                    ok = True
193                    break
194                if l == r:
195                    ok = False
196            i += 1
197        return ok
198
199    @staticmethod
200    def dump_loaded():
201        for n in sorted(package.loaded):
202            print(package.loaded[n]._str())
203
204    def __init__(self, name = None, prefix = None,
205                 libs_scan = False, output = None, src = None):
206        self._clean()
207        self.name_ = name
208        self.libs_scan = libs_scan
209        self.output = output
210        self.src = src
211        self.prefix = None
212        self.paths = []
213        if prefix is None:
214            prefix = default_prefix()
215        if prefix:
216            self._log('prefix: %s' % (prefix))
217            if type(prefix) is str or type(prefix) is unicode:
218                prefix = str(prefix)
219                self.prefix = []
220                for p in prefix.split(os.pathsep):
221                    self.prefix += [path.shell(p)]
222            elif type(prefix) is list:
223                self.prefix = prefix
224            else:
225                raise error('invalid type of prefix: %s' % (type(prefix)))
226            for p in self.prefix:
227                if path.exists(p):
228                    self.paths += [p]
229            self._log('paths: %s' % (', '.join(self.paths)))
230        if 'sysroot' in self.defines:
231            self._log('sysroot: %s' % (self.defines['sysroot']))
232        if 'top_builddir' in self.defines:
233            self._log('top_builddir: %s' % (self.defines['top_builddir']))
234        if self.name_:
235            self.load(self.name_)
236
237    def __str__(self):
238        s = self._str()
239        for nt in package.node_types:
240            for n in sorted(self.nodes[nt]):
241                s += ' ' + \
242                     ' '.join(['%s%s' % (s, os.linesep) \
243                               for s in str(self.nodes[nt][n]).split(os.linesep)])
244        return s[:-1]
245
246    def _str(self):
247        if self.file_ or len(self.libraries):
248            e = 'E'
249        else:
250            e = '-'
251        s = '> %s %s (%s)%s' % (self.name_, e, self.file_, os.linesep)
252        for d in sorted(self.defines):
253            s += 'd: %s: %s%s' % (d, self.defines[d], os.linesep)
254        for f in sorted(self.fields):
255            s += 'f: %s: %s%s' % (f, self.fields[f], os.linesep)
256        for l in sorted(self.libraries):
257            s += 'l: %s%s' % (l, os.linesep)
258        for nt in package.node_types + ['failed']:
259            s += '%s: ' % (package.node_type_labels[nt])
260            if len(self.nodes[nt]):
261                txt = []
262                for n in sorted(self.nodes[nt]):
263                    if self.nodes[nt][n].exists():
264                        e = ''
265                    else:
266                        e = '*'
267                    txt += ['%s%s' % (n, e)]
268                s += '%s%s' % (', '.join(txt), os.linesep)
269            else:
270                s += 'none' + os.linesep
271        return s #[:-1]
272
273    def _clean(self):
274        self.name_ = None
275        self.file_ = None
276        self.defines = {}
277        self.fields = {}
278        self.nodes = { 'failed': {} }
279        for nt in package.node_types:
280            self.nodes[nt] = {}
281        self.libraries = []
282        if 'PKG_CONFIG_SYSROOT_DIR' in os.environ:
283            self.defines['sysroot'] = os.environ['PKG_CONFIG_SYSROOT_DIR']
284        if 'PKG_CONFIG_BUILD_TOP_DIR' in os.environ:
285            self.defines['top_builddir'] = os.environ['PKG_CONFIG_BUILD_TOP_DIR']
286
287    def _log(self, s):
288        if self.output:
289            self.output(s)
290
291    def _find_package(self, name):
292        if len(self.paths):
293            for p in self.paths:
294                pc = path.join(p, '%s.pc' % (name))
295                if path.isfile(pc):
296                    return pc;
297        return None
298
299    def _find_libraries(self, name):
300        libraries = []
301        if self.libs_scan:
302            for prefix in self.prefix:
303                prefix = path.join(prefix, 'lib')
304                if path.exists(prefix):
305                    for l in os.listdir(path.host(prefix)):
306                        if l.startswith(name + '.'):
307                            libraries += [path.join(prefix, l)]
308                            break
309        return libraries
310
311    def _filter_sysroot(self, s):
312        if 'sysroot' in self.defines:
313            sysroot = self.defines['sysroot']
314            offset = 0
315            while True:
316                dash = s[offset:].find('-')
317                if dash < 0:
318                    break
319                if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
320                    s = s[:offset + dash + 2] + sysroot + s[offset + dash + 2:]
321                offset += dash + 1
322        return s
323
324    def _filter_top_builddir(self, s):
325        if 'top_builddir' in self.defines:
326            top_builddir = self.defines['top_builddir']
327            if self.file_.startswith(top_builddir):
328                offset = 0
329                while True:
330                    dash = s[offset:].find('-')
331                    if dash < 0:
332                        break
333                    if offset + dash + 2 < len(s) and s[offset + dash + 1] in 'LI':
334                        p = s[offset + dash + 2:]
335                        if not p.startswith(top_builddir):
336                            s = s[:offset + dash + 2] + top_builddir + p
337                    offset += dash + 1
338        return s
339
340    def _filter_duplicates(self, s):
341        clean = ''
342        present = {}
343        ss = shlex.split(s)
344        i = 0
345        while i < len(ss):
346            added = False
347            for do in package.dual_opts:
348                if ss[i].startswith(do):
349                    if ss[i] == do:
350                        i += 1
351                        if i == len(ss):
352                            clean += ' %s' % (do)
353                        else:
354                            key = '%s%s' % (do, ss[i])
355                            if key not in present:
356                                if ' ' in ss[i]:
357                                    clean += ' %s"%s"' % (do, ss[i])
358                                else:
359                                    clean += ' %s' % (key)
360                    else:
361                        key = ss[i]
362                        if key not in present:
363                            clean += ' %s' % (key)
364                    added = True
365                    present[key] = True
366                    break
367            if not added:
368                if ss[i] not in present:
369                    clean += ' %s' % (ss[i])
370                    present[ss[i]] = True
371            i += 1
372        return clean
373
374    def _filter(self, s):
375        s = self._filter_top_builddir(s)
376        s = self._filter_sysroot(s)
377        s = self._filter_duplicates(s)
378        return s.strip()
379
380    def _splitter(self, pkg_list):
381        pkgs = []
382        pls = pkg_list.split()
383        i = 0
384        while i < len(pls):
385            pkg = [pls[i]]
386            i += 1
387            if i < len(pls) and pls[i] in package.version_ops:
388                pkg += [pls[i]]
389                i += 1
390                if i < len(ls):
391                    pkg += [pls[i]]
392                    i += 1
393            pkgs += [pkg]
394        return pkgs
395
396    def name_from_file(self, file = None):
397        if file is None:
398            file = self.file_
399        if file is None:
400            return None
401        name = path.basename(file)
402        if name.endswith('.pc'):
403            name = name[:-3]
404        return name
405
406    def name(self):
407        return self.name_
408
409    def file(self):
410        return self.file_
411
412    def exists(self):
413        ok = False
414        if self.file_:
415            ok = True
416        if self.libraries:
417            ok = True
418        return ok
419
420    def load(self, name):
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            if file in package.loaded:
428                package._copy(package.loaded[file], self)
429                return
430            self._log('load: %s (%s)' % (name, file))
431            if self.src:
432                self.src('==%s%s' % ('=' * 80, os.linesep))
433                self.src(' %s %s%s' % (file, '=' * (80 - len(file)), os.linesep))
434                self.src('==%s%s' % ('=' * 80, os.linesep))
435            f = open(path.host(file))
436            tm = False
437            for l in f.readlines():
438                if self.src:
439                    self.src(l)
440                l = l[:-1]
441                hash = l.find('#')
442                if hash >= 0:
443                    l = l[:hash]
444                if len(l):
445                    d = 0
446                    define = False
447                    eq = l.find('=')
448                    dd = l.find(':')
449                    if eq > 0 and dd > 0:
450                        if eq < dd:
451                            define = True
452                            d = eq
453                        else:
454                            define = False
455                            d = dd
456                    elif eq >= 0:
457                        define = True
458                        d = eq
459                    elif dd >= 0:
460                        define = False
461                        d = dd
462                    if d > 0:
463                        lhs = l[:d].lower()
464                        rhs = l[d + 1:]
465                        if tm:
466                            print(('define: ' + str(define) + ', lhs: ' + lhs + ', ' + rhs))
467                        if define:
468                            self.defines[lhs] = rhs
469                        else:
470                            self.fields[lhs] = rhs
471            self.file_ = file
472        else:
473            self.libraries = self._find_libraries(name)
474        for nt in package.node_types:
475            requires = self.get(nt, private = False)
476            if requires:
477                for r in package.splitter(requires):
478                    if r[0] not in self.nodes[nt]:
479                        file = self._find_package(r[0])
480                        if file in package.loaded:
481                            pkg = package.loaded[file]
482                        else:
483                            pkg = package(r[0], self.prefix, self.output)
484                        ver = pkg.get('version')
485                        self._log(' checking: %s (%s) %s %s' % (r[0], ver, r[1], r[2]))
486                        if ver and package.check_versions(ver, r[1], r[2]):
487                            self.nodes[nt][r[0]] = pkg
488                        else:
489                            self._log('failed: %s (%s %s %s)' % (r[0], ver, r[1], r[2]))
490                            self.nodes['failed'][r[0]] = pkg
491        if self.exists():
492            self._log('load: exists and loaded; cache as loaded')
493            package.loaded[self.file_] = self
494
495    def get(self, label, private = True):
496        self._log('get: %s (%s)' % (label, ','.join(self.fields)))
497        if label.lower() not in self.fields:
498            return None
499        s = ''
500        if self.file_:
501            mre = re.compile('\$\{[^\}]+\}')
502            s = self.fields[label.lower()]
503            expanded = True
504            tm = False
505            while expanded:
506                expanded = False
507                if tm:
508                    self._log('pc:get: "' + s + '"')
509                ms = mre.findall(s)
510                for m in ms:
511                    mn = m[2:-1]
512                    if mn.lower() in self.defines:
513                        s = s.replace(m, self.defines[mn.lower()])
514                        expanded = True
515            if label in package.get_recursion:
516                for nt in package.node_types:
517                    if 'private' not in nt or ('private' in nt and private):
518                        for n in self.nodes[nt]:
519                            r = self.nodes[nt][n].get(label, private = private)
520                            self._log('node: %s: %s' % (self.nodes[nt][n].name(), r))
521                            if r:
522                                s += ' ' + r
523        elif label == 'libs' and len(self.libraries):
524            s = '-l%s' % (self.name_[3:])
525        return self._filter(s)
526
527    def check(self, op, version):
528        self._log('checking: %s %s %s' % (self.name_, op, version))
529        ok = False
530        if self.file_:
531            pkgver = self.get('version')
532            if pkgver is None:
533                self._log('check: %s %s failed (no version)' % (op, version))
534                return False
535            ok = package.check_versions(pkgver, op, version)
536            if ok:
537                self._log('check: %s %s %s ok' % (pkgver, op, version))
538            else:
539                self._log('check: %s %s %s failed' % (pkgver, op, version))
540        else:
541            if len(self.libraries):
542                ok = True
543            else:
544                self._log('check: %s not found' % (self.name_))
545        return ok
546
547def check_package(libraries, args, output, src):
548    ec = 1
549    pkg = None
550    flags = { 'cflags': '',
551              'libs': '' }
552    output('libraries: %s' % (libraries))
553    libs = package.splitter(libraries)
554    for lib in libs:
555        output('pkg: %s' % (lib))
556        pkg = package(lib[0], prefix = args.prefix, output = output, src = src)
557        if args.dump:
558            output(pkg)
559        if pkg.exists():
560            if len(lib) == 1:
561                if args.exact_version:
562                    if pkg.check('=', args.exact_version):
563                        ec = 0
564                elif args.atleast_version:
565                    if pkg.check('>=', args.atleast_version):
566                        ec = 0
567                elif args.max_version:
568                    if pkg.check('<=', args.max_version):
569                        ec = 0
570                else:
571                    ec = 0
572            else:
573                if len(lib) != 3:
574                    raise error('invalid package check: %s' % (' '.join(lib)))
575                if pkg.check(lib[1], lib[2]):
576                    ec = 0
577            if ec == 0:
578                cflags = pkg.get('cflags')
579                if cflags:
580                    flags['cflags'] += cflags
581                libs = pkg.get('libs', private = False)
582                if libs:
583                    flags['libs'] += libs
584                break
585        if ec > 0:
586            break
587    return ec, pkg, flags
Note: See TracBrowser for help on using the repository browser.