#!/usr/bin/env ruby # Small gem to sync multiple gem installations. # (c) 2010 Josh Ellithorpe require 'rubygems' require 'trollop' # Setup arguments from the command line. # This uses the trollop library on github. opts = Trollop::options do version "gemsync 0.1.7 (c) 2010 Josh Ellithorpe" banner <<-EOS Small gem to sync multiple gem installations. Usage: gemsync [options] Available Options: EOS opt :source, "Either the ruby installation directory, or a list of gems produced from 'gem list'. Ex: /usr or /path/to/gemlist.txt", :type => String, :required => true opt :destination, "Ruby installation directory you want to sync up. Ex: /opt/ruby-enterprise", :type => String, :required => false opt :disable_sudo, "Disable sudo when installing gems", :default => false opt :build_docs, "Build documentation", :default => false end # Check if the file or directory exists, if not exit Trollop::die :source, "\n\t-- Directory or file '#{opts[:source]}' does not exist" unless File.exists?(opts[:source]) # Check if source is a file or directory. if File.ftype(opts[:source]) == 'file' @source_type = 'file' else @source_type = 'directory' Trollop::die :source, "\n\t-- Binary '#{opts[:source]}/bin/gem' does not exist" unless File.exists?("#{opts[:source]}/bin/gem") end # Attempt to set default destination based upon location of 'gem' binary if opts[:destination].nil? guessed_destination_path = `which gem`.split('/')[0..-3].join('/') Trollop::die :destination, "\n\t-- Destination was not set, and gemsync could not guess it" if (guessed_destination_path == opts[:source] || !File.directory?(guessed_destination_path)) else # Die if destination is set incorrectly Trollop::die :destination, "\n\t-- Directory '#{opts[:destination]}' does not exist" unless File.directory?(opts[:destination]) Trollop::die :destination, "\n\t-- Binary '#{opts[:destination]}/bin/gem' does not exist" unless File.exists?("#{opts[:destination]}/bin/gem") end # Cleanup main and sync dir, they shouldn't end in a '/' so lets chomp it. # Couldn't use chomp! since opts are frozen. @main_dir = opts[:source].chomp('/') @sync_dir = opts[:destination].nil? ? guessed_destination_path.chomp('/') : opts[:destination].chomp('/') # Setup additional flags @docstring = opts[:build_docs] ? '' : '--no-ri --no-rdoc' @sudostring = opts[:disable_sudo] ? '' : (`which sudo`.strip + ' ') # Just stripping newline # Gems you don't want to sync def get_exceptions # Setup hash of common gems that certain ruby installs can't support # For now this is just rubynode for REE and JRuby, feel free to add more # hpricot is not in the jruby list because 0.6.x is supported. common_exceptions = { :ree => ["rubynode"], :jruby => ["acts_as_ferret", "bleak_house", "bluecloth", "dnssd", "fastthread", "fcgi", "ferret", "fraction", "jk-ferret", "json", "libxml-ruby", "linecache","mysql", "mysql2", "mysqlplus", "passenger", "pauldix-feedzirra", "pcaprub", "rb-appscript", "rmagick", "ruby-debug-base", "ruby-prof", "rubynode", "sqlite3-ruby", "system_timer", "taf2-curb", "termios", "timetrap", "unicorn", "wikitext", "yajl-ruby"] } mandated_version = {:ruby187 => ["bleak_house"]} returned_exceptions = [''] sync_version = `#{@sync_dir}/bin/gem --version` if @sync_dir.match('enterprise') returned_exceptions += common_exceptions[:ree] elsif @sync_dir.match('jruby') returned_exceptions += common_exceptions[:jruby] end if sync_version != '1.8.7' returned_exceptions += mandated_version[:ruby187] end return returned_exceptions end # Get file as string def get_file_as_string(filename) data = '' f = File.open(filename, "r") f.each_line do |line| data += line end return data end # Get gem list from source if @source_type == 'directory' gem_list = %x[#{@main_dir}/bin/gem list].split("\n") else gem_list = get_file_as_string(opts[:source]).split("\n") end # Get gem list from destination sync_gem_list = %x[#{@sync_dir}/bin/gem list].split("\n") # Cleanup gems we know we don't need to update gems = gem_list - sync_gem_list # Loop through gems and update them. for gem in gems # Trap regexps in try block incase matches don't work out. begin gem_name = gem.match(/(.*) \((.*)\)/)[1] gem_versions = gem.match(/(.*) \((.*)\)/)[2] rescue gem_name = nil gem_versions = nil end next if gem_name.nil? || gem_versions.nil? # If valid gem start checking/updating it. puts "## Checking #{gem_name}" if get_exceptions().include?(gem_name) puts "This gem is known to be incompatible with the sync'd ruby installation" next end current_gem_info = %x[#{@sync_dir}/bin/gem list #{gem_name}] current_gem_info_items = current_gem_info.split("\n") for curgem in current_gem_info_items curgem_name = curgem.match(/(.*) \((.*)\)/)[1] if gem_name == curgem_name current_gem_details = curgem end end if current_gem_details.nil? || current_gem_details == "\n" || current_gem_details == "" current_gem_versions = [] else current_gem_versions = current_gem_details.match(/(.*) \((.*)\)/)[2].split(", ") end for version in (gem_versions.split(", ") - current_gem_versions) if gem_name == "mysql" # Also look for mysql5 type binaries that some package managers use # The array below is a list of mysql suffixes mysql_config = '' mysql_dir = '' ['', '5'].each do |v| mysql_config = `which mysql_config#{v}`.chomp if mysql_config == '' mysql_dir = `which mysql#{v}` if mysql_dir == '' end mysql_dir = mysql_dir.split('/')[0..-3].join('/') system("#{@sudostring}#{@sync_dir}/bin/gem install #{gem_name} -v #{version} -- --with-mysql-dir=#{mysql_dir} --with-mysql-config=#{mysql_config} #{@docstring}") else system("#{@sudostring}#{@sync_dir}/bin/gem install #{gem_name} -v #{version} #{@docstring}") end end end