# Copyright 2008-2010 Kouhei Sutou <kou@cozmixng.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
require "rbconfig"
require 'mkmf'
require 'shellwords'
require 'English'
require 'pathname'
class PackageConfig
SEPARATOR = File::PATH_SEPARATOR
class << self
@native_pkg_config = nil
def native_pkg_config
@native_pkg_config ||= guess_native_pkg_config
end
@custom_override_variables = nil
def custom_override_variables
@custom_override_variables ||= with_config("override-variables", "")
end
def clear_configure_args_cache
@native_pkg_config = nil
@custom_override_variables = nil
end
private
def guess_native_pkg_config
pkg_config = with_config("pkg-config", ENV["PKG_CONFIG"] || "pkg-config")
pkg_config = Pathname.new(pkg_config)
unless pkg_config.absolute?
found_pkg_config = search_pkg_config_from_path(pkg_config)
pkg_config = found_pkg_config if found_pkg_config
end
unless pkg_config.absolute?
found_pkg_config = search_pkg_config_by_dln_find_exe(pkg_config)
pkg_config = found_pkg_config if found_pkg_config
end
pkg_config
end
def search_pkg_config_from_path(pkg_config)
(ENV["PATH"] || "").split(SEPARATOR).each do |path|
try_pkg_config = Pathname(path) + pkg_config
return try_pkg_config if try_pkg_config.exist?
end
nil
end
def search_pkg_config_by_dln_find_exe(pkg_config)
begin
require "dl/import"
rescue LoadError
return nil
end
dln = Module.new
dln.module_eval do
if DL.const_defined?(:Importer)
extend DL::Importer
else
extend DL::Importable
end
begin
dlload RbConfig::CONFIG["LIBRUBY"]
rescue RuntimeError
return nil if $!.message == "unknown error"
return nil if /: image not found\z/ =~ $!.message
raise
rescue DL::DLError
return nil
end
extern "const char *dln_find_exe(const char *, const char *)"
end
path = dln.dln_find_exe(pkg_config.to_s, nil)
if path.size.zero?
nil
else
Pathname(path.to_s)
end
end
end
attr_reader :paths
attr_accessor :msvc_syntax
def initialize(name, options={})
@name = name
@options = options
path = @options[:path] || ENV["PKG_CONFIG_PATH"]
@paths = [path, guess_default_path].compact.join(SEPARATOR).split(SEPARATOR)
@paths.unshift(@options[:paths] || [])
@msvc_syntax = @options[:msvc_syntax]
@variables = @declarations = nil
override_variables = self.class.custom_override_variables
@override_variables = parse_override_variables(override_variables)
default_override_variables = @options[:override_variables] || {}
@override_variables = default_override_variables.merge(@override_variables)
end
def exist?
not pc.nil?
end
def requires
parse_requires(declaration("Requires"))
end
def requires_private
parse_requires(declaration("Requires.private"))
end
def cflags
path_flags, other_flags = collect_cflags
(path_flags + other_flags).join(" ")
end
def cflags_only_I
collect_cflags[0].join(" ")
end
def libs
path_flags, other_flags = collect_libs
(path_flags + other_flags).join(" ")
end
def libs_only_l
collect_libs[1].find_all do |arg|
if @msvc_syntax
/\.lib\z/ =~ arg
else
/\A-l/ =~ arg
end
end.join(" ")
end
def libs_only_L
collect_libs[0].find_all do |arg|
if @msvc_syntax
/\A\/libpath:/ =~ arg
else
/\A-L/ =~ arg
end
end.join(" ")
end
def version
declaration("Version")
end
def description
declaration("Description")
end
def variable(name)
parse_pc if @variables.nil?
expand_value(@override_variables[name] || @variables[name])
end
def declaration(name)
parse_pc if @declarations.nil?
expand_value(@declarations[name])
end
private
def pc
@paths.each do |path|
pc_name = File.join(path, "#{@name}.pc")
return pc_name if File.exist?(pc_name)
end
return nil
end
def collect_cflags
all_cflags = (requires_private + requires.reverse).collect do |package|
self.class.new(package, @options).cflags
end
all_cflags = [declaration("Cflags")] + all_cflags
all_cflags = all_cflags.join(" ").gsub(/-I /, '-I').split.uniq
path_flags, other_flags = all_cflags.partition {|flag| /\A-I/ =~ flag}
path_flags = path_flags.reject do |flag|
flag == "-I/usr/include"
end
if @msvc_syntax
path_flags = path_flags.collect do |flag|
flag.gsub(/\A-I/, "/I")
end
end
[path_flags, other_flags]
end
def collect_libs
all_libs = requires.collect do |package|
self.class.new(package, @options).libs
end
all_libs = [declaration("Libs")] + all_libs
all_libs = all_libs.join(" ").gsub(/-([Ll]) /, '\1').split.uniq
path_flags, other_flags = all_libs.partition {|flag| /\A-L/ =~ flag}
path_flags = path_flags.reject do |flag|
/\A-L\/usr\/lib(?:64)?\z/ =~ flag
end
if @msvc_syntax
path_flags = path_flags.collect do |flag|
flag.gsub(/\A-L/, "/libpath:")
end
other_flags = other_flags.collect do |flag|
if /\A-l/ =~ flag
"#{$POSTMATCH}.lib"
else
flag
end
end
end
[path_flags, other_flags]
end
IDENTIFIER_RE = /[a-zA-Z\d_\.]+/
def parse_pc
raise ".pc for #{@name} doesn't exist." unless exist?
@variables = {}
@declarations = {}
File.open(pc) do |input|
input.each_line do |line|
line = line.gsub(/#.*/, '').strip
next if line.empty?
case line
when /^(#{IDENTIFIER_RE})=/
@variables[$1] = $POSTMATCH.strip
when /^(#{IDENTIFIER_RE}):/
@declarations[$1] = $POSTMATCH.strip
end
end
end
end
def parse_requires(requires)
return [] if requires.nil?
requires_without_version = requires.gsub(/[<>]?=\s*[\d.]+\s*/, '')
requires_without_version.split(/[,\s]+/)
end
def parse_override_variables(override_variables)
variables = {}
override_variables.split(",").each do |variable|
name, value = variable.split("=", 2)
variables[name] = value
end
variables
end
def expand_value(value)
return nil if value.nil?
value.gsub(/\$\{(#{IDENTIFIER_RE})\}/) do
variable($1)
end
end
def guess_default_path
default_path = ["/usr/local/lib64/pkgconfig",
"/usr/local/lib/pkgconfig",
"/usr/local/libdata/pkgconfig",
"/opt/local/lib/pkgconfig",
"/usr/lib64/pkgconfig",
"/usr/lib/pkgconfig",
"/usr/X11/lib/pkgconfig/",
"/usr/share/pkgconfig"].join(SEPARATOR)
libdir = ENV["PKG_CONFIG_LIBDIR"]
default_path = [libdir, default_path].join(SEPARATOR) if libdir
pkg_config = self.class.native_pkg_config
return default_path unless pkg_config.absolute?
[(pkg_config.parent.parent + "lib" + "pkgconfig").to_s,
(pkg_config.parent.parent + "libdata" + "pkgconfig").to_s,
default_path].join(SEPARATOR)
end
end
module PKGConfig
VERSION = "1.0.7"
@@paths = []
@@override_variables = {}
module_function
def add_path(path)
@@paths << path
end
def set_override_variable(key, value)
@@override_variables[key] = value
end
def msvc?
/mswin32/.match(RUBY_PLATFORM) and /^cl\b/.match(Config::CONFIG['CC'])
end
def package_config(package)
PackageConfig.new(package,
:msvc_syntax => msvc?,
:override_variables => @@override_variables,
:paths => @@paths)
end
def exist?(pkg)
package_config(pkg).exist?
end
def libs(pkg)
package_config(pkg).libs
end
def libs_only_l(pkg)
package_config(pkg).libs_only_l
end
def libs_only_L(pkg)
package_config(pkg).libs_only_L
end
def cflags(pkg)
package_config(pkg).cflags
end
def cflags_only_I(pkg)
package_config(pkg).cflags_only_I
end
def modversion(pkg)
package_config(pkg).version
end
def description(pkg)
package_config(pkg).description
end
def variable(pkg, name)
package_config(pkg).variable(name)
end
def check_version?(pkg, major = 0, minor = 0, micro = 0)
return false unless exist?(pkg)
ver = modversion(pkg).split(".").collect{|item| item.to_i}
(0..2).each {|i| ver[i] = 0 unless ver[i]}
(ver[0] > major ||
(ver[0] == major && ver[1] > minor) ||
(ver[0] == major && ver[1] == minor &&
ver[2] >= micro))
end
def have_package(pkg, major = nil, minor = 0, micro = 0)
message = "#{pkg}"
unless major.nil?
message << " version (>= #{major}.#{minor}.#{micro})"
end
major ||= 0
enough_version = checking_for(checking_message(message)) do
check_version?(pkg, major, minor, micro)
end
if enough_version
libraries = libs_only_l(pkg)
dldflags = libs(pkg)
dldflags = (Shellwords.shellwords(dldflags) -
Shellwords.shellwords(libraries))
dldflags = dldflags.map {|s| /\s/ =~ s ? "\"#{s}\"" : s }.join(' ')
$libs += ' ' + libraries
if /mswin32/ =~ RUBY_PLATFORM
$DLDFLAGS += ' ' + dldflags
else
$LDFLAGS += ' ' + dldflags
end
$CFLAGS += ' ' + cflags(pkg)
end
enough_version
end
end