summaryrefslogtreecommitdiff
path: root/lib/rubygems/dependency_installer.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/dependency_installer.rb')
-rw-r--r--lib/rubygems/dependency_installer.rb264
1 files changed, 264 insertions, 0 deletions
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
new file mode 100644
index 0000000000..c842714d95
--- /dev/null
+++ b/lib/rubygems/dependency_installer.rb
@@ -0,0 +1,264 @@
+# frozen_string_literal: true
+
+require_relative "../rubygems"
+require_relative "dependency_list"
+require_relative "package"
+require_relative "installer"
+require_relative "spec_fetcher"
+require_relative "user_interaction"
+require_relative "available_set"
+
+##
+# Installs a gem along with all its dependencies from local and remote gems.
+
+class Gem::DependencyInstaller
+ include Gem::UserInteraction
+
+ DEFAULT_OPTIONS = { # :nodoc:
+ env_shebang: false,
+ document: %w[ri],
+ domain: :both, # HACK: dup
+ force: false,
+ format_executable: false, # HACK: dup
+ ignore_dependencies: false,
+ prerelease: false,
+ security_policy: nil, # HACK: NoSecurity requires OpenSSL. AlmostNo? Low?
+ wrappers: true,
+ build_args: nil,
+ build_docs_in_background: false,
+ }.freeze
+
+ ##
+ # Documentation types. For use by the Gem.done_installing hook
+
+ attr_reader :document
+
+ ##
+ # Errors from SpecFetcher while searching for remote specifications
+
+ attr_reader :errors
+
+ ##
+ # List of gems installed by #install in alphabetic order
+
+ attr_reader :installed_gems
+
+ ##
+ # Creates a new installer instance.
+ #
+ # Options are:
+ # :cache_dir:: Alternate repository path to store .gem files in.
+ # :domain:: :local, :remote, or :both. :local only searches gems in the
+ # current directory. :remote searches only gems in Gem::sources.
+ # :both searches both.
+ # :env_shebang:: See Gem::Installer::new.
+ # :force:: See Gem::Installer#install.
+ # :format_executable:: See Gem::Installer#initialize.
+ # :ignore_dependencies:: Don't install any dependencies.
+ # :install_dir:: See Gem::Installer#install.
+ # :prerelease:: Allow prerelease versions. See #install.
+ # :security_policy:: See Gem::Installer::new and Gem::Security.
+ # :user_install:: See Gem::Installer.new
+ # :wrappers:: See Gem::Installer::new
+ # :build_args:: See Gem::Installer::new
+
+ def initialize(options = {})
+ @only_install_dir = !options[:install_dir].nil?
+ @install_dir = options[:install_dir] || Gem.dir
+ @build_root = options[:build_root]
+
+ options = DEFAULT_OPTIONS.merge options
+
+ @bin_dir = options[:bin_dir]
+ @dev_shallow = options[:dev_shallow]
+ @development = options[:development]
+ @document = options[:document]
+ @domain = options[:domain]
+ @env_shebang = options[:env_shebang]
+ @force = options[:force]
+ @format_executable = options[:format_executable]
+ @ignore_dependencies = options[:ignore_dependencies]
+ @prerelease = options[:prerelease]
+ @security_policy = options[:security_policy]
+ @user_install = options[:user_install]
+ @wrappers = options[:wrappers]
+ @build_args = options[:build_args]
+ @build_jobs = options[:build_jobs]
+ @build_docs_in_background = options[:build_docs_in_background]
+ @dir_mode = options[:dir_mode]
+ @data_mode = options[:data_mode]
+ @prog_mode = options[:prog_mode]
+ @build_extension = options[:build_extension]
+ @install_plugin = options[:install_plugin]
+
+ # Indicates that we should not try to update any deps unless
+ # we absolutely must.
+ @minimal_deps = options[:minimal_deps]
+
+ @available = nil
+ @installed_gems = []
+ @toplevel_specs = nil
+
+ @cache_dir = options[:cache_dir] || @install_dir
+
+ @errors = []
+ end
+
+ ##
+ # Indicated, based on the requested domain, if local
+ # gems should be considered.
+
+ def consider_local?
+ @domain == :both || @domain == :local
+ end
+
+ ##
+ # Indicated, based on the requested domain, if remote
+ # gems should be considered.
+
+ def consider_remote?
+ @domain == :both || @domain == :remote
+ end
+
+ def in_background(what) # :nodoc:
+ fork_happened = false
+ if @build_docs_in_background && Process.respond_to?(:fork)
+ begin
+ Process.fork do
+ yield
+ end
+ fork_happened = true
+ say "#{what} in a background process."
+ rescue NotImplementedError
+ end
+ end
+ yield unless fork_happened
+ end
+
+ ##
+ # Installs the gem +dep_or_name+ and all its dependencies. Returns an Array
+ # of installed gem specifications.
+ #
+ # If the +:prerelease+ option is set and there is a prerelease for
+ # +dep_or_name+ the prerelease version will be installed.
+ #
+ # Unless explicitly specified as a prerelease dependency, prerelease gems
+ # that +dep_or_name+ depend on will not be installed.
+ #
+ # If c-1.a depends on b-1 and a-1.a and there is a gem b-1.a available then
+ # c-1.a, b-1 and a-1.a will be installed. b-1.a will need to be installed
+ # separately.
+
+ def install(dep_or_name, version = Gem::Requirement.default)
+ request_set = resolve_dependencies dep_or_name, version
+
+ @installed_gems = []
+
+ options = {
+ bin_dir: @bin_dir,
+ build_args: @build_args,
+ build_jobs: @build_jobs,
+ document: @document,
+ env_shebang: @env_shebang,
+ force: @force,
+ format_executable: @format_executable,
+ ignore_dependencies: @ignore_dependencies,
+ prerelease: @prerelease,
+ security_policy: @security_policy,
+ user_install: @user_install,
+ wrappers: @wrappers,
+ build_root: @build_root,
+ dir_mode: @dir_mode,
+ data_mode: @data_mode,
+ prog_mode: @prog_mode,
+ build_extension: @build_extension,
+ install_plugin: @install_plugin,
+ }
+ options[:install_dir] = @install_dir if @only_install_dir
+
+ request_set.install options do |_, installer|
+ @installed_gems << installer.spec if installer
+ end
+
+ @installed_gems.sort!
+
+ # Since this is currently only called for docs, we can be lazy and just say
+ # it's documentation. Ideally the hook adder could decide whether to be in
+ # the background or not, and what to call it.
+ in_background "Installing documentation" do
+ Gem.done_installing_hooks.each do |hook|
+ hook.call self, @installed_gems
+ end
+ end unless Gem.done_installing_hooks.empty?
+
+ @installed_gems
+ end
+
+ def install_development_deps # :nodoc:
+ if @development && @dev_shallow
+ :shallow
+ elsif @development
+ :all
+ else
+ :none
+ end
+ end
+
+ def resolve_dependencies(dep_or_name, version) # :nodoc:
+ request_set = Gem::RequestSet.new
+ request_set.development = @development
+ request_set.development_shallow = @dev_shallow
+ request_set.soft_missing = @force
+ request_set.prerelease = @prerelease
+
+ installer_set = Gem::Resolver::InstallerSet.new @domain
+ installer_set.ignore_installed = (@minimal_deps == false) || @only_install_dir
+ installer_set.force = @force
+
+ if consider_local?
+ if dep_or_name =~ /\.gem$/ && File.file?(dep_or_name)
+ src = Gem::Source::SpecificFile.new dep_or_name
+ installer_set.add_local dep_or_name, src.spec, src
+ version = src.spec.version if version == Gem::Requirement.default
+ elsif dep_or_name =~ /\.gem$/ # rubocop:disable Performance/RegexpMatch
+ Dir[dep_or_name].each do |name|
+ src = Gem::Source::SpecificFile.new name
+ installer_set.add_local dep_or_name, src.spec, src
+ rescue Gem::Package::FormatError
+ end
+ # else This is a dependency. InstallerSet handles this case
+ end
+ end
+
+ dependency =
+ if spec = installer_set.local?(dep_or_name)
+ installer_set.remote = nil if spec.dependencies.none?
+ Gem::Dependency.new spec.name, version
+ elsif String === dep_or_name
+ Gem::Dependency.new dep_or_name, version
+ else
+ dep_or_name
+ end
+
+ dependency.prerelease = @prerelease
+
+ request_set.import [dependency]
+
+ installer_set.add_always_install dependency
+
+ request_set.always_install = installer_set.always_install
+ request_set.remote = installer_set.consider_remote?
+
+ if @ignore_dependencies
+ installer_set.ignore_dependencies = true
+ request_set.ignore_dependencies = true
+ request_set.soft_missing = true
+ end
+
+ request_set.resolve installer_set
+
+ @errors.concat request_set.errors
+
+ request_set
+ end
+end