class Simp::RPM
An Simp::RPM
instance represents RPM
metadata extracted from an RPM
or an RPM
spec file.
Simp::RPM
also contains class methods that are useful for processing RPMs in the SIMP build process.
@note Set the environment variable ‘SIMP_RPM_dist` to ensure all
packages use a particular dist.
Attributes
Public Class Methods
Copies specific content from one directory to another.
- start_dir
-
the root directory where the original files are located within
- src
-
a pattern given to find(1) to match against the desired files to copy
- dest
-
the destination directory to receive the copies
# File lib/simp/rpm.rb, line 275 def self.copy_wo_vcs(start_dir, src, dest, dereference=true) if dereference.nil? || dereference dereference = "--dereference" else dereference = "" end Dir.chdir(start_dir) do sh %{find #{src} \\( -path "*/.svn" -a -type d -o -path "*/.git*" \\) -prune -o -print | cpio -u --warning none --quiet --make-directories #{dereference} -p "#{dest}" 2>&1 > /dev/null} end end
# File lib/simp/rpm.rb, line 455 def self.create_rpm_build_metadata(project_dir, srpms=nil, rpms=nil) require 'yaml' last_build = { 'git_hash' => %x(git rev-list --max-count=1 HEAD).chomp, 'srpms' => {}, 'rpms' => {} } Dir.chdir(File.join(project_dir, 'dist')) do if srpms.nil? or rpms.nil? all_rpms = Dir.glob('*.rpm') srpms = Dir.glob('src.rpm') rpms = all_rpms - srpms end srpms.each do |srpm| file_stat = File.stat(srpm) last_build['srpms'][File.basename(srpm)] = { 'metadata' => Simp::RPM.get_info(srpm), 'size' => file_stat.size, 'timestamp' => file_stat.ctime, 'path' => File.absolute_path(srpm) } end rpms.each do |rpm| file_stat = File.stat(rpm) last_build['rpms'][File.basename(rpm)] = { 'metadata' => Simp::RPM.get_info(rpm), 'size' => file_stat.size, 'timestamp' => file_stat.ctime, 'path' => File.absolute_path(rpm) } end FileUtils.mkdir_p(File.join(project_dir, 'dist', 'logs')) File.open('logs/last_rpm_build_metadata.yaml','w') do |fh| fh.puts(last_build.to_yaml) end end end
Executes a command and returns a hash with the exit status, stdout output and stderr output.
- cmd
-
command to be executed
# File lib/simp/rpm.rb, line 290 def self.execute(cmd) if @verbose ||= ENV.fetch('SIMP_RPM_verbose','no') =='yes' puts "== Simp::RPM.execute(#{cmd})" puts " #{cmd}" end outfile = File.join('/tmp', "#{ENV['USER']}_#{SecureRandom.hex}") errfile = File.join('/tmp', "#{ENV['USER']}_#{SecureRandom.hex}") pid = spawn(cmd, :out=>outfile, :err=>errfile) begin pid,status = Process.wait2(pid) rescue Errno::ECHILD # process exited before status could be determined end exit_status = status.nil? ? nil : status.exitstatus stdout = IO.read(outfile) stderr = IO.read(errfile) { :exit_status => exit_status, :stdout => stdout, :stderr => stderr } ensure if @verbose puts " -------- exit_status: #{exit_status}" puts " -------- stdout ",'' puts File.readlines(outfile).map{|x| " #{x}"}.join puts ''," -------- stderr ",'' puts File.readlines(errfile).map{|x| " #{x}"}.join end FileUtils.rm_f([outfile, errfile]) end
Parses information, such as the version, from the given specfile or RPM
into a hash.
If the information from only single RPM
is extracted, returns a single Hash
with the following possible keys:
:has_dist_tag = a boolean indicating whether the RPM release has a distribution field; only evaluated when rpm_source is a spec file, otherwise false :basename = The name of the package (as it would be queried in yum) :version = The version of the package :release = The release version of the package :arch = The machine architecture of the package :full_version = The full version of the package: <version>-<release> :name = The full name of the package: <basename>-<full_version> :rpm_name = The full name of the RPM: <basename>-<full_version>.<arch>.rpm :signature = RPM signature key id; only present if rpm_source is an RPM and the RPM is signed
If the information from more than one RPM
is extracted, as is the case when a spec file specifies sub-packages, returns an Array of Hashes.
# File lib/simp/rpm.rb, line 347 def self.get_info(rpm_source) raise "Error: unable to read '#{rpm_source}'" unless File.readable?(rpm_source) if ENV['SIMP_RPM_dist'] target_dist = (ENV['SIMP_RPM_dist'] =~ /^\./) ? ENV['SIMP_RPM_dist'] : ('.' + ENV['SIMP_RPM_dist']) else target_dist = system_dist end info_array = [] common_info = {} rpm_version_query = %Q(#{rpm_cmd} -q --queryformat '%{NAME} %{VERSION} %{RELEASE} %{ARCH}\\n') rpm_signature_query = %Q(#{rpm_cmd} -q --queryformat '%|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{%|SIGGPG?{%{SIGGPG:pgpsig}}:{%|SIGPGP?{%{SIGPGP:pgpsig}}:{(none)}|}|}|}|\\n') source_is_rpm = rpm_source.split('.').last == 'rpm' if source_is_rpm dist_info = rpm_source.split('-').last.split('.')[1..-3] unless dist_info.empty? common_info[:has_dist_tag] = true common_info[:dist] = '.' + dist_info.first else common_info[:has_dist_tag] = false common_info[:dist] = target_dist end elsif File.read(rpm_source).include?('%{?dist}') common_info[:dist] =target_dist common_info[:has_dist_tag] = true else common_info[:has_dist_tag] = false common_info[:dist] = target_dist end unless source_is_rpm macros = { 'dist' => target_dist } macros.each do |k,v| rpm_version_query += %Q[ -D '#{k} #{v}'] end end if source_is_rpm query_source = "-p #{rpm_source}" version_results = execute("#{rpm_version_query} #{query_source}") signature_results = execute("#{rpm_signature_query} #{query_source}") else query_source = "--specfile #{rpm_source}" version_results = execute("#{rpm_version_query} #{query_source}") signature_results = nil end if version_results[:exit_status] != 0 raise <<~EOE #{indent('Error getting RPM info for #{query_source}:', 2)} #{indent(version_results[:stderr].strip, 5)} #{indent("Run '#{rpm_version_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)} EOE end unless signature_results.nil? if signature_results[:exit_status] != 0 raise <<~EOE #{indent('Error getting RPM signature for #{query_source}:', 2)} #{indent(signature_results[:stderr].strip, 5)} #{indent("Run '#{rpm_signature_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)} EOE else signature = signature_results[:stdout].strip end end version_results[:stdout].strip.lines.each do |line| info = common_info.dup parts = line.split(' ') info[:basename], info[:version], info[:release], info[:arch] = parts info[:signature] = signature unless signature.nil? or signature.include?('none') info[:full_version] = "#{info[:version]}-#{info[:release]}" info[:name] = "#{info[:basename]}-#{info[:full_version]}" info[:rpm_name] = "#{info[:name]}.#{info[:arch]}.rpm" info_array << info end if @verbose puts "== SIMP::RPM.get_info" require 'pp' pp info_array end if info_array.size == 1 return info_array[0] else # will only happen when source is spec file and that spec file # specifies sub-packages return info_array end end
# File lib/simp/rpm.rb, line 451 def self.indent(message, indent_length) message.split("\n").map {|line| ' '*indent_length + line }.join("\n") end
Constructs a new Simp::RPM
object. Requires the path to the spec file, or RPM
, from which information will be gathered.
When the information is from a spec file, multiple packages may exist.
The following information will be retrieved per package:
- basename
-
The name of the package (as it would be queried in yum)
- version
-
The version of the package
- release
-
The release version of the package
full_version
-
The full version of the package: [version]-
- name
-
The full name of the package: [basename]-
- arch
-
The machine architecture of the package
- signature
-
The signature key of the package, if it exists. Will not
apply when +rpm_source+ is an RPM spec file.
rpm_name
-
The full name of the rpm
# File lib/simp/rpm.rb, line 46 def initialize(rpm_source) @verbose = ENV.fetch('SIMP_RPM_verbose','no') =='yes' update_rpmmacros # Simp::RPM.get_info returns a Hash or an Array of Hashes. # Steps below prevent single Hash from implicitly being converted # to Array using Hash.to_a. info_array = [] info_array << Simp::RPM.get_info(rpm_source) info_array.flatten! @info = {} info_array.each do |package_info| @info[package_info[:basename]] = package_info end @packages = @info.keys if @verbose require 'pp' puts "== Simp::RPM @packages" puts @packages.pretty_inspect end end
# File lib/simp/rpm.rb, line 25 def self.rpm_cmd @rpm_cmd ||= (ENV.fetch('SIMP_RPM_LUA_debug','no') =='yes') ? "rpm -D 'lua_debug 1'" : 'rpm' end
# File lib/simp/rpm.rb, line 20 def self.sh(args) system args end
@returns The RPM
‘.dist’ of the system. ‘nil’ will be will be
returned if the dist is not found.
@note This causes problems for ISO builds that target a particular
OS if it doesn't match the host. Set the environment variable `SIMP_RPM_dist` to ensure all packages use a particular dist.
# File lib/simp/rpm.rb, line 77 def self.system_dist # We can only have one of these unless defined?(@@system_dist) cmd = %Q(#{rpm_cmd} -E '%{dist}' 2> /dev/null) if @verbose puts "== Simp::RPM.system_dist" puts " #{cmd} " end dist = %x{#{cmd}}.strip.split('.') puts " result = '#{dist}'" if @verbose if dist.size > 1 @@system_dist = (dist[1] =~ /^\./) ? dist[1] : ('.' + dist[1]) else @@system_dist = nil end puts " @@system_dist = #{@@system_dist ||'nil'}" if @verbose end return @@system_dist end
Returns the version of RPM
installed on the system
# File lib/simp/rpm.rb, line 501 def self.version %x{rpm --version}.strip.split.last end
Public Instance Methods
@returns The machine architecture of the package
@fails if package is invalid
# File lib/simp/rpm.rb, line 184 def arch(package=@packages.first) valid_package?(package) @info[package][:arch] end
@returns The name of the package (as it would be queried in yum)
@fails if package is invalid
# File lib/simp/rpm.rb, line 145 def basename(package=@packages.first) valid_package?(package) @info[package][:basename] end
@returns The ‘dist` of the package. If no `dist` is found, returns the `dist` of the OS itself. Logic should check both `has_dist_tag?` and `dist`
@fails if package is invalid
# File lib/simp/rpm.rb, line 220 def dist(package=@packages.first) valid_package?(package) @info[package][:dist] end
@returns The full version of the package: [version]-
@fails if package is invalid
# File lib/simp/rpm.rb, line 169 def full_version(package=@packages.first) valid_package?(package) @info[package][:full_version] end
@returns Whether or not the package has a ‘dist` tag
@fails if package is invalid
# File lib/simp/rpm.rb, line 210 def has_dist_tag?(package=@packages.first) valid_package?(package) @info[package][:has_dist_tag] end
@returns The full name of the package: [basename]- @fails if package is invalid
# File lib/simp/rpm.rb, line 176 def name(package=@packages.first) valid_package?(package) @info[package][:name] end
Returns whether or not the current RPM
sub-package is newer than the passed RPM
.
# File lib/simp/rpm.rb, line 236 def package_newer?(package, other_rpm) valid_package?(package) return true if other_rpm.nil? || other_rpm.empty? unless other_rpm.match(%r(\.rpm$)) raise ArgumentError.new("You must pass valid RPM name! Got: '#{other_rpm}'") end if File.readable?(other_rpm) other_full_version = Simp::RPM.get_info(other_rpm)[:full_version] else # determine RPM info in a hacky way, ASSUMING, the other RPM has the # same basename and arch other_full_version = other_rpm.gsub(/#{package}\-/,'').gsub(/.rpm$/,'') package_arch = arch(package) unless package_arch.nil? or package_arch.empty? other_full_version.gsub!(/.#{package_arch}/,'') end end begin return Gem::Version.new(full_version(package)) > Gem::Version.new(other_full_version) rescue ArgumentError, NoMethodError fail("Could not compare RPMs '#{rpm_name(package)}' and '#{other_rpm}'") end end
@returns The release version of the package
@fails if package is invalid
# File lib/simp/rpm.rb, line 161 def release(package=@packages.first) valid_package?(package) @info[package][:release] end
@returns The full name of the RPM
@fails if package is invalid
# File lib/simp/rpm.rb, line 202 def rpm_name(package=@packages.first) valid_package?(package) @info[package][:rpm_name] end
@returns The signature key of the package, if it exists or nil
otherwise. Will always be nil when the information for this object was derived from an RPM spec file.
@fails if package is invalid
# File lib/simp/rpm.rb, line 194 def signature(package=@packages.first) valid_package?(package) @info[package][:signature] end
# File lib/simp/rpm.rb, line 99 def system_dist return Simp::RPM.system_dist end
Work around the silliness with ‘centos’ being tacked onto things via the ‘dist’ flag
# File lib/simp/rpm.rb, line 105 def update_rpmmacros unless defined?(@@macros_updated) # Workaround for CentOS system builds dist = ENV['SIMP_RPM_dist'] || system_dist dist_macro = %(%dist #{dist}) rpmmacros = [dist_macro] rpmmacros_file = File.join(ENV['HOME'], '.rpmmacros') if File.exist?(rpmmacros_file) rpmmacros = File.read(rpmmacros_file).split("\n") dist_index = rpmmacros.each_index.select{|i| rpmmacros[i] =~ /^%dist\s+/}.first if dist_index rpmmacros[dist_index] = dist_macro else rpmmacros << dist_macro end end File.open(rpmmacros_file, 'w') do |fh| fh.puts rpmmacros.join("\n") fh.flush end if @verbose puts "== SIMP::RPM#update_rpmmacros:" puts " wrote to '#{rpmmacros_file}': " puts " #{'-'*20}" puts rpmmacros.map{|x| " #{x}\n"}.join puts end @@macros_updated = true end end
# File lib/simp/rpm.rb, line 265 def valid_package?(package) unless @packages.include?(package) raise ArgumentError.new("'#{package}' is not a valid sub-package") end end
@returns The version of the package
@fails if package is invalid
# File lib/simp/rpm.rb, line 153 def version(package=@packages.first) valid_package?(package) @info[package][:version] end