Ticket #8511: gems_loaded_from_vendor_like_plugins.2.diff
| File gems_loaded_from_vendor_like_plugins.2.diff, 30.6 kB (added by josh, 1 year ago) |
|---|
-
railties/test/plugin_locator_test.rb
old new 1 1 require File.dirname(__FILE__) + '/plugin_test_helper' 2 require 'fileutils' 3 require 'rubygems/builder' 2 4 3 5 class TestPluginFileSystemLocator < Test::Unit::TestCase 4 6 def setup … … 9 11 @initializer = Rails::Initializer.new(configuration) 10 12 @locator = new_locator 11 13 end 12 14 13 15 def test_no_plugins_are_loaded_if_the_configuration_has_an_empty_plugin_list 14 16 only_load_the_following_plugins! [] 15 17 assert_equal [], @locator.plugins … … 37 39 private 38 40 def new_locator(initializer = @initializer) 39 41 Rails::Plugin::FileSystemLocator.new(initializer) 40 end 42 end 43 44 end 45 46 class TestPluginGemLocator < Test::Unit::TestCase 47 def setup 48 configuration = Rails::Configuration.new 49 configuration.gem_home = gem_home_root_path 50 @initializer = Rails::Initializer.new(configuration) 51 @locator = new_gem_locator 52 end 53 54 def teardown 55 purge_gems 56 end 57 58 def test_gem_paths_are_set_to_the_appropriate_gem_home_for_the_app 59 application_gem_home_registered_with_rubygems = lambda do 60 !Gem.path.grep(/#{@initializer.configuration.gem_home}/).empty? 61 end 62 @locator.send(:setup_gem_environment) 63 assert application_gem_home_registered_with_rubygems.call 64 end 65 66 def test_gems_are_not_installed_unecessarily_if_they_are_already_installed 67 gem_home_ctime = lambda { File.ctime(@initializer.configuration.gem_home) } 68 using_scenario 'gem_with_dep_that_has_its_own_deps' do 69 gem_home_ctime_before_installing = gem_home_ctime.call 70 # Resolution of ctime on OS X is in seconds so we have to sleep to verify 71 sleep 1 72 @locator.send(:prepare_gems) 73 after_installing = gem_home_ctime.call 74 # Gems were unpacked 75 assert_not_equal gem_home_ctime_before_installing, after_installing 76 new_gem_locator.send(:prepare_gems) 77 # No need to unpack any gems 78 assert_equal after_installing, gem_home_ctime.call 79 end 80 end 81 82 def test_that_all_gems_in_the_bundled_gem_directory_are_installed_prior_to_loading 83 bundled_gems_are_installed = lambda do 84 !@locator.installer.installed_gems.empty? 85 end 86 87 using_scenario 'gem_with_no_deps' do 88 assert !bundled_gems_are_installed.call 89 @locator.send(:prepare_gems) 90 assert bundled_gems_are_installed.call 91 end 92 end 93 94 %w[gem_with_one_level_of_deps gem_with_dep_that_has_its_own_deps].each do |scenario| 95 define_method("test_unpacking_order_for_the_#{scenario}_scenario") do 96 using_scenario scenario do 97 @locator.send(:setup_gem_environment) 98 assert_equal expected_gem_loading_order, @locator.installer.send(:gems_to_install).map(&:full_name) 99 end 100 end 101 end 102 103 %w[gem_with_no_deps gem_with_one_level_of_deps gem_with_dep_that_has_its_own_deps].each do |scenario| 104 define_method("test_the_appropriate_gem_plugins_are_located_for_the_#{scenario}_scenario") do 105 using_scenario scenario do 106 assert_equal expected_gem_loading_order, @locator.plugin_names 107 end 108 end 109 end 110 111 def test_declaring_an_explicit_plugin_load_order_that_contradicts_the_gem_dependency_loading_order_raises_a_load_error 112 using_scenario 'gem_with_dep_that_has_its_own_deps' do 113 only_load_the_following_plugins! expected_gem_loading_order.reverse 114 assert_raises(LoadError) do 115 @initializer.load_plugins 116 end 117 end 118 end 119 120 def test_declaring_an_explicit_plugin_load_order_that_jives_with_the_gem_depency_loading_order_works 121 using_scenario 'gem_with_dep_that_has_its_own_deps' do 122 only_load_the_following_plugins! expected_gem_loading_order 123 assert_nothing_raised do 124 @initializer.load_plugins 125 end 126 end 127 end 128 129 def test_trying_to_load_a_gem_with_a_missing_depedency_raises_an_install_error 130 using_scenario 'gem_with_a_missing_dep' do 131 assert_raises(Gem::InstallError) do 132 @locator.send(:prepare_gems) 133 end 134 end 135 end 136 137 def test_trying_load_a_gem_whose_dependency_constraint_can_not_be_satisfied_raises 138 using_scenario 'gem_with_version_constraint_on_dep_that_can_not_be_satisfied' do 139 assert_raises(Gem::InstallError) do 140 @locator.send(:prepare_gems) 141 end 142 end 143 end 144 145 private 146 def new_gem_locator(initializer = @initializer) 147 Rails::Plugin::GemLocator.new(initializer) 148 end 149 150 def using_scenario(scenario) 151 set_bundled_gem_path! scenario 152 generate_gems 153 yield 154 ensure 155 purge_gems 156 end 157 158 def set_bundled_gem_path!(bundled_gem_path) 159 @initializer.configuration.bundled_gem_path = File.join(plugin_gem_fixture_root_path, bundled_gem_path) 160 end 161 162 def expected_gem_loading_order 163 @expected_gem_loading_order ||= YAML.load_file(File.join(bundled_gem_path, 'dependency_loading_order.yml')) 164 end 165 166 def bundled_gem_path 167 @initializer.configuration.bundled_gem_path 168 end 169 170 def generate_gems 171 Dir["#{bundled_gem_path}/**/*.gemspec"].each do |spec_file| 172 # Initialize outside the block so it is in scope 173 gem_path, specification = nil 174 execute_from_within(File.dirname(spec_file)) do 175 specification = Gem::Specification.load(File.basename(spec_file)) 176 # Usually the Gem::Builder prints out its progress. We want to silent that. 177 Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do 178 Gem::Builder.new(specification).build 179 end 180 end 181 FileUtils.mv File.join(File.dirname(spec_file), "#{specification.full_name}.gem"), bundled_gem_path 182 end 183 end 184 185 # This is a hack. Gem::Specification.load eval's the passed in spec file. Usually, 186 # when this is done with the gem command, it is executed from the same directory as 187 # where the gemspec is located. If this is not the case though, the eval is done relative to 188 # the calling code's directory. To work around this, while loading the gem spec, we move to 189 # the gemspec's directory. 190 def execute_from_within(path) 191 originating_path = Dir.pwd 192 Dir.chdir path 193 yield 194 ensure 195 Dir.chdir originating_path 196 end 197 198 def purge_gems 199 FileUtils.rm_rf(generated_gems) 200 FileUtils.rm_rf(installed_gems) 201 end 202 203 def generated_gems 204 Dir["#{bundled_gem_path}/*.gem"] 205 end 206 207 def installed_gems 208 Dir["#{gem_home_root_path}/*"] 209 end 210 211 def gem_home_root_path 212 File.join(fixture_path, 'tmp', 'gem_home') 213 end 214 215 def plugin_gem_fixture_root_path 216 File.join(fixture_path, 'gems') 217 end 41 218 end -
railties/test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_one/dependency_one.gemspec
old new 1 Gem::Specification.new do |s| 2 s.name = 'dependency_one' 3 s.version = '1.0.0' 4 s.summary = 'This gem is a terminal dependency on top_level_gem but its version is too low' 5 s.files = Dir['*.rb'] + Dir['lib/*'] 6 end -
railties/test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_one/init.rb
old new -
railties/test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/top_level_gem_with_unsatisfiable_dependency/init.rb
old new -
railties/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
old new 1 Gem::Specification.new do |s| 2 s.name = 'top_level_gem_with_unsatisfiable_dependency' 3 s.version = '1.0.0' 4 s.summary = 'This gem has a dependency on dependency_one' 5 s.files = Dir['*.rb'] + Dir['lib/*'] 6 s.add_dependency 'dependency_one', '> 3.0.0' 7 end -
railties/test/fixtures/gems/gem_with_version_constraint_on_dep_that_can_not_be_satisfied/dependency_loading_order.yml
old new 1 - dependency_one-1.0.0 2 - top_level_gem_with_unsatisfiable_dependency-1.0.0 -
railties/test/fixtures/gems/gem_with_dep_that_has_its_own_deps/dependency_loading_order.yml
old new 1 - second_generation_dep-1.0.0 2 - first_generation_dep-1.0.0 3 - top_level_gem_with_nested_deps-1.0.0 -
railties/test/fixtures/gems/gem_with_dep_that_has_its_own_deps/top_level_gem_with_nested_deps/init.rb
old new -
railties/test/fixtures/gems/gem_with_dep_that_has_its_own_deps/top_level_gem_with_nested_deps/top_level_gem_with_nested_deps.gemspec
old new 1 Gem::Specification.new do |s| 2 s.name = 'top_level_gem_with_nested_deps' 3 s.version = '1.0.0' 4 s.summary = 'This gem has a depency on first_generation_dep which has its own dependency' 5 s.files = Dir['*.rb'] + Dir['lib/*'] 6 s.add_dependency 'first_generation_dep' 7 end -
railties/test/fixtures/gems/gem_with_dep_that_has_its_own_deps/first_generation_dep/init.rb
old new -
railties/test/fixtures/gems/gem_with_dep_that_has_its_own_deps/first_generation_dep/first_generation_dep.gemspec
old new 1 Gem::Specification.new do |s| 2 s.name = 'first_generation_dep' 3 s.version = '1.0.0' 4 s.summary = 'This gem is an intermediary dependency' 5 s.files = Dir['*.rb'] + Dir['lib/*'] 6 s.add_dependency 'second_generation_dep' 7 end -
railties/test/fixtures/gems/gem_with_dep_that_has_its_own_deps/second_generation_dep/second_generation_dep.gemspec
old new 1 Gem::Specification.new do |s| 2 s.name = 'second_generation_dep' 3 s.version = '1.0.0' 4 s.summary = 'This is the terminal dependency in multiple levels of dependencies' 5 s.files = Dir['*.rb'] + Dir['lib/*'] 6 end -
railties/test/fixtures/gems/gem_with_dep_that_has_its_own_deps/second_generation_dep/init.rb
old new -
railties/test/fixtures/gems/gem_with_a_missing_dep/dependency_loading_order.yml
old new 1 - dependency_one-1.0.0 2 - top_level_gem_with_missing_dep-1.0.0 -
railties/test/fixtures/gems/gem_with_a_missing_dep/top_level_gem_with_missing_dep/top_level_gem_with_missing_dep.gemspec
old new 1 Gem::Specification.new do |s| 2 s.name = 'top_level_gem_with_missing_dep' 3 s.version = '1.0.0' 4 s.summary = 'This gem has a dependency on dependency_one but dependency_one is missing' 5 s.files = Dir['*.rb'] + Dir['lib/*'] 6 s.add_dependency 'dependency_one' 7 end -
railties/test/fixtures/gems/gem_with_a_missing_dep/top_level_gem_with_missing_dep/init.rb
old new -
railties/test/fixtures/gems/gem_with_no_deps/top_level_gem_with_no_deps/init.rb
old new -
railties/test/fixtures/gems/gem_with_no_deps/top_level_gem_with_no_deps/top_level_gem_with_no_deps.gemspec
old new 1 Gem::Specification.new do |s| 2 s.name = 'top_level_gem_with_no_deps' 3 s.version = '1.0.0' 4 s.summary = 'This gem has no depencies' 5 s.files = Dir['*.rb'] + Dir['lib/*'] 6 end -
railties/test/fixtures/gems/gem_with_no_deps/dependency_loading_order.yml
old new -
railties/test/fixtures/gems/gem_with_one_level_of_deps/dependency_one/dependency_one.gemspec
old new 1 Gem::Specification.new do |s| 2 s.name = 'dependency_one' 3 s.version = '1.0.0' 4 s.summary = 'This gem is a terminal dependency on top_level_gem' 5 s.files = Dir['*.rb'] + Dir['lib/*'] 6 end -
railties/test/fixtures/gems/gem_with_one_level_of_deps/dependency_one/init.rb
old new -
railties/test/fixtures/gems/gem_with_one_level_of_deps/top_level_gem/top_level_gem.gemspec
old new 1 Gem::Specification.new do |s| 2 s.name = 'top_level_gem' 3 s.version = '1.0.0' 4 s.summary = 'This gem has a dependency on dependency_one' 5 s.files = Dir['*.rb'] + Dir['lib/*'] 6 s.add_dependency 'dependency_one' 7 end -
railties/test/fixtures/gems/gem_with_one_level_of_deps/top_level_gem/init.rb
old new -
railties/test/fixtures/gems/gem_with_one_level_of_deps/dependency_loading_order.yml
old new 1 - dependency_one-1.0.0 2 - top_level_gem-1.0.0 -
railties/test/plugin_test_helper.rb
old new 9 9 RAILS_ROOT = '.' unless defined?(RAILS_ROOT) 10 10 class Test::Unit::TestCase 11 11 def plugin_fixture_root_path 12 File.join( File.dirname(__FILE__), 'fixtures', 'plugins')12 File.join(fixture_path, 'plugins') 13 13 end 14 14 15 def fixture_path 16 File.join(File.dirname(__FILE__), 'fixtures') 17 end 18 15 19 def only_load_the_following_plugins!(plugins) 16 20 @initializer.configuration.plugins = plugins 17 21 end -
railties/lib/rails_generator/generators/applications/app/app_generator.rb
old new 155 155 test/unit 156 156 vendor 157 157 vendor/plugins 158 vendor/gems 159 vendor/gems/home 158 160 tmp/sessions 159 161 tmp/sockets 160 162 tmp/cache -
railties/lib/initializer.rb
old new 1 1 require 'logger' 2 2 require 'set' 3 require 'fileutils' 3 4 require 'pathname' 4 5 5 6 $LOAD_PATH.unshift File.dirname(__FILE__) … … 341 342 unless configuration.plugins.nil? 342 343 unless loaded_plugins == configuration.plugins 343 344 missing_plugins = configuration.plugins - loaded_plugins 344 raise LoadError, "Could not locate the following plugins: #{missing_plugins.to_sentence}" 345 message = if missing_plugins.any? 346 "Could not locate the following plugins: #{missing_plugins.to_sentence}" 347 else 348 "It seems as though you have specified an explicit plugin loading order " + 349 "that contradicts the dependency loading order of a gem plugin. " + 350 "Try comparing your explicit loading order with the dependency loading " + 351 "order of the gem plugins you have installed in #{configuration.gem_home}. " + 352 "You probably should just remove the explicit plugin loading. " 353 end 354 raise LoadError, message 345 355 end 346 356 end 347 357 end … … 432 442 # <tt>vendor/plugins</tt>. 433 443 attr_accessor :plugin_paths 434 444 445 # The path to the root of the bundled gems directory. By default, it is in 446 # <tt>vendor/gems</tt>. 447 attr_accessor :bundled_gem_path 448 449 # The path into which Rails will on-the-fly install gems from its 450 # bundled_gem_path on startup. By default, it is in <tt>vendor/gems/home</tt> 451 attr_accessor :gem_home 452 435 453 # The classes that handle finding the desired plugins that you'd like to load for 436 454 # your application. By default it is the Rails::Plugin::FileSystemLocator which finds 437 455 # plugins to load in <tt>vendor/plugins</tt>. You can hook into gem location by subclassing … … 468 486 self.cache_classes = default_cache_classes 469 487 self.whiny_nils = default_whiny_nils 470 488 self.plugins = default_plugins 489 self.bundled_gem_path = default_bundled_gem_path 490 self.gem_home = default_gem_home 471 491 self.plugin_paths = default_plugin_paths 472 492 self.plugin_locators = default_plugin_locators 473 493 self.plugin_loader = default_plugin_loader … … 637 657 ["#{root_path}/vendor/plugins"] 638 658 end 639 659 660 def default_bundled_gem_path 661 "#{root_path}/vendor/gems/" 662 end 663 664 def default_gem_home 665 "#{root_path}/vendor/gems/home" 666 end 667 640 668 def default_plugin_locators 641 [Plugin:: FileSystemLocator]669 [Plugin::GemLocator, Plugin::FileSystemLocator] 642 670 end 643 671 644 672 def default_plugin_loader -
railties/lib/rails/plugin/locator.rb
old new 27 27 end 28 28 29 29 class FileSystemLocator < Locator 30 private 31 def located_plugins 32 initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path| 30 private 31 def located_plugins 32 initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path| 33 plugins.concat locate_plugins_under(path) 34 plugins 35 end.flatten 36 end 37 38 # This starts at the base path looking for directories that pass the plugin_path? test of the Plugin::Loader. 39 # Since plugins can be nested arbitrarily deep within an unspecified number of intermediary directories, 40 # this method runs recursively until it finds a plugin directory. 41 # 42 # e.g. 43 # 44 # locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon') 45 # => 'acts_as_chunky_bacon' 46 def locate_plugins_under(base_path) 47 Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path| 48 plugin_loader = initializer.configuration.plugin_loader.new(initializer, path) 49 if plugin_loader.loadable? 50 plugins << plugin_loader 51 elsif File.directory?(path) 33 52 plugins.concat locate_plugins_under(path) 34 plugins35 end.flatten53 end 54 plugins 36 55 end 56 end 57 end 58 59 class GemLocator < Locator 60 require 'rubygems/dependency_list' 61 require 'rubygems/format' 62 require 'rubygems/installer' 63 64 attr_reader :installer 65 66 def initialize(*args) 67 super 68 @installer = Installer.new(self) 69 end 70 71 def plugins 72 # Unlike the parent class, we don't want to sort the loaders, 73 # as RubyGems takes care of sorting them in depency order already. 74 located_plugins.select(&:enabled?) 75 end 76 77 def spec_to_path_mapping 78 @spec_to_path_mapping ||= Dir[File.join(initializer.configuration.bundled_gem_path, "*.gem")].inject({}) do |mapping, gem_path| 79 mapping[specification_for(gem_path)] = gem_path 80 mapping 81 end 82 end 83 84 def specification_for(gem_path) 85 Gem::Format.from_file_by_path(gem_path).spec 86 end 87 88 def bundled_gems 89 spec_to_path_mapping.values 90 end 37 91 38 # This starts at the base path looking for directories that pass the plugin_path? test of the Plugin::Loader. 39 # Since plugins can be nested arbitrarily deep within an unspecified number of intermediary directories, 40 # this method runs recursively until it finds a plugin directory. 41 # 42 # e.g. 43 # 44 # locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon') 45 # => 'acts_as_chunky_bacon' 46 def locate_plugins_under(base_path) 47 Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path| 48 plugin_loader = initializer.configuration.plugin_loader.new(initializer, path) 49 if plugin_loader.plugin_path? && plugin_loader.enabled? 50 plugins << plugin_loader 51 elsif File.directory?(path) 52 plugins.concat locate_plugins_under(path) 92 def path_for(spec) 93 spec_to_path_mapping[spec] 94 end 95 96 def plugin_path_for(spec) 97 File.join(initializer.configuration.gem_home, 'gems', spec.full_name) 98 end 99 100 private 101 def located_plugins 102 prepare_gems 103 104 installer.installed_gems.inject([]) do |plugins, gem_plugin_directory| 105 plugin_loader = initializer.configuration.plugin_loader.new(initializer, gem_plugin_directory) 106 plugins << plugin_loader if plugin_loader.loadable? 107 plugins 108 end 109 end 110 111 def prepare_gems 112 setup_gem_environment 113 install_gems 114 end 115 116 # Sets a local GEM_HOME for this Rails application. Installs all gems from vendor/gems 117 # (or configured bundled_gem_path) into this 118 # directory and makes them available to be loaded as plugins. 119 def setup_gem_environment 120 Gem.manage_gems 121 # N.B. the gem_home must be an absolute path or else the Gem::Installer will fail 122 Gem.use_paths(File.expand_path(initializer.configuration.gem_home), [Gem.dir]) 123 end 124 125 def install_gems 126 installer.install 127 end 128 129 class Installer 130 attr_reader :locator, :installed_gems, :source_index 131 132 def initialize(locator) 133 @locator = locator 134 @source_index = Gem::SourceIndex.from_gems_in(File.join(locator.initializer.configuration.gem_home, 'specifications')) 135 @installed_gems = [] 136 end 137 138 def install 139 gems_to_install.each do |spec| 140 gem_path = locator.path_for(spec) 141 Gem::Installer.new(gem_path).install unless installed?(spec) 142 @installed_gems << locator.plugin_path_for(spec) 143 end 144 uninstall_unused_gems! 145 end 146 147 private 148 def gems_to_install 149 @gems_to_install ||= locator.bundled_gems.inject(Gem::DependencyList.new) do |dependency_list, gem_path| 150 dependency_list.add locator.specification_for(gem_path) 151 dependency_list 152 end.dependency_order.reverse 153 end 154 155 def installed?(spec) 156 !source_index.find_name(spec.name, spec.version).empty? 157 end 158 159 def uninstall_unused_gems! 160 unused_gems.each do |spec| 161 Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do 162 Gem::Uninstaller.new(spec.name, :version => "= #{spec.version}").uninstall 163 end 53 164 end 54 plugins55 165 end 56 end 166 167 def unused_gems 168 previously_installed_gems - gems_to_install 169 end 170 171 def previously_installed_gems 172 source_index.latest_specs.values 173 end 174 end 57 175 end 58 176 end 59 177 end -
railties/lib/rails/plugin/loader.rb
old new 28 28 def loaded? 29 29 initializer.loaded_plugins.include?(name) 30 30 end 31 32 def loadable? 33 plugin_path? && enabled? 34 end 31 35 32 36 def plugin_path? 33 37 File.directory?(directory) && (has_lib_directory? || has_init_file?)