1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2013 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import json
8import os
9import subprocess
10import sys
11import re
12from optparse import OptionParser
13
14# This script runs pkg-config, optionally filtering out some results, and
15# returns the result.
16#
17# The result will be [ <includes>, <cflags>, <libs>, <lib_dirs>, <ldflags> ]
18# where each member is itself a list of strings.
19#
20# You can filter out matches using "-v <regexp>" where all results from
21# pkgconfig matching the given regular expression will be ignored. You can
22# specify more than one regular expression my specifying "-v" more than once.
23#
24# You can specify a sysroot using "-s <sysroot>" where sysroot is the absolute
25# system path to the sysroot used for compiling. This script will attempt to
26# generate correct paths for the sysroot.
27#
28# When using a sysroot, you must also specify the architecture via
29# "-a <arch>" where arch is either "x86" or "x64".
30# cross systemroots place pkgconfig files at <systemroot>/usr/share/pkgconfig
31# and one of <systemroot>/usr/lib/pkgconfig or <systemroot>/usr/lib64/pkgconfig
32# depending on whether the systemroot is for a 32 or 64 bit architecture. They
33# specify the 'lib' or 'lib64' of the pkgconfig path by defining the
34# 'system_libdir' variable in the args.gn file. pkg_config.gni communicates
35# this variable to this script with the "--system_libdir <system_libdir>" flag.
36# If no flag is provided, then pkgconfig files are assumed to come from
37# <systemroot>/usr/lib/pkgconfig.
38#
39# Additionally, you can specify the option --atleast-version. This will skip
40# the normal outputting of a dictionary and instead print true or false,
41# depending on the return value of pkg-config for the given package.
42
43
44def set_config_path(options: OptionParser) -> str:
45    """Set the PKG_CONFIG_LIBDIR environment variable.
46
47    This takes into account any sysroot and architecture specification from the
48    options on the given command line.
49    """
50
51    sysroot = options.sysroot
52    assert sysroot
53
54    # Compute the library path name based on the architecture.
55    arch = options.arch
56    if sysroot and not arch:
57        print("You must specify an architecture via -a if using a sysroot.")
58        sys.exit(1)
59
60    libdir = os.path.join(sysroot, 'usr', options.system_libdir, 'pkgconfig')
61    libdir += ':' + os.path.join(sysroot, 'usr', 'share', 'pkgconfig')
62    os.environ['PKG_CONFIG_LIBDIR'] = libdir
63    return libdir
64
65
66def get_pkg_config_prefix_to_strip(options: OptionParser, args) -> str:
67    """Returns the prefix from pkg-config where packages are installed.
68
69    This returned prefix is the one that should be stripped from the beginning of
70    directory names to take into account sysroots.
71    """
72    # Some sysroots, like the Chromium OS ones, may generate paths that are not
73    # relative to the sysroot. For example,
74    # /path/to/chroot/build/x86-generic/usr/lib/pkgconfig/pkg.pc may have all
75    # paths relative to /path/to/chroot (i.e. prefix=/build/x86-generic/usr)
76    # instead of relative to /path/to/chroot/build/x86-generic (i.e prefix=/usr).
77    # To support this correctly, it's necessary to extract the prefix to strip
78    # from pkg-config's |prefix| variable.
79    prefix = subprocess.check_output([options.pkg_config,
80                                     "--variable=prefix"] + args,
81                                     env=os.environ)
82    if prefix[-4] == '/usr':
83        return prefix[4:]
84    return prefix
85
86
87def matches_any_regexp(flag: bool, list_of_regexps: list) -> bool:
88    """Returns true if the first argument matches any regular expression in the
89    given list.
90    """
91    for regexp in list_of_regexps:
92        if regexp.search(flag) is not None:
93            return True
94    return False
95
96
97def rewrite_path(path: str, strip_prefix: str, sysroot: str) -> str:
98    """Rewrites a path by stripping the prefix and prepending the sysroot."""
99    if os.path.isabs(path) and not path.startswith(sysroot):
100        if path.startswith(strip_prefix):
101            path = path[len(strip_prefix):]
102        path = path.lstrip('/')
103        return os.path.join(sysroot, path)
104    else:
105        return path
106
107
108def main() -> int:
109    # If this is run on non-Linux platforms, just return nothing and indicate
110    # success. This allows us to "kind of emulate" a Linux build from other
111    # platforms.
112    if "linux" not in sys.platform:
113        print("[[],[],[],[],[]]")
114        return 0
115
116    parser = OptionParser()
117    parser.add_option('-d', '--debug', action='store_true')
118    parser.add_option('-p', action='store', dest='pkg_config', type='string',
119                                        default='pkg-config')
120    parser.add_option('-v', action='append', dest='strip_out', type='string')
121    parser.add_option('-s', action='store', dest='sysroot', type='string')
122    parser.add_option('-a', action='store', dest='arch', type='string')
123    parser.add_option('--system_libdir', action='store', dest='system_libdir',
124                                        type='string', default='lib')
125    parser.add_option('--atleast-version', action='store',
126                                        dest='atleast_version', type='string')
127    parser.add_option('--libdir', action='store_true', dest='libdir')
128    parser.add_option('--dridriverdir', action='store_true', dest='dridriverdir')
129    parser.add_option('--version-as-components', action='store_true',
130                                        dest='version_as_components')
131    (options, args) = parser.parse_args()
132
133    # Make a list of regular expressions to strip out.
134    strip_out = []
135    if options.strip_out is not None:
136        for regexp in options.strip_out:
137            strip_out.append(re.compile(regexp))
138
139    if options.sysroot:
140        libdir = set_config_path(options)
141        if options.debug:
142            sys.stderr.write('PKG_CONFIG_LIBDIR=%s\n' % libdir)
143        prefix = get_pkg_config_prefix_to_strip(options, args)
144    else:
145        prefix = ''
146
147    if options.atleast_version:
148        # When asking for the return value, just run pkg-config and print the
149        # return value, no need to do other work.
150        if not subprocess.call([options.pkg_config,
151                               "--atleast-version=" + options.atleast_version] +
152                               args):
153            print("true")
154        else:
155            print("false")
156        return 0
157
158    if options.version_as_components:
159        cmd = [options.pkg_config, "--modversion"] + args
160        try:
161            version_string = subprocess.check_output(cmd)
162        except subprocess.CalledProcessError as e:
163            sys.stderr.write('Error from pkg-config.\n')
164            return 1
165        print(json.dumps(list(map(int, version_string.strip().split(".")))))
166        return 0
167
168    if options.libdir:
169        cmd = [options.pkg_config, "--variable=libdir"] + args
170        if options.debug:
171            sys.stderr.write('Running: %s\n' % cmd)
172        try:
173            libdir = subprocess.check_output(cmd)
174        except subprocess.CalledProcessError as e:
175            print("Error from pkg-config.")
176            return 1
177        sys.stdout.write(libdir.strip())
178        return 0
179
180    if options.dridriverdir:
181        cmd = [options.pkg_config, "--variable=dridriverdir"] + args
182        if options.debug:
183            sys.stderr.write('Running: %s\n' % cmd)
184        try:
185            dridriverdir = subprocess.check_output(cmd)
186        except subprocess.CalledProcessError as e:
187            print("Error from pkg-config.")
188            return 1
189        sys.stdout.write(dridriverdir.strip())
190        return
191
192    cmd = [options.pkg_config, "--cflags", "--libs"] + args
193    if options.debug:
194        sys.stderr.write('Running: %s\n' % ' '.join(cmd))
195
196    try:
197        flag_string = subprocess.check_output(cmd)
198    except subprocess.CalledProcessError as e:
199        sys.stderr.write('Could not run pkg-config.\n')
200        return 1
201
202    # For now just split on spaces to get the args out. This will break if
203    # pkgconfig returns quoted things with spaces in them, but that doesn't seem
204    # to happen in practice.
205    all_flags = flag_string.strip().split(' ')
206
207    sysroot = options.sysroot
208    if not sysroot:
209        sysroot = ''
210
211    includes = []
212    cflags = []
213    libs = []
214    lib_dirs = []
215
216    for flag in all_flags[:]:
217        if len(flag) == 0 or matches_any_regexp(flag, strip_out):
218            continue
219
220        if flag[:2] == '-l':
221            libs.append(rewrite_path(flag[2:], prefix, sysroot))
222        elif flag[:2] == '-L':
223            lib_dirs.append(rewrite_path(flag[2:], prefix, sysroot))
224        elif flag[:2] == '-I':
225            includes.append(rewrite_path(flag[2:], prefix, sysroot))
226        elif flag[:3] == '-Wl':
227            # Don't allow libraries to control ld flags. These should be specified
228            # only in build files.
229            pass
230        elif flag == '-pthread':
231            # Many libs specify "-pthread" which we don't need since we always
232            # include this anyway. Removing it here prevents a bunch of duplicate
233            # inclusions on the command line.
234            pass
235        else:
236            cflags.append(flag)
237
238    # Output a GN array, the first one is the cflags, the second are the libs.
239    # The JSON formatter prints GN compatible lists when everything is a list of
240    # strings.
241    print(json.dumps([includes, cflags, libs, lib_dirs]))
242    return 0
243
244
245if __name__ == '__main__':
246    sys.exit(main())
247