模块 Bundler::Plugin
这是代表我们打算提供给插件使用的 API
的接口类。
为了使插件独立于 Bundler
内部,它们应将其交互限制为仅此类的方法。这将使它们在某些内部更改时不会中断。
目前,我们将 Bundler
类中定义的方法委派给自身。因此,此类充当缓冲区。
如果 Bundler
类中存在与先前行为不兼容的更改,或者在其他方面需要时,我们可以重新实现(或实现)该方法以保持兼容性。
要使用它,该类可以继承此类或直接使用它。有关两种使用类型的示例,请参考文件“spec/plugins/command.rb”
要在不继承的情况下使用它,您必须创建此对象才能使用这些函数(除了诸如 command、source 和 hooks 之类的声明函数)。
管理安装了哪些插件及其来源。这也应该映射哪个插件执行什么操作(目前这些功能尚未实现,因此此类现在是一个存根类)。
处理插件在适当目录中的安装。
此类应该是现有 gem 安装基础设施的包装器,但目前它自己处理所有事情,因为 Source 的子类(例如 Source::RubyGems)严重依赖 Gemfile。
在解析 Gemfile 时要使用的 SourceList
对象,设置要与 Source
类一起使用的适当选项以进行插件安装
常量
- PLUGIN_FILE_NAME
公共实例方法
通过 API
调用以注册来处理命令
# File bundler/plugin.rb, line 160 def add_command(command, cls) @commands[command] = cls end
通过 API
调用以注册钩子和相应的块,该块将被调用以处理钩子
# File bundler/plugin.rb, line 209 def add_hook(event, &block) unless Events.defined_event?(event) raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events" end @hooks_by_event[event.to_s] << block end
通过 API
调用以注册来处理源插件
# File bundler/plugin.rb, line 180 def add_source(source, cls) @sources[source] = cls end
插件内容的缓存目录
# File bundler/plugin.rb, line 155 def cache @cache ||= root.join("cache") end
检查是否有任何插件处理该命令
# File bundler/plugin.rb, line 165 def command?(command) !index.command_plugin(command).nil? end
从 Cli 类调用以将命令和参数传递给适当的插件类
# File bundler/plugin.rb, line 171 def exec_command(command, args) raise UndefinedCommandError, "Command `#{command}` not found" unless command? command load_plugin index.command_plugin(command) unless @commands.key? command @commands[command].new.exec(command, args) end
@param [Hash] 锁定文件中存在的选项 @return [API::Source] 处理该来源的类的实例
type passed in locked_opts
# File bundler/plugin.rb, line 201 def from_lock(locked_opts) src = source(locked_opts["type"]) src.new(locked_opts.merge("uri" => locked_opts["remote"])) end
使用有限的 DSL
评估 Gemfile,并安装由 plugin 方法指定的插件
@param [Pathname] gemfile 路径 @param [Proc] 可以为(内联)Gemfile 求值的块
# File bundler/plugin.rb, line 103 def gemfile_install(gemfile = nil, &inline) Bundler.settings.temporary(frozen: false, deployment: false) do builder = DSL.new if block_given? builder.instance_eval(&inline) else builder.eval_gemfile(gemfile) end builder.check_primary_source_safety definition = builder.to_definition(nil, true) return if definition.dependencies.empty? plugins = definition.dependencies.map(&:name).reject {|p| index.installed? p } installed_specs = Installer.new.install_definition(definition) save_plugins plugins, installed_specs, builder.inferred_plugins end rescue RuntimeError => e unless e.is_a?(GemfileError) Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}" end raise end
所有插件相关数据的全局目录根
# File bundler/plugin.rb, line 150 def global_root Bundler.user_bundle_path("plugin") end
运行为传递的事件注册的所有钩子
它将传递的参数和块传递给使用 api 注册的块。
@param [String] 事件
# File bundler/plugin.rb, line 222 def hook(event, *args, &arg_blk) return unless Bundler.feature_flag.plugins? unless Events.defined_event?(event) raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events" end plugins = index.hook_plugins(event) return unless plugins.any? plugins.each {|name| load_plugin(name) } @hooks_by_event[event].each {|blk| blk.call(*args, &arg_blk) } end
用于存储有关插件详细信息的索引对象
# File bundler/plugin.rb, line 129 def index @index ||= Index.new end
通过给定的名称安装新插件
@param [Array<String>] 要安装的插件名称 @param [Hash] 描述中描述的各种参数。
Refer to cli/plugin for available options
# File bundler/plugin.rb, line 38 def install(names, options) raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"] specs = Installer.new.install(names, options) save_plugins names, specs rescue PluginError specs_to_delete = specs.select {|k, _v| names.include?(k) && !index.commands.values.include?(k) } specs_to_delete.each_value {|spec| Bundler.rm_rf(spec.full_gem_path) } raise end
目前仅用于规范
@return [String, nil] 安装路径
# File bundler/plugin.rb, line 239 def installed?(plugin) Index.new.installed?(plugin) end
列出已安装的插件和命令
# File bundler/plugin.rb, line 80 def list installed_plugins = index.installed_plugins if installed_plugins.any? output = String.new installed_plugins.each do |plugin| output << "#{plugin}\n" output << "-----\n" index.plugin_commands(plugin).each do |command| output << " #{command}\n" end output << "\n" end else output = "No plugins installed" end Bundler.ui.info output end
执行 plugins.rb 文件
@param [String] 插件名称
# File bundler/plugin.rb, line 336 def load_plugin(name) return unless name && !name.empty? return if loaded?(name) # Need to ensure before this that plugin root where the rest of gems # are installed to be on load path to support plugin deps. Currently not # done to avoid conflicts path = index.plugin_path(name) paths = index.load_paths(name) invalid_paths = paths.reject {|p| File.directory?(p) } if invalid_paths.any? Bundler.ui.warn <<~MESSAGE The following plugin paths don't exist: #{invalid_paths.join(", ")}. This can happen if the plugin was installed with a different version of Ruby that has since been uninstalled. If you would like to reinstall the plugin, run: bundler plugin uninstall #{name} && bundler plugin install #{name} Continuing without installing plugin #{name}. MESSAGE return end Gem.add_to_load_path(*paths) load path.join(PLUGIN_FILE_NAME) @loaded_plugin_names << name rescue RuntimeError => e Bundler.ui.error "Failed loading plugin #{name}: #{e.message}" raise end
@return [true, false] 插件是否已加载
# File bundler/plugin.rb, line 244 def loaded?(plugin) @loaded_plugin_names.include?(plugin) end
# File bundler/plugin.rb, line 145 def local_root Bundler.app_config_path.join("plugin") end
在隔离的命名空间中运行 plugins.rb 文件,记录它注册的插件操作,然后将数据传递给索引以进行存储。
@param [String] 插件名称 @param [Specification] 已安装插件的规范 @param [Boolean] optional_plugin,如果存在冲突则删除
other plugin (used for default source plugins)
@raise [MalformattedPlugin] 如果 plugins.rb 引发任何错误
# File bundler/plugin.rb, line 300 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 Gem.add_to_load_path(*load_paths) path = Pathname.new spec.full_gem_path begin load path.join(PLUGIN_FILE_NAME), true rescue StandardError => e raise MalformattedPlugin, "#{e.class}: #{e.message}" end if optional_plugin && @sources.keys.any? {|s| source? s } Bundler.rm_rf(path) false else 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
# File bundler/plugin.rb, line 22 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
所有插件相关数据的目录根
如果在应用程序中运行,则指向本地根,在 app_config_path 中。否则,指向全局根,在 Bundler.user_bundle_path
(“plugin”) 中
# File bundler/plugin.rb, line 137 def root @root ||= if SharedHelpers.in_bundle? local_root else global_root end end
验证并注册插件。
@param [String] 插件名称 @param [Specification] 已安装插件的规范 @param [Boolean] optional_plugin,如果存在冲突则删除
other plugin (used for default source plugins)
@raise [PluginInstallError] 如果验证或注册引发任何错误
# File bundler/plugin.rb, line 283 def save_plugin(name, spec, optional_plugin = false) validate_plugin! Pathname.new(spec.full_gem_path) installed = register_plugin(name, spec, optional_plugin) Bundler.ui.info "Installed plugin #{name}" if installed rescue PluginError => e raise PluginInstallError, "Failed to install plugin `#{spec.name}`, due to #{e.class} (#{e.message})" end
安装后处理并使用索引注册
@param [Array<String>] 要安装的插件列表 @param [Hash] 插件的规范映射到安装路径(目前它们
contain all the installed specs, including plugins)
@param [Array<String>] 可以忽略的推断源插件的名称
# File bundler/plugin.rb, line 254 def save_plugins(plugins, specs, optional_plugins = []) plugins.each do |name| next if index.installed?(name) spec = specs[name] save_plugin(name, spec, optional_plugins.include?(name)) end end
@return [Class] 处理该来源的类。该类包括 API::Source
# File bundler/plugin.rb, line 190 def source(name) raise UnknownSourceError, "Source #{name} not found" unless source? name load_plugin(index.source_plugin(name)) unless @sources.key? name @sources[name] end
检查是否有任何插件声明了该来源
# File bundler/plugin.rb, line 185 def source?(name) !index.source_plugin(name.to_s).nil? end
通过给定的名称卸载插件
@param [Array<String>] 要卸载的插件名称
# File bundler/plugin.rb, line 54 def uninstall(names, options) if names.empty? && !options[:all] Bundler.ui.error "No plugins to uninstall. Specify at least 1 plugin to uninstall.\n"\ "Use --all option to uninstall all the installed plugins." return end names = index.installed_plugins if options[:all] if names.any? names.each do |name| if index.installed?(name) path = index.plugin_path(name).to_s Bundler.rm_rf(path) if index.installed_in_plugin_root?(name) index.unregister_plugin(name) Bundler.ui.info "Uninstalled plugin #{name}" else Bundler.ui.error "Plugin #{name} is not installed \n" end end else Bundler.ui.info "No plugins installed" end end
检查该 gem 是否适合作为插件
目前,它仅检查它是否包含 plugins.rb 文件
@param [Pathname] 插件安装路径 @raise [MalformattedPlugin] 如果未找到 plugins.rb 文件
# File bundler/plugin.rb, line 270 def validate_plugin!(plugin_path) plugin_file = plugin_path.join(PLUGIN_FILE_NAME) raise MalformattedPlugin, "#{PLUGIN_FILE_NAME} was not found in the plugin." unless plugin_file.file? end