master
alexey 3 weeks ago
parent 9cf2aabc65
commit 998f87cee3

@ -32,3 +32,5 @@ gem "sqlite3", "~> 2.5"
gem "rugged", "~> 1.9"
gem "sequel", "~> 5.89"
gem "ffi", "~> 1.17"

@ -171,6 +171,7 @@ PLATFORMS
DEPENDENCIES
carrierwave (~> 3.1)
data_mapper (~> 1.2)
ffi (~> 1.17)
inifile (~> 3.0)
json (~> 1.8)
puma (~> 6.6)

@ -27,6 +27,12 @@ end
class BuildTask < Sequel::Model(:buildtask)
end
class Rpms < Sequel::Model(:rpms)
end
class BuildRpms < Sequel::Model(:build_rpm)
end
class DBase
attr :error, :last_id, :cfg
@ -34,7 +40,7 @@ class DBase
@cfg = cfg
end
def creategit(project_name, description, cfg)
def creategit(project_name, description)
@error = nil
data = Repos.where(reponame: project_name).first
if data.nil?
@ -228,7 +234,7 @@ class DBase
def create_build_task(prj_id, git_id, proj_path)
id = BuildTask.insert(repo_id: git_id.to_i, proj_id: prj_id.to_i, signpath: "", logpath: "", errlogpath: "", result: 0)
@last_id = id
BuildTask.where(id: id).update(logpath: File.join(proj_path, "#{id}"))
BuildTask.where(id: id).update(logpath: File.join(proj_path, "#{id}"), errlogpath: File.join(proj_path, "#{id}", "process.log"))
end
def update_build_task_status(build_id, status)
@ -250,4 +256,10 @@ class DBase
def after_fork()
Sequel.connect(@cfg.get_db)
end
def save_rpm(build_id, path_to_rpm, rpm_name, git_id)
id = Rpms.insert(savepath: path_to_rpm, rpmname: rpm_name, sign: 0, signpath: "", repo_id: git_id.to_i)
@last_id = id
BuildRpms.insert(build_id: build_id.to_i, rpm_id: id)
end
end

@ -2,6 +2,7 @@ require_relative "spork"
require_relative "runner"
require "fileutils"
require "logger"
require_relative "repomanage"
BUILD_STRUCTURE = {
:SRC => "src",
@ -10,12 +11,10 @@ BUILD_STRUCTURE = {
:RESULT_SRPM => "result_srpm",
}
#mock -r /home/alexey/workspace/ruby-projects/mock-gui/projects/prjt1.prj/configs/prjt1.cfg result/bayrepo-neuro-farm-0.1-2.src.rpm --resultdir result2/ --isolation simple
class MockManager
attr :path, :config, :error, :last_status, :last_pid, :prep_dir, :db, :resultpath, :process_log, :repo_path, :git_path, :build_id, :log, :recips, :spec, :repo_lock
attr :path, :config, :error, :last_status, :last_pid, :prep_dir, :db, :resultpath, :process_log, :repo_path, :git_path, :build_id, :log, :recips, :spec, :repo_lock, :git_id
def initialize(path, config, cfg_counter_path, db, result_path, repo_path, git_path, build_id, recips, spec_file, repo_lock)
def initialize(path, config, cfg_counter_path, db, result_path, repo_path, git_path, build_id, recips, spec_file, repo_lock, git_id)
@error = nil
unless File.exist? (path)
Dir.mkdir(path)
@ -31,6 +30,7 @@ class MockManager
@recips = recips
@spec = spec_file
@repo_lock = repo_lock
@git_id = git_id
File.open(cfg_counter_path, "r+") do |f|
f.flock(File::LOCK_EX)
@ -52,10 +52,6 @@ class MockManager
@process_log
end
def finalize_build_task()
@log.close
end
def clean_build
@log.info("Удаление временной сборочной среды #{@path}")
FileUtils.rm_rf(@path)
@ -91,7 +87,7 @@ class MockManager
@recips.each_with_index do |item, index|
@log.info("Формируем рецепт #{item[:filepath]}")
rcp_name = "#{index}rcp_#{item[:filepath]}"
File.open(File.join(@prep_dir, BUILD_STRUCTURE[:SRC], rcp_name)) do |f|
File.open(File.join(@prep_dir, BUILD_STRUCTURE[:SRC], rcp_name), "w") do |f|
f.write(item[:content])
end
Dir.chdir(File.join(@prep_dir, BUILD_STRUCTURE[:SRC])) do
@ -100,6 +96,7 @@ class MockManager
cmd = Runner.new(cmd_args, @log)
cmd.run_clean
@error = true if cmd.exit_status != 0
@log.error("Ошибка операции") if @error
end
break if @error
end
@ -110,11 +107,11 @@ class MockManager
spec_file = File.join(@prep_dir, BUILD_STRUCTURE[:SRC], @spec)
if File.exist?(spec_file)
Dir.chdir(File.join(@prep_dir, BUILD_STRUCTURE[:SRC])) do
script = File.join(@prep_dir, BUILD_STRUCTURE[:SRC], rcp_name)
cmd_args = %Q(/usr/bin/mock -r #{@config} --buildsrpm --spec #{spec_file} --sources #{File.join(@prep_dir, BUILD_STRUCTURE[:SRC])} --resultdir #{File.join(@prep_dir, BUILD_STRUCTURE[:RESULT_SRPM])} --isolation=simple --disable-plugin=ccache")
cmd = Runner.new(cmd_args, @log)
cmd.run_clean
@error = true if cmd.exit_status != 0
@log.error("Ошибка операции") if @error
end
else
@error = true
@ -123,40 +120,138 @@ class MockManager
end
def build_rpm()
@log.info("Начало сборки пакетов")
srpm_result_dir = File.join(@prep_dir, BUILD_STRUCTURE[:RESULT_SRPM])
srpms = get_src_rpm_files_in_dir(srpm_result_dir)
if srpms.nil? || srpms.length == 0
@error = true
@log.info("Нечего собирать, нет src.rpm пакетов")
else
srpm = srpms.first
path_srpm = File.join(@prep_dir, srpm)
cmd_args = %Q(/usr/bin/mock -r #{@config} #{path_srpm} --resultdir #{File.join(@prep_dir, BUILD_STRUCTURE[:RESULT])} --isolation simple)
cmd = Runner.new(cmd_args, @log)
cmd.run_clean
@error = true if cmd.exit_status != 0
@log.error("Ошибка операции") if @error
end
end
def save_logs()
FileUtils.mkdir_p(File.join(@resultpath, "#{@build_id}"))
src_result = File.join(@prep_dir, BUILD_STRUCTURE[:RESULT_SRPM])
rpm_result = File.join(@prep_dir, BUILD_STRUCTURE[:RESULT])
if File.exist?(src_result)
logs = get_log_paths(src_result)
logs.each do |item|
src = File.join(src_result, item)
dst_dir = File.dirname(item)
dst_fname = File.basename(item)
dst = File.join(@resultpath, "#{@build_id}", dst_dir, "srpms_build_#{dst_fname}")
FileUtils.cp_r(src, dst, verbose: true, remove_destination: true)
end
end
if File.exist?(rpm_result)
logs = get_log_paths(rpm_result)
logs.each do |item|
src = File.join(rpm_result, item)
dst_dir = File.dirname(item)
dst_fname = File.basename(item)
dst = File.join(@resultpath, "#{@build_id}", dst_dir, "rpm_build_#{dst_fname}")
FileUtils.cp_r(src, dst, verbose: true, remove_destination: true)
end
end
end
def save_rpms()
File.open(@repo_lock, File::RDWR | File::CREAT) do |f|
f.flock(File::LOCK_EX)
# выклдака пакетов и пересоздание repodata
@log.info("Формирование репозитория")
repo = RepoManager.new(@repo_path)
rpms = get_rpm_paths(File.join(@prep_dir, BUILD_STRUCTURE[:RESULT]))
if rpms.nil? || rpms.length == 0
@error = true
@log.error("Пакеты не найдены")
else
prep_rpms = []
rpms.each do |item|
res = {}
rpm_path = File.join(@prep_dir, BUILD_STRUCTURE[:RESULT], item)
result = repo.get_rpm_info(rpm_path)
if result[:error].nil?
res[:src] = rpm_path
res[:name] = result[:pkginfo].to_s
arch = result[:pkginfo].arch
if rpm_path =~ /\.src\.rpm$/
res[:dst] = File.join(@repo_path, "SRPMS", File.basename(rpm_path))
elsif rpm_path =~ /(debuginfo.+rpm$)|(debugsource.+rpm$)/
res[:dst] = File.join(@repo_path, "Debug", File.basename(rpm_path))
else
if arch.nil? || arch.strip == ""
arch = "noarch"
end
res[:dst] = File.join(@repo_path, arch, File.basename(rpm_path))
end
prep_rpms << res
else
@error = true
@log.error("Ошибка пакета #{rpm_path}")
break
end
end
if @error == false
File.open(@repo_lock, File::RDWR | File::CREAT) do |f|
f.flock(File::LOCK_EX)
# выклдака пакетов и пересоздание repodata
prep_rpms.each do |item|
FileUtils.cp_r(item[:src], item[:dst], verbose: true, remove_destination: true)
@db.save_rpm(@build_id, item[:dst], item[:name], @git_id)
@log.info("Копируется пакет #{item[:src]} в репозиторий #{item[:dst]}")
end
repo.create_repo
end
end
end
end
def save_prg_log()
FileUtils.mkdir_p(File.join(@resultpath, "#{@build_id}"))
if File.exist?(@process_log)
dst = File.join(@resultpath, "#{@build_id}")
FileUtils.cp_r(@process_log, dst, verbose: true, remove_destination: true)
end
end
def build_task()
@error = false
@db.before_fork
spock = Spork.spork(:logger => log) do
@db.after_fork
$stdout = File.open(@process_log, "w")
@log = Logger.new($stdout)
if @sepc == ""
@error = true
@log.error("Не могу найти spec файл")
end
prepare_structure if @error == false
prepare_src if @error == false
prepare_source if @error == false
prepare_src_rpm if @error == false
build_rpm if @error == false
save_logs
save_rpms if @error == false
clean_build
@log.close
#@db.before_fork
#spock = Spork.spork(:logger => log) do
# @db.after_fork
#$stdout = File.open(@process_log, "w")
@log = Logger.new($stdout)
if @spec == ""
@error = true
@log.error("Не могу найти spec файл")
end
#begin
prepare_structure if @error == false
prepare_src if @error == false
prepare_source if @error == false
prepare_src_rpm if @error == false
build_rpm if @error == false
save_logs
save_rpms if @error == false
#rescue => e
# puts e
#end
save_prg_log
clean_build
@log.close
if @error
@db.update_build_task_status(@build_id, 1)
else
@db.update_build_task_status(@build_id, 2)
end
@db.after_fork
spock
#end
#@db.after_fork
#spock
end
end

@ -305,11 +305,10 @@ class ProjectsActions
git_source = File.join(proj_path, PROJECTS_STRUCTURE[:SRC], git_name[:reponame])
@db.create_build_task(prj_id, git_id, build_path)
build_id = @db.last_id
mock = MockManager.new(prepare_path, get_project_config(prj_id), counter_file, @db, build_path, repo_path, git_source, build_id, prep_script, spec_file, repo_lock)
mock = MockManager.new(prepare_path, get_project_config(prj_id), counter_file, @db, build_path, repo_path, git_source, build_id, prep_script, spec_file, repo_lock, git_id)
bld_id = build_id
@db.update_build_task_error_log(build_id, mock.get_build_process_log)
mock.build_task
mock.finalize_build_task
f.flock(File::LOCK_UN)
end
end

@ -1,3 +1,7 @@
$LOAD_PATH.unshift File.expand_path(".", "locallibs/ruby-rpm-ffi/lib")
require "rpm"
require_relative "runner"
class RepoManager
@ -22,4 +26,15 @@ class RepoManager
@last_status = cmd.exit_status
@last_pid = cmd.pid
end
def get_rpm_info(path_to_rpm)
res = { :error => nil }
if File.exist?(path_to_rpm)
pkg = RPM::Package.open(path_to_rpm)
res[:pkginfo] = pkg
else
res[:error] = "#{path_to_rpm} не существует"
end
res
end
end

@ -34,3 +34,15 @@ end
def get_spec_files_in_dir(directory)
Dir.glob(File.join(directory, "**", "*")).reject { |f| File.directory?(f) }.select { |f| File.extname(f) == ".spec" }.map { |f| f.delete_prefix(directory + "/") }
end
def get_src_rpm_files_in_dir(directory)
Dir.glob(File.join(directory, "**", "*")).reject { |f| File.directory?(f) }.select { |f| f.end_with?(".src.rpm") }.map { |f| f.delete_prefix(directory + "/") }
end
def get_log_paths(directory)
Dir.glob(File.join(directory, "**", "*")).reject { |f| File.directory?(f) }.select { |f| File.extname(f) == ".log" }.map { |f| f.delete_prefix(directory + "/") }
end
def get_rpm_paths(directory)
Dir.glob(File.join(directory, "**", "*")).reject { |f| File.directory?(f) }.select { |f| File.extname(f) == ".rpm" }.map { |f| f.delete_prefix(directory + "/") }
end

@ -0,0 +1,8 @@
*.gem
.bundle
Gemfile.lock
pkg/*
*.rbc
.yardoc
.idea
test/test_many_files.rb

@ -0,0 +1 @@
inherit_from: .rubocop_todo.yml

@ -0,0 +1,111 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2016-07-09 11:50:54 +0200 using RuboCop version 0.41.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 6
# Cop supports --auto-correct.
# Configuration parameters: AlignWith, SupportedStyles, AutoCorrect.
# SupportedStyles: keyword, variable, start_of_line
Lint/EndAlignment:
Exclude:
- 'lib/rpm/dependency.rb'
- 'lib/rpm/file.rb'
- 'lib/rpm/package.rb'
- 'lib/rpm/transaction.rb'
- 'test/test_transaction.rb'
# Offense count: 1
Lint/UnreachableCode:
Exclude:
- 'lib/rpm/transaction.rb'
# Offense count: 11
Lint/UselessAssignment:
Exclude:
- 'lib/rpm.rb'
- 'lib/rpm/package.rb'
- 'lib/rpm/transaction.rb'
- 'lib/rpm/version.rb'
# Offense count: 1
Lint/Void:
Exclude:
- 'test/test_rpm.rb'
# Offense count: 17
Metrics/AbcSize:
Max: 74
# Offense count: 1
Metrics/BlockNesting:
Max: 4
# Offense count: 3
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 225
# Offense count: 3
Metrics/CyclomaticComplexity:
Max: 17
# Offense count: 66
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
# URISchemes: http, https
Metrics/LineLength:
Max: 129
# Offense count: 21
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 57
# Offense count: 1
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 295
# Offense count: 4
# Configuration parameters: CountKeywordArgs.
Metrics/ParameterLists:
Max: 11
# Offense count: 2
Metrics/PerceivedComplexity:
Max: 19
# Offense count: 2
Style/AccessorMethodName:
Exclude:
- 'lib/rpm/match_iterator.rb'
# Offense count: 1
Style/AsciiComments:
Exclude:
- 'lib/rpm/file.rb'
# Offense count: 34
Style/Documentation:
Enabled: false
# Offense count: 2
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: snake_case, camelCase
Style/MethodName:
Exclude:
- 'lib/rpm/c/rpmprob.rb'
- 'lib/rpm/c/rpmtag.rb'
# Offense count: 3
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
# NamePrefix: is_, has_, have_
# NamePrefixBlacklist: is_, has_, have_
# NameWhitelist: is_a?
Style/PredicateName:
Exclude:
- 'spec/**/*'
- 'lib/rpm/file.rb'

@ -0,0 +1,10 @@
2013-10-24 Duncan Mac-Vicar P. <dmacvicar@suse.de>
* v0.0.4
* Bugfix: too many open files on transaction with blocks
2012-02-23 Duncan Mac-Vicar P. <dmacvicar@suse.de>
* RPM::FFI module is now RPM::C
* implement Trasaction#commit, Trasaction#install, Trasaction#upgrade,

@ -0,0 +1,9 @@
source 'http://rubygems.org'
# Specify your gem's dependencies in rpm.gemspec
gemspec
group :test do
gem 'minitest'
gem 'rake'
end

@ -0,0 +1,24 @@
Copyright © 2011 Duncan Mac-Vicar Prett <dmacvicar@suse.de>
Copyright © 2011 SUSE Linux Products GmbH
Copyright © 2002 Kenta Murata
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,378 @@
# RPM bindings for ruby
![maintained](https://img.shields.io/maintenance/yes/2016.svg) [![Build Status](https://travis-ci.org/dmacvicar/ruby-rpm-ffi.svg?branch=master)](https://travis-ci.org/dmacvicar/ruby-rpm-ffi)
[![CI](https://github.com/dmacvicar/ruby-rpm-ffi/actions/workflows/ci.yaml/badge.svg)](https://github.com/dmacvicar/ruby-rpm-ffi/actions/workflows/ci.yaml)
* http://github.com/dmacvicar/ruby-rpm-ffi
# WARNING
This is an alpha release! There is still work to be done
# Quickstart
## Working with RPM package files
```ruby
require 'rpm'
pkg = RPM::Package.open("file.rpm")
pkg.arch => "x86_64"
pkg.files.each do |file|
puts file.path
end
pkg.changelog.each do |entry|
puts "#{entry.name} #{entry.time} #{entry.text}"
end
```
## Querying the rpm database
```ruby
require 'rpm'
RPM.transaction do |ts|
ts.each do |pkg|
puts pkg
end
end
```
## Install a package
```ruby
require 'rpm'
pkg = RPM::Package.open('foo.rpm')
RPM.transaction(rootdir) do |t|
t.install(pkg, 'foo.rpm')
t.commit
end
```
## Introduction
This library is a replacement for the ruby-rpm gem, originally
writen by Kenta Murata around 2002 for the Kondara distribution. Later
mantained by David Lutterkort and myself.
Why?
* The original gem supports ancient rpm versions not in use anymore
* The original gem was written in C using MRI API
* The #ifdef'ing required to support multiple rpm versions made the code
hard to maintain
This gem:
* Is pure ruby
* Is documented
* Has as a goal to support only the latest rpm version plus the ones in
use some releases back in popular rpm based distros
* Uses FFI, so it should work with other interpreters
(Because https://github.com/rubinius/rubinius/issues/682 it currently does
not work on Rubinius)
* Does not target rpm5, but it may support it someday
As an example the code that implements RPM::Package was reduced
from 1130 lines of code to 320.
# Architecture
The gem is divided in two modules:
* RPM::C:: which contains the 1:1 mapping to the librpm API
Not all functions are attached, only the ones we actually use.
* RPM:: contains the actual higher level API
# Status, Compatibility and Differences with ruby-rpm
* Only rpm 4.11.x or later will be supported
* You can use symbols: instead of RPM::TAG_DESCRIPTION you
can use just :description. 'rpm/compat' is by default loaded
and provides compatibility with the RPM::TAG_* style constants
* RPM::DB is not supported. Use RPM::Transaction
* Spec and Source classes are not implemented yet
## TESTING
Unit tests can be run using the `rake test` command.
### Docker tests
In order to not damage your system, you can run the testsuite under docker:
* Build the docker images:
```console
rake docker_images
```
* Run the testsuite under Docker
```console
rake docker_test
```
## TODO
* Check Package#signature should return String?
=> ruby-rpm seems to return symbol
* Food for thought: Package dependencies and changelog
methods could just use []. Calling headerGet directly saves
us from doing one iteration per attribute
* Not sure if Spec can be implemented as it was before with
newer rpms.
## API Checklist and TODO
### Low level 1:1 RPM::C API
* http://rpm.org/wiki/Releases/4.14.0
- [ ] Add rpmfiVerify() and rpmfilesVerify()
- [ ] Add pmsqPoll(), rpmsqActivate(), rpmsqSetAction(), rpmsqBlock()
- [ ] Add rpmDigestBundleAddID()
- [ ] Add RPMTRANS_FLAG_NOCAPS flag to disable file capabilities
- [ ] Add RPMVSF_NOPAYLOAD flag to disable payload digest verification
- [ ] Add pgpPubkeyKeyID()
- [X] Add rpmPushMacro() and rpmPopMacro() (to replace addMacro() and delMacro())
- [ ] Remove headerNVR(), headerNEVRA(), headerGetNEVR(), headerGetNEVRA(), headerGetEVR(), headerGetColor(), rpmfiMD5(), expandMacros(), addMacro(), delMacro()
* http://rpm.org/wiki/Releases/4.13.0
- [ ] Add rpmsqSetInterruptSafety()
- [ ] Add/Change rpmPkgSign()
- [ ] Add RPMCALLBACK_ELEM_PROGRESS callback type
- [ ] Add rpmExpandMacros()
* http://rpm.org/wiki/Releases/4.12.0
- [ ] Add rpmtxnBegin() and rpmtxnEnd()
- [ ] Add rpmtsImportHeader()
- [ ] Add rpmtsAddReinstallElement()
- [ ] Add rpmdbIndexIteratorNextTd()
- [ ] Add file info set iterator functions: rpmfiFLinks(), rpmfiFindFN(), rpmfiStat()
- [ ] Add rpmfiOFN(), rpmfiOBN(), rpmfiODN(), rpmfiFindOFN()
- [ ] Add rpmteFiles()
- [ ] Add rpmdsTagF(), rpmdsTagEVR(), rpmdsD(), rpmdsPutToHeader(), rpmdsTi(), rpmdsTagTi() and rpmdsSinglePoolTix()
* http://rpm.org/wiki/Releases/4.11.0
- [ ] Add rpmstrPool object + associated functions
- [ ] Add rpmIsGlob()
- [ ] Add rpmtdToPool()
- [ ] Add rpmGetArchColor()
### RPM
- [ ] RPM#expand
- [X] RPM#[]
- [X] RPM#[]=
- [ ] RPM#readrc
- [ ] RPM#init_macros
- [ ] RPM#verbosity
- [ ] RPM#verbosity=
### RPM::Package
- [X] Package#open
- [X] Package#new
- [X] Package#create
- [ ] Package#load
- [ ] Package#clear_cache
- [ ] Package#use_cache
- [X] Package#[]
- [ ] Package#delete_tag
- [X] Package#sprintf
[?] Package#signature
- [X] Package#arch
- [X] Package#name
- [X] Package#version
- [X] Package#files
- [X] Package#provides
- [X] Package#requires
- [X] Package#conflicts
- [X] Package#obsoletes
- [X] Package#changelog
- [ ] Package#add_dependency
- [ ] Package#add_string
- [ ] Package#add_string_array
- [ ] Package#add_int32
- [ ] Package#dump
- [X] Package#to_s
- [ ] Package#inspect
- [ ] Package#copy_to
### RPM::Dependency
- [X] Dependency#initialize
- [X] Dependency#name
- [X] Dependency#version
- [X] Dependency#flags
- [X] Dependency#owner
- [X] Dependency#lt?
- [X] Dependency#gt?
- [X] Dependency#eq?
- [X] Dependency#le?
- [X] Dependency#ge?
- [X] Dependency#satisfy?
- [X] Dependency#nametag
- [X] Dependency#versiontag
- [X] Dependency#flagstag
### RPM::Provide
- [X] Provide#initialize
### RPM::Require
- [X] Require#initialize
- [ ] Require#pre?
### RPM::Conflict
- [X] Conflict#initialize
### RPM::Obsolete
- [X] Obsolete#initialize
### RPM::ChangeLog
- [X] ChangeLog#time
- [X] ChangeLog#name
- [X] ChangeLog#text
### RPM::Version
- [X] Version (Comparable)
- [X] Version#initialize
- [X] Version#<=>
- [X] Version#newer?
- [X] Version#older?
- [X] Version#v
- [X] Version#r
- [X] Version#e
- [X] Version#to_s
- [X] Version#to_vre
- [X] Version#inspect
- [X] Version#hash
### RPM::File
- [X] File#initialize
- [X] File#path
- [ ] File#to_s (alias path)
- [X] File#md5sum
- [X] File#link_to
- [X] File#size
- [X] File#mtime
- [X] File#owner
- [X] File#group
- [X] File#rdev
- [X] File#mode
- [X] File#attr
- [X] File#state
- [X] File#symlink?
- [X] File#config?
- [X] File#doc?
- [X] File#donotuse?
- [X] File#missingok?
- [X] File#specfile?
- [X] File#ghost?
- [X] File#license?
- [X] File#readme?
- [X] File#exclude?
- [X] File#replaced?
- [X] File#notinstalled?
- [X] File#netshared?
### RPM::DB
- [ ] DB (Enumerable)
- [ ] DB#new
- [ ] DB#open
- [ ] DB#init
- [ ] DB#rebuild
- [ ] DB#close
- [ ] DB#closed?
- [ ] DB#root
- [ ] DB#home
- [ ] DB#writable?
- [ ] DB#each_match
- [ ] DB#each
- [ ] DB#transaction
- [ ] DB#init_iterator
- [ ] DB#dup
- [ ] DB#clone
### RPM::MatchIterator
- [X] MatchIterator (Enumerable)
- [X] MatchIterator#each
- [X] MatchIterator#next_iterator
- [X] MatchIterator#offset
- [X] MatchIterator#set_iterator_re
- [X] MatchIterator#regexp
- [X] MatchIterator#set_iterator_version
- [X] MatchIterator#version
- [X] MatchIterator#get_iterator_count
- [X] MatchIterator#length
### RPM::Transaction
- [ ] Transaction#db
- [ ] Transaction#script_file
- [ ] Transaction#script_file=
- [ ] Transaction#install
- [ ] Transaction#upgrade
- [ ] Transaction#available
- [ ] Transaction#delete
- [ ] Transaction#check
- [ ] Transaction#order
- [ ] Transaction#keys
- [ ] Transaction#commit
- [ ] Transaction#abort
- [ ] Transaction#dup
- [ ] Transaction#clone
### RPM::Source
- [ ] Source#initialize
- [ ] Source#fullname
- [ ] Source#to_s (alias fullname)
- [ ] Source#num
- [ ] Source#no?
### RPM::Patch
### RPM::Icon
### RPM::Spec
- [ ] Spec#open
- [ ] Spec#new
- [ ] Spec#buildroot
- [ ] Spec#buildsubdir
- [ ] Spec#buildarchs
- [ ] Spec#buildrequires
- [ ] Spec#build_restrictions
- [ ] Spec#sources
- [ ] Spec#packages
- [ ] Spec#build
- [ ] Spec#expand_macros
- [ ] Spec#dup
- [ ] Spec#clone
# LICENSE
* Copyright © 2011 Duncan Mac-Vicar Prett <dmacvicar@suse.de>
* Copyright © 2011 SUSE Linux Products GmbH
* This gem is a pure-ruby rewrite of ruby-rpm:
Copyright © 2002 Kenta Murata. Relicensed with his permission.
Licensed under the MIT license. See MIT-LICENSE for details.

@ -0,0 +1,52 @@
$LOAD_PATH.push(File.join(File.dirname(__FILE__), 'lib'))
require 'bundler/gem_tasks'
require 'rpm/gem_version'
require 'rake/testtask'
task default: [:test]
Rake::TestTask.new do |t|
t.libs << File.expand_path('../test', __FILE__)
t.libs << File.expand_path('../', __FILE__)
t.test_files = FileList['test/test*.rb']
t.verbose = true
t.loader = :direct
end
extra_docs = ['README*', 'TODO*', 'CHANGELOG*']
begin
require 'yard'
YARD::Rake::YardocTask.new(:doc) do |t|
t.files = ['lib/**/*.rb', *extra_docs]
t.options = ['--no-private']
end
rescue LoadError
STDERR.puts 'Install yard if you want prettier docs'
begin
require 'rdoc/task'
Rake::RDocTask.new(:doc) do |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "rpm for Ruby #{RPM::GEM_VERSION}"
extra_docs.each { |ex| rdoc.rdoc_files.include ex }
end
rescue LoadError
STDERR.puts 'rdoc not available'
end
end
desc "Build the docker images for test"
task :docker_images do
Dir.glob('_docker/Dockerfile.*').each do |dockerfile|
tag = 'ruby-rpm-ffi:' + File.extname(dockerfile).delete('.')
sh %(podman build -f #{dockerfile} -t #{tag} .)
end
end
desc "Run the tests from within the docker images"
task :docker_test do
Dir.glob('_docker/Dockerfile.*').each do |dockerfile|
tag = 'ruby-rpm-ffi:' + File.extname(dockerfile).delete('.')
sh %(podman run -ti -v #{Dir.pwd}:/src #{tag} rake test)
end
end

@ -0,0 +1,8 @@
FROM docker.io/redhat/ubi8:latest
RUN dnf module -y enable ruby:3.1
RUN dnf install -y ruby git
RUN gem install bundler
WORKDIR /src
RUN ls -lR
COPY ../ /src
RUN bundle install

@ -0,0 +1,8 @@
FROM docker.io/redhat/ubi9:latest
RUN dnf module -y enable ruby:3.3
RUN dnf install -y ruby git
RUN gem install bundler
WORKDIR /src
RUN ls -lR
COPY ../ /src
RUN bundle install

@ -0,0 +1,88 @@
require 'rpm/c'
require 'rpm/package'
require 'rpm/file'
require 'rpm/db'
require 'rpm/problem'
require 'rpm/transaction'
require 'rpm/match_iterator'
require 'rpm/version'
require 'rpm/dependency'
require 'rpm/utils'
module RPM
TAG = RPM::C::Tag
LOG = RPM::C::Log
SENSE = RPM::C::Sense
FILE = RPM::C::FileAttrs
FILE_STATE = RPM::C::FileState
TRANS_FLAG = RPM::C::TransFlags
PROB_FILTER = RPM::C::ProbFilter
MIRE = RPM::C::RegexpMode
# Creates a new transaction and pass it
# to the given block
#
# @param [String] root dir, default '/'
#
# @example
# RPM.transaction do |ts|
# ...
# end
#
def self.transaction(root = '/')
ts = Transaction.new
ts.root_dir = root
yield ts
ensure
ts.ptr.free
end
# @param [String] name Name of the macro
# @return [String] value of macro +name+
def self.[](name)
if C::rpm_version_code >= ((4 << 16) + (14 << 8) + (0 << 0))
obuf = ::FFI::MemoryPointer.new(:pointer, 1)
sbuf = FFI::MemoryPointer.from_string("%{#{name}}")
ret = RPM::C.rpmExpandMacros(nil, sbuf, obuf, 0)
raise if ret < 0
val = obuf.read_pointer
val.nil? ? nil : val.read_string
else
buffer = ::FFI::MemoryPointer.new(:pointer, 1024)
buffer.write_string("%{#{name}}")
ret = RPM::C.expandMacros(nil, nil, buffer, 1024)
raise if ret < 0
buffer.read_string
end
end
# Setup a macro
# @param [String] name Name of the macro
# @param [String] value Value of the macro or +nil+ to delete it
def self.[]=(name, value)
if value.nil?
if C::rpm_version_code >= ((4 << 16) + (14 << 8) + (0 << 0))
RPM::C.rpmPopMacro(nil, name.to_s)
else
RPM::C.delMacro(nil, name.to_s)
end
else
if C::rpm_version_code >= ((4 << 16) + (14 << 8) + (0 << 0))
RPM::C.rpmPushMacro(nil, name.to_s, '', value.to_s, RPM::C::RMIL_DEFAULT)
else
RPM::C.addMacro(nil, name.to_s, '', value.to_s, RPM::C::RMIL_DEFAULT)
end
end
end
end
RPM::C.rpmReadConfigFiles(nil, nil)
RPM::C.rpmInitMacros(nil, RPM::C.MACROFILES)
# TODO
# set verbosity
require 'rpm/compat'

@ -0,0 +1,49 @@
require 'ffi'
module RPM
module C
extend ::FFI::Library
begin
ffi_lib ['rpm',
'librpm.so.9',
'librpm.so.8', # Tumbleweed
'librpm.so.7', # fedora 23
'librpm.so.3', 'librpm.so.2', 'librpm.so.1']
rescue LoadError => e
raise(
"Can't find rpm libs on your system: #{e.message}"
)
end
end
end
require 'rpm/c/rpmtypes'
require 'rpm/c/rpmcallback'
require 'rpm/c/rpmtag'
require 'rpm/c/rpmlib'
module RPM
module C
def self.rpm_version_code
ver = ::RPM::C.RPMVERSION.split('.', 3)
return (ver[0].to_i<<16) + (ver[1].to_i<<8) + (ver[2].to_i<<0)
end
end
end
require 'rpm/c/rpmlog'
require 'rpm/c/rpmmacro'
require 'rpm/c/rpmio'
require 'rpm/c/header'
require 'rpm/c/rpmprob'
require 'rpm/c/rpmps'
require 'rpm/c/rpmfi'
require 'rpm/c/rpmdb'
require 'rpm/c/rpmcallback'
require 'rpm/c/rpmcli'
require 'rpm/c/rpmts'
require 'rpm/c/rpmds'
require 'rpm/c/rpmtd'

@ -0,0 +1,36 @@
module RPM
module C
typedef :pointer, :header
attach_function 'headerNew', [], :header
attach_function 'headerFree', [:header], :header
attach_function 'headerLink', [:header], :header
# ..
HEADERGET_DEFAULT = [0,
HEADERGET_MINMEM = (1 << 0)].freeze
HEADERGET_EXT = (1 << 1)
HEADERGET_RAW = (1 << 2)
HEADERGET_ALLOC = (1 << 3)
HEADERGET_ARGV = (1 << 4)
# ..
attach_function 'headerGet', %i[header rpmTagVal pointer uint32], :int
attach_function 'headerPut', %i[header pointer uint32], :int
# ...
attach_function 'headerFormat', %i[header string pointer], :pointer
# ...
# http://rpm.org/wiki/Releases/4.14.0 deprecated addMacro/delMacro
unless rpm_version_code >= ((4 << 16) + (14 << 8) + (0 << 0))
attach_function 'headerNVR', [:header, :pointer, :pointer, :pointer], :int
end
# ...
attach_function 'headerGetAsString', %i[header rpmTagVal], :string
# ...
attach_function 'headerPutString', %i[header rpmTagVal string], :int
# ...
attach_function 'headerPutUint32', %i[header rpmTagVal pointer rpm_count_t], :int
# ...
attach_function 'rpmReadPackageFile', %i[header FD_t string pointer], Rc
end
end

@ -0,0 +1,27 @@
module RPM
module C
CallbackType = enum(:rpmCallbackType, [
:unknown, 0,
:inst_progress, (1 << 0),
:inst_start, (1 << 1),
:inst_open_file, (1 << 2),
:inst_close_file, (1 << 3),
:trans_progress, (1 << 4),
:trans_start, (1 << 5),
:trans_stop, (1 << 6),
:uninst_progress, (1 << 7),
:uninst_start, (1 << 8),
:uninst_stop, (1 << 9),
:repackage_progress, (1 << 10),
:repackage_start, (1 << 11),
:repackage_stop, (1 << 12),
:unpack_error, (1 << 13),
:cpio_error, (1 << 14),
:script_error, (1 << 15)
])
typedef :pointer, :rpmCallbackData
callback :rpmCallbackFunction, %i[pointer rpmCallbackType rpm_loff_t rpm_loff_t fnpyKey rpmCallbackData], :pointer
end
end

@ -0,0 +1,6 @@
module RPM
module C
attach_function 'rpmShowProgress', %i[pointer rpmCallbackType rpm_loff_t rpm_loff_t fnpyKey pointer], :pointer
end
end

@ -0,0 +1,20 @@
module RPM
module C
typedef :pointer, :rpmdb
typedef :pointer, :rpmdbMatchIterator
RegexpMode = enum(:rpmMireMode, %i[
default strcmp regex glob
])
attach_function 'rpmdbCountPackages', %i[rpmdb string], :int
attach_function 'rpmdbGetIteratorOffset', [:rpmdbMatchIterator], :uint
attach_function 'rpmdbGetIteratorCount', [:rpmdbMatchIterator], :int
attach_function 'rpmdbSetIteratorRE', %i[rpmdbMatchIterator rpmTagVal rpmMireMode string], :int
attach_function 'rpmdbInitIterator', %i[rpmdb rpmDbiTagVal pointer size_t], :rpmdbMatchIterator
attach_function 'rpmdbNextIterator', [:rpmdb], :header
attach_function 'rpmdbFreeIterator', [:rpmdb], :rpmdbMatchIterator
end
end

@ -0,0 +1,43 @@
module RPM
module C
typedef :pointer, :rpmds
Sense = enum(:rpmsenseFlags_e, [
:any, 0,
:less, (1 << 1),
:greater, (1 << 2),
:equal, (1 << 3),
# bit 4 unused
:posttrans, (1 << 5),
:prereq, (1 << 6),
#
:pretrans, (1 << 7),
:interp, (1 << 8),
:script_pre, (1 << 9),
:script_post, (1 << 10),
:script_preun, (1 << 11),
:script_postun, (1 << 12),
:script_verify, (1 << 13),
:find_requires, (1 << 14),
:find_provides, (1 << 15),
#
:triggerin, (1 << 16),
:triggerun, (1 << 17),
:triggerpostun, (1 << 18),
:missingok, (1 << 19),
# 20 23 unused
:rpmlib, (1 << 24),
:triggerprein, (1 << 25),
:keyring, (1 << 26),
:strong, (1 << 27),
:config, (1 << 28)
])
typedef :rpmFlags, :rpmsenseFlags
# ...
attach_function 'rpmdsSingle', %i[rpmTagVal string string rpmsenseFlags], :rpmds
# ...
attach_function 'rpmdsCompare', %i[rpmds rpmds], :int
end
end

@ -0,0 +1,31 @@
module RPM
module C
FileAttrs = enum(:rpmfileAttrs, [
:none, 0,
:config, (1 << 0),
:doc, (1 << 1),
:icon, (1 << 2),
:missingok, (1 << 3),
:noreplace, (1 << 4),
:specfile, (1 << 5),
:ghost, (1 << 6),
:license, (1 << 7),
:readme, (1 << 8),
:exclude, (1 << 9),
:unpatched, (1 << 10),
:pubkey, (1 << 11)
])
typedef :rpmFlags, :rpmfileAttrs
FileState = enum(:rpmfileState,
:missing, -1,
:normal, 0,
:replaced, 1,
:notinstalled, 2,
:netshared, 3,
:wrongcolor, 4)
typedef :pointer, :rpmRelocation
end
end

@ -0,0 +1,19 @@
module RPM
module C
typedef :pointer, :FD_t
# RPMIO
attach_function 'Fstrerror', [:FD_t], :string
# ...
attach_function 'Fclose', [:FD_t], :int
# ...
attach_function 'Fopen', %i[string string], :FD_t
# ...
attach_function 'Ferror', [:FD_t], :int
attach_function 'fdDup', [:int], :FD_t
attach_function 'Fstrerror', [:FD_t], :string
attach_function 'fdLink', [:pointer], :FD_t
end
end

@ -0,0 +1,12 @@
module RPM
module C
attach_variable :RPMVERSION, :RPMVERSION, :string
attach_variable :RPMEVR, :rpmEVR, :string
attach_function 'rpmReadConfigFiles', %i[string string], :int
# ...
attach_function 'rpmvercmp', %i[string string], :int
end
end

@ -0,0 +1,23 @@
module RPM
module C
# rpmlog
RPMLOG_PRIMASK = 0x07
Log = enum(
:emerg, 0,
:alert, 1,
:crit, 2,
:err, 3,
:warning, 4,
:notice, 5,
:info, 6,
:debug, 7
)
attach_function 'rpmlogSetMask', [:int], :int
# TODO: defines to set verbosity
# ...
attach_function 'rpmlogMessage', [], :string
end
end

@ -0,0 +1,33 @@
module RPM
module C
attach_variable :MACROFILES, :macrofiles, :string
# ...
# Markers for sources of macros added throughout rpm.
RMIL_DEFAULT = -15
RMIL_MACROFILES = -13
RMIL_RPMRC = -11
RMIL_CMDLINE = -7
RMIL_TARBALL = -5
RMIL_SPEC = -3
RMIL_OLDSPEC = -1
RMIL_GLOBAL = 0
# ...
# http://rpm.org/wiki/Releases/4.14.0 deprecated addMacro/delMacro
if rpm_version_code >= ((4 << 16) + (14 << 8) + (0 << 0))
attach_function 'rpmPushMacro', [:pointer, :string, :string, :string, :int], :void
attach_function 'rpmPopMacro', [:pointer, :string], :void
attach_function 'rpmExpandMacros', [:pointer, :pointer, :pointer, :int], :int
else
attach_function 'addMacro', [:pointer, :string, :string, :string, :int], :void
attach_function 'delMacro', [:pointer, :string], :void
attach_function 'expandMacros', [:pointer, :pointer, :pointer, :size_t], :int
end
# ...
# ...
attach_function 'rpmInitMacros', %i[pointer string], :void
# ...
end
end

@ -0,0 +1,53 @@
module RPM
module C
typedef :pointer, :rpmProblem
ProbFilter = enum(:rpmprobFilterFlags_e, [
:none, 0,
:ignoreos, (1 << 0),
:ignorearch, (1 << 1),
:replacepkg, (1 << 2),
:forcerelocate, (1 << 3),
:replacenewfiles, (1 << 4),
:replaceoldfiles, (1 << 5),
:oldpackage, (1 << 6),
:diskspace, (1 << 7),
:disknodes, (1 << 8)
])
typedef :rpmFlags, :rpmprobFilterFlags
ProblemType = enum(:rpmProblemType, %i[
badarch
bados
pkg_installed
badrelocate
requires
conflict
new_file_conflict
file_conflict
oldpackage
diskspace
disknodes
obsoletes
])
attach_function 'rpmProblemCreate', %i[rpmProblemType string fnpyKey string string uint64], :rpmProblem
attach_function 'rpmProblemFree', [:rpmProblem], :rpmProblem
attach_function 'rpmProblemLink', [:rpmProblem], :rpmProblem
attach_function 'rpmProblemGetType', [:rpmProblem], :rpmProblemType
attach_function 'rpmProblemGetKey', [:rpmProblem], :fnpyKey
attach_function 'rpmProblemGetStr', [:rpmProblem], :string
attach_function 'rpmProblemString', [:rpmProblem], :string
begin
attach_function 'rpmProblemCompare', %i[rpmProblem rpmProblem], :int
rescue ::FFI::NotFoundError
# TODO: Implement this for librpm 4.8.
def self.rpmProblemCompare(_a, _b)
raise NotImplementedError, 'rpmProblemCompare is not present in librpm 4.8 and below'
end
end
end
end

@ -0,0 +1,11 @@
module RPM
module C
typedef :pointer, :rpmps
typedef :pointer, :rpmpsi
attach_function 'rpmpsInitIterator', [:rpmps], :rpmpsi
attach_function 'rpmpsNextIterator', [:rpmpsi], :int
attach_function 'rpmpsGetProblem', [:rpmpsi], :rpmProblem
attach_function 'rpmpsFree', [:rpmps], :rpmps
end
end

@ -0,0 +1,304 @@
module RPM
module C
Tag = enum(:rpmTag, [
:not_found, -1,
:headerimage, 61,
:headersignatures, 62,
:headerimmutable, 63,
:headerregions, 64,
:headeri18ntable, 100,
:sig_base, 256,
:sigsize, 256 + 1,
:siglemd5_1, 256 + 2,
:sigpgp, 256 + 3,
:siglemd5_2, 256 + 4,
:sigmd5, 256 + 5,
:siggpg, 256 + 6,
:sigpgp5, 256 + 7,
:badsha1_1, 256 + 8,
:badsha1_2, 256 + 9,
:pubkeys, 256 + 10,
:dsaheader, 256 + 11,
:rsaheader, 256 + 12,
:sha1header, 256 + 13,
:longsigsize, 256 + 14,
:longarchivesize, 256 + 15,
:name, 1000,
:version, 1001,
:release, 1002,
:epoch, 1003,
:summary, 1004,
:description, 1005,
:buildtime, 1006,
:buildhost, 1007,
:installtime, 1008,
:size, 1009,
:distribution, 1010,
:vendor, 1011,
:gif, 1012,
:xpm, 1013,
:license, 1014,
:packager, 1015,
:group, 1016,
:changelog, 1017,
:source, 1018,
:patch, 1019,
:url, 1020,
:os, 1021,
:arch, 1022,
:prein, 1023,
:postin, 1024,
:preun, 1025,
:postun, 1026,
:oldfilenames, 1027,
:filesizes, 1028,
:filestates, 1029,
:filemodes, 1030,
:fileuids, 1031,
:filegids, 1032,
:filerdevs, 1033,
:filemtimes, 1034,
:filedigests, 1035,
:filemd5s, 1035,
:filelinktos, 1036,
:fileflags, 1037,
:root, 1038,
:fileusername, 1039,
:filegroupname, 1040,
:exclude, 1041,
:exclusive, 1042,
:icon, 1043,
:sourcerpm, 1044,
:fileverifyflags, 1045,
:archivesize, 1046,
:providename, 1047,
:requireflags, 1048,
:requirename, 1049,
:requireversion, 1050,
:nosource, 1051,
:nopatch, 1052,
:conflictflags, 1053,
:conflictname, 1054,
:conflictversion, 1055,
:defaultprefix, 1056,
:buildroot, 1057,
:installprefix, 1058,
:excludearch, 1059,
:excludeos, 1060,
:exclusivearch, 1061,
:exclusiveos, 1062,
:autoreqprov, 1063,
:rpmversion, 1064,
:triggerscripts, 1065,
:triggername, 1066,
:triggerversion, 1067,
:triggerflags, 1068,
:triggerindex, 1069,
:verifyscript, 1079,
:changelogtime, 1080,
:changelogname, 1081,
:changelogtext, 1082,
:brokenmd5, 1083,
:prereq, 1084,
:preinprog, 1085,
:postinprog, 1086,
:preunprog, 1087,
:postunprog, 1088,
:buildarchs, 1089,
:obsoletename, 1090,
:verifyscriptprog, 1091,
:triggerscriptprog, 1092,
:docdir, 1093,
:cookie, 1094,
:filedevices, 1095,
:fileinodes, 1096,
:filelangs, 1097,
:prefixes, 1098,
:instprefixes, 1099,
:triggerin, 1100,
:triggerun, 1101,
:triggerpostun, 1102,
:autoreq, 1103,
:autoprov, 1104,
:capability, 1105,
:sourcepackage, 1106,
:oldorigfilenames, 1107,
:buildprereq, 1108,
:buildrequires, 1109,
:buildconflicts, 1110,
:buildmacros, 1111,
:provideflags, 1112,
:provideversion, 1113,
:obsoleteflags, 1114,
:obsoleteversion, 1115,
:dirindexes, 1116,
:basenames, 1117,
:dirnames, 1118,
:origdirindexes, 1119,
:origbasenames, 1120,
:origdirnames, 1121,
:optflags, 1122,
:disturl, 1123,
:payloadformat, 1124,
:payloadcompressor, 1125,
:payloadflags, 1126,
:installcolor, 1127,
:installtid, 1128,
:removetid, 1129,
:sha1rhn, 1130,
:rhnplatform, 1131,
:platform, 1132,
:patchesname, 1133,
:patchesflags, 1134,
:patchesversion, 1135,
:cachectime, 1136,
:cachepkgpath, 1137,
:cachepkgsize, 1138,
:cachepkgmtime, 1139,
:filecolors, 1140,
:fileclass, 1141,
:classdict, 1142,
:filedependsx, 1143,
:filedependsn, 1144,
:dependsdict, 1145,
:sourcepkgid, 1146,
:filecontexts, 1147,
:fscontexts, 1148,
:recontexts, 1149,
:policies, 1150,
:pretrans, 1151,
:posttrans, 1152,
:pretransprog, 1153,
:posttransprog, 1154,
:disttag, 1155,
:suggestsname, 1156,
:suggestsversion, 1157,
:suggestsflags, 1158,
:enhancesname, 1159,
:enhancesversion, 1160,
:enhancesflags, 1161,
:priority, 1162,
:cvsid, 1163,
:blinkpkgid, 1164,
:blinkhdrid, 1165,
:blinknevra, 1166,
:flinkpkgid, 1167,
:flinkhdrid, 1168,
:flinknevra, 1169,
:packageorigin, 1170,
:triggerprein, 1171,
:buildsuggests, 1172,
:buildenhances, 1173,
:scriptstates, 1174,
:scriptmetrics, 1175,
:buildcpuclock, 1176,
:filedigestalgos, 1177,
:variants, 1178,
:xmajor, 1179,
:xminor, 1180,
:repotag, 1181,
:keywords, 1182,
:buildplatforms, 1183,
:packagecolor, 1184,
:packageprefcolor, 1185,
:xattrsdict, 1186,
:filexattrsx, 1187,
:depattrsdict, 1188,
:conflictattrsx, 1189,
:obsoleteattrsx, 1190,
:provideattrsx, 1191,
:requireattrsx, 1192,
:buildprovides, 1193,
:buildobsoletes, 1194,
:dbinstance, 1195,
:nvra, 1196,
:filenames, 5000,
:fileprovide, 5001,
:filerequire, 5002,
:fsnames, 5003,
:fssizes, 5004,
:triggerconds, 5005,
:triggertype, 5006,
:origfilenames, 5007,
:longfilesizes, 5008,
:longsize, 5009,
:filecaps, 5010,
:filedigestalgo, 5011,
:bugurl, 5012,
:evr, 5013,
:nvr, 5014,
:nevr, 5015,
:nevra, 5016,
:headercolor, 5017,
:verbose, 5018,
:epochnum, 5019,
:preinflags, 5020,
:postinflags, 5021,
:preunflags, 5022,
:postunflags, 5023,
:pretransflags, 5024,
:posttransflags, 5025,
:verifyscriptflags, 5026,
:triggerscriptflags, 5027,
:collections, 5029,
:policynames, 5030,
:policytypes, 5031,
:policytypesindexes, 5032,
:policyflags, 5033,
:vcs, 5034,
:ordername, 5035,
:orderversion, 5036,
:orderflags, 5037,
:firstfree_tag
])
Dbi = enum(:rpmDbiTag_e, [
:packages, 0,
:label, 2,
:name, Tag[:name],
:basenames, Tag[:basenames],
:group, Tag[:group],
:requirename, Tag[:requirename],
:providename, Tag[:providename],
:conflictname, Tag[:conflictname],
:obsoletename, Tag[:obsoletename],
:triggername, Tag[:triggername],
:dirnames, Tag[:dirnames],
:installtid, Tag[:installtid],
:sigmd5, Tag[:sigmd5],
:sha1header, Tag[:sha1header]
])
TagType = enum(:rpmTagType, [
:null_type, 0,
:char_type, 1,
:int8_type, 2,
:int16_type, 3,
:int32_type, 4,
:int64_type, 5,
:string_type, 6,
:bin_type, 7,
:string_array_type, 8,
:i18nstring_type, 9
])
TagReturnType = enum(:rpmTagReturnType_e, [
:any_return_type, 0,
:scalar_return_type, 0x00010000,
:array_return_type, 0x00020000,
:mapping_return_type, 0x00040000,
:mask_return_type, 0xffff0000
])
typedef :rpmFlags, :rpmTagReturnType
begin
attach_function 'rpmTagGetReturnType', [:rpmTagVal], :rpmTagReturnType
rescue ::FFI::NotFoundError
attach_function 'rpmTagGetType', [:rpmTagVal], :rpmTagType
def self.rpmTagGetReturnType(tag)
TagReturnType[rpmTagGetType(tag) & TagReturnType[:mask_return_type]]
end
end
end
end

@ -0,0 +1,34 @@
module RPM
module C
typedef :pointer, :rpmtd
attach_function 'rpmtdNew', [], :pointer
attach_function 'rpmtdFree', [:rpmtd], :pointer
attach_function 'rpmtdReset', [:rpmtd], :void
attach_function 'rpmtdFreeData', [:rpmtd], :void
attach_function 'rpmtdCount', [:rpmtd], :uint32
attach_function 'rpmtdTag', [:rpmtd], :rpmTagVal
attach_function 'rpmtdType', [:rpmtd], TagType
# ...
attach_function 'rpmtdInit', [:rpmtd], :int
attach_function 'rpmtdNext', [:rpmtd], :int
# ...
attach_function 'rpmtdNextUint32', [:rpmtd], :pointer
attach_function 'rpmtdNextUint64', [:rpmtd], :pointer
attach_function 'rpmtdNextString', [:rpmtd], :string
attach_function 'rpmtdGetChar', [:rpmtd], :pointer
attach_function 'rpmtdGetUint16', [:rpmtd], :pointer
attach_function 'rpmtdGetUint32', [:rpmtd], :pointer
attach_function 'rpmtdGetUint64', [:rpmtd], :pointer
attach_function 'rpmtdGetString', [:rpmtd], :string
attach_function 'rpmtdGetNumber', [:rpmtd], :uint64
# ...
attach_function 'rpmtdFromUint8', %i[rpmtd rpmTagVal pointer rpm_count_t], :int
attach_function 'rpmtdFromUint16', %i[rpmtd rpmTagVal pointer rpm_count_t], :int
attach_function 'rpmtdFromUint32', %i[rpmtd rpmTagVal pointer rpm_count_t], :int
attach_function 'rpmtdFromUint64', %i[rpmtd rpmTagVal pointer rpm_count_t], :int
attach_function 'rpmtdFromString', %i[rpmtd rpmTagVal string], :int
attach_function 'rpmtdFromStringArray', %i[rpmtd rpmTagVal pointer rpm_count_t], :int
# ...
end
end

@ -0,0 +1,71 @@
module RPM
module C
TransFlags = enum(:rpmtransFlags_e, [
:none, 0,
:test, (1 << 0),
:build_probs, (1 << 1),
:noscripts, (1 << 2),
:justdb, (1 << 3),
:notriggers, (1 << 4),
:nodocs, (1 << 5),
:allfiles, (1 << 6),
# bit 7 unused
:nocontexts, (1 << 8),
# bits 9-15 unused
:notriggerprein, (1 << 16),
:nopre, (1 << 17),
:nopost, (1 << 18),
:notriggerin, (1 << 19),
:notriggerun, (1 << 20),
:nopreun, (1 << 21),
:nopostun, (1 << 22),
:notriggerpostun, (11 << 23),
# bits 24-25 unused
:nocollections, (1 << 26),
:nomd5, (1 << 27),
:nofiledigest, (1 << 27),
# bits 28-29 unused
:noconfigs, (1 << 30),
:deploops, (1 << 31)
])
typedef :pointer, :rpmts
typedef :pointer, :rpmps
typedef :rpmFlags, :rpmtransFlags
attach_function 'rpmtsCheck', [:rpmts], :int
attach_function 'rpmtsOrder', [:rpmts], :int
attach_function 'rpmtsRun', %i[rpmts rpmps int], :int
attach_function 'rpmtsLink', [:rpmts], :rpmts
attach_function 'rpmtsCloseDB', [:rpmts], :int
attach_function 'rpmtsOpenDB', %i[rpmts int], :int
attach_function 'rpmtsInitDB', %i[rpmts int], :int
attach_function 'rpmtsGetDBMode', [:rpmts], :int
attach_function 'rpmtsSetDBMode', %i[rpmts int], :int
attach_function 'rpmtsRebuildDB', [:rpmts], :int
attach_function 'rpmtsVerifyDB', [:rpmts], :int
attach_function 'rpmtsInitIterator', %i[rpmts rpmDbiTagVal pointer int], :rpmdbMatchIterator
# ...
attach_function 'rpmtsProblems', [:rpmts], :rpmps
# ...
attach_function 'rpmtsClean', [:rpmts], :void
# more...
attach_function 'rpmtsFree', [:rpmts], :pointer
# ..
attach_function 'rpmtsSetNotifyCallback', %i[rpmts rpmCallbackFunction rpmCallbackData], :int
# ...
attach_function 'rpmtsRootDir', [:rpmts], :string
attach_function 'rpmtsSetRootDir', %i[rpmts string], :int
# ...
attach_function 'rpmtsGetRdb', [:rpmts], :rpmdb
# ..
attach_function 'rpmtsFlags', [:rpmts], :rpmtransFlags
attach_function 'rpmtsSetFlags', %i[rpmts rpmtransFlags], :rpmtransFlags
# ...
attach_function 'rpmtsSetNotifyCallback', %i[rpmts rpmCallbackFunction rpmCallbackData], :int
# ...
attach_function 'rpmtsCreate', [], :rpmts
attach_function 'rpmtsAddInstallElement', %i[rpmts header fnpyKey int rpmRelocation], :int
attach_function 'rpmtsAddEraseElement', %i[rpmts header int], :int
end
end

@ -0,0 +1,28 @@
module RPM
module C
Rc = enum(
:ok, 0,
:notfound, 1,
:fail, 2,
:nottrusted, 3,
:nokey, 4
)
typedef :int32, :rpm_tag_t
typedef :uint32, :rpm_tagtype_t
typedef :uint32, :rpm_count_t
typedef :rpm_tag_t, :rpmTagVal
typedef :rpm_tag_t, :rpmDbiTagVal
typedef :uint32, :rpmFlags
typedef :uint32, :rpm_off_t
typedef :uint64, :rpm_loff_t
typedef :pointer, :FD_t
typedef :pointer, :fnpyKey
typedef :pointer, :rpmCallbackData
typedef :uint64, :rpm_loff_t
end
end

@ -0,0 +1,40 @@
module RPM
# compatibility
TAG.to_h.each do |k, v|
const_set "TAG_#{k.to_s.upcase}", v.to_i
end
LOG.to_h.each do |k, v|
const_set "LOG_#{k.to_s.upcase}", v.to_i
end
SENSE.to_h.each do |k, v|
const_set "SENSE_#{k.to_s.upcase}", v.to_i
end
# RPMFILE_*
FILE.to_h.each do |k, v|
const_set "FILE_#{k.to_s.upcase}", v.to_i
end
# RPMFILE_STATE_*
FILE_STATE.to_h.each do |k, v|
const_set "FILE_STATE_#{k.to_s.upcase}", v.to_i
end
# RPMTRANS_FLAG_*
TRANS_FLAG.to_h.each do |k, v|
const_set "TRANS_FLAG_#{k.to_s.upcase}", v.to_i
end
# RPMPROB_FILTER_*
PROB_FILTER.to_h.each do |k, v|
const_set "PROB_FILTER_#{k.to_s.upcase}", v.to_i
end
# RPMPROB_FILTER_*
MIRE.to_h.each do |k, v|
const_set "MIRE_#{k.to_s.upcase}", v.to_i
end
end

@ -0,0 +1,117 @@
require 'fcntl'
module RPM
class DB
include Enumerable
# @visibility private
# @param ts [Transaction] transaction object
def initialize(ts, opts = {})
opts[:writable] ||= false
@ts = ts
RPM::C.rpmtsOpenDB(@ts.ptr, opts[:writable] ? Fcntl::O_RDWR | Fcntl::O_CREAT : Fcntl::O_RDONLY)
end
# @return [RPM::MatchIterator] Creates an iterator for +tag+ and +val+
def init_iterator(tag, val)
@ts.init_iterator(tag, val)
end
#
# @yield [Package] Called for each match
# @param [Number] key RPM tag key
# @param [String] val Value to match
# @example
# RPM.transaction do |t|
# t.each_match(RPM::TAG_ARCH, "x86_64") do |pkg|
# puts pkg.name
# end
# end
#
def each_match(key, val, &block)
@ts.each_match(key, val, &block)
end
#
# @yield [Package] Called for each package in the database
# @example
# db.each do |pkg|
# puts pkg.name
# end
#
def each(&block)
@ts.each(&block)
end
# @visibility private
def ptr
RPM::C.rpmtsGetRdb(@ts.ptr)
end
def close
RPM::C.rpmtsCloseDB(@ts.ptr)
end
def closed?
ptr.null?
end
#
# The package database is opened, but transactional processing
# (@see RPM::DB#transaction) cannot be done for when +writable+ is false.
# When +writable+ is +false+ then the generated object gets freezed.
# @param [Boolean] writable Whether the database is writable. Default is +false+.
# @param [String] root Root path for the database, default is empty.
# @return [RPM::DB]
#
# @example
# db = RPM::DB.open
# db.each do |pkg|
# puts pkg.name
# end
#
def self.open(_writable = false, root = '/', &block)
open_for_transaction(Transaction.new(root: root), writable: false, &block)
end
# @visibility private
def self.open_for_transaction(ts, opts = {})
db = new(ts, opts)
return db unless block_given?
begin
yield db
ensure
db.close unless db.closed?
end
end
# @deprecated Not possible to get home value in
# newer RPM versions
def home
raise NotImplementedError
end
# @return [String] The root path of the database
def root
RPM::C.rpmtsRootDir(@ts.ptr)
end
# @deprecated Use RPM::Transaction#each
def self.each
DB.open do |db|
it = MatchIterator.from_ptr(RPM::C.rpmdbInitIterator(db.ptr, 0, nil, 0))
if block_given?
it.each do |pkg|
yield pkg
end
end
end
end
# @return number of instances of +name+ in the
# database
def count_packages(name); end
end
end

@ -0,0 +1,120 @@
module RPM
class Dependency
# @return [String] dependency name
attr_accessor :name
# @return [String] dependency version
attr_accessor :version
# @return [String] dependency flags
attr_accessor :flags
# @return [Package] package this dependency belongs to
attr_accessor :owner
attr_accessor :nametag
attr_accessor :versiontag
attr_accessor :flagstag
def initialize(name, version, flags, owner)
RPM::Utils.check_type(version, RPM::Version)
@name = name
@version = version
@flags = flags
@owner = owner
end
# @param [Package, Dependency, Version] other
# @return [Boolean] true if +other+ satisfies this dependency
def satisfy?(other)
case other
when RPM::Package then
other.provides.each do |prov|
return true if satisfy?(prov)
end
false
when RPM::Dependency then
RPM::C.rpmdsCompare(
RPM::C.rpmdsSingle(:providename, other.name,
other.version.to_vre, other.flags),
RPM::C.rpmdsSingle(:providename, name,
version.to_vre, flags)
) != 0
when RPM::Version then
RPM::C.rpmdsCompare(
RPM::C.rpmdsSingle(:providename, name,
other.to_vre, other.to_vre.empty? ? 0 : :equal),
RPM::C.rpmdsSingle(:providename, name,
version.to_vre, flags)
) != 0
else
raise(TypeError, "#{other} is not a Version or Dependency")
end
end
# @return [Boolean] true if '<' or '=<' are used to compare the version
def lt?
flags & RPM::SENSE[:less]
end
# @return [Boolean] true if '>' or '>=' are used to compare the version
def gt?
flags & RPM::SENSE[:greater]
end
# @return [Boolean] true if '=', '=<' or '>=' are used to compare the version
def eq?
flags & RPM::SENSE[:equal]
end
# @return [Boolean] true if '=<' is used to compare the version
def le?
(flags & RPM::SENSE[:less]) && (flags & RPM::SENSE[:equal])
end
# @return [Boolean] true if '>=' is used to compare the version
def ge?
(flags & RPM::SENSE[:greater]) && (flags & RPM::SENSE[:equal])
end
# @return [Boolean] true if this is a pre-requires
def pre?
flags & RPM::SENSE[:prereq]
end
end
class Provide < Dependency
def initialize(name, version, flags, owner)
super(name, version, flags, owner)
@nametag = RPM::TAG[:providename]
@versiontag = RPM::TAG[:provideversion]
@flagstag = RPM::TAG[:provideflags]
end
end
class Require < Dependency
def initialize(name, version, flags, owner)
super(name, version, flags, owner)
@nametag = RPM::TAG[:requirename]
@versiontag = RPM::TAG[:requireversion]
@flagstag = RPM::TAG[:requireflags]
end
end
class Conflict < Dependency
def initialize(name, version, flags, owner)
super(name, version, flags, owner)
@nametag = RPM::TAG[:conflictname]
@versiontag = RPM::TAG[:conflictversion]
@flagstag = RPM::TAG[:conflictflags]
end
end
class Obsolete < Dependency
def initialize(name, version, flags, owner)
super(name, version, flags, owner)
@nametag = RPM::TAG[:obsoletename]
@versiontag = RPM::TAG[:obsoleteversion]
@flagstag = RPM::TAG[:obsoleteflags]
end
end
end

@ -0,0 +1,134 @@
# coding: utf-8
module RPM
class File
# @return [String] file path
attr_accessor :path
# @return [String] md5sum as string
attr_accessor :md5sum
# @return [String] Path to the destination if the file is a symbolic link
# @note
# This path is sometimes relative. To convert an absolute path from relative path:
# File.expand_path (file.link_to, File.dirname (file.path))
attr_accessor :link_to
# @return [Number] File size
attr_accessor :size
# @return [Time] File modification time.
attr_accessor :mtime
# @return [String] File owner. Nil may be returned.
attr_accessor :owner
# @return [String] Group that owns the file. Nil may be returned.
attr_accessor :group
# @return [Number] Device type of the file
attr_accessor :mode
attr_accessor :attr
attr_accessor :state
attr_accessor :rdev
# @return [Boolean] True if the file is a symbolic link
def symlink?
!@link_to.nil?
end
# @return [Boolean] True if the file is marked as a configuration file
def config?
!(@attr & RPM::C::FileAttrs[:config]).zero?
end
# @return [Boolean] True if the file is marked as documentation
def doc?
!(@attr & RPM::C::FileAttrs[:doc]).zero?
end
# @return [Boolean] True if the file is marked as do not use
# @deprecated RPMFILE_DONOTUSE was removed in recent versions of RPM.
def donotuse?
msg = 'RPMFILE_DONOTUSE was removed in recent versions of RPM.'
warn "#{Kernel.caller.first} #{msg}"
raise NotImplementedError
end
# @return [Boolean] True if the file is marked that can be missing on disk
#
# This modifier is used for files or links that are created during the %post scripts
# but will need to be removed if the package is removed
def is_missingok?
!(@attr & RPM::C::FileAttrs[:missingok]).zero?
end
# @return [Boolean] True if the file is marked as configuration not to be replaced
#
# This flag is used to protect local modifications.
# If used, the file will not overwrite an existing file that has been modified.
# If the file has not been modified on disk, the rpm command will overwrite the file. But,
# if the file has been modified on disk, the rpm command will copy the new file with an extra
# file-name extension of .rpmnew.
def is_noreplace?
!(@attr & RPM::C::FileAttrs[:noreplace]).zero?
end
# @return [Boolean] True if the file is marked as a spec file
def is_specfile?
!(@attr & RPM::C::FileAttrs[:specfile]).zero?
end
# @return [Boolean] True if the file is marked as ghost
#
# This flag indicates the file should not be included in the package.
# It can be used to name the needed attributes for a file that the program, when installed,
# will create.
# For example, you may want to ensure that a programs log file has certain attributes.
def ghost?
!(@attr & RPM::C::FileAttrs[:ghost]).zero?
end
# @return [Boolean] True if the file is a license
def license?
!(@attr & RPM::C::FileAttrs[:license]).zero?
end
# @return [Boolean] True if the file is a README
def readme?
!(@attr & RPM::C::FileAttrs[:readme]).zero?
end
# @raise NotImplementedError
# @deprecated RPMFILE_EXCLUDE was removed in recent versions of RPM.
def exclude?
msg = 'RPMFILE_EXCLUDE was removed in recent versions of RPM.'
warn "#{Kernel.caller.first} #{msg}"
raise NotImplementedError
end
# @return [Boolean] True if the file is replaced during installation
def replaced?
!(@attr & RPM::C::FileState[:replaced]).zero?
end
# @return [Boolean] True if the file is not installed
def notinstalled?
!(@attr & RPM::C::FileState[:notinstalled]).zero?
end
# @return [Boolean] True if the file is shared over the network
def netshared?
!(@attr & RPM::C::FileState[:netshared]).zero?
end
def initialize(path, md5sum, link_to, size, mtime, owner, group, rdev, mode, attr, state)
@path = path
@md5sum = md5sum
# If link_to is "" save it as nil
@link_to = (link_to && link_to.empty? ? nil : link_to)
@size = size
@mtime = mtime
@owner = owner
@group = group
@rdev = rdev
@mode = mode
@attr = attr
@state = state
end
end
end

@ -0,0 +1,7 @@
# The reason this file is gem_version.rb and not version.rb
# is because it conflicts with the version.rb class
module RPM
PKG_NAME = 'ruby-rpm'.freeze
GEM_VERSION = '0.0.5'.freeze
end

@ -0,0 +1,66 @@
module RPM
class MatchIterator
include Enumerable
# @visibility private
def self.release(ptr)
RPM::C.rpmdbFreeIterator(ptr)
end
# Creates a managed MatchIterator from a raw pointer
# @visibility private
def self.from_ptr(ptr)
new(::FFI::AutoPointer.new(ptr, MatchIterator.method(:release)))
end
def initialize(ptr)
@ptr = ptr
end
def each
while (pkg = next_iterator)
yield pkg
end
end
def next_iterator
pkg_ptr = RPM::C.rpmdbNextIterator(@ptr)
return RPM::Package.new(pkg_ptr) unless pkg_ptr.null?
nil
end
# @ return header join key for current position of rpm
# database iterator
def offset
RPM::C.rpmdbGetIteratorOffset(@ptr)
end
def set_iterator_re(tag, mode, string)
ret = RPM::C.rpmdbSetIteratorRE(@ptr, tag, mode, string)
raise "Error when setting regular expression '#{string}'" if ret != 0
self
end
alias regexp set_iterator_re
def set_iterator_version(version)
unless version.is_a?(RPM::Version)
raise TypeError, 'illegal argument type'
end
set_iterator_re(:version, :default, version.v)
set_iterator_re(:release, :default, version.r) if version.r
self
end
alias version set_iterator_version
def get_iterator_count
RPM::C.rpmdbGetIteratorCount(@ptr)
end
alias count get_iterator_count
alias length get_iterator_count
end
end

@ -0,0 +1,333 @@
module RPM
class ChangeLog
attr_accessor :time, :name, :text
end
class Package
# Create a new package object from data
# @param [String] str Header data
# @return [Package]
def load(_data)
raise NotImplementedError
end
def self.create(name, version)
unless name.is_a?(String)
raise TypeError, 'illegal argument type: name should be String'
end
unless version.is_a?(RPM::Version)
raise TypeError, 'illegal argument type: version should be RPM::Version'
end
hdr = RPM::C.headerNew
if RPM::C.headerPutString(hdr, :name, name) != 1
raise "Can't set package name: #{name}"
end
if RPM::C.headerPutString(hdr, :version, version.v) != 1
raise "Can't set package version: #{version.v}"
end
if version.e
if RPM::C.headerPutUint32(hdr, :epoch, version.e) != 1
raise "Can't set package epoch: #{version.e}"
end
end
Package.new(hdr)
end
# Add a dependency to the package header
# @param [Dependency] dep Dependency to add
def add_dependency(dep)
unless dep.is_a?(Dependency)
raise TypeError, 'illegal argument type: must be a Dependency'
end
raise NotImplementedError
end
# Add a int32 value to the package header
# @param [Number] tag Tag
# @param [Number] val Value
def add_int32(_tag, _val)
raise NotImplementedError
end
# Add a list of strings to the package header
# @param [Number] tag Tag
# @param [Array<String>] val Strings to add
def add_string_array(_tag, _val)
raise NotImplementedError
end
# Add a binary value to the package header
# @param [Number] tag Tag
# @param [String] val String to add
def add_string(_tag, _val)
raise NotImplementedError
end
# Add a binary value to the package header
# @param [Number] tag Tag
# @param [String] val Value
def add_binary(_tag, _val)
raise NotImplementedError
end
# Deletes a tag of the package header
# @param [Number] tag Tag
def delete_tag(_tag)
raise NotImplementedError
end
# @return a formated string
# @example
# pkg.sprintf("%{name}") => "apache2"
def sprintf(fmt)
error = ::FFI::MemoryPointer.new(:pointer, 1)
val = RPM::C.headerFormat(@hdr, fmt, error)
raise error.get_pointer(0).read_string if val.null?
val.read_string
end
# @return [Number] This package signature
def signature
sprintf('%{sigmd5}')
end
# @return [Array<RPM::File>] File list for this package
def files
basenames = self[:basenames]
return [] if basenames.nil?
dirnames = self[:dirnames]
diridxs = self[:dirindexes]
statelist = self[:filestates]
flaglist = self[:fileflags]
sizelist = self[:filesizes]
modelist = self[:filemodes]
mtimelist = self[:filemtimes]
rdevlist = self[:filerdevs]
linklist = self[:filelinktos]
md5list = self[:filemd5s]
ownerlist = self[:fileusername]
grouplist = self[:filegroupname]
ret = []
basenames.each_with_index do |_basename, i|
file = RPM::File.new("#{dirnames[diridxs[i]]}#{basenames[i]}",
md5list[i],
linklist[i],
sizelist[i],
mtimelist[i],
ownerlist[i],
grouplist[i],
rdevlist[i],
modelist[i],
flaglist.nil? ? RPM::C::FileAttrs[:none] : flaglist[i],
statelist.nil? ? RPM::C::FileState[:normal] : statelist[i])
ret << file
end
ret
end
# @return [Array<RPM::Dependency>] Dependencies for +klass+
# @example
# dependencies(RPM::Provide, :providename, :provideversion, :provideflags)
#
# @visibility private
def dependencies(klass, nametag, versiontag, flagtag)
deps = []
nametd = ::FFI::AutoPointer.new(RPM::C.rpmtdNew, Package.method(:release_td))
versiontd = ::FFI::AutoPointer.new(RPM::C.rpmtdNew, Package.method(:release_td))
flagtd = ::FFI::AutoPointer.new(RPM::C.rpmtdNew, Package.method(:release_td))
min = RPM::C::HEADERGET_MINMEM
return deps if RPM::C.headerGet(@hdr, nametag, nametd, min) != 1
return deps if RPM::C.headerGet(@hdr, versiontag, versiontd, min) != 1
return deps if RPM::C.headerGet(@hdr, flagtag, flagtd, min) != 1
RPM::C.rpmtdInit(nametd)
while RPM::C.rpmtdNext(nametd) != -1
deps << klass.new(RPM::C.rpmtdGetString(nametd),
RPM::Version.new(RPM::C.rpmtdNextString(versiontd)),
RPM::C.rpmtdNextUint32(flagtd).read_uint, self)
end
deps
end
# @return [Array<RPM::Provide>] Provides list for this package
def provides
dependencies(RPM::Provide, :providename, :provideversion, :provideflags)
end
# @return [Array<RPM::Require>] Requires list for this package
def requires
dependencies(RPM::Require, :requirename, :requireversion, :requireflags)
end
# @return [Array<RPM::Conflicts>] Conflicts list for this package
def conflicts
dependencies(RPM::Conflict, :conflictname, :conflictversion, :conflictflags)
end
# @return [Array<RPM::Obsolete>] Obsoletes list for this package
def obsoletes
dependencies(RPM::Obsolete, :obsoletename, :obsoleteversion, :obsoleteflags)
end
# @return [Array<RPM::Changelog>] changelog of the package as an array
def changelog
entries = []
nametd = ::FFI::AutoPointer.new(RPM::C.rpmtdNew, Package.method(:release_td))
timetd = ::FFI::AutoPointer.new(RPM::C.rpmtdNew, Package.method(:release_td))
texttd = ::FFI::AutoPointer.new(RPM::C.rpmtdNew, Package.method(:release_td))
min = RPM::C::HEADERGET_MINMEM
return deps if RPM::C.headerGet(@hdr, :changelogtime, timetd, min) != 1
return deps if RPM::C.headerGet(@hdr, :changelogname, nametd, min) != 1
return deps if RPM::C.headerGet(@hdr, :changelogtext, texttd, min) != 1
RPM::C.rpmtdInit(timetd)
while RPM::C.rpmtdNext(timetd) != -1
entry = RPM::ChangeLog.new
entry.time = RPM::C.rpmtdGetUint32(timetd)
entry.name = RPM::C.rpmtdNextString(nametd)
entry.text = RPM::C.rpmtdNextString(texttd)
entries << entry
end
entries
end
# Access a header entry
# @param [Number] tag Tag to return
# @return [] Value of the entry
# @example
# pkg => #<RPM::Package name="xmlgraphics-fop", version=#<RPM::Version v="1.0", r="22.4">>
# pkg[:name] => "xmlgraphics-fop"
#
# or if you have the old ruby-rpm compat loaded
#
# require 'rpm/compat'
# pkg[RPM::TAG_NAME] => "xmlgraphics-fop"
#
# @return [String, Fixnum, Array<String>, Array<Fixnum>, nil]
# The value of the entry
def [](tag)
val = nil
tagc = ::FFI::AutoPointer.new(RPM::C.rpmtdNew, Package.method(:release_td))
return nil if RPM::C.headerGet(ptr, tag, tagc,
RPM::C::HEADERGET_MINMEM) == 0
type = RPM::C.rpmtdType(tagc)
count = RPM::C.rpmtdCount(tagc)
ret_type = RPM::C.rpmTagGetReturnType(tag)
method_name = case type
when :int8_type, :char_type, :int16_type, :int32_type, :int64_type then :rpmtdGetNumber
when :string_type, :string_array_type, :bin_type then :rpmtdGetString
else raise NotImplementedError, "Don't know how to retrieve type '#{type}'"
end
is_array = if count > 1 then true
elsif ret_type == :array_return_type then true
elsif type == :string_array_type then true
else false
end
if is_array
ret = []
RPM::C.rpmtdInit(tagc)
ret << RPM::C.send(method_name, tagc) while RPM::C.rpmtdNext(tagc) != -1
return ret
end
RPM::C.send(method_name, tagc)
end
# @return [String] This package name
def name
self[:name]
end
# @return [String] This package architecture
def arch
self[:arch]
end
# TODO: signature
# @return [Version] Version for this package
def version
Version.new(self[:version], self[:release], self[:epoch])
end
# String representation of the package: "name-version-release-arch"
# @return [String]
def to_s
return '' if name.nil?
return name if version.nil?
return "#{name}-#{version}" if arch.nil?
"#{name}-#{version}-#{arch}"
end
def self.open(filename)
Package.new(filename)
end
# @visibility private
def self.release(ptr)
RPM::C.headerFree(ptr)
end
# @visibility private
def self.release_td(ptr)
RPM::C.rpmtdFree(ptr)
end
# @visibility private
def initialize(what)
case what
when String then initialize_from_filename(what)
else initialize_from_header(what)
end
end
# @visibility private
def initialize_from_header(hdr = nil)
if hdr.nil?
@hdr = ::FFI::AutoPointer.new(RPM::C.headerNew, Header.method(:release))
elsif hdr.is_a?(::FFI::Pointer)
# ref
hdr = RPM::C.headerLink(hdr)
@hdr = ::FFI::AutoPointer.new(hdr, Package.method(:release))
else
raise "Can't initialize header with '#{hdr}'"
end
end
def initialize_from_filename(filename)
# it sucks not using the std File.open here
hdr = ::FFI::MemoryPointer.new(:pointer)
fd = nil
begin
fd = RPM::C.Fopen(filename, 'r')
raise "#{filename} : #{RPM::C.Fstrerror(fd)}" if RPM::C.Ferror(fd) != 0
RPM.transaction do |ts|
rc = RPM::C.rpmReadPackageFile(ts.ptr, fd, filename, hdr)
end
ensure
RPM::C.Fclose(fd) unless fd.nil?
end
initialize_from_header(hdr.get_pointer(0))
end
# @return [RPM::C::Header] header pointer
# @visibility private
def ptr
@hdr
end
end
end

@ -0,0 +1,65 @@
module RPM
class Problem
def self.release(ptr)
RPM::C.rpmProblemFree(ptr)
end
# Creates a problem from an existing C pointer, refcounting it
# first.
# @param [FFI::Pointer] ptr existing C pointer
# @return [RPM::Problem] wrapped object
def self.from_ptr(ptr)
case ptr
when FFI::Pointer
new(FFI::AutoPointer.new(RPM::C.rpmProblemLink(ptr), Problem.method(:release)))
else
raise "Can't initialize header with '#{ptr}'"
end
end
# Create a problem item.
# @param [RPM::ProblemType] type problem type
# @param [String] pkg_nver name-version-edition-release of the related package
# @param [String] key key of the related package
# @param [String] alt_nver name-version-edition-release of the other related package
# @param [String] str generic data string from a problem
def self.create(type, pkg_nevr, key, alt_nevr, str, number)
ptr = ::FFI::AutoPointer.new(RPM::C.rpmProblemCreate(type, pkg_nevr, key, alt_nevr, str, number), Problem.method(:release))
new(ptr)
end
# @visibility private
def initialize(ptr)
@ptr = ptr
end
# @return [RPM::ProblemType] type of problem (dependency, diskpace etc).
def type
RPM::C.rpmProblemGetType(@ptr)
end
# @return [String] filename or python object address of a problem.
def key
RPM::C.rpmProblemGetKey(@ptr).read_string
end
# @return [String] a generic data string from a problem.
def str
RPM::C.rpmProblemGetStr(@ptr)
end
# @return [String] formatted string representation of a problem
def to_s
RPM::C.rpmProblemString(@ptr)
end
# @return [Fixnum] compare two problems for equality.
def <=>(other)
RPM::C.rpmProblemCompare(@ptr, other.ptr)
end
# @visibility private
attr_reader :ptr
end
end

@ -0,0 +1,270 @@
module RPM
CallbackData = Struct.new(:type, :key, :package, :amount, :total) do
def to_s
"#{type} #{key} #{package} #{amount} #{total}"
end
end
class Transaction
def self.release(ptr)
RPM::C.rpmtsFree(ptr)
end
def initialize(opts = {})
# http://markmail.org/message/ypsiqxop442p7rzz
# The key pointer needs to stay valid during commit
# so we keep a reference to them mapping from
# object_id to ruby object.
@keys = {}
opts[:root] ||= '/'
@ptr = ::FFI::AutoPointer.new(RPM::C.rpmtsCreate, Transaction.method(:release))
RPM::C.rpmtsSetRootDir(@ptr, opts[:root])
end
# @return [RPM::MatchIterator] Creates an iterator for +tag+ and +val+
def init_iterator(tag, val)
raise TypeError if val && !val.is_a?(String)
it_ptr = RPM::C.rpmtsInitIterator(@ptr, tag.nil? ? 0 : tag, val, 0)
raise "Can't init iterator for [#{tag}] -> '#{val}'" if it_ptr.null?
MatchIterator.from_ptr(it_ptr)
end
# @visibility private
attr_reader :ptr
#
# @yield [Package] Called for each match
# @param [Number] key RPM tag key
# @param [String] val Value to match
# @example
# RPM.transaction do |t|
# t.each_match(RPM::TAG_ARCH, "x86_64") do |pkg|
# puts pkg.name
# end
# end
#
def each_match(key, val, &block)
it = init_iterator(key, val)
return it unless block_given?
it.each(&block)
end
#
# @yield [Package] Called for each package in the database
# @example
# db.each do |pkg|
# puts pkg.name
# end
#
def each(&block)
each_match(0, nil, &block)
end
# Add a install operation to the transaction
# @param [Package] pkg Package to install
# @param [String] key e.g. filename where to install from
def install(pkg, key)
install_element(pkg, key, upgrade: false)
end
# Add an upgrade operation to the transaction
# @param [Package] pkg Package to upgrade
# @param [String] key e.g. filename where to install from
def upgrade(pkg, key)
install_element(pkg, key, upgrade: true)
end
# Add a delete operation to the transaction
# @param [String, Package, Dependency] pkg Package to delete
def delete(pkg)
iterator = case pkg
when Package
pkg[:sigmd5] ? each_match(:sigmd5, pkg[:sigmd5]) : each_match(:label, pkg[:label])
when String
each_match(:label, pkg)
when Dependency
each_match(:label, pkg.name).set_iterator_version(pkg.version)
else
raise TypeError, 'illegal argument type'
end
iterator.each do |header|
ret = RPM::C.rpmtsAddEraseElement(@ptr, header.ptr, iterator.offset)
raise "Error while adding erase/#{pkg} to transaction" if ret != 0
end
end
# Sets the root directory for this transaction
# @param [String] root directory
def root_dir=(dir)
rc = RPM::C.rpmtsSetRootDir(@ptr, dir)
raise "Can't set #{dir} as root directory" if rc < 0
end
# @return [String ] the root directory for this transaction
def root_dir
RPM::C.rpmtsRootDir(@ptr)
end
def flags=(fl)
RPM::C.rpmtsSetFlags(@ptr, fl)
end
def flags
RPM::C.rpmtsFlags(@ptr)
end
# Determine package order in the transaction according to dependencies
#
# The final order ends up as installed packages followed by removed
# packages, with packages removed for upgrades immediately following
# the new package to be installed.
#
# @returns [Fixnum] no. of (added) packages that could not be ordered
def order
RPM::C.rpmtsOrder(@ptr)
end
# Free memory needed only for dependency checks and ordering.
def clean
RPM::C.rpmtsClean(@ptr)
end
def check
rc = RPM::C.rpmtsCheck(@ptr)
probs = RPM::C.rpmtsProblems(@ptr)
return if rc < 0
begin
psi = RPM::C.rpmpsInitIterator(probs)
while RPM::C.rpmpsNextIterator(psi) >= 0
problem = Problem.from_ptr(RPM::C.rpmpsGetProblem(psi))
yield problem
end
ensure
RPM::C.rpmpsFree(probs)
end
end
# Performs the transaction.
# @param [Number] flag Transaction flags, default +RPM::TRANS_FLAG_NONE+
# @param [Number] filter Transaction filter, default +RPM::PROB_FILTER_NONE+
# @example
# transaction.commit
# You can supply your own callback
# @example
# transaction.commit do |data|
# end
# end
# @yield [CallbackData] sig Transaction progress
def commit
flags = RPM::C::TransFlags[:none]
callback = proc do |hdr, type, amount, total, key_ptr, data_ignored|
key_id = key_ptr.address
key = @keys.include?(key_id) ? @keys[key_id] : nil
if block_given?
package = hdr.null? ? nil : Package.new(hdr)
data = CallbackData.new(type, key, package, amount, total)
yield(data)
else
RPM::C.rpmShowProgress(hdr, type, amount, total, key, data_ignored)
end
end
# We create a callback to pass to the C method and we
# call the user supplied callback from there
#
# The C callback expects you to return a file handle,
# We expect from the user to get a File, which we
# then convert to a file handle to return.
callback = proc do |hdr, type, amount, total, key_ptr, data_ignored|
key_id = key_ptr.address
key = @keys.include?(key_id) ? @keys[key_id] : nil
if block_given?
package = hdr.null? ? nil : Package.new(hdr)
data = CallbackData.new(type, key, package, amount, total)
ret = yield(data)
# For OPEN_FILE we need to do some type conversion
# for certain callback types we need to do some
case type
when :inst_open_file
# For :inst_open_file the user callback has to
# return the open file
unless ret.is_a?(::File)
raise TypeError, "illegal return value type #{ret.class}. Expected File."
end
fdt = RPM::C.fdDup(ret.to_i)
if fdt.null? || RPM::C.Ferror(fdt) != 0
raise "Can't use opened file #{data.key}: #{RPM::C.Fstrerror(fdt)}"
RPM::C.Fclose(fdt) unless fdt.nil?
else
fdt = RPM::C.fdLink(fdt)
@fdt = fdt
end
# return the (RPM type) file handle
fdt
when :inst_close_file
fdt = @fdt
RPM::C.Fclose(fdt)
@fdt = nil
else
ret
end
else
# No custom callback given, use the default to show progress
RPM::C.rpmShowProgress(hdr, type, amount, total, key, data_ignored)
end
end
rc = RPM::C.rpmtsSetNotifyCallback(@ptr, callback, nil)
raise "Can't set commit callback" if rc != 0
rc = RPM::C.rpmtsRun(@ptr, nil, :none)
raise "#{self}: #{RPM::C.rpmlogMessage}" if rc < 0
if rc > 0
ps = RPM::C.rpmtsProblems(@ptr)
psi = RPM::C.rpmpsInitIterator(ps)
while RPM::C.rpmpsNextIterator(psi) >= 0
problem = Problem.from_ptr(RPM::C.rpmpsGetProblem(psi))
STDERR.puts problem
end
RPM::C.rpmpsFree(ps)
end
end
# @return [DB] the database associated with this transaction
def db
RPM::DB.new(self)
end
private
# @param [Package] pkg package to install
# @param [String] key e.g. filename where to install from
# @param opts options
# @option :upgrade Upgrade packages if true
def install_element(pkg, key, opts = {})
raise TypeError, 'illegal argument type' unless pkg.is_a?(RPM::Package)
raise ArgumentError, "#{self}: key '#{key}' must be unique" if @keys.include?(key.object_id)
# keep a reference to the key as rpmtsAddInstallElement will keep a copy
# of the passed pointer (we pass the object_id)
@keys[key.object_id] = key
ret = RPM::C.rpmtsAddInstallElement(@ptr, pkg.ptr, FFI::Pointer.new(key.object_id), opts[:upgrade] ? 1 : 0, nil)
raise RuntimeError if ret != 0
nil
end
end
end

@ -0,0 +1,7 @@
module RPM
module Utils
def self.check_type(var, type)
raise(TypeError, "wrong argument type #{var.class} (expected #{type.class})") unless var.is_a?(type)
end
end
end

@ -0,0 +1,146 @@
module RPM
class Version
include Comparable
# Parses a "epoch:version-release" string
# @return [Array] tuple [epoch, version, release]
def self.parse_evr(evr)
raise ArgumentError, "version can't be nil" if evr.nil?
version = evr
epoch = nil
release = nil
idx = version.rindex('-')
if idx
release = version[idx + 1..-1]
version = version[0..idx - 1]
end
idx = version.index(/\D/)
if idx && version[idx] == ':'
epoch = version[0..idx - 1]
version = version[idx + 1..-1]
end
[epoch ? epoch.to_i : nil, version, release]
end
#
# @overload new(vr, e = nil)
# Creates a version object from a string representation
# @param [String] vr version and release in the form "v-r"
# @param [Number] e epoch
# @return [Version]
# @overload new(v, r, e = nil)
# Creates a version object from a string representation
# @param [String] v version
# @param [String] r release
# @param [Number] e epoch
# @return [Version]
# @example
# RPM:: Version.new "1.0.0-3"
# RPM:: Version.new "1.04"
# RPM:: Version.new "1.0.0-3k", 1
# RPM:: Version.new "2.0.3", "5k"
#
def initialize(*argv)
case argv.size
when 0
raise(ArgumentError('wrong number of arguments (0 for 1..3)'))
when 1
RPM::Utils.check_type(argv[0], String)
@e, @v, @r = RPM::Version.parse_evr(argv[0])
when 2
# (vr, e)
RPM::Utils.check_type(argv[0], String)
@e, @v, @r = RPM::Version.parse_evr(argv[0])
raise(TypeError, 'illegal argument value') unless e.nil?
@e = argv[1].to_i
when 3
RPM::Utils.check_type(argv[0], String)
RPM::Utils.check_type(argv[1], String)
@v = argv[0]
@r = argv[1]
@e = argv[2].to_i
else
raise(ArgumentError("too many arguments (#{args.size} for 1..3)"))
end
end
# @return [String] the version component
attr_reader :v
# @return [String] the release component
# or +nil+
attr_reader :r
# @return [String] the epoch component
# or +nil+
attr_reader :e
# Comparison between versions
# @param [Version] other
# @return [Number] -1 if +other+ is greater than, 0 if +other+ is equal to,
# and +1 if other is less than version.
#
# @example
# v1 = RPM::Version.new("3.0-0",1)
# v2 = RPM::Version.new("3.1-0",1)
# v1 <=> v2
# => -1
#
def <=>(other)
RPM::Utils.check_type(other, RPM::Version)
ret = RPM::C.rpmvercmp(to_vre_epoch_zero, other.to_vre_epoch_zero)
end
# @param [Version] other Version to compare against
# @return [Boolean] true if the version is newer than +other+
def newer?(other)
self > other
end
# @param [Version] other Version to compare against
# @return [Boolean] true if the version is older than +other+
def older?(other)
self < other
end
# String representation in the form "v-r"
# @return [String]
# @note The epoch is not included
def to_vr
vr = @r.nil? ? @v.to_s : "#{@v}-#{@r}"
end
# String representation in the form "e:v-r"
# @return [String]
# @note The epoch is included if present
def to_vre(_opts = {})
vr = to_vr
vre = @e.nil? ? vr : "#{@e}:#{vr}"
end
# Alias for +to_vr+
# @see Version#to_vr
def to_s
to_vr
end
# Hash based on the version content
# @return [String]
def hash
h = @e.nil? ? 0 : @e
h = (h << 1) ^ @r.hash
h = (h << 1) ^ @v.hash
end
# String representation in the form "e:v-r"
# @return [String]
# @note The epoch is included always. As 0 if not present
def to_vre_epoch_zero
vr = to_vr
vre = @e.nil? ? "0:#{vr}" : "#{@e}:#{vr}"
end
end
end

@ -0,0 +1,25 @@
# -*- encoding: utf-8 -*-
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
require 'rpm/gem_version'
Gem::Specification.new do |s|
s.name = 'rpm'
s.version = RPM::GEM_VERSION
s.authors = ['Duncan Mac-Vicar P.']
s.email = ['dmacvicar@suse.de']
s.homepage = ''
s.summary = 'Ruby bindings for rpm (package manager)'
s.description = 'Ruby bindings for rpm. Almost a drop-in replacement for ruby-rpm. Uses FFI.'
s.rubyforge_project = 'rpm'
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
s.require_paths = ['lib']
# specify any dependencies here; for example:
s.add_development_dependency 'rake'
s.add_runtime_dependency 'ffi'
end

@ -0,0 +1,49 @@
Name: a
Version: 1.0
Release: 0
License: GPLv2
Summary: Minimal package example
Url: http://www.a.com
Group: Development
Source: a-1.0.tar.gz
BuildRequires: c d
Provides: something
BuildRoot: %{_tmppath}/%{name}-%{version}-build
%description
Description
%package devel
Summary: Development part
%description devel
Development headers
%prep
%setup -q
%build
%configure
make %{?_smp_mflags}
%install
%make_install
%clean
%{?buildroot:%__rm -rf "%{buildroot}"}
%post
%postun
%files
%defattr(-,root,root)
%doc ChangeLog README COPYING
%{_datadir}/a/README
%files devel
%{_includedir}/a.h
%changelog

@ -0,0 +1,38 @@
Name: simple
Version: 1.0
Release: 0
License: GPL
Summary: Simple dummy package
Summary(es): Paquete simple de muestra
Url: http://www.dummmy.com
Group: Development
BuildRoot: %{_tmppath}/%{name}-%{version}-build
%description
Dummy package
%description -l es
Paquete de muestra
%prep
%build
mkdir -p %{buildroot}%{_datadir}/%{name}
echo "Hello" > %{buildroot}%{_datadir}/%{name}/README
echo "Hola" > %{buildroot}%{_datadir}/%{name}/README.es
%install
%clean
%{?buildroot:%__rm -rf "%{buildroot}"}
%files
%defattr(-,root,root)
%{_datadir}/%{name}/README
%{_datadir}/%{name}/README.es
%changelog
* Wed Nov 06 2011 Duncan Mac-Vicar P. <dmacvicar@suse.de>
- Fix something
* Tue Nov 05 2011 Duncan Mac-Vicar P. <dmacvicar@suse.de>
- Fix something else

@ -0,0 +1,41 @@
Name: simple_with_deps
Version: 1.0
Release: 0
License: GPL
Summary: Simple dummy package
Url: http://www.dummmy.com
Group: Development
Requires: a
Requires: b > 1.0
Conflicts: c d
Obsoletes: f
BuildRoot: %{_tmppath}/%{name}-%{version}-build
%description
Dummy package
%description -l es
Paquete de muestra
%prep
%build
mkdir -p %{buildroot}%{_datadir}/%{name}
echo "Hello" > %{buildroot}%{_datadir}/%{name}/README
echo "Hola" > %{buildroot}%{_datadir}/%{name}/README.es
%install
%clean
%{?buildroot:%__rm -rf "%{buildroot}"}
%files
%defattr(-,root,root)
%{_datadir}/%{name}/README
%{_datadir}/%{name}/README.es
%changelog
* Wed Nov 06 2011 Duncan Mac-Vicar P. <dmacvicar@suse.de>
- Fix something
* Tue Nov 05 2011 Duncan Mac-Vicar P. <dmacvicar@suse.de>
- Fix something else

@ -0,0 +1,7 @@
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
require 'minitest/autorun'
require 'rpm'
def fixture(name)
File.expand_path(File.join(File.dirname(__FILE__), 'data', name))
end

@ -0,0 +1,28 @@
require_relative('helper')
require 'rpm/compat'
class RPMDependencyTests < Minitest::Test
EQ = RPM::SENSE_EQUAL
LT = RPM::SENSE_LESS
GT = RPM::SENSE_GREATER
def test_satisfy
prv = provides('foo', '2', '1', 0, EQ)
req = requires('foo', '1', '1', 0, EQ | GT)
assert(req.satisfy?(prv))
assert(prv.satisfy?(req))
# Different names don't overlap
prv = provides('foo', '2', '1', 0, EQ)
req = requires('bar', '1', '1', 0, EQ | GT)
assert(!req.satisfy?(prv))
end
def provides(name, v, r, e, sense)
RPM::Provide.new(name, RPM::Version.new(v, r, e), sense, nil)
end
def requires(name, v, r, e, sense)
RPM::Require.new(name, RPM::Version.new(v, r, e), sense, nil)
end
end

@ -0,0 +1,36 @@
require File.join(File.dirname(__FILE__), 'helper')
class RPMFileTests < Minitest::Test
def test_link_to
f = RPM::File.new('path', 'md5sum', nil, 42, 1,
'owner', 'group', 43, 0o777, 44, 45)
assert_equal(nil, f.link_to)
f = RPM::File.new('path', 'md5sum', 'link_to', 42, 1,
'owner', 'group', 43, 0o777, 44, 45)
assert_equal('link_to', f.link_to)
end
def test_flags
f = RPM::File.new('path', 'md5sum', nil, 42, 1,
'owner', 'group', 43, 0o777, 44, 45)
f.config?
f.doc?
f.is_missingok?
f.is_noreplace?
f.is_specfile?
f.ghost?
f.license?
f.readme?
f.replaced?
f.notinstalled?
f.netshared?
assert_raises NotImplementedError do
f.exclude?
end
assert_raises NotImplementedError do
f.donotuse?
end
end
end

@ -0,0 +1,33 @@
require File.join(File.dirname(__FILE__), 'helper')
class RPMLibTests < Minitest::Test
def test_lib_lib
assert_kind_of String, RPM::C.RPMVERSION
# "x.y.z"
assert(RPM::C.RPMVERSION.size >= 5)
assert_kind_of Integer, RPM::C.rpm_version_code
# >= 4.0.0
assert(RPM::C.rpm_version_code >= ((4 << 16) + (0 << 8) + (0 << 0)))
end
def test_lib_header
ptr = RPM::C.headerNew
RPM::C.headerFree(ptr)
end
def test_lib_ts
ts = RPM::C.rpmtsCreate
RPM::C.rpmtsSetRootDir(ts, '/')
it = RPM::C.rpmtsInitIterator(ts, 0, nil, 0)
hdrs = []
until (hdr = RPM::C.rpmdbNextIterator(it)).null?
hdrs << hdr
assert_kind_of String, RPM::C.headerGetAsString(hdr, :name)
end
RPM::C.rpmdbFreeIterator(it)
end
def test_lib_macros
assert_kind_of String, RPM::C.MACROFILES
end
end

@ -0,0 +1,76 @@
require File.join(File.dirname(__FILE__), 'helper')
class RPMHeaderTests < Minitest::Test
def test_create
pkg = RPM::Package.create('foo', RPM::Version.new('1.0'))
assert_equal 'foo', pkg.name
assert_equal '(none)', pkg.signature
end
def test_open
pkg = RPM::Package.open(fixture('simple-1.0-0.i586.rpm'))
req = RPM::Require.new('simple', RPM::Version.new('1.0', '0'), RPM::SENSE_GREATER | RPM::SENSE_EQUAL, nil)
assert req.satisfy?(pkg)
assert_equal 'simple-1.0-0-i586', pkg.to_s
assert_equal '3b5f9d468c877166532c662e29f43bc3', pkg.signature
assert_kind_of RPM::Package, pkg
assert_equal 'simple', pkg[:name]
assert_equal 'i586', pkg[:arch]
assert_kind_of RPM::Version, pkg.version
assert_equal '1.0-0', pkg.version.to_s
backup_lang = ENV['LC_ALL']
ENV['LC_ALL'] = 'C'
assert_equal 'Simple dummy package', pkg[:summary]
assert_equal 'Dummy package', pkg[:description]
ENV['LC_ALL'] = 'es_ES.UTF-8'
assert_equal 'Paquete simple de muestra', pkg[:summary]
assert_equal 'Paquete de muestra', pkg[:description]
ENV['LC_ALL'] = backup_lang
# Arrays
assert_equal %w[root root], pkg[:fileusername]
assert_equal [6, 5], pkg[:filesizes]
assert pkg.provides.map(&:name).include?('simple(x86-32)')
assert pkg.provides.map(&:name).include?('simple')
assert pkg.files.map(&:path).include?('/usr/share/simple/README')
assert pkg.files.map(&:path).include?('/usr/share/simple/README.es')
assert pkg.conflicts.empty?
assert pkg.requires.map(&:name).include?('rpmlib(PayloadIsLzma)')
assert pkg.obsoletes.empty?
file = pkg.files.select { |x| x.path == '/usr/share/simple/README' }.first
assert_nil file.link_to
assert !file.symlink?
assert_equal ['- Fix something', '- Fix something else'], pkg.changelog.map(&:text)
end
def test_dependencies
pkg = RPM::Package.open(fixture('simple_with_deps-1.0-0.i586.rpm'))
assert_equal 'simple_with_deps', pkg.name
assert pkg.provides.map(&:name).include?('simple_with_deps(x86-32)')
assert pkg.provides.map(&:name).include?('simple_with_deps')
assert pkg.requires.map(&:name).include?('a')
b = pkg.requires.find { |x| x.name == 'b' }
assert b
assert_equal '1.0', b.version.to_s
assert pkg.conflicts.map(&:name).include?('c')
assert pkg.conflicts.map(&:name).include?('d')
assert pkg.obsoletes.map(&:name).include?('f')
end
end

@ -0,0 +1,18 @@
require File.join(File.dirname(__FILE__), 'helper')
class RPMHeaderTests < Minitest::Test
def test_create
problem = RPM::Problem.create(:requires, 'foo-1.0-0', 'foo.rpm', 'bar-1.0-0', 'Hello', 1)
assert_equal 'foo.rpm', problem.key
assert_equal :requires, problem.type
assert_equal 'Hello', problem.str
assert_equal 'Hello is needed by (installed) bar-1.0-0', problem.to_s
# Create a RPM::Problem from an existing pointer
problem2 = RPM::Problem.new(problem.ptr)
assert_equal problem.key, problem2.key
assert_equal problem.type, problem2.type
assert_equal problem.str, problem2.str
assert_equal problem.to_s, problem2.to_s
end
end

@ -0,0 +1,35 @@
require File.join(File.dirname(__FILE__), 'helper')
class RPMRPMTests < Minitest::Test
def test_enum
assert RPM::TAG[:not_found]
end
def test_compat
# puts RPM::LOG_ALERT
# assert_raise(NameError) { RPM::LOG_ALERT }
# require 'rpm/compat'
# Nothing should be raised by the following statement
RPM::LOG_ALERT
assert_equal RPM::LOG_ALERT, RPM::LOG[:alert]
end
def test_iterator
RPM.transaction do |t|
assert_kind_of RPM::Transaction, t
# t.each do |pkg|
# puts pkg[:name]
# end
end
end
def test_macro_read
assert_equal '/usr', RPM['_usr']
end
def test_macro_write
RPM['hoge'] = 'hoge'
assert_equal(RPM['hoge'], 'hoge')
end
end # class RPM_RPM_Tests < Test::Unit::TestCase

@ -0,0 +1,156 @@
require_relative('helper')
require 'tmpdir'
require 'pathname'
class RPMTransactionTests < Minitest::Test
PACKAGE_FILENAME = 'simple-1.0-0.i586.rpm'.freeze
def test_flags
RPM.transaction do |t|
assert_equal RPM::TRANS_FLAG_NONE, t.flags
t.flags = RPM::TRANS_FLAG_TEST
assert_equal RPM::TRANS_FLAG_TEST, t.flags
end
end
def test_iterator
RPM.transaction do |t|
it = t.init_iterator(nil, nil)
assert_kind_of RPM::MatchIterator, it
# assert it.count > 0
end
RPM.transaction do |t|
it = t.init_iterator(nil, nil)
it.regexp(:name, :glob, '*audio*')
it.each do |pkg|
assert pkg.name.include?('audio'), "'#{pkg.name}' contains 'audio'"
end
end
end
# FIXME: this is not working
def test_iterator_version
RPM.transaction do |t|
it = t.init_iterator(nil, nil)
it.version(RPM::Version.new('2.1'))
it.each do |sig|
# FIXME: check that this worked
end
end
end
def test_basic_transaction_setters
Dir.mktmpdir do |dir|
RPM.transaction do |t|
assert_equal '/', t.root_dir
t.root_dir = dir
assert_equal dir + '/', t.root_dir
end
end
Dir.mktmpdir do |dir|
RPM.transaction(dir) do |t|
assert_equal dir + '/', t.root_dir
end
end
end
def test_test_flag_install
pkg = RPM::Package.open(fixture(PACKAGE_FILENAME))
Dir.mktmpdir do |dir|
RPM.transaction(dir) do |t|
t.flags = RPM::TRANS_FLAG_TEST
t.install(pkg, fixture(PACKAGE_FILENAME))
t.commit
rpmdb_file = RPM::C.rpmvercmp(RPM::C.RPMVERSION, '4.16.0') >= 0 ? 'rpmdb.sqlite' : 'Packages'
assert File.exist?(File.join(dir, RPM['_dbpath'], rpmdb_file)), 'rpm db exists'
assert !File.exist?('/usr/share/simple/README'), "package #{pkg} was not installed"
ensure
# Force close so that RPM does not try to do it
# when the tmpdir is deleted
t.db.close
end
end
end
def test_install_and_remove
pkg = RPM::Package.open(fixture(PACKAGE_FILENAME))
Dir.mktmpdir do |dir|
RPM.transaction(dir) do |t|
begin
t.install(pkg, fixture(PACKAGE_FILENAME))
t.commit
rpmdb_file = RPM::C.rpmvercmp(RPM::C.RPMVERSION, '4.16.0') >= 0 ? 'rpmdb.sqlite' : 'Packages'
assert File.exist?(File.join(dir, RPM['_dbpath'], rpmdb_file)), 'rpm db exists'
assert File.exist?(File.join(dir, '/usr/share/simple/README')), "package #{pkg} should be installed"
ensure
# Force close so that RPM does not try to do it
# when the tmpdir is deleted
t.db.close
end
end
skip("Commit hangs on package delete")
RPM.transaction(dir) do |t|
begin
assert_raises TypeError do
t.delete(Object.new)
end
t.delete(pkg)
t.order
t.clean
t.commit
assert !File.exist?(File.join(dir, '/usr/share/simple/README')), "package #{pkg} should not be installed"
ensure
# Force close so that RPM does not try to do it
# when the tmpdir is deleted
t.db.close
end
end
end
end
def test_install_with_custom_callback
pkg = RPM::Package.open(fixture(PACKAGE_FILENAME))
Dir.mktmpdir do |dir|
RPM.transaction(dir) do |t|
begin
t.install(pkg, fixture(PACKAGE_FILENAME))
t.check do |problem|
STDERR.puts "Problem: #{problem}"
end
t.order
t.clean
t.commit do |data|
next case data.type
when :inst_open_file then
@f = File.open(data.key, 'r')
when :inst_close_file then @f.close
end
end
assert File.exist?(File.join(dir, '/usr/share/simple/README')),
"package #{pkg} should be installed"
ensure
# Force close so that RPM does not try to do it
# when the tmpdir is deleted
t.db.close
end
end
end
end
end

@ -0,0 +1,64 @@
require File.join(File.dirname(__FILE__), 'helper')
class RPMVersionTests < Minitest::Test
def setup
@a = RPM::Version.new('1.0.0-0.1m')
@b = RPM::Version.new('0.9.0-1m')
@c = RPM::Version.new('1.0.0-0.11m')
@d = RPM::Version.new('0.9.0-1m', 1)
end
def test_parse_evr
assert_equal [23, '1.0.3', '1suse'],
RPM::Version.parse_evr('23:1.0.3-1suse')
assert_equal [nil, '1.0', nil],
RPM::Version.parse_evr('1.0')
assert_equal [nil, '2.0', '3'],
RPM::Version.parse_evr('2.0-3')
end
def test_version_compare
assert(@a > @b)
assert(@a < @c)
assert(@a < @d)
end
def test_version_newer?
assert(@a.newer?(@b))
assert(@c.newer?(@a))
assert(@d.newer?(@a))
assert(!@a.newer?(@a))
end
def test_version_older?
assert(@b.older?(@a))
assert(@a.older?(@c))
assert(@a.older?(@d))
assert(!@a.older?(@a))
end
def test_vre
assert_equal('0.9.0', @d.v)
assert_equal('1m', @d.r)
assert_equal(1, @d.e)
end
def test_to_s
assert_equal('0.9.0-1m', @b.to_s)
assert_equal('0.9.0-1m', @d.to_s)
end
def test_to_vre
assert_equal('0.9.0-1m', @b.to_vre)
assert_equal('1:0.9.0-1m', @d.to_vre)
end
def test_epoch_none_zero
v1 = RPM::Version.new('1-2')
v2 = RPM::Version.new('0:1-2')
assert_nil v1.e
assert_equal(0, v2.e)
assert(v1 == v2)
assert_equal(v1.hash, v2.hash)
end
end
Loading…
Cancel
Save