Skip to content
This repository has been archived by the owner on Apr 14, 2021. It is now read-only.

Commit

Permalink
Auto merge of #4815 - asutoshpalai:plugin-hooks, r=segiddins
Browse files Browse the repository at this point in the history
[Plugin] Life-cycle hooks

Implements life-cycle hook plugins.

This shows only [`before-install-all` hook](https://github.com/bundler/bundler/pull/4815/files#diff-0964e829e3db904ba7935ca3a933f111R22), but other hooks can be easily added in the similar fashion.
  • Loading branch information
homu committed Aug 16, 2016
2 parents e9591fd + de65807 commit 138b8c1
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 52 deletions.
1 change: 1 addition & 0 deletions lib/bundler/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class << self
# For more information see the #run method on this class.
def self.install(root, definition, options = {})
installer = new(root, definition)
Plugin.hook("before-install-all", definition.dependencies)
installer.run(options)
installer
end
Expand Down
55 changes: 42 additions & 13 deletions lib/bundler/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ class UnknownSourceError < PluginError; end

module_function

@commands = {}
@sources = {}
def reset!
instance_variables.each {|i| remove_instance_variable(i) }

@sources = {}
@commands = {}
@hooks_by_event = Hash.new {|h, k| h[k] = [] }
@loaded_plugin_names = []
end

reset!

# Installs a new plugin by the given name
#
Expand All @@ -30,7 +38,7 @@ def install(names, options)
save_plugins names, specs
rescue PluginError => e
specs.values.map {|spec| Bundler.rm_rf(spec.full_gem_path) } if specs
Bundler.ui.error "Failed to install plugin #{name}: #{e.message}\n #{e.backtrace[0]}"
Bundler.ui.error "Failed to install plugin #{name}: #{e.message}\n #{e.backtrace.join("\n ")}"
end

# Evaluates the Gemfile with a limited DSL and installs the plugins
Expand Down Expand Up @@ -137,21 +145,36 @@ def source_from_lock(locked_opts)
src.new(locked_opts.merge("uri" => locked_opts["remote"]))
end

# To be called via the API to register a hooks and corresponding block that
# will be called to handle the hook
def add_hook(event, &block)
@hooks_by_event[event.to_s] << block
end

# Runs all the hooks that are registered for the passed event
#
# It passes the passed arguments and block to the block registered with
# the api.
#
# @param [String] event
def hook(event, *args, &arg_blk)
return unless Bundler.settings[:plugins]

plugins = index.hook_plugins(event)
return unless plugins.any?

(plugins - @loaded_plugin_names).each {|name| load_plugin(name) }

@hooks_by_event[event].each {|blk| blk.call(*args, &arg_blk) }
end

# currently only intended for specs
#
# @return [String, nil] installed path
def installed?(plugin)
Index.new.installed?(plugin)
end

# Used by specs
def reset!
instance_variables.each {|i| remove_instance_variable(i) }

@sources = {}
@commands = {}
end

# Post installation processing and registering with index
#
# @param [Array<String>] plugins list to be installed
Expand All @@ -162,7 +185,7 @@ def save_plugins(plugins, specs, optional_plugins = [])
plugins.each do |name|
spec = specs[name]
validate_plugin! Pathname.new(spec.full_gem_path)
installed = register_plugin name, spec, optional_plugins.include?(name)
installed = register_plugin(name, spec, optional_plugins.include?(name))
Bundler.ui.info "Installed plugin #{name}" if installed
end
end
Expand Down Expand Up @@ -190,9 +213,11 @@ def validate_plugin!(plugin_path)
def register_plugin(name, spec, optional_plugin = false)
commands = @commands
sources = @sources
hooks = @hooks_by_event

@commands = {}
@sources = {}
@hooks_by_event = Hash.new {|h, k| h[k] = [] }

load_paths = spec.load_paths
add_to_load_path(load_paths)
Expand All @@ -208,12 +233,14 @@ def register_plugin(name, spec, optional_plugin = false)
Bundler.rm_rf(path)
false
else
index.register_plugin name, path.to_s, load_paths, @commands.keys, @sources.keys
index.register_plugin(name, path.to_s, load_paths, @commands.keys,
@sources.keys, @hooks_by_event.keys)
true
end
ensure
@commands = commands
@sources = sources
@hooks_by_event = hooks
end

# Executes the plugins.rb file
Expand All @@ -228,6 +255,8 @@ def load_plugin(name)
add_to_load_path(index.load_paths(name))

load path.join(PLUGIN_FILE_NAME)

@loaded_plugin_names << name
rescue => e
Bundler.ui.error "Failed loading plugin #{name}: #{e.message}"
raise
Expand Down
4 changes: 4 additions & 0 deletions lib/bundler/plugin/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def self.source(source, cls = self)
Plugin.add_source source, cls
end

def self.hook(event, &block)
Plugin.add_hook(event, &block)
end

# The cache dir to be used by the plugins for storage
#
# @return [Pathname] path of the cache dir
Expand Down
23 changes: 17 additions & 6 deletions lib/bundler/plugin/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def initialize
@plugin_paths = {}
@commands = {}
@sources = {}
@hooks = {}
@load_paths = {}

load_index(global_index_file, true)
Expand All @@ -39,7 +40,7 @@ def initialize
# @param [Array<String>] load_paths for the plugin
# @param [Array<String>] commands that are handled by the plugin
# @param [Array<String>] sources that are handled by the plugin
def register_plugin(name, path, load_paths, commands, sources)
def register_plugin(name, path, load_paths, commands, sources, hooks)
old_commands = @commands.dup

common = commands & @commands.keys
Expand All @@ -50,6 +51,8 @@ def register_plugin(name, path, load_paths, commands, sources)
raise SourceConflict.new(name, common) unless common.empty?
sources.each {|k| @sources[k] = name }

hooks.each {|e| (@hooks[e] ||= []) << name }

@plugin_paths[name] = path
@load_paths[name] = load_paths
save_index
Expand Down Expand Up @@ -98,6 +101,11 @@ def source_plugin(name)
@sources[name]
end

# Returns the list of plugin names handling the passed event
def hook_plugins(event)
@hooks[event] || []
end

private

# Reads the index file from the directory and initializes the instance
Expand All @@ -112,12 +120,14 @@ def load_index(index_file, global = false)
break unless valid_file

data = index_f.read

require "bundler/yaml_serializer"
index = YAMLSerializer.load(data)

@plugin_paths.merge!(index["plugin_paths"])
@load_paths.merge!(index["load_paths"])
@commands.merge!(index["commands"])
@hooks.merge!(index["hooks"])
@load_paths.merge!(index["load_paths"])
@plugin_paths.merge!(index["plugin_paths"])
@sources.merge!(index["sources"]) unless global
end
end
Expand All @@ -127,10 +137,11 @@ def load_index(index_file, global = false)
# to be only String key value pairs)
def save_index
index = {
"commands" => @commands,
"hooks" => @hooks,
"load_paths" => @load_paths,
"plugin_paths" => @plugin_paths,
"load_paths" => @load_paths,
"commands" => @commands,
"sources" => @sources,
"sources" => @sources,
}

require "bundler/yaml_serializer"
Expand Down
11 changes: 11 additions & 0 deletions spec/bundler/plugin/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@
UserPluginClass.source "a_source", NewClass
end
end

describe "#hook" do
it "accepts a block and passes it to Plugin module" do
foo = double("tester")
expect(foo).to receive(:called)

expect(Bundler::Plugin).to receive(:add_hook).with("post-foo").and_yield

Bundler::Plugin::API.hook("post-foo") { foo.called }
end
end
end

context "bundler interfaces provided" do
Expand Down
Loading

0 comments on commit 138b8c1

Please sign in to comment.