6324_defer_plugin_requirements_evaluation.patch

Jean-Baptiste Barth, 2014-08-14 13:06

Download (6.97 KB)

View differences:

lib/redmine/plugin.rb (working copy)
49 49
    cattr_accessor :public_directory
50 50
    self.public_directory = File.join(Rails.root, 'public', 'plugin_assets')
51 51

  
52
    cattr_accessor :plugin_requirements
53

  
52 54
    @registered_plugins = {}
53 55
    @used_partials = {}
54 56

  
......
138 140
    end
139 141

  
140 142
    def self.load
143
      clear_plugin_requirements
141 144
      Dir.glob(File.join(self.directory, '*')).sort.each do |directory|
142 145
        if File.directory?(directory)
143 146
          lib = File.join(directory, "lib")
......
151 154
          end
152 155
        end
153 156
      end
157
      check_plugin_requirements
154 158
    end
155 159

  
156 160
    def initialize(id)
......
239 243
    #   # Requires a specific version of a Redmine plugin
240 244
    #   requires_redmine_plugin :foo, :version => '0.7.3'              # 0.7.3 only
241 245
    #   requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0']   # 0.7.3 or 0.8.0
246
    #
247
    # For Redmine >= 2.6.0, the requirement check is postponed until all
248
    # plugins are loaded, so that you can require a plugin which name is
249
    # lexically greater than the current one (before that plugin were loaded in
250
    # lexical order, hence no possibility for plugin "bar" to depend on plugin
251
    # "foo" which was not loaded).
252
    #
253
    # Note that this is still not perfect, as you cannot directly "require
254
    # foo_file" in plugin "bar" if "foo_file" is in the plugin "foo" in
255
    # "lib/foo_file". But that should be a minor problem for plugin authors for
256
    # now.
242 257
    def requires_redmine_plugin(plugin_name, arg)
243
      arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
244
      arg.assert_valid_keys(:version, :version_or_higher)
258
      self.class.plugin_requirements << [id, plugin_name, arg]
259
    end
245 260

  
246
      plugin = Plugin.find(plugin_name)
247
      current = plugin.version.split('.').collect(&:to_i)
261
    def self.clear_plugin_requirements
262
      self.plugin_requirements = []
263
    end
248 264

  
249
      arg.each do |k, v|
250
        v = [] << v unless v.is_a?(Array)
251
        versions = v.collect {|s| s.split('.').collect(&:to_i)}
252
        case k
253
        when :version_or_higher
254
          raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
255
          unless (current <=> versions.first) >= 0
256
            raise PluginRequirementError.new("#{id} plugin requires the #{plugin_name} plugin #{v} or higher but current is #{current.join('.')}")
265
    def self.check_plugin_requirements
266
      self.plugin_requirements.each do |plugin_id, required_plugin_name, arg|
267
        arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
268
        arg.assert_valid_keys(:version, :version_or_higher)
269

  
270
        plugin = Plugin.find(required_plugin_name)
271
        current = plugin.version.split('.').collect(&:to_i)
272

  
273
        arg.each do |k, v|
274
          v = [] << v unless v.is_a?(Array)
275
          versions = v.collect {|s| s.split('.').collect(&:to_i)}
276
          case k
277
          when :version_or_higher
278
            raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
279
            unless (current <=> versions.first) >= 0
280
              raise PluginRequirementError.new("#{plugin_id} plugin requires the #{required_plugin_name} plugin #{v} or higher but current is #{current.join('.')}")
281
            end
282
          when :version
283
            unless versions.include?(current.slice(0,3))
284
              raise PluginRequirementError.new("#{plugin_id} plugin requires one the following versions of #{required_plugin_name}: #{v.join(', ')} but current is #{current.join('.')}")
285
            end
257 286
          end
258
        when :version
259
          unless versions.include?(current.slice(0,3))
260
            raise PluginRequirementError.new("#{id} plugin requires one the following versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}")
261
          end
262 287
        end
263 288
      end
264 289
      true
test/unit/lib/redmine/plugin_test.rb (working copy)
146 146
      name 'Other'
147 147
      version other_version
148 148
    end
149
    @klass.register :foo do
150
      test.assert requires_redmine_plugin(:other, :version_or_higher => '0.1.0')
151
      test.assert requires_redmine_plugin(:other, :version_or_higher => other_version)
152
      test.assert requires_redmine_plugin(:other, other_version)
153
      test.assert_raise Redmine::PluginRequirementError do
154
        requires_redmine_plugin(:other, :version_or_higher => '99.0.0')
149
    should_pass = [
150
      [:other, :version_or_higher => '0.1.0'],
151
      [:other, :version_or_higher => other_version],
152
      [:other, other_version],
153
      [:other, :version => other_version],
154
      [:other, :version => [other_version, '99.0.0']],
155
    ]
156
    should_fail_with_plugin_requirement = [
157
      [:other, :version_or_higher => '99.0.0'],
158
      [:other, :version => '99.0.0'],
159
      [:other, :version => ['98.0.0', '99.0.0']],
160
    ]
161
    should_fail_with_plugin_not_found = [
162
      [:missing, :version_or_higher => '0.1.0'],
163
      [:missing, '0.1.0'],
164
      [:missing, :version => '0.1.0'],
165
    ]
166
    should_pass.each do |(name, constraint)|
167
      test.assert_block do
168
        @klass.clear_plugin_requirements
169
        @klass.register :foo do
170
          requires_redmine_plugin(name, constraint)
171
        end
172
        @klass.check_plugin_requirements
155 173
      end
156
      test.assert requires_redmine_plugin(:other, :version => other_version)
157
      test.assert requires_redmine_plugin(:other, :version => [other_version, '99.0.0'])
174
    end
175
    should_fail_with_plugin_requirement.each do |(name, constraint)|
158 176
      test.assert_raise Redmine::PluginRequirementError do
159
        requires_redmine_plugin(:other, :version => '99.0.0')
177
        @klass.clear_plugin_requirements
178
        @klass.register :foo do
179
          requires_redmine_plugin(name, constraint)
180
        end
181
        @klass.check_plugin_requirements
160 182
      end
161
      test.assert_raise Redmine::PluginRequirementError do
162
        requires_redmine_plugin(:other, :version => ['98.0.0', '99.0.0'])
163
      end
164
      # Missing plugin
183
    end
184
    should_fail_with_plugin_not_found.each do |(name, constraint)|
165 185
      test.assert_raise Redmine::PluginNotFound do
166
        requires_redmine_plugin(:missing, :version_or_higher => '0.1.0')
186
        @klass.clear_plugin_requirements
187
        @klass.register :foo do
188
          requires_redmine_plugin(name, constraint)
189
        end
190
        @klass.check_plugin_requirements
167 191
      end
168
      test.assert_raise Redmine::PluginNotFound do
169
        requires_redmine_plugin(:missing, '0.1.0')
170
      end
171
      test.assert_raise Redmine::PluginNotFound do
172
        requires_redmine_plugin(:missing, :version => '0.1.0')
173
      end
174 192
    end
193

  
175 194
  end
176 195

  
177 196
  def test_settings_warns_about_possible_partial_collision