On Thu, Apr 19, 2007 at 07:39:03PM -1000, Ben Munat wrote:
> Also, the idea of specifying dependencies (ala Maven) in yaml sounds 
> pretty cool. Were you thinking about doing something like that?

Gems already have a mechanism for specifying dependencies.

Several weeks ago when I checked in the changes to the plugin loading code
Chad and I worked on functionality similar to the proposed patch. We took it
a step further  with a slightly different approach whereby gem files placed
in a certain location would automaticaly be unpacked and loaded like regular
plugins on startup. The code is complete (as we intended it), tested and
documented, but despite Chad's repeated insistance, I haven't checked it in
yet...I've attached a diff for those who are interested.

marcel
-- 
Marcel Molina Jr. <[EMAIL PROTECTED]>

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Core" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-core?hl=en
-~----------~----~----~----~------~----~------~--~---

Index: test/plugin_locator_test.rb
===================================================================
--- test/plugin_locator_test.rb (revision 6292)
+++ test/plugin_locator_test.rb (working copy)
@@ -1,4 +1,6 @@
 require File.dirname(__FILE__) + '/plugin_test_helper'
+require 'fileutils'
+require 'rubygems/builder'
 
 class TestPluginFileSystemLocator < Test::Unit::TestCase
   def setup
@@ -37,5 +39,180 @@
   private
     def new_locator(initializer = @initializer)
       Rails::Plugin::FileSystemLocator.new(initializer)
-    end   
+    end  
+    
+end
+
+class TestPluginGemLocator < Test::Unit::TestCase
+  def setup
+    configuration = Rails::Configuration.new
+    configuration.gem_home = gem_home_root_path
+    @initializer  = Rails::Initializer.new(configuration)
+    @locator      = new_gem_locator
+  end
+  
+  def teardown
+    purge_gems
+  end
+  
+  def test_gem_paths_are_set_to_the_appropriate_gem_home_for_the_app
+    application_gem_home_registered_with_rubygems = lambda do
+      !Gem.path.grep(/[EMAIL PROTECTED]/).empty?
+    end
+    @locator.send(:setup_gem_environment)
+    assert application_gem_home_registered_with_rubygems.call
+  end
+  
+  def test_gems_are_not_installed_unecessarily_if_they_are_already_installed
+    gem_home_mtime = lambda { File.mtime(@initializer.configuration.gem_home) }
+    using_scenario 'gem_with_dep_that_has_its_own_deps' do
+      gem_home_mtime_before_installing = gem_home_mtime.call
+      @locator.send(:prepare_gems)
+      after_installing = gem_home_mtime.call
+      assert_not_equal gem_home_mtime_before_installing, after_installing
+      new_gem_locator.send(:prepare_gems)
+      assert_equal after_installing, gem_home_mtime.call
+    end
+  end
+  
+  def 
test_that_all_gems_in_the_bundled_gem_directory_are_installed_prior_to_loading
+    bundled_gems_are_installed = lambda do
+      [EMAIL PROTECTED]
+    end
+
+    using_scenario 'gem_with_no_deps' do
+      assert !bundled_gems_are_installed.call
+      @locator.send(:prepare_gems)
+      assert bundled_gems_are_installed.call
+    end
+  end
+  
+  %w[gem_with_one_level_of_deps gem_with_dep_that_has_its_own_deps].each do 
|scenario|
+    define_method("test_unpacking_order_for_the_#{scenario}_scenario") do
+      using_scenario scenario do
+        @locator.send(:setup_gem_environment)
+        assert_equal expected_gem_loading_order, 
@locator.installer.send(:gems_to_install).map(&:full_name)
+      end
+    end
+  end
+  
+  %w[gem_with_no_deps gem_with_one_level_of_deps 
gem_with_dep_that_has_its_own_deps].each do |scenario|
+    
define_method("test_the_appropriate_gem_plugins_are_located_for_the_#{scenario}_scenario")
 do
+      using_scenario scenario do
+        assert_equal expected_gem_loading_order, @locator.plugin_names
+      end
+    end
+  end
+  
+  def 
test_declaring_an_explicit_plugin_load_order_that_contradicts_the_gem_dependency_loading_order_raises_a_load_error
+    using_scenario 'gem_with_dep_that_has_its_own_deps' do
+      only_load_the_following_plugins! expected_gem_loading_order.reverse
+      assert_raises(LoadError) do
+        @initializer.load_plugins
+      end
+    end
+  end
+  
+  def 
test_declaring_an_explicit_plugin_load_order_that_jives_with_the_gem_depency_loading_order_works
+    using_scenario 'gem_with_dep_that_has_its_own_deps' do
+      only_load_the_following_plugins! expected_gem_loading_order
+      assert_nothing_raised do
+        @initializer.load_plugins
+      end
+    end
+  end
+  
+  def 
test_trying_to_load_a_gem_with_a_missing_depedency_raises_an_install_error
+    using_scenario 'gem_with_a_missing_dep' do
+      assert_raises(Gem::InstallError) do
+        @locator.send(:prepare_gems)
+      end
+    end
+  end
+  
+  def 
test_trying_load_a_gem_whose_dependency_constraint_can_not_be_satisfied_raises
+    using_scenario 
'gem_with_version_constraint_on_dep_that_can_not_be_satisfied' do
+      assert_raises(Gem::InstallError) do
+        @locator.send(:prepare_gems)
+      end
+    end
+  end
+  
+  def test_unneeded_gems_are_uninstalled
+    flunk 'Need a test for this'
+  end
+  
+  private
+    def new_gem_locator(initializer = @initializer)
+      Rails::Plugin::GemLocator.new(initializer)
+    end
+    
+    def using_scenario(scenario)
+      set_bundled_gem_path! scenario
+      generate_gems
+      yield
+    ensure
+      purge_gems
+    end
+    
+    def set_bundled_gem_path!(bundled_gem_path)
+      @initializer.configuration.bundled_gem_path = 
File.join(plugin_gem_fixture_root_path, bundled_gem_path)
+    end
+    
+    def expected_gem_loading_order
+      @expected_gem_loading_order ||= 
YAML.load_file(File.join(bundled_gem_path, 'dependency_loading_order.yml'))
+    end
+    
+    def bundled_gem_path
+      @initializer.configuration.bundled_gem_path
+    end
+    
+    def generate_gems
+      Dir["#{bundled_gem_path}/**/*.gemspec"].each do |spec_file|
+        # Initialize outside the block so it is in scope
+        gem_path, specification = nil
+        execute_from_within(File.dirname(spec_file)) do 
+          specification = Gem::Specification.load(File.basename(spec_file))
+          # Usually the Gem::Builder prints out its progress. We want to 
silent that.
+          Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do
+            Gem::Builder.new(specification).build
+          end
+        end
+        FileUtils.mv File.join(File.dirname(spec_file), 
"#{specification.full_name}.gem"), bundled_gem_path
+      end
+    end
+    
+    # This is a hack. Gem::Specification.load eval's the passed in spec file. 
Usually,
+    # when this is done with the gem command, it is executed from the same 
directory as 
+    # where the gemspec is located. If this is not the case though, the eval 
is done relative to
+    # the calling code's directory. To work around this, while loading the gem 
spec, we move to 
+    # the gemspec's directory.
+    def execute_from_within(path)
+      originating_path = Dir.pwd
+      Dir.chdir path
+      yield
+    ensure
+      Dir.chdir originating_path
+    end
+    
+    def purge_gems
+      FileUtils.rm_rf(generated_gems)
+      FileUtils.rm_rf(installed_gems)
+    end
+    
+    def generated_gems
+      Dir["#{bundled_gem_path}/*.gem"]
+    end
+    
+    def installed_gems
+      Dir["#{gem_home_root_path}/*"]
+    end      
+    
+    def gem_home_root_path
+      File.join(fixture_path, 'tmp', 'gem_home')
+    end
+    
+    def plugin_gem_fixture_root_path
+      File.join(fixture_path, 'gems')
+    end
 end
\ No newline at end of file
Index: 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_one/dependency_one.gemspec
===================================================================
--- 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_one/dependency_one.gemspec
       (revision 0)
+++ 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_one/dependency_one.gemspec
       (revision 0)
@@ -0,0 +1,6 @@
+Gem::Specification.new do |s|
+  s.name     = 'dependency_one'
+  s.version  = '1.0.0'
+  s.summary  = 'This gem is a terminal dependency on top_level_gem but its 
version is too low'
+  s.files    = Dir['*.rb'] + Dir['lib/*']
+end
Index: 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_one/init.rb
===================================================================
--- 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_one/init.rb
      (revision 0)
+++ 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_one/init.rb
      (revision 0)
@@ -0,0 +1 @@
+# This gem does nothing for now
Index: 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/top_level_gem_with_unsatisfiable_dependency/init.rb
===================================================================
--- 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/top_level_gem_with_unsatisfiable_dependency/init.rb
 (revision 0)
+++ 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/top_level_gem_with_unsatisfiable_dependency/init.rb
 (revision 0)
@@ -0,0 +1 @@
+# This gem does nothing for now
Index: 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/top_level_gem_with_unsatisfiable_dependency/top_level_gem_with_unsatisfiable_dependency.gemspec
===================================================================
--- 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/top_level_gem_with_unsatisfiable_dependency/top_level_gem_with_unsatisfiable_dependency.gemspec
     (revision 0)
+++ 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/top_level_gem_with_unsatisfiable_dependency/top_level_gem_with_unsatisfiable_dependency.gemspec
     (revision 0)
@@ -0,0 +1,7 @@
+Gem::Specification.new do |s|
+  s.name     = 'top_level_gem_with_unsatisfiable_dependency'
+  s.version  = '1.0.0'
+  s.summary  = 'This gem has a dependency on dependency_one'
+  s.files    = Dir['*.rb'] + Dir['lib/*']
+  s.add_dependency 'dependency_one', '> 3.0.0'
+end
Index: 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_loading_order.yml
===================================================================
--- 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_loading_order.yml
        (revision 0)
+++ 
test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_loading_order.yml
        (revision 0)
@@ -0,0 +1,2 @@
+- dependency_one-1.0.0
+- top_level_gem_with_unsatisfiable_dependency-1.0.0
\ No newline at end of file
Index: 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/dependency_loading_order.yml
===================================================================
--- 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/dependency_loading_order.yml
  (revision 0)
+++ 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/dependency_loading_order.yml
  (revision 0)
@@ -0,0 +1,3 @@
+- second_generation_dep-1.0.0
+- first_generation_dep-1.0.0
+- top_level_gem_with_nested_deps-1.0.0
\ No newline at end of file
Index: 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/top_level_gem_with_nested_deps/init.rb
===================================================================
--- 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/top_level_gem_with_nested_deps/init.rb
        (revision 0)
+++ 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/top_level_gem_with_nested_deps/init.rb
        (revision 0)
@@ -0,0 +1 @@
+# This gem does nothing for now
Index: 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/top_level_gem_with_nested_deps/top_level_gem_with_nested_deps.gemspec
===================================================================
--- 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/top_level_gem_with_nested_deps/top_level_gem_with_nested_deps.gemspec
 (revision 0)
+++ 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/top_level_gem_with_nested_deps/top_level_gem_with_nested_deps.gemspec
 (revision 0)
@@ -0,0 +1,7 @@
+Gem::Specification.new do |s|
+  s.name     = 'top_level_gem_with_nested_deps'
+  s.version  = '1.0.0'
+  s.summary  = 'This gem has a depency on first_generation_dep which has its 
own dependency'
+  s.files    = Dir['*.rb'] + Dir['lib/*']
+  s.add_dependency 'first_generation_dep'
+end
Index: 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/first_generation_dep/init.rb
===================================================================
--- 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/first_generation_dep/init.rb
  (revision 0)
+++ 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/first_generation_dep/init.rb
  (revision 0)
@@ -0,0 +1 @@
+# This gem does nothing for now
Index: 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/first_generation_dep/first_generation_dep.gemspec
===================================================================
--- 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/first_generation_dep/first_generation_dep.gemspec
     (revision 0)
+++ 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/first_generation_dep/first_generation_dep.gemspec
     (revision 0)
@@ -0,0 +1,7 @@
+Gem::Specification.new do |s|
+  s.name     = 'first_generation_dep'
+  s.version  = '1.0.0'
+  s.summary  = 'This gem is an intermediary dependency'
+  s.files    = Dir['*.rb'] + Dir['lib/*']
+  s.add_dependency 'second_generation_dep'
+end
Index: 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/second_generation_dep/second_generation_dep.gemspec
===================================================================
--- 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/second_generation_dep/second_generation_dep.gemspec
   (revision 0)
+++ 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/second_generation_dep/second_generation_dep.gemspec
   (revision 0)
@@ -0,0 +1,6 @@
+Gem::Specification.new do |s|
+  s.name     = 'second_generation_dep'
+  s.version  = '1.0.0'
+  s.summary  = 'This is the terminal dependency in multiple levels of 
dependencies'
+  s.files    = Dir['*.rb'] + Dir['lib/*']
+end
Index: 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/second_generation_dep/init.rb
===================================================================
--- 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/second_generation_dep/init.rb
 (revision 0)
+++ 
test/fixtures/gems/gem_with_dep_that_has_its_own_deps/second_generation_dep/init.rb
 (revision 0)
@@ -0,0 +1 @@
+# This gem does nothing for now
Index: test/fixtures/gems/gem_with_a_missing_dep/dependency_loading_order.yml
===================================================================
--- test/fixtures/gems/gem_with_a_missing_dep/dependency_loading_order.yml      
(revision 0)
+++ test/fixtures/gems/gem_with_a_missing_dep/dependency_loading_order.yml      
(revision 0)
@@ -0,0 +1,2 @@
+- dependency_one-1.0.0
+- top_level_gem_with_missing_dep-1.0.0
\ No newline at end of file
Index: 
test/fixtures/gems/gem_with_a_missing_dep/top_level_gem_with_missing_dep/top_level_gem_with_missing_dep.gemspec
===================================================================
--- 
test/fixtures/gems/gem_with_a_missing_dep/top_level_gem_with_missing_dep/top_level_gem_with_missing_dep.gemspec
     (revision 0)
+++ 
test/fixtures/gems/gem_with_a_missing_dep/top_level_gem_with_missing_dep/top_level_gem_with_missing_dep.gemspec
     (revision 0)
@@ -0,0 +1,7 @@
+Gem::Specification.new do |s|
+  s.name     = 'top_level_gem_with_missing_dep'
+  s.version  = '1.0.0'
+  s.summary  = 'This gem has a dependency on dependency_one but dependency_one 
is missing'
+  s.files    = Dir['*.rb'] + Dir['lib/*']
+  s.add_dependency 'dependency_one'
+end
Index: 
test/fixtures/gems/gem_with_a_missing_dep/top_level_gem_with_missing_dep/init.rb
===================================================================
--- 
test/fixtures/gems/gem_with_a_missing_dep/top_level_gem_with_missing_dep/init.rb
    (revision 0)
+++ 
test/fixtures/gems/gem_with_a_missing_dep/top_level_gem_with_missing_dep/init.rb
    (revision 0)
@@ -0,0 +1 @@
+# This gem does nothing for now
Index: test/fixtures/gems/gem_with_no_deps/top_level_gem_with_no_deps/init.rb
===================================================================
--- test/fixtures/gems/gem_with_no_deps/top_level_gem_with_no_deps/init.rb      
(revision 0)
+++ test/fixtures/gems/gem_with_no_deps/top_level_gem_with_no_deps/init.rb      
(revision 0)
@@ -0,0 +1 @@
+# This gem does nothing for now
Index: 
test/fixtures/gems/gem_with_no_deps/top_level_gem_with_no_deps/top_level_gem_with_no_deps.gemspec
===================================================================
--- 
test/fixtures/gems/gem_with_no_deps/top_level_gem_with_no_deps/top_level_gem_with_no_deps.gemspec
   (revision 0)
+++ 
test/fixtures/gems/gem_with_no_deps/top_level_gem_with_no_deps/top_level_gem_with_no_deps.gemspec
   (revision 0)
@@ -0,0 +1,6 @@
+Gem::Specification.new do |s|
+  s.name     = 'top_level_gem_with_no_deps'
+  s.version  = '1.0.0'
+  s.summary  = 'This gem has no depencies'
+  s.files    = Dir['*.rb'] + Dir['lib/*']
+end
Index: test/fixtures/gems/gem_with_no_deps/dependency_loading_order.yml
===================================================================
--- test/fixtures/gems/gem_with_no_deps/dependency_loading_order.yml    
(revision 0)
+++ test/fixtures/gems/gem_with_no_deps/dependency_loading_order.yml    
(revision 0)
@@ -0,0 +1 @@
+- top_level_gem_with_no_deps-1.0.0
\ No newline at end of file
Index: 
test/fixtures/gems/gem_with_one_level_of_deps/dependency_one/dependency_one.gemspec
===================================================================
--- 
test/fixtures/gems/gem_with_one_level_of_deps/dependency_one/dependency_one.gemspec
 (revision 0)
+++ 
test/fixtures/gems/gem_with_one_level_of_deps/dependency_one/dependency_one.gemspec
 (revision 0)
@@ -0,0 +1,6 @@
+Gem::Specification.new do |s|
+  s.name     = 'dependency_one'
+  s.version  = '1.0.0'
+  s.summary  = 'This gem is a terminal dependency on top_level_gem'
+  s.files    = Dir['*.rb'] + Dir['lib/*']
+end
Index: test/fixtures/gems/gem_with_one_level_of_deps/dependency_one/init.rb
===================================================================
--- test/fixtures/gems/gem_with_one_level_of_deps/dependency_one/init.rb        
(revision 0)
+++ test/fixtures/gems/gem_with_one_level_of_deps/dependency_one/init.rb        
(revision 0)
@@ -0,0 +1 @@
+# This gem does nothing for now
Index: 
test/fixtures/gems/gem_with_one_level_of_deps/top_level_gem/top_level_gem.gemspec
===================================================================
--- 
test/fixtures/gems/gem_with_one_level_of_deps/top_level_gem/top_level_gem.gemspec
   (revision 0)
+++ 
test/fixtures/gems/gem_with_one_level_of_deps/top_level_gem/top_level_gem.gemspec
   (revision 0)
@@ -0,0 +1,7 @@
+Gem::Specification.new do |s|
+  s.name     = 'top_level_gem'
+  s.version  = '1.0.0'
+  s.summary  = 'This gem has a dependency on dependency_one'
+  s.files    = Dir['*.rb'] + Dir['lib/*']
+  s.add_dependency 'dependency_one'
+end
Index: test/fixtures/gems/gem_with_one_level_of_deps/top_level_gem/init.rb
===================================================================
--- test/fixtures/gems/gem_with_one_level_of_deps/top_level_gem/init.rb 
(revision 0)
+++ test/fixtures/gems/gem_with_one_level_of_deps/top_level_gem/init.rb 
(revision 0)
@@ -0,0 +1 @@
+# This gem does nothing for now
Index: 
test/fixtures/gems/gem_with_one_level_of_deps/dependency_loading_order.yml
===================================================================
--- test/fixtures/gems/gem_with_one_level_of_deps/dependency_loading_order.yml  
(revision 0)
+++ test/fixtures/gems/gem_with_one_level_of_deps/dependency_loading_order.yml  
(revision 0)
@@ -0,0 +1,2 @@
+- dependency_one-1.0.0
+- top_level_gem-1.0.0
\ No newline at end of file
Index: test/plugin_test_helper.rb
===================================================================
--- test/plugin_test_helper.rb  (revision 6292)
+++ test/plugin_test_helper.rb  (working copy)
@@ -9,9 +9,13 @@
 RAILS_ROOT = '.' unless defined?(RAILS_ROOT)
 class Test::Unit::TestCase
   def plugin_fixture_root_path
-    File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
+    File.join(fixture_path, 'plugins')
   end
   
+  def fixture_path
+    File.join(File.dirname(__FILE__), 'fixtures')
+  end
+  
   def only_load_the_following_plugins!(plugins)
     @initializer.configuration.plugins = plugins
   end
Index: lib/rails_generator/generators/applications/app/app_generator.rb
===================================================================
--- lib/rails_generator/generators/applications/app/app_generator.rb    
(revision 6292)
+++ lib/rails_generator/generators/applications/app/app_generator.rb    
(working copy)
@@ -154,10 +154,12 @@
     test/unit
     vendor
     vendor/plugins
+    vendor/gems
     tmp/sessions
     tmp/sockets
     tmp/cache
     tmp/pids
+    tmp/gem_home
   )
 
   MYSQL_SOCKET_LOCATIONS = [
Index: lib/initializer.rb
===================================================================
--- lib/initializer.rb  (revision 6293)
+++ lib/initializer.rb  (working copy)
@@ -1,5 +1,6 @@
 require 'logger'
 require 'set'
+require 'fileutils'
 require File.join(File.dirname(__FILE__), 'railties_path')
 require File.join(File.dirname(__FILE__), 'rails/version')
 require File.join(File.dirname(__FILE__), 'rails/plugin/locator')
@@ -346,7 +347,16 @@
         unless configuration.plugins.nil?
           unless loaded_plugins == configuration.plugins
             missing_plugins = configuration.plugins - loaded_plugins
-            raise LoadError, "Could not locate the following plugins: 
#{missing_plugins.to_sentence}"
+            message = if missing_plugins.any?
+              "Could not locate the following plugins: 
#{missing_plugins.to_sentence}"
+            else
+              "It seems as though you have specified an explicit plugin 
loading order "    +
+              "that contradicts the dependency loading order of a gem plugin. 
"            +
+              "Try comparing your explicit loading order with the dependency 
loading "     +
+              "order of the gem plugins you have installed in 
#{configuration.gem_home}. " +
+              "You probably should just remove the explicit plugin loading. "  
 
+            end
+            raise LoadError, message
           end
         end
       end
@@ -441,7 +451,15 @@
     # The path to the root of the plugins directory. By default, it is in
     # <tt>vendor/plugins</tt>.
     attr_accessor :plugin_paths
+
+    # The path to the root of the bundled gems directory. By default, it is in
+    # <tt>vendor/gems</tt>.
+    attr_accessor :bundled_gem_path
     
+    # The path into which Rails will on-the-fly install gems from its 
+    # bundled_gem_path on startup.  By default, it is in <tt>tmp/gem_home</tt>
+    attr_accessor :gem_home
+        
     # The classes that handle finding the desired plugins that you'd like to 
load for
     # your application. By default it is the Rails::Plugin::FileSystemLocator 
which finds
     # plugins to load in <tt>vendor/plugins</tt>. You can hook into gem 
location by subclassing
@@ -468,6 +486,8 @@
       self.whiny_nils                   = default_whiny_nils
       self.plugins                      = default_plugins
       self.plugin_paths                 = default_plugin_paths
+      self.bundled_gem_path             = default_bundled_gem_path      
+      self.gem_home                     = default_gem_home      
       self.plugin_locators              = default_plugin_locators
       self.plugin_loader                = default_plugin_loader
       self.database_configuration_file  = default_database_configuration_file
@@ -625,8 +645,16 @@
         ["#{root_path}/vendor/plugins"]
       end
       
+      def default_bundled_gem_path
+        "#{root_path}/vendor/gems/"
+      end
+      
+      def default_gem_home
+        "#{root_path}/tmp/gem_home"
+      end
+      
       def default_plugin_locators
-        [Plugin::FileSystemLocator]
+        [Plugin::GemLocator, Plugin::FileSystemLocator]
       end
       
       def default_plugin_loader
Index: lib/rails/plugin/locator.rb
===================================================================
--- lib/rails/plugin/locator.rb (revision 6293)
+++ lib/rails/plugin/locator.rb (working copy)
@@ -27,33 +27,151 @@
     end
     
     class FileSystemLocator < Locator
-        private
-          def located_plugins
-            initializer.configuration.plugin_paths.flatten.inject([]) do 
|plugins, path|
+      private
+        def located_plugins
+          initializer.configuration.plugin_paths.flatten.inject([]) do 
|plugins, path|
+            plugins.concat locate_plugins_under(path)
+            plugins
+          end.flatten
+        end
+
+        # This starts at the base path looking for directories that pass the 
plugin_path? test of the Plugin::Loader.
+        # Since plugins can be nested arbitrarily deep within an unspecified 
number of intermediary directories, 
+        # this method runs recursively until it finds a plugin directory.
+        #
+        #   e.g.
+        #
+        #     locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon')
+        #     => 'acts_as_chunky_bacon' 
+        def locate_plugins_under(base_path)
+           Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path|
+            plugin_loader = 
initializer.configuration.plugin_loader.new(initializer, path)
+            if plugin_loader.loadable?
+              plugins << plugin_loader
+            elsif File.directory?(path)
               plugins.concat locate_plugins_under(path)
-              plugins
-            end.flatten
+            end
+            plugins
           end
+        end
+    end
+    
+    class GemLocator < Locator
+      require 'rubygems/dependency_list'
+      require 'rubygems/format'
+      require 'rubygems/installer'
+      
+      attr_reader :installer
+      
+      def initialize(*args)
+        super
+        @installer = Installer.new(self)
+      end
+      
+      def plugins
+        # Unlike the parent class, we don't want to sort the loaders, 
+        # as RubyGems takes care of sorting them in depency order already.
+        located_plugins.select(&:enabled?)
+      end
+          
+      def spec_to_path_mapping
+        @spec_to_path_mapping ||= 
Dir[File.join(initializer.configuration.bundled_gem_path, "*.gem")].inject({}) 
do |mapping, gem_path|
+          mapping[specification_for(gem_path)] = gem_path
+          mapping
+        end
+      end
+      
+      def specification_for(gem_path)
+        Gem::Format.from_file_by_path(gem_path).spec
+      end
+      
+      def bundled_gems
+        spec_to_path_mapping.values
+      end
 
-          # This starts at the base path looking for directories that pass the 
plugin_path? test of the Plugin::Loader.
-          # Since plugins can be nested arbitrarily deep within an unspecified 
number of intermediary directories, 
-          # this method runs recursively until it finds a plugin directory.
-          #
-          #   e.g.
-          #
-          #     
locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon')
-          #     => 'acts_as_chunky_bacon' 
-          def locate_plugins_under(base_path)
-             Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path|
-              plugin_loader = 
initializer.configuration.plugin_loader.new(initializer, path)
-              if plugin_loader.plugin_path? && plugin_loader.enabled?
-                plugins << plugin_loader
-              elsif File.directory?(path)
-                plugins.concat locate_plugins_under(path)
+      def path_for(spec)
+        spec_to_path_mapping[spec]
+      end
+
+      def plugin_path_for(spec)
+        File.join(initializer.configuration.gem_home, 'gems', spec.full_name)
+      end
+      
+      private        
+        def located_plugins
+          prepare_gems
+
+          installer.installed_gems.inject([]) do |plugins, 
gem_plugin_directory|
+            plugin_loader = 
initializer.configuration.plugin_loader.new(initializer, gem_plugin_directory)
+            plugins << plugin_loader if plugin_loader.loadable?
+            plugins
+          end
+        end
+        
+        def prepare_gems
+          setup_gem_environment
+          install_gems
+        end
+      
+        # Sets a local GEM_HOME for this Rails application.  Installs all gems 
from vendor/gems 
+        # (or configured bundled_gem_path) into this
+        # directory and makes them available to be loaded as plugins.
+        def setup_gem_environment
+          Gem.manage_gems
+          # N.B. the gem_home must be an absolute path or else the 
Gem::Installer will fail
+          Gem.use_paths(File.expand_path(initializer.configuration.gem_home), 
[Gem.dir])
+        end
+        
+        def install_gems
+          installer.install
+        end
+        
+        class Installer          
+          attr_reader :locator, :installed_gems, :source_index
+          
+          def initialize(locator)
+            @locator        = locator
+            @source_index   = 
Gem::SourceIndex.from_gems_in(File.join(locator.initializer.configuration.gem_home,
 'specifications'))
+            @installed_gems = []
+          end
+          
+          def install
+            gems_to_install.each do |spec|
+              gem_path = locator.path_for(spec)
+              Gem::Installer.new(gem_path).install unless installed?(spec)
+              @installed_gems << locator.plugin_path_for(spec)
+            end
+            uninstall_unused_gems!
+          end
+          
+          private
+            def gems_to_install
+              @gems_to_install ||= 
locator.bundled_gems.inject(Gem::DependencyList.new) do |dependency_list, 
gem_path|
+                dependency_list.add locator.specification_for(gem_path)
+                dependency_list
+              end.dependency_order.reverse
+            end
+            
+            def installed?(spec)
+              !source_index.find_name(spec.name, spec.version).empty?
+            end
+            
+            def uninstall_unused_gems!
+              unused_gems.each do |spec|
+                Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do
+                  Gem::Uninstaller.new(spec.name, :version => "= 
#{spec.version}").uninstall
+                end
               end
-              plugins
             end
-          end
+            
+            def unused_gems
+              previously_installed_gems - gems_to_install
+            end
+            
+            def previously_installed_gems
+              source_index.latest_specs.values
+            end
+        end
     end
   end
 end
\ No newline at end of file
Index: lib/rails/plugin/loader.rb
===================================================================
--- lib/rails/plugin/loader.rb  (revision 6293)
+++ lib/rails/plugin/loader.rb  (working copy)
@@ -28,6 +28,10 @@
       def loaded?
         initializer.loaded_plugins.include?(name)
       end
+      
+      def loadable?
+        plugin_path? && enabled?
+      end
   
       def plugin_path?
         File.directory?(directory) && (has_lib_directory? || has_init_file?)

Reply via email to