Gemify redmine plugins
I want to install redmine plugins by adding gem name to Gemfile.local like redmine_github_hook plugin.
Current installing/updating method have below cautions for me.
- Difficult to specify plugin version
- Server maintainer should exec `$ git clone [--depth=1] -b <release version> https://github.com/foo/bar/`, but plugin README often describes installing master
- Difficult to detect plugins vulnerability with using bundler-audit
- We have to check each plugins by hand now
I'm happy if Redmine::Plugin.load requires gem's init.rb.
- Easy to install plugin: only writing gem name to Gemfile.local
- Easy to specify plugin version: only specifying gem version to Gemfile.local
- Easy to update plugin: only executing `$ bundle update <plugin gem name>`
- Easy to detect (only gemified) plugins vulnerability with using bundler-audit
- Redmine should detect wheather redmine plugin or not from all bundle installed gem
For example, support_gem_plugin.patch can load gemified plugin.
Gemfile.local example is below(I fixed little existing sidebar_hide plugin).
gem 'sidebar_hide', github: 'sho-h/sidebar_hide', branch: 'redmine_support_gem_plugin_test'
This was gemified and specified directory in init.rb.
Updated by Sho HASHIMOTO over 5 years ago
"require initializer" is called twice. Is it correct?
One is in this patch and another is in original code.
Oh... Maybe it's cause problem.
I left original code. Original code is for loading current structure plugins.
I think, there are below patterns.
- Same plugin and same version: Maybe, problem doesn't break to the surface.
- Same plugin and different version: Cause problems. Both require will success. Overwritten by plugin dir's one halfway.
- Different plugins and same ID: Cause problems. Both require will success. Each function will not be conflicted. But redmine will misunderstand only plugin dir's one was loaded.
- Different plugins and different IDs: No problem.
1., 2., 3. have same problem. @registered_plugins[id] was overwritten.
To fix this, for example, Redmine::Plugin.register(id) raises(or fails) if plugin was already installed.
def self.register(id, &block) + raise ... if installed?(id) # or return + p = new(id) p.instance_eval(&block)
I think, it is difficult to find 2. by administrator if only return.
Updated by Takashi Kato about 1 month ago
- File 0001-Enable-to-use-gem-as-a-Redmine-plugin.patch 0001-Enable-to-use-gem-as-a-Redmine-plugin.patch added
- File 0002-Add-redmine_plugin_gem-generator.patch 0002-Add-redmine_plugin_gem-generator.patch added
- File 0003-Sohw-gems-badge-on-plugin-list.patch 0003-Sohw-gems-badge-on-plugin-list.patch added
- File 0004-Add-dependency-test.patch 0004-Add-dependency-test.patch added
Patches for Redmine 5.0 or later (Rails 6.1 or later) are available here(tested in r22282).
Making plugins as gems allows you to install all plugins at once by listing them in the
Gemfile.extension and running bundle install.
This can be especially useful in a Docker environment where you don't need to create a separate script to install plugins.
On the other hand, plugins placed under
plugins/ directory will still work. I will refer to current plugins as "filesystem plugin" and gemified plugins as "gem plugin".
If there's a "filesystem plugin" and a "gem plugin" with the same ID, the "filesystem plugin" takes priority
To install a "gem plugin", add its name to
Gemfile.extension. Note that the reason why
Gemfile.extension is not named
Gemfile.plugin is that we assume that themes will also be gemmed in the future.
For a "gem plugin", the plugin information is divided into two files:
gemspec. Basic information like plugin name and author goes in
gemspec, while Redmine-specific information like menus and settings stays in
As a trial, I converted
redmine-view-customize into a "gem plugin". It works well. https://github.com/tohosaku/redmine-view-customize/tree/rubygems
The gem name, Redmine plugin ID, git repository name, and class names under the lib directory do not need to match. However, you can match all of them.
|git repository name||redmine-view-cusomize|
On the other hand, if you try to register a plugin with a different gem name and the same plugin id, an error will occur.
Differences from the previous version¶
"filesystem plugin" allowed adding a gem to Redmine itself by adding the gem in the plugin's
Gemfile. This is because Redmine was reading each plugin's Gemfile and requiring it as a dependent gem of redmine itself. gem plugin requires an
add_runtime_dependency to be listed in each
gemspec. The dependencies are automatically loaded when the plugin is registered with Redmine.
Creating a new plugin¶
bin/rails g redmine_plugin_gem foo
When creating a gem, it is common to use the
bundle gemcommand to create a template, but since there is a difference between a normal gem and a "gem plugin" in the way the
gemspecis described below, we recommend using bin/rails g to create the template.
Notes on gemspec for "gem plugin"¶
- In the
gemspecfile, Do not use variables, constants, etc. defined by gem (eg.
gemspecis evaluated before rails initialization is completed. redmine plugin variables and constants are supposed to be managed by the zeitwerk autoloader after rails is started, so using them at initialization time will cause errors.
- Redmine plugin dependencies can also be described in
spec.metadata["redmine_plugin_id"] = "plugin id"is required as metadata in
- The following attributes that were previously listed in
init.rbwill be overwritten by the corresponding attributes in
|attributes of init.rb||attributes of gemspec|
Added the ability to distinguish "gem plugin" or not in the list of plugins.
How to develop “filesystem plugin” and “gem plugin” in one development branch¶
- Place the gem plugin directory in the plugins directory, and enter the gem information required by the "filesystem plugin" for Redmine 4.2 and Redmine 5.0 in the
Gemfileis ignored. Dependent gem information must be included in both the
- To create a plugin that works with both Redmine 4.2 and Redmine 5.0 or later, the
init.rbmust look like this
if Rails.version > '6.0' && Rails.autoloaders.zeitwerk_enabled? # for Redmine5.0 or later else Rails.configuration.to_prepare do # for Redmine4.* end end