library/packages/src/lib/y2packager/resolvable.rb
# ------------------------------------------------------------------------------
# Copyright (c) 2019 SUSE LINUX GmbH, Nuremberg, Germany.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of version 2 of the GNU General Public License as published by the
# Free Software Foundation.
#
# This program 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 General Public License for more details.
# ------------------------------------------------------------------------------
require "yast"
Yast.import "Pkg"
module Y2Packager
#
# This class represents a libzypp resolvable object (package, pattern, patch,
# product, source package)
#
# @note The returned Resolvables might not be valid anymore after changing
# the package manager status (installing/removing packages, changing
# repositories, etc.). After such a change you need to load the resolvables
# again, avoid storing them for later if possible.
#
# @example All installed packages
# Y2Packager::Resolvable.find(kind: :package, status: :installed)
#
# @example Available (not installed) "yast2" packages
# Y2Packager::Resolvable.find(kind: :package, status: :available, name: "yast2")
#
# @example Lazy loading
# res = Y2Packager::Resolvable.find(kind: :package, status: :installed)
# # the `summary` attribute is loaded from libzypp when needed
# res.each {|r| puts "#{r.name} - {r.summary}"}
#
# @example Preloading the attributes
# # the `summary` attribute is loaded from libzypp already at the initial state
# res = Y2Packager::Resolvable.find(kind: :package, status: :installed, [:summary])
# # this returns the cached `summary` attribute, this is much more efficient
# res.each {|r| puts "#{r.name} - {r.summary}"}
#
# @since 4.2.6
class Resolvable
include Yast::Logger
#
# Find the resolvables which match the input parameters. See Yast::Pkg.Resolvables
#
# @param params [Hash<Symbol,Object>] The search filter, only the matching resolvables
# are returned.
# @param preload [Array<Symbol>] The list of attributes which should be preloaded.
# The missing attributes are lazy loaded, however for performance reasons
# you might ask to preload the attributes right at the beginning and avoid
# querying libzypp again later.
# @return [Array<Y2Packager::Resolvable>] Found resolvables or empty array if nothing found
# @raise [ArgumentError] Raises ArgumentError when the passed regular expression is invalid
# @note The regular expressions used in the RPM dependency filters (e.g. "provides_regexp",
# "supplements_regexp") are POSIX extended regular expressions, not Ruby regular expressions!
# @see https://yast-pkg-bindings.surge.sh/ Yast::Pkg.Resolvables
def self.find(params, preload = [])
attrs = (preload + UNIQUE_ATTRIBUTES + OPTIONAL_ATTRIBUTES).uniq
resolvables = Yast::Pkg.Resolvables(params, attrs)
# currently nil is returned only when an invalid regular expression
# is passed in a RPM dependency filter (like supplements_regexp: "autoyast(")
raise ArgumentError, Yast::Pkg.LastError if resolvables.nil?
resolvables.map { |r| new(r) }
end
#
# Is there any resolvable matching the requested parameters? This is similar to
# the .find method, just instead of a resolvable list it returns a simple Boolean.
#
# @param params [Hash<Symbol,Object>] The requested attributes
# @return [Boolean] `true` if any matching resolvable is found, `false` otherwise.
# @see .find
def self.any?(params)
Yast::Pkg.AnyResolvable(params)
end
# Return true when there is no resolvable matching the requested parameters or false
# otherwise.
#
# @param params [Hash<Symbol,Object>] The requested attributes
# @return [Boolean] `true` if no matching resolvable is found, `false` otherwise.
def self.none?(params)
!any?(params)
end
#
# Constructor, initialize the object from a pkg-bindings resolvable hash.
#
# @param hash [Hash<Symbol,Object>] A pkg-bindings resolvable hash.
def initialize(hash)
from_hash(hash)
end
# Backward compatibility method to access resolvable like hash.
def [](key)
log.info "Calling [] with #{key}. It is deprecated. Use method name " \
"directly. Called from #{caller(1).first}"
public_send(key.to_sym)
# key not found, so return nil to be compatible
rescue NameError
log.warn "attribute #{key} not defined, returning nil."
nil
end
#
# Dynamically load the missing attributes from libzypp.
#
# @param method [Symbol] the method called
# @param args not used so far, raises ArgumentError if anything is passed
#
# @return the loaded value from libzypp
#
def method_missing(method, *args)
if instance_variable_defined?("@#{method}")
raise ArgumentError, "Method #{method} does not accept arguments" unless args.empty?
return instance_variable_get("@#{method}")
end
# load a missing attribute
raise "Missing attributes for identifying the resolvable." if !UNIQUE_ATTRIBUTES.all? { |a| instance_variable_defined?("@#{a}") }
load_attribute(method)
super unless instance_variable_defined?("@#{method}")
raise ArgumentError, "Method #{method} does not accept arguments" unless args.empty?
instance_variable_get("@#{method}")
end
# defines for dynamic methods also respond_to?
def respond_to_missing?(method, _private)
return true if instance_variable_defined?("@#{method}")
return true if UNIQUE_ATTRIBUTES.include?(method.to_sym)
false
end
private
# attributes required for identifying a resolvable
UNIQUE_ATTRIBUTES = [:kind, :name, :version, :arch, :source].freeze
# additional identifiers, not defined for all resolvable kinds
# - path - defined only for Package and Product resolvables
OPTIONAL_ATTRIBUTES = [:path].freeze
# Load the attributes from a Hash
#
# @param hash [Hash] The resolvable Hash obtained from pkg-bindings.
def from_hash(hash)
hash.each do |k, v|
instance_variable_set("@#{k}", v)
end
end
#
# Lazy load a missing attribute.
#
# @param attr [Symbol] The required attribute to load.
# @return [Object] The read value.
def load_attribute(attr)
attrs = ((UNIQUE_ATTRIBUTES + OPTIONAL_ATTRIBUTES).map { |a| [a, instance_variable_get("@#{a}")] }).to_h
resolvables = Yast::Pkg.Resolvables(attrs, [attr])
# Finding more than one result is suspicious, log a warning
if resolvables.size > 1
log.warn("Found several resolvables: #{resolvables.inspect}")
log.warn("Resolvable details: #{Yast::Pkg.ResolvableProperties(@name, @kind, "").inspect}")
end
resolvable = resolvables.first
return unless resolvable&.key?(attr.to_s)
instance_variable_set("@#{attr}", resolvable[attr.to_s])
end
end
end