source: rtems-source-builder/source-builder/sb/pkgconfig.py @ c434884

4.11
Last change on this file since c434884 was c434884, checked in by Chris Johns <chrisj@…>, on Sep 21, 2015 at 11:01:28 PM

Fix pkgconfig for MSYS to allow QEMU to build.

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