Created
May 26, 2015 16:41
-
-
Save robinbowes/0761ddc19a9708e000dc to your computer and use it in GitHub Desktop.
convert_gems_to_rpm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'trollop' | |
require 'fpm' | |
# used to ensure versions extracted from gems are converted to | |
# semantic versioning-compliant strings before being used | |
# in RPM requires | |
require 'semverly' | |
class Converter | |
def initialize(logger = Logger.new(STDOUT)) | |
@logger = logger | |
end | |
# extra RPM dependencies required by individual packages | |
# keyed on gem name, each entry is an array | |
EXTRA_DEPS = { | |
'nokogiri' => ['libxslt'], | |
'gpgme' => ['gpgme'], | |
} | |
# Sometimes we need to require a pre-existing ruby gem that is not | |
# installed with a "*rubygem-*" name. An example of this is hiera | |
# which is installed as pe-hiera on PE, rather than pe-rubygem-hiera | |
# This hash is used to convert one requirement to another | |
TRANSLATE_DEPS = { | |
'rubygem-hiera' => { | |
'name' => 'hiera', | |
'epoch' => '', | |
} | |
} | |
# library prefix for the target packages | |
DST_LIB_DIR = { | |
'puppet' => '/opt/puppet/lib/ruby/gems/1.9.1', | |
'ruby193' => '/opt/rh/ruby193/root/usr/share/gems', | |
'system' => '/usr/share/gems', | |
} | |
# name prefix for the target packages | |
RPM_RUBYGEM_PREFIX = { | |
'puppet' => 'pe-', | |
'ruby193' => 'ruby193-', | |
'system' => '', | |
} | |
# location of the gem binary to use | |
GEM_BIN = { | |
'puppet' => '/opt/puppet/bin/gem', | |
'ruby193' => '/opt/rh/ruby193/root/usr/bin/gem', | |
'system' => '/usr/bin/gem' | |
} | |
def convert( | |
src_gem, | |
target, | |
iteration, | |
epoch | |
) | |
# get just the filename from the supplied full-path | |
gem_file = File.basename(src_gem) | |
# get the name of the gem, ie. strip the version and .gem | |
if gem_file =~ /(.*)-(?:[0-9]+\.)+[0-9]+\.gem/ | |
gem_name = $1 | |
@logger.debug "gem name: #{gem_name}" | |
else | |
@logger.warn "Couldn't extract gem name from: #{src_gem}" | |
return | |
end | |
dst_lib_dir = DST_LIB_DIR[target] | |
prefix = RPM_RUBYGEM_PREFIX[target] | |
extra_deps = EXTRA_DEPS[gem_name] || [] | |
rpm_name = "#{prefix}rubygem-#{gem_name}" | |
@logger.debug "rpm name: #{rpm_name}" | |
# this is needed to prevent the stupid nokogiri stupid build process | |
# from stupidly building & bundling the entire libxslt and libxml2 | |
# libraries | |
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] = "1" | |
# And the same thing here for gpgme | |
ENV['RUBY_GPGME_USE_SYSTEM_LIBRARIES'] = "1" | |
package = FPM::Package::Gem.new | |
package.input(src_gem) | |
# get the dependencies from the gem | |
dependencies = package.dependencies | |
@logger.debug "gem dependencies: #{dependencies}" | |
# clear dependencies from the gem (we will add them manually later) | |
package.dependencies = [] | |
# make sure we use the correct gem bin | |
package.attributes[:gem_gem] = GEM_BIN[target] | |
@logger.debug "using gem binary from #{GEM_BIN[target]}" | |
# convert the ruby deps to rpm deps (using code copied from fpm) | |
# but modified to include epoch | |
fixed_deps = [] | |
dependencies.each do |dep| | |
@logger.debug "Processing dependency: #{dep}" | |
name, op, version = dep.split(/\s+/) | |
@logger.debug "name: [#{name}] op: [#{op}] version: [#{version}]" | |
sem_ver = SemVer.parse(version) | |
@logger.debug "semantic version: [#{sem_ver}]" | |
if TRANSLATE_DEPS.key?(name) | |
h = TRANSLATE_DEPS[name] | |
name = h['name'] | |
if h.key?('epoch') | |
epoch = h['epoch'] | |
end | |
end | |
epoch_str = epoch ? "%s:" % epoch : '' | |
@logger.debug "Using epoch: #{epoch_str}" | |
name = "#{prefix}#{name}" | |
@logger.debug "Using package name: #{name}" | |
if op == "~>" | |
# ~> x.y means: > x.y and < (x+1).0 | |
# ~> x.y.z means: > x.y.z and < x.(y+1).0 | |
case version.split('.').size | |
when 1 | |
upper_ver = false | |
when 2 | |
upper_ver = SemVer.new(sem_ver.major + 1, 0, 0) | |
when 3 | |
upper_ver = SemVer.new(sem_ver.major, sem_ver.minor + 1, 0) | |
else | |
raise Exception.new "Don't know how to deal with ~> #{version}" | |
end | |
fixed_deps << "#{name} >= #{epoch_str}#{sem_ver}" | |
fixed_deps << "#{name} < #{epoch_str}#{upper_ver}" if upper_ver | |
else | |
fixed_deps << "#{name} #{op} #{epoch_str}#{sem_ver}" | |
end | |
end | |
# Add in ruby and rubygems deps (with appropriate prefix) | |
fixed_deps << "#{prefix}ruby" | |
fixed_deps << "#{prefix}rubygems" | |
extra_deps.each do |dep| | |
fixed_deps << dep | |
end | |
@logger.debug "RPM dependencies: #{fixed_deps}" | |
rpm = package.convert(FPM::Package::RPM) | |
rpm.iteration = iteration | |
rpm.epoch = epoch | |
rpm.name = rpm_name | |
rpm.attributes[:prefix] = dst_lib_dir | |
# set the rpm deps | |
rpm.dependencies = fixed_deps | |
begin | |
output = "NAME-VERSION-ITERATION.ARCH.rpm" | |
rpm.output(rpm.to_s(output)) | |
ensure | |
rpm.cleanup | |
end | |
end | |
end | |
opts = Trollop::options do | |
banner <<-EOS | |
Convert ruby gems to RPMs | |
The target specifies which ruby to use: | |
* system - install to /usr/lib64/ruby/gems, prefix: rubygem- | |
* puppet - install to /opt/puppet/lib/ruby/gems, prefix: pe-rubygem- | |
Usage: | |
convert_gems_to_rpms.rb [options] <filenames>+ | |
where [options] are: | |
EOS | |
opt :epoch, 'RPM epoch to use. Defaults to 2 to trump EPEL.', :default => "2" | |
opt :release, 'RPM release to create', :default => "1" | |
opt :target, 'which ruby to use - system, ruby193 (SCL), puppet', :default => 'system' | |
opt :verbose, 'Show info-level logging' | |
opt :debug, 'Show debug-level logging' | |
end | |
# Make sure we have a sane umask | |
File.umask(0022) | |
logger = Cabin::Channel.get | |
logger.subscribe(STDOUT) | |
if opts[:debug] | |
logger.level = :debug | |
elsif opts[:verbose] | |
logger.level = :info | |
else | |
logger.level = :warn | |
end | |
ARGV.each{ |src_gem| | |
Converter.new(logger).convert(src_gem, opts[:target], opts[:release], opts[:epoch]) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment