# frozen_string_literal: truerequire"bundler/plugin/api"moduleBundlermodulePluginautoload:DSL,"bundler/plugin/dsl"autoload:Index,"bundler/plugin/index"autoload:Installer,"bundler/plugin/installer"autoload:SourceList,"bundler/plugin/source_list"classMalformattedPlugin<PluginError;endclassUndefinedCommandError<PluginError;endclassUnknownSourceError<PluginError;endPLUGIN_FILE_NAME="plugins.rb".freezemodule_functiondefreset!instance_variables.each{|i|remove_instance_variable(i)}@sources={}@commands={}@hooks_by_event=Hash.new{|h,k|h[k]=[]}@loaded_plugin_names=[]endreset!# Installs a new plugin by the given name## @param [Array<String>] names the name of plugin to be installed# @param [Hash] options various parameters as described in description.# Refer to cli/plugin for available optionsdefinstall(names,options)specs=Installer.new.install(names,options)save_pluginsnames,specsrescuePluginError=>eifspecsspecs_to_delete=Hash[specs.select{|k,_v|names.include?(k)&&!index.commands.values.include?(k)}]specs_to_delete.values.each{|spec|Bundler.rm_rf(spec.full_gem_path)}endBundler.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# specified by plugin method## @param [Pathname] gemfile path# @param [Proc] block that can be evaluated for (inline) Gemfiledefgemfile_install(gemfile=nil,&inline)builder=DSL.newifblock_given?builder.instance_eval(&inline)elsebuilder.eval_gemfile(gemfile)enddefinition=builder.to_definition(nil,true)returnifdefinition.dependencies.empty?plugins=definition.dependencies.map(&:name).reject{|p|index.installed?p}installed_specs=Installer.new.install_definition(definition)save_pluginsplugins,installed_specs,builder.inferred_pluginsrescue=>eunlesse.is_a?(GemfileError)Bundler.ui.error"Failed to install plugin: #{e.message}\n#{e.backtrace[0]}"endraiseend# The index object used to store the details about the plugindefindex@index||=Index.newend# The directory root for all plugin related data## Points to root in app_config_path if ran in an app else points to the one# in user_bundle_pathdefroot@root||=ifSharedHelpers.in_bundle?local_rootelseglobal_rootendenddeflocal_rootBundler.app_config_path.join("plugin")end# The global directory root for all plugin related datadefglobal_rootBundler.user_bundle_path.join("plugin")end# The cache directory for plugin stuffsdefcache@cache||=root.join("cache")end# To be called via the API to register to handle a commanddefadd_command(command,cls)@commands[command]=clsend# Checks if any plugin handles the commanddefcommand?(command)!index.command_plugin(command).nil?end# To be called from Cli class to pass the command and argument to# approriate plugin classdefexec_command(command,args)raiseUndefinedCommandError,"Command `#{command}` not found"unlesscommand?commandload_pluginindex.command_plugin(command)unless@commands.key?command@commands[command].new.exec(command,args)end# To be called via the API to register to handle a source plugindefadd_source(source,cls)@sources[source]=clsend# Checks if any plugin declares the sourcedefsource?(name)!index.source_plugin(name.to_s).nil?end# @return [Class] that handles the source. The calss includes API::Sourcedefsource(name)raiseUnknownSourceError,"Source #{name} not found"unlesssource?nameload_plugin(index.source_plugin(name))unless@sources.key?name@sources[name]end# @param [Hash] The options that are present in the lock file# @return [API::Source] the instance of the class that handles the source# type passed in locked_optsdefsource_from_lock(locked_opts)src=source(locked_opts["type"])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 hookdefadd_hook(event,&block)@hooks_by_event[event.to_s]<<blockend# 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] eventdefhook(event,*args,&arg_blk)returnunlessBundler.feature_flag.plugins?plugins=index.hook_plugins(event)returnunlessplugins.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 pathdefinstalled?(plugin)Index.new.installed?(plugin)end# Post installation processing and registering with index## @param [Array<String>] plugins list to be installed# @param [Hash] specs of plugins mapped to installation path (currently they# contain all the installed specs, including plugins)# @param [Array<String>] names of inferred source plugins that can be ignoreddefsave_plugins(plugins,specs,optional_plugins=[])plugins.eachdo|name|spec=specs[name]validate_plugin!Pathname.new(spec.full_gem_path)installed=register_plugin(name,spec,optional_plugins.include?(name))Bundler.ui.info"Installed plugin #{name}"ifinstalledendend# Checks if the gem is good to be a plugin## At present it only checks whether it contains plugins.rb file## @param [Pathname] plugin_path the path plugin is installed at# @raise [MalformattedPlugin] if plugins.rb file is not founddefvalidate_plugin!(plugin_path)plugin_file=plugin_path.join(PLUGIN_FILE_NAME)raiseMalformattedPlugin,"#{PLUGIN_FILE_NAME} was not found in the plugin."unlessplugin_file.file?end# Runs the plugins.rb file in an isolated namespace, records the plugin# actions it registers for and then passes the data to index to be stored.## @param [String] name the name of the plugin# @param [Specification] spec of installed plugin# @param [Boolean] optional_plugin, removed if there is conflict with any# other plugin (used for default source plugins)## @raise [MalformattedPlugin] if plugins.rb raises any errordefregister_plugin(name,spec,optional_plugin=false)commands=@commandssources=@sourceshooks=@hooks_by_event@commands={}@sources={}@hooks_by_event=Hash.new{|h,k|h[k]=[]}load_paths=spec.load_pathsadd_to_load_path(load_paths)path=Pathname.newspec.full_gem_pathbeginloadpath.join(PLUGIN_FILE_NAME),truerescueStandardError=>eraiseMalformattedPlugin,"#{e.class}: #{e.message}"endifoptional_plugin&&@sources.keys.any?{|s|source?s}Bundler.rm_rf(path)falseelseindex.register_plugin(name,path.to_s,load_paths,@commands.keys,@sources.keys,@hooks_by_event.keys)trueendensure@commands=commands@sources=sources@hooks_by_event=hooksend# Executes the plugins.rb file## @param [String] name of the plugindefload_plugin(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 conflictspath=index.plugin_path(name)add_to_load_path(index.load_paths(name))loadpath.join(PLUGIN_FILE_NAME)@loaded_plugin_names<<namerescue=>eBundler.ui.error"Failed loading plugin #{name}: #{e.message}"raiseenddefadd_to_load_path(load_paths)ifinsert_index=Bundler.rubygems.load_path_insert_index$LOAD_PATH.insert(insert_index,*load_paths)else$LOAD_PATH.unshift(*load_paths)endendclass<<selfprivate:load_plugin,:register_plugin,:save_plugins,:validate_plugin!,:add_to_load_pathendendend