#!/usr/bin/env ruby # -*- ruby -*- #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ # gemlock -- Generate a lockdown list of a set of gems # # Usage: gemlock gem_name-version... # # gemlock will generate a list of +gem+ statements that will # lock down the versions for the gem given in the command line. It # will specify exact versions in the requirements list to ensure that # the gems loaded will always be consistent. A full recursive search # of all effected gems will be generated. # # Example: # # gemlock rails-1.0.0 >lockdown.rb # # will produce in lockdown.rb: # # require "rubygems" # gem 'rails', '= 1.0.0' # gem 'rake', '= 0.7.0.1' # gem 'activesupport', '= 1.2.5' # gem 'activerecord', '= 1.13.2' # gem 'actionpack', '= 1.11.2' # gem 'actionmailer', '= 1.1.5' # gem 'actionwebservice', '= 1.0.0' # # Just load lockdown.rb from your application to ensure that the # current versions are loaded. Make sure that lockdown.rb is loaded # *before* any other require statements. # # Notice that rails 1.0.0 only requires that rake 0.6.2 or better be # used. Rake-0.7.0.1 is the most recent version installed that # satisfies that, so we lock it down to the exact version. require 'rubygems' require 'ostruct' require 'optparse' require 'yaml' Gem.manage_gems def handle_options(args) options = OpenStruct.new options.verbose = false options.strict = false opts = OptionParser.new do |opts| opts.banner = "Usage: #$0 [options] GEM_NAME-VERSION..." opts.separator "" opts.separator "Where options are:" opts.on('--verbose', '-v', 'Verbose output') do |value| options.verbose = value end opts.on('--[no-]strict', '-s', 'Fail if unable to satisfy a dependency') do |value| options.strict = value end opts.on_tail('--help', '-h', 'Show this message') do puts opts exit end opts.on_tail('--version', '-V', 'Show version') do puts "gemlock (#{Gem::RubyGemsVersion})" exit end end options.usage = opts opts.parse!(args) options end def spec_path(gem_full_name) File.join(Gem.path, "specifications", gem_full_name + ".gemspec") end def complain(message, options) if options.strict fail message else puts "# #{message}" end end def lock_down(pending, options) puts 'require "rubygems"' locked = {} while ! pending.empty? full_name = pending.shift spec = Gem::SourceIndex.load_specification(spec_path(full_name)) puts "gem '#{spec.name}', '= #{spec.version}'" unless locked[spec.name] locked[spec.name] = true spec.dependencies.each do |dep| next if locked[dep.name] candidates = Gem.source_index.search(dep.name, dep.requirement_list) if candidates.empty? complain( "Unable to satisfy '#{dep}' from currently installed gems.", options) else pending << candidates.last.full_name end end end end if __FILE__ == $0 then options = handle_options(ARGV) if ARGV.empty? puts options.usage exit(1) else lock_down(ARGV, options) end end