public
Description: Merb Core: All you need. None you don't.
Homepage: http://www.merbivore.com
Clone URL: git://github.com/wycats/merb-core.git
fabien (author)
Sat Oct 11 06:37:28 -0700 2008
commit  dad0d934c0294e082d7dc525e61ed547daf4bf64
tree    2e9e0af214840be9b94251d3bf327e8640b3de39
parent  a7fb18c7f3cf24321ccd8a8e03f6ddafcb6be651
merb-core / lib / merb-core / tasks / gem_management.rb
100644 332 lines (286 sloc) 10.083 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
require 'rubygems'
 
module ColorfulMessages
  
  # red
  def error(*messages)
    puts messages.map { |msg| "\033[1;31m#{msg}\033[0m" }
  end
  
  # yellow
  def warning(*messages)
    puts messages.map { |msg| "\033[1;33m#{msg}\033[0m" }
  end
  
  # green
  def success(*messages)
    puts messages.map { |msg| "\033[1;32m#{msg}\033[0m" }
  end
  
  alias_method :message, :success
  
  # magenta
  def note(*messages)
    puts messages.map { |msg| "\033[1;35m#{msg}\033[0m" }
  end
  
  # blue
  def info(*messages)
    puts messages.map { |msg| "\033[1;34m#{msg}\033[0m" }
  end
  
end
 
##############################################################################
 
require 'rubygems/dependency_installer'
require 'rubygems/uninstaller'
require 'rubygems/dependency'
 
module GemManagement
  
  include ColorfulMessages
  
  # Install a gem - looks remotely and local gem cache;
  # won't process rdoc or ri options.
  def install_gem(gem, options = {})
    refresh = options.delete(:refresh) || []
    from_cache = (options.key?(:cache) && options.delete(:cache))
    if from_cache
      install_gem_from_cache(gem, options)
    else
      version = options.delete(:version)
      Gem.configuration.update_sources = false
 
      # Limit source index to install dir
      update_source_index(options[:install_dir]) if options[:install_dir]
 
      installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
      
      # Force-refresh certain gems by excluding them from the current index
      if !options[:ignore_dependencies] && refresh.respond_to?(:include?) && !refresh.empty?
        source_index = installer.instance_variable_get(:@source_index)
        source_index.gems.each do |name, spec|
          source_index.gems.delete(name) if refresh.include?(spec.name)
        end
      end
      
      exception = nil
      begin
        installer.install gem, version
      rescue Gem::InstallError => e
        exception = e
      rescue Gem::GemNotFoundException => e
        if from_cache && gem_file = find_gem_in_cache(gem, version)
          puts "Located #{gem} in gem cache..."
          installer.install gem_file
        else
          exception = e
        end
      rescue => e
        exception = e
      end
      if installer.installed_gems.empty? && exception
        error "Failed to install gem '#{gem} (#{version || 'any version'})' (#{exception.message})"
      end
      installer.installed_gems.each do |spec|
        success "Successfully installed #{spec.full_name}"
      end
      return !installer.installed_gems.empty?
    end
  end
 
  # Install a gem - looks in the system's gem cache instead of remotely;
  # won't process rdoc or ri options.
  def install_gem_from_cache(gem, options = {})
    version = options.delete(:version)
    Gem.configuration.update_sources = false
    installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
    exception = nil
    begin
      if gem_file = find_gem_in_cache(gem, version)
        puts "Located #{gem} in gem cache..."
        installer.install gem_file
      else
        raise Gem::InstallError, "Unknown gem #{gem}"
      end
    rescue Gem::InstallError => e
      exception = e
    end
    if installer.installed_gems.empty? && exception
      error "Failed to install gem '#{gem}' (#{e.message})"
    end
    installer.installed_gems.each do |spec|
      success "Successfully installed #{spec.full_name}"
    end
  end
 
  # Install a gem from source - builds and packages it first then installs.
  #
  # Examples:
  # install_gem_from_source(source_dir, :install_dir => ...)
  # install_gem_from_source(source_dir, gem_name)
  # install_gem_from_source(source_dir, :skip => [...])
  def install_gem_from_source(source_dir, *args)
    installed_gems = []
    Dir.chdir(source_dir) do
      opts = args.last.is_a?(Hash) ? args.pop : {}
      gem_name = args[0] || File.basename(source_dir)
      gem_pkg_dir = File.join(source_dir, 'pkg')
      gem_pkg_glob = File.join(gem_pkg_dir, "#{gem_name}-*.gem")
      skip_gems = opts.delete(:skip) || []
 
      # Cleanup what's already there
      clobber(source_dir)
      FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir)
 
      # Recursively process all gem packages within the source dir
      skip_gems << gem_name
      packages = package_all(source_dir, skip_gems)
      
      if packages.length == 1
        # The are no subpackages for the main package
        refresh = [gem_name]
      else
        # Gather all packages into the top-level pkg directory
        packages.each do |pkg|
          FileUtils.copy_entry(pkg, File.join(gem_pkg_dir, File.basename(pkg)))
        end
        
        # Finally package the main gem - without clobbering the already copied pkgs
        package(source_dir, false)
        
        # Gather subgems to refresh during installation of the main gem
        refresh = packages.map do |pkg|
          File.basename(pkg, '.gem')[/^(.*?)-([\d\.]+)$/, 1] rescue nil
        end.compact
        
        # Install subgems explicitly even if ignore_dependencies is set
        if opts[:ignore_dependencies]
          refresh.each do |name|
            gem_pkg = Dir[File.join(gem_pkg_dir, "#{name}-*.gem")][0]
            install_pkg(gem_pkg, opts)
          end
        end
      end
      
      # Finally install the main gem
      if install_pkg(Dir[gem_pkg_glob][0], opts.merge(:refresh => refresh))
        installed_gems = refresh
      else
        installed_gems = []
      end
    end
    installed_gems
  end
  
  def install_pkg(gem_pkg, opts = {})
    if (gem_pkg && File.exists?(gem_pkg))
      # Needs to be executed from the directory that contains all packages
      Dir.chdir(File.dirname(gem_pkg)) { install_gem(gem_pkg, opts) }
    else
      false
    end
  end
  
  # Uninstall a gem.
  def uninstall_gem(gem, options = {})
    if options[:version] && !options[:version].is_a?(Gem::Requirement)
      options[:version] = Gem::Requirement.new ["= #{options[:version]}"]
    end
    update_source_index(options[:install_dir]) if options[:install_dir]
    Gem::Uninstaller.new(gem, options).uninstall
  end
 
  def clobber(source_dir)
    Dir.chdir(source_dir) do
      system "#{Gem.ruby} -S rake -s clobber" unless File.exists?('Thorfile')
    end
  end
 
  def package(source_dir, clobber = true)
    Dir.chdir(source_dir) do
      if File.exists?('Thorfile')
        thor ":package"
      elsif File.exists?('Rakefile')
        rake "clobber" if clobber
        rake "package"
      end
    end
    Dir[File.join(source_dir, 'pkg/*.gem')]
  end
 
  def package_all(source_dir, skip = [], packages = [])
    if Dir[File.join(source_dir, '{Rakefile,Thorfile}')][0]
      name = File.basename(source_dir)
      Dir[File.join(source_dir, '*', '{Rakefile,Thorfile}')].each do |taskfile|
        package_all(File.dirname(taskfile), skip, packages)
      end
      packages.push(*package(source_dir)) unless skip.include?(name)
    end
    packages.uniq
  end
  
  def rake(cmd)
    system "#{Gem.ruby} -S #{which('rake')} -s #{cmd}"
  end
  
  def thor(cmd)
    system "#{Gem.ruby} -S #{which('thor')} #{cmd}"
  end
 
  # Use the local bin/* executables if available.
  def which(executable)
    if File.executable?(exec = File.join(Dir.pwd, 'bin', executable))
      exec
    else
      executable
    end
  end
  
  # Partition gems into system, local and missing gems
  def partition_dependencies(dependencies, gem_dir)
    system_specs, local_specs, missing_deps = [], [], []
    if gem_dir && File.directory?(gem_dir)
      gem_dir = File.expand_path(gem_dir)
      ::Gem.clear_paths; ::Gem.path.unshift(gem_dir)
      ::Gem.source_index.refresh!
      dependencies.each do |dep|
        gemspecs = ::Gem.source_index.search(dep)
        local = gemspecs.reverse.find { |s| s.loaded_from.index(gem_dir) == 0 }
        if local
          local_specs << local
        elsif gemspecs.last
          system_specs << gemspecs.last
        else
          missing_deps << dep
        end
      end
      ::Gem.clear_paths
    end
    [system_specs, local_specs, missing_deps]
  end
  
  # Create a modified executable wrapper in the specified bin directory.
  def ensure_bin_wrapper_for(gem_dir, bin_dir, *gems)
    if bin_dir && File.directory?(bin_dir)
      gems.each do |gem|
        if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{gem}-*.gemspec")].last
          spec = Gem::Specification.load(gemspec_path)
          spec.executables.each do |exec|
            executable = File.join(bin_dir, exec)
            message "Writing executable wrapper #{executable}"
            File.open(executable, 'w', 0755) do |f|
              f.write(executable_wrapper(spec, exec))
            end
          end
        end
      end
    end
  end
 
  private
 
  def executable_wrapper(spec, bin_file_name)
    <<-TEXT
#!/usr/bin/env ruby
#
# This file was generated by Merb's GemManagement
#
# The application '#{spec.name}' is installed as part of a gem, and
# this file is here to facilitate running it.
 
begin
require 'minigems'
rescue LoadError
require 'rubygems'
end
 
if File.directory?(gems_dir = File.join(Dir.pwd, 'gems')) ||
File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
$BUNDLE = true; Gem.clear_paths; Gem.path.unshift(gems_dir)
end
 
version = "#{Gem::Requirement.default}"
 
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
version = $1
ARGV.shift
end
 
gem '#{spec.name}', version
load '#{bin_file_name}'
TEXT
  end
 
  def find_gem_in_cache(gem, version)
    spec = if version
      version = Gem::Requirement.new ["= #{version}"] unless version.is_a?(Gem::Requirement)
      Gem.source_index.find_name(gem, version).first
    else
      Gem.source_index.find_name(gem).sort_by { |g| g.version }.last
    end
    if spec && File.exists?(gem_file = "#{spec.installation_path}/cache/#{spec.full_name}.gem")
      gem_file
    end
  end
 
  def update_source_index(dir)
    Gem.source_index.load_gems_in(File.join(dir, 'specifications'))
  end
  
end