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