Created
November 3, 2014 14:50
-
-
Save robinbowes/0e4ea8feffa3261fb33f to your computer and use it in GitHub Desktop.
script to convert gems to RPMs
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', | |
'system' => '/usr/lib64/ruby/gems/1.9.1', | |
} | |
# name prefix for the target packages | |
RPM_RUBYGEM_PREFIX = { | |
'puppet' => 'pe-', | |
'system' => '', | |
} | |
# location of the gem binary to use | |
GEM_BIN = { | |
'puppet' => '/opt/puppet/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 and provides from the gem | |
package.dependencies = [] | |
package.provides = [] | |
# 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 or 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