Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/utilrb/marshal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
require 'utilrb/kernel/require'
require_dir(__FILE__)

39 changes: 18 additions & 21 deletions lib/utilrb/marshal/load_with_missing_constants.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
module Marshal
if defined? BasicObject
class BlackHole < BasicObject
end
end

class BlackHole
class << self
:name
attr_reader :name
end

def initialize(*args)
end

def hash
__id__
end

def eql?(obj)
equal?(obj)
end

attr_reader :__content__
def method_missing(*args)
::Kernel.puts args.inspect
::Kernel.puts ::Kernel.caller
end
def self._load(*args)
hole = BlackHole.new
hole.instance_variable_set(:@__content__, args)
end

def self.method_missing(*args)
def self.method_missing(*args, **options)
BlackHole.new
end
end

Expand All @@ -32,20 +38,11 @@ def self.load_with_missing_constants(str_or_io)
self.load(str_or_io)
rescue Exception => e
case e.message
when /undefined class\/module ((?:\w+::)+)$/
names = $1.split('::')
missing = names.pop
base = names.inject(Object) { |m, n| m.const_get(n) }
base.const_set(missing, Module.new)

if original_pos
str_or_io.seek(original_pos)
end
retry
when /undefined class\/module ((?:\w+::)+)(\w+)$/
mod, klass = $1, $2
full_name = "#{mod}#{klass}"
mod = mod.split('::').inject(Object) { |m, n| m.const_get(n) }
when /undefined class\/module ((?:\w+)(?:::\w+)*)(?:::)?$/
full_name = $1
path = $1.split('::')
*path, klass = *path
mod = path.inject(Object) { |m, n| m.const_get(n) }

blackhole = Class.new(BlackHole) do
@name = full_name
Expand Down
186 changes: 119 additions & 67 deletions lib/utilrb/pkgconfig.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,44 +41,48 @@ class PkgConfig
VAR_NAME_RX = /\w+/
FIELD_NAME_RX = /[\w\.\-]+/

class << self
attr_reader :loaded_packages

def clear_cache
loaded_packages.clear
end
end
@loaded_packages = Hash.new

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

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

# @deprecated {PkgConfig} does not cache the packages anymore, so no
# need to call this method
def self.clear_cache
end

# Returns the pkg-config object that matches the given name, and
# optionally a version string
def self.get(name, version_spec = nil, preset_variables = Hash.new)
if !(candidates = loaded_packages[name])
paths = find_all_package_files(name)
if paths.empty?
raise NotFound.new(name), "cannot find the pkg-config specification for #{name}"
end
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 = Array.new
paths.each do |p|
candidates << PkgConfig.load(p, preset_variables)
end
loaded_packages[name] = candidates
candidates = paths.map do |p|
PkgConfig.load_minimal(p, preset_variables)
end

# Now try to find a matching spec
if version_match = find_matching_version(candidates, version_spec)
version_match
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

# Finds the provided package and optional version and returns its
Expand Down Expand Up @@ -140,7 +144,6 @@ def initialize(name); @name = name end
end


attr_reader :file
attr_reader :path

# The module name
Expand All @@ -151,6 +154,8 @@ def initialize(name); @name = name end
# The module version, as an array of integers
attr_reader :version

attr_reader :raw_fields

# Information extracted from the file
attr_reader :variables
attr_reader :fields
Expand All @@ -169,7 +174,7 @@ def initialize(name)
# +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
def expand_variables(value, variables, current)
def perform_substitution(value, variables, current)
value = value.gsub(/\$\{(\w+)\}/) do |rx|
expand_name = $1
if expand_name == current
Expand Down Expand Up @@ -211,16 +216,13 @@ def self.parse_dependencies(string)

SHELL_VARS = %w{Cflags Libs Libs.private}

# Loads the information contained in +path+
def load(path, preset_variables = Hash.new)
@path = path
@file = File.readlines(path).map(&:strip)

raw_variables = preset_variables.dup
raw_fields = Hash.new

# 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
def parse(path)
running_line = nil
@file = file.map do |line|
file = File.readlines(path).map do |line|
line = line.gsub(/\s*#.*$/, '')
line = line.strip
next if line.empty?
Expand All @@ -240,6 +242,7 @@ def load(path, preset_variables = Hash.new)
end.compact


raw_variables, raw_fields = Hash.new, Hash.new
file.each do |line|
case line
when /^(#{VAR_NAME_RX})\s*=(.*)/
Expand All @@ -250,44 +253,78 @@ def load(path, preset_variables = Hash.new)
raise NotImplementedError, "#{path}: cannot parse pkg-config line #{line.inspect}"
end
end
return raw_variables, raw_fields
end

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 = expand_variables(value, raw_variables, name)
value = perform_substitution(value, raw_variables, name)
raw_variables[name] = value
if value !~ /\$\{#{VAR_NAME_RX}\}/
variables[name] = value
end
end
end

# Shell-split the fields, and expand the variables in them
raw_fields.each do |name, value|
if SHELL_VARS.include?(name)
value = Shellwords.shellsplit(value)
resolved = Array.new
while !value.empty?
value = value.flat_map do |v|
expanded = expand_variables(v, variables, name)
if expanded == v
resolved << v
nil
else
Shellwords.shellsplit(expanded)
end
end.compact
end
fields[name] = resolved
else
fields[name] = expand_variables(value, variables, name)
variables
end

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

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

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

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
@raw_version = (fields['Version'] || '')
@version = raw_version.split('.').map { |v| Integer(v) if v =~ /^\d+$/ }.compact
@description = (fields['Description'] || '')

# Get the requires/conflicts
Expand Down Expand Up @@ -326,6 +363,14 @@ def load(path, preset_variables = Hash.new)
end
end

# Loads the information contained in +path+
def load(path, preset_variables = Hash.new)
if !@raw_fields
load_minimal(path, preset_variables)
end
load_fields
end

def self.define_pkgconfig_action(action) # :nodoc:
class_eval <<-EOD, __FILE__, __LINE__+1
def pkgconfig_#{action.gsub(/-/, '_')}(static = false)
Expand Down Expand Up @@ -415,17 +460,22 @@ def method_missing(varname, *args, &proc) # :nodoc:
end
end

def self.each_pkgconfig_directory(&block)
if path = ENV['PKG_CONFIG_PATH']
path.split(':').each(&block)
def self.pkg_config_path
ENV['PKG_CONFIG_PATH']
end

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
def self.find_all_package_files(name)
def self.find_all_package_files(name, pkg_config_path: self.pkg_config_path)
result = []
each_pkgconfig_directory do |dir|
each_pkgconfig_directory(pkg_config_path: pkg_config_path) do |dir|
path = File.join(dir, "#{name}.pc")
if File.exist?(path)
result << path
Expand All @@ -434,9 +484,9 @@ def self.find_all_package_files(name)
result
end

def self.available_package_names
def self.available_package_names(pkg_config_path: self.pkg_config_path)
result = []
each_pkgconfig_directory do |dir|
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
Expand All @@ -445,15 +495,17 @@ def self.available_package_names
end

# Returns true if there is a package with this name
def self.has_package?(name)
!find_all_package_files(name).empty?
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

# Yields the package names of available packages. If +regex+ is given,
# lists only the names that match the regular expression.
def self.each_package(regex = nil)
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 do |dir|
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)
Expand Down
8 changes: 4 additions & 4 deletions lib/utilrb/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
begin
require 'simplecov'
require 'coveralls'
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
SimpleCov::Formatter::HTMLFormatter,
Coveralls::SimpleCov::Formatter
]
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
[SimpleCov::Formatter::HTMLFormatter,
Coveralls::SimpleCov::Formatter]
)
SimpleCov.start do
add_filter "/test/"
end
Expand Down
Loading