1require 'rubygems/command'
2
3class Gem::Commands::LockCommand < Gem::Command
4
5  def initialize
6    super 'lock', 'Generate a lockdown list of gems',
7          :strict => false
8
9    add_option '-s', '--[no-]strict',
10               'fail if unable to satisfy a dependency' do |strict, options|
11      options[:strict] = strict
12    end
13  end
14
15  def arguments # :nodoc:
16    "GEMNAME       name of gem to lock\nVERSION       version of gem to lock"
17  end
18
19  def defaults_str # :nodoc:
20    "--no-strict"
21  end
22
23  def description # :nodoc:
24    <<-EOF
25The lock command will generate a list of +gem+ statements that will lock down
26the versions for the gem given in the command line.  It will specify exact
27versions in the requirements list to ensure that the gems loaded will always
28be consistent.  A full recursive search of all effected gems will be
29generated.
30
31Example:
32
33  gem lock rails-1.0.0 > lockdown.rb
34
35will produce in lockdown.rb:
36
37  require "rubygems"
38  gem 'rails', '= 1.0.0'
39  gem 'rake', '= 0.7.0.1'
40  gem 'activesupport', '= 1.2.5'
41  gem 'activerecord', '= 1.13.2'
42  gem 'actionpack', '= 1.11.2'
43  gem 'actionmailer', '= 1.1.5'
44  gem 'actionwebservice', '= 1.0.0'
45
46Just load lockdown.rb from your application to ensure that the current
47versions are loaded.  Make sure that lockdown.rb is loaded *before* any
48other require statements.
49
50Notice that rails 1.0.0 only requires that rake 0.6.2 or better be used.
51Rake-0.7.0.1 is the most recent version installed that satisfies that, so we
52lock it down to the exact version.
53    EOF
54  end
55
56  def usage # :nodoc:
57    "#{program_name} GEMNAME-VERSION [GEMNAME-VERSION ...]"
58  end
59
60  def complain(message)
61    if options[:strict] then
62      raise Gem::Exception, message
63    else
64      say "# #{message}"
65    end
66  end
67
68  def execute
69    say "require 'rubygems'"
70
71    locked = {}
72
73    pending = options[:args]
74
75    until pending.empty? do
76      full_name = pending.shift
77
78      spec = Gem::Specification.load spec_path(full_name)
79
80      if spec.nil? then
81        complain "Could not find gem #{full_name}, try using the full name"
82        next
83      end
84
85      say "gem '#{spec.name}', '= #{spec.version}'" unless locked[spec.name]
86      locked[spec.name] = true
87
88      spec.runtime_dependencies.each do |dep|
89        next if locked[dep.name]
90        candidates = dep.matching_specs
91
92        if candidates.empty? then
93          complain "Unable to satisfy '#{dep}' from currently installed gems"
94        else
95          pending << candidates.last.full_name
96        end
97      end
98    end
99  end
100
101  def spec_path(gem_full_name)
102    gemspecs = Gem.path.map { |path|
103      File.join path, "specifications", "#{gem_full_name}.gemspec"
104    }
105
106    gemspecs.find { |path| File.exist? path }
107  end
108
109end
110
111