class Utilrb::PkgConfig
Access to information from pkg-config(1)
This class allows to enumerate the pkg-config packages available, and create a PkgConfig
object that allows to get access to the pkgconfig information.
Create a new pkgconfig object with
pkg = PkgConfig.new(name)
It raises PkgConfig::NotFound
if the package is not available.
Then, the classical include directory and library directory flags can be listed with
pkg.include_dirs pkg.library_dirs
Standard fields are available with
pkg.cflags pkg.cflags_only_I pkg.cflags_only_other pkg.libs pkg.libs_only_L pkg.libs_only_l pkg.libs_only_other pkg.static
Arbitrary variables defined in the .pc file can be accessed with
pkg.prefix pkg.libdir
Constants
- ACTIONS
- FIELD_NAME_RX
- FOUND_PATH_RX
- NONEXISTENT_PATH_RX
- PACKAGE_NAME_RX
- SHELL_VARS
- VAR_NAME_RX
Attributes
The module name
The module version as a string
The list of packages that are Require:'d by this package
@return [Array<PkgConfig>]
Information extracted from the file
The module version, as an array of integers
Public Class Methods
# File lib/utilrb/pkgconfig.rb, line 530 def self.available_package_names(pkg_config_path: self.pkg_config_path) result = [] each_pkgconfig_directory(pkg_config_path: pkg_config_path) do |dir| Dir.glob(File.join(dir, "*.pc")) do |path| result << File.basename(path, ".pc") end end result end
@deprecated {PkgConfig} does not cache the packages anymore, so no
need to call this method
# File lib/utilrb/pkgconfig.rb, line 60 def self.clear_cache end
Returns the system-wide search path that is embedded in pkg-config
# File lib/utilrb/pkgconfig.rb, line 568 def self.default_search_path if !@default_search_path output = `LANG=C PKG_CONFIG_PATH= pkg-config --debug 2>&1`.split("\n") @default_search_path = output.grep(FOUND_PATH_RX). map { |l| l.gsub(FOUND_PATH_RX, '\1\2') } end return @default_search_path end
Returns the system-wide standard suffixes that should be appended to new prefixes to find pkg-config files
# File lib/utilrb/pkgconfig.rb, line 580 def self.default_search_suffixes if !@default_search_suffixes output = `LANG=C PKG_CONFIG_PATH= pkg-config --debug 2>&1`.split("\n") found_paths = output.grep(FOUND_PATH_RX). map { |l| l.gsub(FOUND_PATH_RX, '\2') }. to_set not_found = output.grep(NONEXISTENT_PATH_RX). map { |l| l.gsub(NONEXISTENT_PATH_RX, '\1') }. to_set @default_search_suffixes = found_paths | not_found end return @default_search_suffixes end
Yields the package names of available packages. If regex
is given, lists only the names that match the regular expression.
# File lib/utilrb/pkgconfig.rb, line 547 def self.each_package(regex = nil, pkg_config_path: self.pkg_config_path) return enum_for(__method__) if !block_given? seen = Set.new each_pkgconfig_directory(pkg_config_path: pkg_config_path) do |dir| Dir.glob(File.join(dir, '*.pc')) do |file| pkg_name = File.basename(file, ".pc") next if seen.include?(pkg_name) next if regex && pkg_name !~ regex seen << pkg_name yield(pkg_name) end end end
# File lib/utilrb/pkgconfig.rb, line 510 def self.each_pkgconfig_directory(pkg_config_path: self.pkg_config_path, &block) return enum_for(__method__) if !block_given? if pkg_config_path pkg_config_path.split(':').each(&block) end default_search_path.each(&block) end
Returns true if there is a package with this name
# File lib/utilrb/pkgconfig.rb, line 519 def self.find_all_package_files(name, pkg_config_path: self.pkg_config_path) result = [] each_pkgconfig_directory(pkg_config_path: pkg_config_path) do |dir| path = File.join(dir, "#{name}.pc") if File.exist?(path) result << path end end result end
Returns the first package in candidates
that match the given version spec
# File lib/utilrb/pkgconfig.rb, line 102 def self.find_matching_version(candidates, version_spec) if version_spec version_spec =~ /([<>=]+)\s*([\d\.]+)/ op, requested_version = $1, $2 requested_op = if op == "=" then [0] elsif op == ">" then [1] elsif op == "<" then [-1] elsif op == "<=" then [-1, 0] elsif op == ">=" then [1, 0] end requested_version = requested_version.split('.').map { |v| Integer(v) } result = candidates.find do |pkg| requested_op.include?(pkg.version <=> requested_version) end if !result name = candidates.first.name raise NotFound.new(name), "no version of #{name} match #{version_spec}. Available versions are: #{candidates.map(&:raw_version).join(", ")}" end result else candidates.first end end
Returns the pkg-config object that matches the given name, and optionally a version string
# File lib/utilrb/pkgconfig.rb, line 65 def self.get(name, version_spec = nil, preset_variables = Hash.new, minimal: false, pkg_config_path: self.pkg_config_path) paths = find_all_package_files(name, pkg_config_path: pkg_config_path) if paths.empty? raise NotFound.new(name), "cannot find the pkg-config specification for #{name}" end candidates = paths.map do |p| PkgConfig.load_minimal(p, preset_variables) end # Now try to find a matching spec if match = find_matching_version(candidates, version_spec) match else raise NotFound, "found #{candidates.size} packages for #{name}, but none match the version specification #{version_spec}" end if !minimal match.load_fields end match end
Returns true if there is a package with this name
# File lib/utilrb/pkgconfig.rb, line 541 def self.has_package?(name, pkg_config_path: self.pkg_config_path) !find_all_package_files(name, pkg_config_path: pkg_config_path).empty? end
# File lib/utilrb/pkgconfig.rb, line 44 def self.load(path, preset_variables) pkg_name = File.basename(path, ".pc") pkg = Class.instance_method(:new).bind(PkgConfig).call(pkg_name) pkg.load(path, preset_variables) pkg end
# File lib/utilrb/pkgconfig.rb, line 51 def self.load_minimal(path, preset_variables) pkg_name = File.basename(path, ".pc") pkg = Class.instance_method(:new).bind(PkgConfig).call(pkg_name) pkg.load_minimal(path, preset_variables) pkg end
Finds the provided package and optional version and returns its PkgConfig
description
@param [String] version_spec version specification, of the form “op number”, where op is < <= >= > or == and the version number X, X.y, … @return [PkgConfig] the pkg-config description @raise [NotFound] if the package is not found
# File lib/utilrb/pkgconfig.rb, line 96 def self.new(name, version_spec = nil, options = Hash.new) get(name, version_spec, options) end
Create a PkgConfig
object for the package name
Raises PkgConfig::NotFound
if the module does not exist
# File lib/utilrb/pkgconfig.rb, line 170 def initialize(name) @name = name @fields = Hash.new @variables = Hash.new end
# File lib/utilrb/pkgconfig.rb, line 195 def self.parse_dependencies(string) if string =~ /,/ packages = string.split(',') else packages = [] words = string.split(' ') while !words.empty? w = words.shift if w =~ /[<>=]/ packages[-1] += " #{w} #{words.shift}" else packages << w end end end result = packages.map do |dep| dep = dep.strip if dep =~ /^(#{PACKAGE_NAME_RX})\s*([=<>]+.*)/ PkgConfig.get($1, $2.strip) else PkgConfig.get(dep) end end result end
# File lib/utilrb/pkgconfig.rb, line 506 def self.pkg_config_path ENV['PKG_CONFIG_PATH'] end
Public Instance Methods
# File lib/utilrb/pkgconfig.rb, line 443 def cflags raw_cflags.join(" ") end
# File lib/utilrb/pkgconfig.rb, line 447 def cflags_only_I raw_cflags_only_I.join(" ") end
# File lib/utilrb/pkgconfig.rb, line 451 def cflags_only_other raw_cflags_only_other.join(" ") end
# File lib/utilrb/pkgconfig.rb, line 292 def expand_field(name, field) if SHELL_VARS.include?(name) value = Shellwords.shellsplit(field) resolved = Array.new while !value.empty? value = value.flat_map do |v| expanded = perform_substitution(v, variables, name) if expanded == v resolved << v nil else Shellwords.shellsplit(expanded) end end.compact end resolved else perform_substitution(field, variables, name) end end
# File lib/utilrb/pkgconfig.rb, line 275 def expand_variables(raw_variables) raw_variables = raw_variables.dup variables = Hash.new # Resolve the variables while variables.size != raw_variables.size raw_variables.each do |name, value| value = perform_substitution(value, raw_variables, name) raw_variables[name] = value if value !~ /\$\{#{VAR_NAME_RX}\}/ variables[name] = value end end end variables end
Returns the list of include directories listed in the Cflags: section of the pkgconfig file
# File lib/utilrb/pkgconfig.rb, line 409 def include_dirs result = raw_cflags_only_I.map { |v| v[2..-1] } if result.any?(&:empty?) raise Invalid.new(name), "empty include directory (-I without argument) found in pkg-config package #{name}" end result end
Returns the list of library directories listed in the Libs: section of the pkgconfig file
# File lib/utilrb/pkgconfig.rb, line 419 def library_dirs result = raw_libs_only_L.map { |v| v[2..-1] } if result.any?(&:empty?) raise Invalid.new(name), "empty link directory (-L without argument) found in pkg-config package #{name}" end result end
# File lib/utilrb/pkgconfig.rb, line 482 def libs(static = false) raw_libs(static).join(" ") end
# File lib/utilrb/pkgconfig.rb, line 486 def libs_only_L(static = false) raw_libs_only_L(static).join(" ") end
# File lib/utilrb/pkgconfig.rb, line 490 def libs_only_l(static = false) raw_libs_only_l(static).join(" ") end
# File lib/utilrb/pkgconfig.rb, line 494 def libs_only_other(static = false) raw_libs_only_other(static).join(" ") end
Loads the information contained in path
# File lib/utilrb/pkgconfig.rb, line 383 def load(path, preset_variables = Hash.new) if !@raw_fields load_minimal(path, preset_variables) end load_fields end
# File lib/utilrb/pkgconfig.rb, line 336 def load_fields fields = Hash.new @raw_fields.each do |name, value| fields[name] = expand_field(name, value) end @fields = fields # Initialize the main flags @description = (fields['Description'] || '') # Get the requires/conflicts @requires = PkgConfig.parse_dependencies(fields['Requires'] || '') @requires_private = PkgConfig.parse_dependencies(fields['Requires.private'] || '') @conflicts = PkgConfig.parse_dependencies(fields['Conflicts'] || '') # And finally resolve the compilation flags @cflags = fields['Cflags'] || [] @requires.each do |pkg| @cflags.concat(pkg.raw_cflags) end @requires_private.each do |pkg| @cflags.concat(pkg.raw_cflags) end @cflags.uniq! @cflags.delete('-I/usr/include') @ldflags = Hash.new @ldflags[false] = fields['Libs'] || [] @ldflags[false].delete('-L/usr/lib') @ldflags[false].uniq! @ldflags[true] = @ldflags[false] + (fields['Libs.private'] || []) @ldflags[true].delete('-L/usr/lib') @ldflags[true].uniq! @ldflags_with_requires = { true => @ldflags[true].dup, false => @ldflags[false].dup } @requires.each do |pkg| @ldflags_with_requires[true].concat(pkg.raw_ldflags_with_requires[true]) @ldflags_with_requires[false].concat(pkg.raw_ldflags_with_requires[false]) end @requires_private.each do |pkg| @ldflags_with_requires[true].concat(pkg.raw_ldflags_with_requires[true]) end end
# File lib/utilrb/pkgconfig.rb, line 319 def load_minimal(path, preset_variables = Hash.new) raw_variables, raw_fields = parse(path) raw_variables = preset_variables.merge(raw_variables) @variables = expand_variables(raw_variables) if raw_fields['Version'] @raw_version = expand_field('Version', raw_fields['Version']) else @raw_version = '' end @version = raw_version.split('.').map { |v| Integer(v) if v =~ /^\d+$/ }.compact # To be used in the call to #load @raw_fields = raw_fields @path = path end
# File lib/utilrb/pkgconfig.rb, line 313 def load_variables(path, preset_variables = Hash.new) raw_variables, raw_fields = parse(path) raw_variables = preset_variables.merge(raw_variables) expand_variables(raw_variables) end
@api private
Normalize a field name to be lowercase with only the first letter capitalized
# File lib/utilrb/pkgconfig.rb, line 228 def normalize_field_name(name) name = name.downcase name[0, 1] = name[0, 1].upcase name end
Parse a pkg-config field and extracts the raw definition of variables and fields
@return [(Hash
,Hash)] the set of variables and the set of fields
# File lib/utilrb/pkgconfig.rb, line 238 def parse(path) running_line = nil file = File.readlines(path).map do |line| line = line.gsub(/\s*#.*$/, '') line = line.strip next if line.empty? value = line.gsub(/\\$/, '') if running_line running_line << " " << value end if line =~ /\\$/ running_line ||= value elsif running_line running_line = nil else value end end.compact raw_variables, raw_fields = Hash.new, Hash.new file.each do |line| case line when /^(#{VAR_NAME_RX})\s*=(.*)/ raw_variables[$1] = $2.strip when /^(#{FIELD_NAME_RX}):\s*(.*)/ field_name = normalize_field_name($1) raw_fields[field_name] = $2.strip else raise NotImplementedError, "#{path}: cannot parse pkg-config line #{line.inspect}" end end return raw_variables, raw_fields end
Helper method that expands ${word} in value
using the name to value map variables
current
is a string that describes what we are expanding. It is used to detect recursion in expansion of variables, and to give meaningful errors to the user
# File lib/utilrb/pkgconfig.rb, line 182 def perform_substitution(value, variables, current) value = value.gsub(/\$\{(\w+)\}/) do |rx| expand_name = $1 if expand_name == current raise "error in pkg-config file #{path}: #{current} contains a reference to itself" elsif !(expanded = variables[expand_name]) raise "error in pkg-config file #{path}: #{current} contains a reference to #{expand_name} but there is no such variable" end expanded end value end
# File lib/utilrb/pkgconfig.rb, line 403 def pkgconfig_variable(varname) `pkg-config --variable=#{varname}`.strip end
# File lib/utilrb/pkgconfig.rb, line 431 def raw_cflags @cflags end
# File lib/utilrb/pkgconfig.rb, line 435 def raw_cflags_only_I @cflags.grep(/^-I/) end
# File lib/utilrb/pkgconfig.rb, line 439 def raw_cflags_only_other @cflags.find_all { |s| s !~ /^-I/ } end
# File lib/utilrb/pkgconfig.rb, line 456 def raw_ldflags @ldflags end
# File lib/utilrb/pkgconfig.rb, line 460 def raw_ldflags_with_requires @ldflags_with_requires end
# File lib/utilrb/pkgconfig.rb, line 465 def raw_libs(static = false) @ldflags_with_requires[static] end
# File lib/utilrb/pkgconfig.rb, line 469 def raw_libs_only_L(static = false) @ldflags_with_requires[static].grep(/^-L/) end
# File lib/utilrb/pkgconfig.rb, line 473 def raw_libs_only_l(static = false) @ldflags_with_requires[static].grep(/^-l/) end
# File lib/utilrb/pkgconfig.rb, line 477 def raw_libs_only_other(static = false) @ldflags_with_requires[static].find_all { |s| s !~ /^-[lL]/ } end