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