Skip to content

Commit

Permalink
Merge pull request #279 from yast/release-notes-from-packages
Browse files Browse the repository at this point in the history
Release notes from packages
  • Loading branch information
imobachgs committed Sep 18, 2017
2 parents 63af676 + c835332 commit 96e4eb4
Show file tree
Hide file tree
Showing 17 changed files with 970 additions and 82 deletions.
7 changes: 7 additions & 0 deletions package/yast2-packager.changes
@@ -1,3 +1,10 @@
-------------------------------------------------------------------
Mon Sep 18 08:32:16 UTC 2017 - igonzalezsosa@suse.com

- Add support to read release notes from an RPM in the
repository (fate#323273)
- 4.0.5

-------------------------------------------------------------------
Mon Sep 18 07:55:11 UTC 2017 - knut.anderssen@suse.com

Expand Down
2 changes: 1 addition & 1 deletion package/yast2-packager.spec
Expand Up @@ -17,7 +17,7 @@


Name: yast2-packager
Version: 4.0.4
Version: 4.0.5
Release: 0

BuildRoot: %{_tmppath}/%{name}-%{version}-build
Expand Down
92 changes: 92 additions & 0 deletions src/lib/y2packager/package.rb
@@ -0,0 +1,92 @@
# ------------------------------------------------------------------------------
# Copyright (c) 2017 SUSE LLC, All Rights Reserved.
#
# 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"
require "yast2/execute"
require "packages/package_downloader"
require "packages/package_extractor"
require "tempfile"

Yast.import "Pkg"

module Y2Packager
# This class represents a libzypp package and it offers an API to common operations.
#
# The idea is extending this class with new methods when needed.
class Package
include Yast::Logger

# @return [String] Package name
attr_reader :name
# @return [Integer] Id of the repository where the package lives
attr_reader :repo_id
# @return [String] Package version
attr_reader :version

class << self
# Find packages by name
#
# @param name [String] Package name
# @return [Array<Package>,nil] Packages named like `name`. It returns `nil`
# if some problem occurs interacting with libzypp.
def find(name)
props = Yast::Pkg.ResolvableProperties(name, :package, "")
return nil if props.nil?
props.map { |i| new(i["name"], i["source"], i["version"]) }
end
end

# Constructor
#
# @param name [String] Package name
# @param repo_id [Integer] Repository ID
# @param version [String] Package version
def initialize(name, repo_id, version)
@name = name
@repo_id = repo_id
@version = version
end

# Return package status
#
# Ask libzypp about package status.
#
# @return [Symbol] Package status (:installed, :available, etc.)
# @see Yast::Pkg.PkgProperties
def status
Yast::Pkg.PkgProperties(name)["status"]
end

# Download a package to the given path
#
# @param path [String,Pathname] Path to download the package to
# @see Packages::PackageDownloader
def download_to(path)
downloader = Packages::PackageDownloader.new(repo_id, name)
downloader.download(path.to_s)
end

# Download and extract the package to the given directory
#
# @param path [String,Pathname] Path to extract the package to
# @see Packages::PackageExtractor
def extract_to(directory)
tmpfile = Tempfile.new("downloaded-package-#{name}-")
download_to(tmpfile.path)
extractor = Packages::PackageExtractor.new(tmpfile.path)
extractor.extract(directory.to_s)
ensure
tmpfile.close
tmpfile.unlink
end
end
end
52 changes: 42 additions & 10 deletions src/lib/y2packager/product.rb
Expand Up @@ -13,6 +13,7 @@
Yast.import "Pkg"
Yast.import "Language"
require "y2packager/product_reader"
require "y2packager/release_notes_reader"

module Y2Packager
# Represent a product which is present in a repository. At this
Expand Down Expand Up @@ -43,17 +44,37 @@ class Product
# @return [String] package including installation.xml for install on top of lean os
attr_reader :installation_package

def self.available_base_products
Y2Packager::ProductReader.available_base_products
end
class << self
# Return all known products
#
# @return [Array<Product>] Known products
def all
Y2Packager::ProductReader.new.all_products
end

# Returns the selected base product
#
# It assumes that at most 1 product could be selected.
#
# @return [Y2Packager::Product] Selected base product
def self.selected_base
available_base_products.find(&:selected?)
# Return available base products
#
# @return [Array<Product>] Available base products
def available_base_products
Y2Packager::ProductReader.new.available_base_products
end

# Returns the selected base product
#
# It assumes that at most 1 base product can be selected.
#
# @return [Product] Selected base product
def selected_base
available_base_products.find(&:selected?)
end

# Return the products with a given status
#
# @param statuses [Array<Product>] Product status (:available, :installed, :selected, etc.)
# @return [Array<Product>] Products with the given status
def with_status(*statuses)
all.select { |s| statuses.include?(s.status) }
end
end

# Constructor
Expand Down Expand Up @@ -185,5 +206,16 @@ def license_confirmation=(confirmed)
def license_confirmed?
Yast::Pkg.PrdHasLicenseConfirmed(name)
end

# Return product's release notes
#
# @param format [Symbol] Release notes format (use :txt as default)
# @return user_lang [String] Preferred language (use current language as default)
# @return [ReleaseNotes] Release notes for product, language and format
# @see ReleaseNotesReader
# @see ReleaseNotes
def release_notes(format = :txt, user_lang = Yast::Language.language)
ReleaseNotesReader.new.release_notes_for(self, user_lang: user_lang, format: format)
end
end
end
108 changes: 61 additions & 47 deletions src/lib/y2packager/product_reader.rb
Expand Up @@ -21,54 +21,90 @@ module Y2Packager
class ProductReader
include Yast::Logger

# In installation Read the available libzypp base products for installation
# @return [Array<Y2Packager::Product>] the found available base products,
# the products are sorted by the 'displayorder' provides value
def self.available_base_products
products = available_products
class << self
# Installation packages map
#
# This map contains the correspondence between products and the
# installation package for each product.
#
# The information is read only once and cached for further queries.
#
# @return [Hash<String,String>] product name -> installation package name
def installation_package_mapping
return @installation_package_mapping if @installation_package_mapping
installation_packages = Yast::Pkg.PkgQueryProvides("system-installation()")
log.info "Installation packages: #{installation_packages.inspect}"

@installation_package_mapping = {}
installation_packages.each do |list|
pkg_name = list.first
# There can be more instances of same package in different version. We except that one
# package provide same product installation. So we just pick the first one.
dependencies = Yast::Pkg.ResolvableDependencies(pkg_name, :package, "").first["deps"]
install_provide = dependencies.find do |d|
d["provides"] && d["provides"].match(/system-installation\(\)/)
end

# parse product name from provides. Format of provide is
# `system-installation() = <product_name>`
product_name = install_provide["provides"][/system-installation\(\)\s*=\s*(\S+)/, 1]
log.info "package #{pkg_name} install product #{product_name}"
@installation_package_mapping[product_name] = pkg_name
end

installation_mapping = installation_package_mapping
result = products.map do |prod|
@installation_package_mapping
end
end

# Available products
#
# @return [Array<Product>] Available products
def all_products
@all_products ||= available_products.map do |prod|
prod_pkg = product_package(prod["product_package"], prod["source"])

if prod_pkg
prod_pkg["deps"].find { |dep| dep["provides"] =~ /\Adisplayorder\(\s*([0-9]+)\s*\)\z/ }
displayorder = Regexp.last_match[1].to_i if Regexp.last_match
end

Y2Packager::Product.new(name: prod["name"], short_name: prod["short_name"],
display_name: prod["display_name"], order: displayorder,
installation_package: installation_mapping[prod["name"]])
Y2Packager::Product.new(
name: prod["name"], short_name: prod["short_name"], display_name: prod["display_name"],
version: prod["version"], status: prod["status"], order: displayorder,
installation_package: installation_package_mapping[prod["name"]]
)
end
end

# In installation Read the available libzypp base products for installation
# @return [Array<Y2Packager::Product>] the found available base products,
# the products are sorted by the 'displayorder' provides value
def available_base_products
# If no product contains a 'system-installation()' tag but there is only 1 product,
# we assume that it is the base one.
if result.size == 1 && installation_mapping.empty?
log.info "Assuming that #{result.inspect} is the base product."
return result
if all_products.size == 1 && installation_package_mapping.empty?
log.info "Assuming that #{all_products.inspect} is the base product."
return all_products
end

# only installable products
result.select!(&:installation_package)

# sort the products
result.sort!(&::Y2Packager::PRODUCT_SORTER)

log.info "available base products #{result}"

result
products = all_products.select(&:installation_package).sort(&::Y2Packager::PRODUCT_SORTER)
log.info "available base products #{products}"
products
end

def self.product_package(name, repo_id)
def product_package(name, repo_id)
return nil unless name
Yast::Pkg.ResolvableDependencies(name, :package, "").find do |prod|
prod["source"] == repo_id
end
end

private

# read the available products, remove potential duplicates
# @return [Array<Hash>] pkg-bindings data structure
def self.available_products
def available_products
products = Yast::Pkg.ResolvableProperties("", :product, "")

# remove duplicates, there migth be different flavors ("DVD"/"POOL")
Expand All @@ -80,30 +116,8 @@ def self.available_products
products
end

private_class_method :available_products

def self.installation_package_mapping
installation_packages = Yast::Pkg.PkgQueryProvides("system-installation()")
log.info "Installation packages: #{installation_packages.inspect}"

mapping = {}
installation_packages.each do |list|
pkg_name = list.first
# There can be more instances of same package in different version. We except that one
# package provide same product installation. So we just pick the first one.
dependencies = Yast::Pkg.ResolvableDependencies(pkg_name, :package, "").first["deps"]
install_provide = dependencies.find do |d|
d["provides"] && d["provides"].match(/system-installation\(\)/)
end

# parse product name from provides. Format of provide is
# `system-installation() = <product_name>`
product_name = install_provide["provides"][/system-installation\(\)\s*=\s*(\S+)/, 1]
log.info "package #{pkg_name} install product #{product_name}"
mapping[product_name] = pkg_name
end

mapping
def installation_package_mapping
self.class.installation_package_mapping
end
end
end
61 changes: 61 additions & 0 deletions src/lib/y2packager/release_notes.rb
@@ -0,0 +1,61 @@
# ------------------------------------------------------------------------------
# Copyright (c) 2017 SUSE LLC, All Rights Reserved.
#
# 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"

module Y2Packager
# Release notes for a given product
#
# This class stores the content and some additional metadata about release
# notes for a given product.
class ReleaseNotes
# @return [String] Product name (internal libzypp name)
attr_reader :product_name
# @return [String] Release notes content
attr_reader :content
# @return [String] Language asked by user
attr_reader :user_lang
# @return [String] Contents language
attr_reader :lang
# @return [Symbol] Contents format
attr_reader :format
# @return [String] Release notes version (from release notes package)
attr_reader :version

# Constructor
#
# @param product_name [String] Product name (internal libzypp name)
# @param content [String] Release notes content
# @param user_lang [String] Language asked by user
# @param lang [String] Contents language
# @param format [Symbol] Contents format
# @param version [String] Release notes version (from release notes package)
def initialize(product_name:, content:, user_lang:, lang:, format:, version:)
@product_name = product_name
@content = content
@user_lang = user_lang
@lang = lang
@version = version
@format = format
end

# Determine whether a release notes matches language, format and version requirements
#
# @param user_lang [String] Language asked by user
# @param format [Symbol] Symbol
# @param version [String] Release note's version
# @return [Boolean] true if it matches; false otherwise.
def matches?(user_lang, format, version)
self.user_lang == user_lang && self.format == format && self.version == version
end
end
end

0 comments on commit 96e4eb4

Please sign in to comment.