def _load_dependencies(self, dependencies, recurse): # Search for and load all packages listed in dependencies, # attempting to recursively load all their required packages # and checking for conflicts as well. # For d in deps: for dep in dependencies: # 0. Check if this package is already loaded if self._get_loaded_package(dep.name): continue # 1. Search for a package matching the spec pkg = self.searcher.search_for_package(dep, self.globals) # 2. Check for conflicts for conflict in pkg.properties['conflicts']: try: ErrorPrinter().debug_print('Searching for conflict %s', conflict.name) self.searcher.search_for_package(conflict) except PackageNotFoundError: # If the conflict was not found, move on to the next ErrorPrinter().debug_print('Conflict not found.') continue # Conflict was found - what to do? raise PackageConflictError(pkg.name, conflict) # 3. Sanity check this package pkg.sanity_check() # 4. Add this package to the dictionary now to avoid infinite recursion self._add_package(dep.name, pkg) # 5. Recursively load requireds if recurse: # requires.private includes requires self._load_dependencies(pkg.properties['requires.private'], recurse)
def search_for_pcfile(self, pkgname): """Search for one or more pkg-config files matching the given package name. If a matching pkg-config file cannot be found, an empty list will be returned. The dictionary of known packages is stored in _known_pkgs and is initialised by calling init_search_dirs(). """ ErrorPrinter().debug_print('Looking for files matching %s', (pkgname)) if Options().get_option('prefer_uninstalled'): if pkgname + '-uninstalled' in self._known_pkgs: # Prefer uninstalled version of a package ErrorPrinter().debug_print( 'Using uninstalled package %s', (self._known_pkgs[pkgname + '-uninstalled'])) return self._known_pkgs[pkgname + '-uninstalled'] elif Options().get_option('uninstalled_only'): ErrorPrinter().debug_print( 'Uninstalled only, no suitable package.') return [] if pkgname in self._known_pkgs: ErrorPrinter().debug_print('Using any package: %s', (self._known_pkgs[pkgname])) return self._known_pkgs[pkgname] else: ErrorPrinter().debug_print('No suitable package found') return []
def _init_search_dirs(self): # Append dirs in PKG_CONFIG_PATH if "config_path" in self.globals and self.globals["config_path"]: for d in self.globals["config_path"]: if not d or not isdir(d): continue self._append_packages(d) # Append dirs in PKG_CONFIG_LIBDIR if "config_libdir" in self.globals and self.globals["config_libdir"]: for d in self.globals["config_libdir"]: if not d or not isdir(d): continue self._append_packages(d) if sys.platform == 'win32': key_path = 'Software\\pkg-config\\PKG_CONFIG_PATH' for root in ((_winreg.HKEY_CURRENT_USER, 'HKEY_CURRENT_USER'), (_winreg.HKEY_LOCAL_MACHINE, 'HKEY_LOCAL_MACHINE')): try: key = _winreg.OpenKey(root[0], key_path) except WindowsError as e: ErrorPrinter().debug_print('Failed to add paths from \ {0}\\{1}: {2}'.format(root[1], key_path, e)) continue try: num_subkeys, num_vals, modified = _winreg.QueryInfoKey(key) for ii in range(num_vals): name, val, type = _winreg.EnumValue(key, ii) if type == _winreg.REG_SZ and isdir(val): self._append_packages(val) except WindowsError as e: ErrorPrinter().debug_print('Failed to add paths from \ {0}\\{1}: {2}'.format(root[1], key_path, e)) finally: _winreg.CloseKey(key) # Default path: If a hard-coded path has been set, use that (excluding # paths that don't exist) if "prefix" in self.globals: prefix = self.globals["prefix"] else: prefix = sys.prefix if pc_path: for d in pc_path.split(self._split_char()): if d and isdir(d): self._append_packages(d) # Default path: Else append prefix/lib/pkgconfig, prefix/share/pkgconfig else: if Options().get_option('is_64bit'): suffix = '64' else: suffix = '' dirs2check = (join(prefix, 'lib' + suffix), join(prefix, 'lib', str(thisArchTriple)), join(prefix, 'share'), join(prefix, "lib")) for d in dirs2check: d = join(d, "pkgconfig") if isdir(d): self._append_packages(d)
def _can_open_file(self, filename): try: result = open(filename, 'r') except IOError as e: ErrorPrinter().debug_print('Could not open {0}'.format(filename)) search_string = Options().get_option('search_string').split() if (not search_string and \ Options().get_option('command') == 'list-all') or \ True in [p.startswith(split(filename)[-1].split('.')[0]) \ for p in search_string]: ErrorPrinter().verbose_error( "Failed to open '{0}': {1}".format(filename, e.strerror)) return False return True
def read_pc_file(filename, global_variables): """Read and parse it into two dictionaries (variables and properties). Returns variables and properties. """ ErrorPrinter().set_variable('filename', filename) ErrorPrinter().debug_print('Parsing %(filename)') pcfile = open(filename, 'r') lines = pcfile.readlines() if not lines: raise EmptyPackageFileError(filename) raw_vars, vars, props = parse_pc_file_lines(lines, global_variables) pcfile.close() return raw_vars, vars, props
def __init__(self, version_string=None): if version_string is not None: self._parse_version(version_string) ErrorPrinter().debug_print('Parsed %s into %s', (version_string, self.comps)) else: self.raw_string = '0' self.comps = [0]
def _append_packages(self, d): ErrorPrinter().debug_print('Adding .pc files from %s to known packages', (d)) files = listdir(d) for filename in files: if filename.endswith('.pc'): # Test if the file can be opened (pkg-config glosses over, # e.g. links that are now dead, as if they were never there). full_path = join(d, filename) name = filename[:-3] if name in self._known_pkgs: if full_path not in self._known_pkgs[name]: self._known_pkgs[name].append(full_path) ErrorPrinter().debug_print('Package %s has a duplicate file: %s', (name, self._known_pkgs[name])) else: self._known_pkgs[name] = [full_path]
def search_for_package(self, dep, globals): """Search for a package matching the given dependency specification (name and version restriction). Raise PackageNotFoundError if no matching package is found. Returns a parsed package object. """ # Get a list of pc files matching the package name if isfile(dep.name) and splitext(dep.name)[1] == '.pc': # No need to search for a pc file ErrorPrinter().debug_print('Using provided pc file %s', (dep.name)) pcfiles = [dep.name] else: ErrorPrinter().debug_print('Searching for package matching %s', (dep)) pcfiles = self.search_for_pcfile(dep.name) ErrorPrinter().debug_print('Found .pc files: %s', (str(pcfiles))) if not pcfiles: raise PackageNotFoundError(str(dep)) # Filter the list by those files that meet the version specification pkgs = [] for pcfile in pcfiles: try: pkgs.append(Package(pcfile, globals)) except IOError as e: ErrorPrinter().verbose_error("Failed to open '{0}': \ {1}".format(pcfile, e.strerror)) continue except UndefinedVarError as e: raise UndefinedVarError(e.variable, pcfile) if not pkgs and pcfiles: # Raise an error indicating that all pc files we could try were # unopenable. This is necessary to match pkg-config's odd lack of # the standard "Package not found" error when a bad file is # encountred. raise NoOpenableFilesError(str(dep)) pkgs = [pkg for pkg in pkgs \ if dep.meets_requirement(pkg.properties['version'])] ErrorPrinter().debug_print('Filtered to %s', ([pkg.properties['name'] for pkg in pkgs])) if not pkgs: raise PackageNotFoundError(str(dep)) return pkgs[0]
def parse_line(line, raw_vars, vars, props, seen_props, globals): # Parse a single line from the file, adding its value to the props or vars # dictionary as appropriate. if not line: return raw_vars, vars, props, seen_props key, value, type = split_pc_file_line(line) # Check first if it's one of the known keys. if type == VARIABLE: # Perform substitution using variables found so far and global # variables, then store the result. if key in vars: raise MultiplyDefinedValueError(key) if key in globals: ErrorPrinter().debug_print('Adding %s -> %s to vars from globals', (key, value)) raw_vars[key] = value.strip () vars[key] = substitute (globals[key], vars, globals) else: ErrorPrinter().debug_print('Adding %s -> %s to vars', (key, value)) raw_vars[key] = value.strip () vars[key] = substitute (value.strip(), vars, globals) elif type == PROPERTY: if key in seen_props: raise MultiplyDefinedValueError(key) if key.lower() in empty_raw_props: if value is None: value = empty_raw_props[key.lower()] ErrorPrinter().debug_print('Adding %s -> %s to props', (key, value)) props[key.lower ()] = value seen_props.append(key) else: # As per the original pkg-config, don't raise errors on unknown # keys because they may be from future additions to the file # format. But log an error ErrorPrinter().debug_print('Unknown key/value in %(filename)s:\n%s: %s', (key, value)) else: # Probably a malformed line. Ignore it. pass return raw_vars, vars, props, seen_props
def _process_props(self, global_variables): # Processing of file data props = self.raw_props # May need to reset the prefix variable if sys.platform == 'win32' and \ not Options().get_option('dont_define_prefix'): # Use the location of the .pc file to guess a suitable value for # the prefix variable. Start by checking if the absolute .pc # location ends with '\lib\pkgconfig'. abs_loc = dirname(abspath(self.filename)) if Options().get_option('normalise_paths'): abs_loc = normpath(abs_loc) else: # If not normalising paths, then all paths should be in / # format for consistency abs_loc = abs_loc.replace('\\', '/') if abs_loc.endswith('\\lib\\pkgconfig'): self.variables[Options().get_option('prefix_variable')] = \ abs_loc.rstrip('\\lib\\pkgconfig') ErrorPrinter().debug_print('Replaced {0} with \ {1}'.format(Options().get_option('prefix_variable'), self.variables[Options().get_option('prefix_variable')])) # Perform substitutions for key in props: props[key] = substitute(props[key], self.variables, global_variables) # Parse the data self.properties = deepcopy(empty_processed_props) self.properties['name'] = props['name'] if props['description']: self.properties['description'] = props['description'] if props['version']: try: self.properties['version'] = Version(props['version']) except BadVersionFormatError as e: raise BadVersionFormatError(e.versionstring, props['name']) self.properties['requires'] = \ parse_package_spec_list(props['requires']) self.properties['requires.private'] = \ parse_package_spec_list(props['requires.private']) + \ self.properties['requires'] self.properties['conflicts'] = \ parse_package_spec_list(props['conflicts']) self._parse_cflags(props['cflags'], global_variables) self._parse_libs(props['libs'], global_variables) self._parse_libs(props['libs.private'], global_variables, dest='private.')
def split_pc_file_line(line): # Split a line into key and value, and determine if it is a property or a # variable. m = property_re.match(line) if m is not None: return m.group('key'), m.group('value'), PROPERTY m = variable_re.match(line) if m is not None: if m.group('value') is None: return m.group('var'), '', VARIABLE else: return m.group('var'), m.group('value'), VARIABLE # Gloss over malformed lines (that's what pkg-config does). ErrorPrinter().debug_print('Malformed line: {0}'.format(line)) return None, None, None
def known_packages_list(self): """Return a list of all packages found on the system, giving a name and a description (from the .pc file) for each, and also a list of any errors encountered. """ result = [] errors = [] for pkgname in self._known_pkgs: # Use the highest-priority version of the package try: pkg = Package(self._known_pkgs[pkgname][0]) except IOError as e: ErrorPrinter().verbose_error("Failed to open '{0}': \ {1}".format(self._known_pkgs[pkgname][0], e.strerror)) continue except UndefinedVarError as e: errors.append("Variable '{0}' not defined in '{1}'".format(e, self._known_pkgs[pkgname][0])) continue result.append((pkgname, pkg.properties['name'], pkg.properties['description'])) return result, errors
def _add_package(self, name, newpkg): ErrorPrinter().debug_print('Adding %s to list of packages as %s', (newpkg.filename, name)) self.packages.append((name, newpkg))
def main(argv): parser = setup_option_parser() try: options, args = parser.parse_args() except OptionError as e: print('OptionError: ' + str (e)) sys.exit(1) if options.realversion: print('{0} (Equivalent to {1}'.format(PYKG_CONFIG_VERSION, CORRESPONDING_VERSION)) sys.exit(0) global_variables = {} zip_name = 'python{0}{1}.zip'.format(sys.version_info[0], sys.version_info[1]) for path in sys.path: if path.endswith('64/' + zip_name): Options().set_option('is_64bit', True) break if getenv('PKG_CONFIG_SYSROOT_DIR'): global_variables['pc_sysrootdir'] = getenv('PKG_CONFIG_SYSROOT_DIR') if getenv('PKG_CONFIG_TOP_BUILD_DIR'): global_variables['pc_topbuilddir'] = getenv('PKG_CONFIG_TOP_BUILD_DIR') if getenv('PKG_CONFIG_DISABLE_UNINSTALLED'): Options().set_option('prefer_uninstalled', False) if getenv('PKG_CONFIG_ALLOW_SYSTEM_LIBS'): Options().set_option('forbidden_libdirs', []) else: if Options().get_option('is_64bit'): Options().set_option('forbidden_libdirs', ['/usr/lib64']) else: Options().set_option('forbidden_libdirs', ['/usr/lib']) if getenv('PKG_CONFIG_ALLOW_SYSTEM_CFLAGS'): Options().set_option('forbidden_cflags', []) else: forbidden = [] if sys.platform != 'win32': forbidden.append('/usr/include') if getenv('C_INCLUDE_PATH'): forbidden.append(getenv('C_INCLUDE_PATH')) if getenv('CPLUS_INCLUDE_PATH'): forbidden.append(getenv('CPLUS_INCLUDE_PATH')) Options().set_option('forbidden_cflags', forbidden) if options.full_compatibility: Options().set_option('full_compatibility', True) else: Options().set_option('full_compatibility', False) if options.atleast_pkgconfig_version: other_version = Version(options.atleast_pkgconfig_version) if other_version > get_pkg_config_version(): sys.exit(1) else: sys.exit(0) if options.static: Options().set_option('private_libs', True) if options.short_errors: Options().set_option('short_errors', True) if options.define_variable: for var_def in options.define_variable: sub_strings = var_def.split('=') if len(sub_strings) != 2: print('Bad argument format for define-variable: {1}'.format(var_def)) sys.exit(1) global_variables[sub_strings[0]] = sub_strings[1] if options.debug: Options().set_option('debug', True) if options.errors_to_stdout: Options().set_option('error_dest', sys.stdout) if sys.platform == 'win32': if options.dont_define_prefix: Options().set_option('dont_define_prefix', True) else: Options().set_option('dont_define_prefix', False) if options.prefix_variable: Options().set_option('prefix_variable', options.prefix_variable) if options.msvc_syntax: Options().set_option('use_msvc_syntax', True) else: Options().set_option('use_msvc_syntax', False) if options.normalise_paths: Options().set_option('normalise_paths', True) else: Options().set_option('normalise_paths', False) if options.modversion or options.libs or options.libs_only_l or \ options.libs_only_big_l or options.libs_only_other or \ options.cflags or options.cflags_only_big_i or \ options.cflags_only_other or options.list_all: if options.silence_errors: Options().set_option('print_errors', False) else: Options().set_option('print_errors', True) else: if options.print_errors: Options().set_option('print_errors', True) else: Options().set_option('print_errors', False) if options.list_all: Options().set_option('command', 'list-all') try: result = PkgCfgResult(global_variables) all_packages, errors = result.known_packages_list() except: ErrorPrinter().error('Exception searching for packages:') traceback.print_exc() sys.exit(1) if all_packages: max_width = max([(len(p), p) for p, n, d in all_packages]) for package, name, description in all_packages: print('{0:{3}}{1} - {2}'.format(package, name, description, max_width[0] + 1)) for e in errors: ErrorPrinter().error(e) sys.exit(0) try: Options().set_option('command', 'search') search = ' '.join(args) Options().set_option('search_string', search) result = PkgCfgResult(global_variables) result.find_packages(search, True) except NoOpenableFilesError as e: ErrorPrinter().verbose_error(str(e)) sys.exit(1) except PackageNotFoundError as e: if not Options().get_option('short_errors'): ErrorPrinter().verbose_error('''Package {0} was not found in the \ pkg-config search path. Perhaps you should add the directory containing `{0}.pc' to the PKG_CONFIG_PATH environment variable'''.format(e.pkgname)) ErrorPrinter().verbose_error(str(e)) sys.exit(1) except NoPackagesSpecifiedError: Options().get_option('error_dest').write( 'Must specify package names on the command line\n') sys.exit(1) except UndefinedVarError as e: ErrorPrinter().error("Variable '{0}' not defined in '{1}'".format( e.variable, e.pkgfile)) sys.exit(1) except: print('Exception searching for packages') traceback.print_exc() sys.exit(1) if options.dump_package: result.dump_package() sys.exit(0) if options.exists: # Even if the packages don't meet the requirements, they exist, which # is good enough for the exists option. sys.exit(0) if options.uninstalled: # Check if any packages loaded (both searched-for and dependencies) # are uninstalled. if result.have_uninstalled(): sys.exit(0) sys.exit(1) if options.modversion: for l in result.get_searched_pkgs_versions(): print(l) found_version = \ result.get_package_version(result.get_searched_pkg_list()[0].name) if options.atleast_version: if found_version < Version(options.atleast_version): sys.exit(1) sys.exit(0) if options.exact_version: if found_version != Version(options.exact_version): sys.exit(1) sys.exit(0) if options.max_version: if found_version > Version(options.max_version): sys.exit(1) sys.exit(0) if options.variable: value = result.get_variable_value(options.variable) if value == None: print('') else: print(value) if options.cflags_only_big_i: print(result.get_big_i_flags()) if options.cflags_only_other: print(result.get_other_i_flags()) if options.cflags: print(result.get_cflags()) if options.libs_only_l: print(result.get_l_flags()) if options.libs_only_big_l: print(result.get_big_l_flags()) if options.libs_only_other: print(result.get_other_l_flags()) if options.libs: print(result.get_all_lib_flags())