Skip to content
Permalink
Browse files

Unify install_gem_without_dependencies back into install_gem

  • Loading branch information...
matthewd committed May 5, 2019
1 parent c2cd922 commit a9d4e2f130e71c405f3208fb450e97d96e8509b1
Showing with 242 additions and 159 deletions.
  1. +3 −1 bootstrap.rb
  2. +77 −0 lib/gel/catalog_set.rb
  3. +43 −90 lib/gel/environment.rb
  4. +7 −0 lib/gel/gemfile_parser.rb
  5. +66 −0 lib/gel/null_solver.rb
  6. +41 −0 lib/gel/pub_grub/solver.rb
  7. +5 −68 lib/gel/pub_grub/source.rb
@@ -15,12 +15,14 @@ def usage
Dir.mkdir "tmp" unless Dir.exist?("tmp")

# `gel install-gem pub_grub`
#
# TODO: Is this still required given +auto_install_pub_grub!+?
require_relative "lib/gel/catalog"
require_relative "lib/gel/work_pool"
Gel::WorkPool.new(2) do |work_pool|
catalog = Gel::Catalog.new("https://rubygems.org", work_pool: work_pool)

Gel::Environment.install_gem_without_dependencies([catalog], "pub_grub", nil, output: $stderr)
Gel::Environment.install_gem([catalog], "pub_grub", nil, output: $stderr, solve: false)
end

# `gel install`
@@ -0,0 +1,77 @@
# frozen_string_literal: true

class Gel::CatalogSet
CatalogEntry = Struct.new(:catalog, :name, :version, :info) do
def gem_version
@gem_version ||= Gel::Support::GemVersion.new(version)
end
end

def initialize(catalogs)
@catalogs = catalogs

@cached_specs = Hash.new { |h, k| h[k] = {} }
@specs_by_package_version = {}
end

def catalog_for_version(package, version)
@specs_by_package_version[package][version.to_s]&.catalog
end

def entries_for(package)
fetch_package_info(package)

@specs_by_package_version[package].values
end

# Returns a list of [name, version_contraint] pairs representing the
# specified package's dependencies. Note that a given +name+ may
# appear multiple times, and the resulting dependency is the
# intersection of all constraints.
def dependencies_for(package, version, platforms: nil)
fetch_package_info(package) # probably already done, can't hurt

spec = @specs_by_package_version[package][version.to_s]
info = spec.info
info = info.select { |p, i| platforms.include?(p) } if platforms

# FIXME: ruby_constraints ???

info.flat_map { |_, i| i[:dependencies] }
end

private

def fetch_package_info(package)
return if @specs_by_package_version.key?(package)

specs = []
@catalogs.each do |catalog|
if catalog.nil?
break unless specs.empty?
next
end

if info = catalog.gem_info(package.name)
@cached_specs[catalog][package.name] ||=
begin
grouped_versions = info.to_a.map do |full_version, attributes|
version, platform = full_version.split("-", 2)
platform ||= "ruby"
[version, platform, attributes]
end.group_by(&:first)

grouped_versions.map { |version, tuples| CatalogEntry.new(catalog, package.name, version, tuples.map { |_, p, a| [p, a] }) }
end

specs.concat @cached_specs[catalog][package.name]
end
end

@specs_by_package_version[package] = {}
specs.each do |spec|
# TODO: are we going to find specs in multiple catalogs this way?
@specs_by_package_version[package][spec.version] = spec
end
end
end
@@ -130,14 +130,14 @@ def self.auto_install_pub_grub!
Gel::WorkPool.new(2) do |work_pool|
catalog = Gel::Catalog.new("https://rubygems.org", work_pool: work_pool)

install_gem_without_dependencies([catalog], "pub_grub", [">= 0.5.0"])
install_gem([catalog], "pub_grub", [">= 0.5.0"], solve: false)
end
end
end
end
end

def self._lock(store: store(), output: nil, gemfile: Gel::Environment.load_gemfile, lockfile: Gel::Environment.lockfile_name, catalog_options: {}, preference_strategy: nil)
def self._lock(store: store(), output: nil, gemfile: Gel::Environment.load_gemfile, lockfile: Gel::Environment.lockfile_name, catalog_options: {}, solve: true, preference_strategy: nil)
output = nil if $DEBUG

if lockfile && File.exist?(lockfile)
@@ -199,26 +199,21 @@ def self._lock(store: store(), output: nil, gemfile: Gel::Environment.load_gemfi
end
end

auto_install_pub_grub!
with_root_store do
gem "pub_grub"
end
require_relative "pub_grub/source"

strategy = gem_set && preference_strategy && preference_strategy.call(gem_set)
source = Gel::PubGrub::Source.new(gemfile, catalogs, ["ruby"], strategy)
solver = PubGrub::VersionSolver.new(source: source)
solver.define_singleton_method(:next_package_to_try) do
self.solution.unsatisfied.min_by do |term|
package = term.package
versions = self.source.versions_for(package, term.constraint.range)

if strategy
strategy.package_priority(package, versions) + @package_depth[package]
else
@package_depth[package]
end * 1000 + versions.count
end.package
require_relative "catalog_set"
catalog_set = Gel::CatalogSet.new(catalogs)

if solve
auto_install_pub_grub!
with_root_store do
gem "pub_grub"
end
require_relative "pub_grub/solver"

strategy = gem_set && preference_strategy && preference_strategy.call(gem_set)
solver = Gel::PubGrub::Solver.new(gemfile: gemfile, catalog_set: catalog_set, platforms: ["ruby"], strategy: strategy)
else
require_relative "null_solver"
solver = Gel::NullSolver.new(gemfile: gemfile, catalog_set: catalog_set, platforms: ["ruby"])
end

if output
@@ -237,25 +232,21 @@ def self._lock(store: store(), output: nil, gemfile: Gel::Environment.load_gemfi
solver.work until solver.solved?
end

solution = solver.result
solution.delete(source.root)

catalog_pool.stop

new_resolution = Gel::ResolvedGemSet.new

solution.each do |(package, version)|
next if package.name =~ /^~/

spec = source.spec_for_version(package, version)
solver.each_resolved_package do |package, version|
catalog = catalog_set.catalog_for_version(package, version)

type = case spec.catalog
when Gel::GitCatalog; :git
when Gel::PathCatalog; :path
else; :gem
end
type =
case catalog
when Gel::GitCatalog; :git
when Gel::PathCatalog; :path
else; :gem
end

deps = source.dependencies_for(package, version)
deps = catalog_set.dependencies_for(package, version, platforms: ["ruby"])

platform = nil # TODO

@@ -273,23 +264,26 @@ def self._lock(store: store(), output: nil, gemfile: Gel::Environment.load_gemfi
[dep_name, req_strings.join(", ")]
end,
set: new_resolution,
catalog: spec.catalog
catalog: catalog
)
end

root_deps = source.root_dependencies
new_resolution.dependencies = root_deps.sort_by { |name, _| name }.map do |name, constraints|
next if name =~ /^~/
new_resolution.dependencies =
gemfile.gems_for_platforms([:ruby, :mri]).
group_by { |name, _constraints, _options| name }.
map do |name, list|
constraints = list.flat_map { |_name, constraints, _options| constraints }.compact

if constraints == []
name
else
r = Gel::Support::GemRequirement.new(constraints)
req_strings = r.requirements.sort_by { |(_op, ver)| ver }.map { |(op, ver)| "#{op} #{ver}" }
if constraints == []
name
else
r = Gel::Support::GemRequirement.new(constraints)
req_strings = r.requirements.sort_by { |(_op, ver)| ver }.map { |(op, ver)| "#{op} #{ver}" }

"#{name} (#{req_strings.join(", ")})"
end
end.compact
"#{name} (#{req_strings.join(", ")})"
end
end.
sort

new_resolution.platforms = ["ruby"]
new_resolution.server_catalogs = server_catalogs
@@ -308,11 +302,11 @@ def self.lock(output: nil, lockfile: lockfile_name, **args)
lock_body
end

def self.install_gem(catalogs, gem_name, requirements = nil, output: nil)
def self.install_gem(catalogs, gem_name, requirements = nil, output: nil, solve: true)
base_store = Gel::Environment.store
base_store = base_store.inner if base_store.is_a?(Gel::LockedStore)

gem_set = _lock(output: output, gemfile: Gel::GemfileParser.parse(<<~END))
gem_set = _lock(output: output, solve: solve, gemfile: Gel::GemfileParser.parse(<<~END))
source "https://rubygems.org"
gem #{gem_name.inspect} #{", #{requirements.inspect}" if requirements}
@@ -322,47 +316,6 @@ def self.install_gem(catalogs, gem_name, requirements = nil, output: nil)
loader.activate(self, base_store, install: true, output: output)
end

# This should only be used to install gel's own dependencies (i.e. pub_grub)
def self.install_gem_without_dependencies(catalogs, gem_name, requirements = nil, output: nil)
base_store = Gel::Environment.store
base_store = base_store.inner if base_store.is_a?(Gel::LockedStore)

req = Gel::Support::GemRequirement.new(requirements)

require_relative "installer"
installer = Gel::Installer.new(base_store)

found_any = false
catalogs.each do |catalog|
info = catalog.gem_info(gem_name)
next if info.nil?

found_any = true
version = info.keys.
map { |v| Gel::Support::GemVersion.new(v.split("-", 2).first) }.
sort_by { |v| [v.prerelease? ? 0 : 1, v] }.
reverse.find { |v| req.satisfied_by?(v) }
next if version.nil?

return false if base_store.gem?(gem_name, version.to_s)

installer.install_gem([catalog], gem_name, version.to_s)

installer.wait(output)

return true
end

if found_any
raise Gel::Error::NoVersionSatisfy.new(
gem_name: gem_name,
requirements: requirements,
)
else
raise Gel::Error::UnknownGemError.new(gem_name: gem_name)
end
end

def self.activate(install: false, output: nil, error: true)
loaded = Gel::Environment.load_gemfile
return if loaded.nil?
@@ -158,5 +158,12 @@ def autorequire(target, gems = self.gems)
end
end
end

def gems_for_platforms(match_platforms)
gems.select do |_, _, options|
next true unless entry_platforms = options[:platforms]
!([*entry_platforms] & match_platforms).empty?
end
end
end
end
@@ -0,0 +1,66 @@
# frozen_string_literal: true

class Gel::NullSolver
NullPackage = Struct.new(:name)

def initialize(gemfile:, catalog_set:, platforms:)
@gemfile = gemfile
@catalog_set = catalog_set
@platforms = platforms

@packages = Hash.new { |h, k| h[k] = NullPackage.new(k) }

@solution = {}
@constraints = nil
end

def next_gem_to_solve
(@constraints.keys - @solution.keys).first
end

def solved?
@constraints && next_gem_to_solve.nil?
end

def work
if @constraints.nil?
@constraints = Hash.new { |h, k| h[k] = [] }

@gemfile.gems_for_platforms(@platforms).each do |name, constraints, _|
@constraints[name].concat(constraints || [])
end
else
name = next_gem_to_solve

req = Gel::Support::GemRequirement.new(@constraints[name])

choice =
@catalog_set.entries_for(@packages[name]).map(&:gem_version).
sort_by { |v| [v.prerelease? ? 0 : 1, v] }.
reverse.find { |v| req.satisfied_by?(v) }

if choice.nil?
raise "Failed to resolve #{name.inspect} (#{req.inspect}) given #{@solution.inspect}"
end

@solution[name] = choice

@catalog_set.dependencies_for(@packages[name], choice, platforms: @platforms).each do |dep_name, constraints|
if @solution[dep_name]
new_req = Gel::Support::GemRequirement.new(constraints)
unless new_req.satisfied_by?(@solution[dep_name])
raise "Already chose #{dep_name.inspect} #{@solution[dep_name]}, which is incompatible with #{name.inspect} #{choice.inspect} (wants #{new_req})"
end
else
@constraints[name].concat(constraints || [])
end
end
end
end

def each_resolved_package
@solution.each do |name, version|
yield @packages[name], version
end
end
end

0 comments on commit a9d4e2f

Please sign in to comment.
You can’t perform that action at this time.