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

Resolve for specific platforms #4836

Merged
merged 14 commits into from
Aug 25, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def setup(*groups)
# Return if all groups are already loaded
return @setup if defined?(@setup) && @setup

definition.validate_ruby!
definition.validate_runtime!

SharedHelpers.print_major_deprecations!

Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/cli/binstubs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def initialize(options, gems)
end

def run
Bundler.definition.validate_ruby!
Bundler.definition.validate_runtime!
Bundler.settings[:bin] = options["path"] if options["path"]
Bundler.settings[:bin] = nil if options["path"] && options["path"].empty?
installer = Installer.new(Bundler.root, Bundler.definition)
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/cli/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def initialize(options)
end

def run
Bundler.definition.validate_ruby!
Bundler.definition.validate_runtime!
Bundler.definition.resolve_with_cache!
setup_cache_all
Bundler.settings[:cache_all_platforms] = options["all-platforms"] if options.key?("all-platforms")
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/cli/check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def run

begin
definition = Bundler.definition
definition.validate_ruby!
definition.validate_runtime!
not_installed = definition.missing_specs
rescue GemNotFound, VersionConflict
Bundler.ui.error "Bundler can't satisfy your Gemfile's dependencies."
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/cli/install.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def run
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?

definition = Bundler.definition
definition.validate_ruby!
definition.validate_runtime!

installer = Installer.install(Bundler.root, definition, options)
Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.settings[:frozen]
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/cli/outdated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def run
Bundler::CLI::Common.select_spec(gem_name)
end

Bundler.definition.validate_ruby!
Bundler.definition.validate_runtime!
current_specs = Bundler.ui.silence { Bundler.load.specs }
current_dependencies = {}
Bundler.ui.silence { Bundler.load.dependencies.each {|dep| current_dependencies[dep.name] = dep } }
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/cli/platform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def run
output << "Your Gemfile specifies a Ruby version requirement:\n* #{ruby_version}"

begin
Bundler.definition.validate_ruby!
Bundler.definition.validate_runtime!
output << "Your current platform satisfies the Ruby version requirement."
rescue RubyVersionMismatch => e
output << e.message
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/cli/show.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def initialize(options, gem_name)

def run
Bundler.ui.silence do
Bundler.definition.validate_ruby!
Bundler.definition.validate_runtime!
Bundler.load.lock
end

Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/cli/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def run
# rubygems plugins sometimes hook into the gem install process
Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins)

Bundler.definition.validate_ruby!
Bundler.definition.validate_runtime!
Installer.install Bundler.root, Bundler.definition, opts
Bundler.load.cache if Bundler.app_cache.exist?

Expand Down
39 changes: 36 additions & 3 deletions lib/bundler/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
if lockfile && File.exist?(lockfile)
@lockfile_contents = Bundler.read_file(lockfile)
@locked_gems = LockfileParser.new(@lockfile_contents)
@platforms = @locked_gems.platforms
@locked_platforms = @locked_gems.platforms
@platforms = @locked_platforms.dup
@locked_bundler_version = @locked_gems.bundler_version
@locked_ruby_version = @locked_gems.ruby_version

Expand All @@ -91,6 +92,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
@locked_deps = []
@locked_specs = SpecSet.new([])
@locked_sources = []
@locked_platforms = []
end

@unlock[:gems] ||= []
Expand All @@ -102,8 +104,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti

@gem_version_promoter = create_gem_version_promoter

current_platform = Bundler.rubygems.platforms.map {|p| generic(p) }.compact.last
add_platform(current_platform)
add_current_platform unless Bundler.settings[:frozen]

@path_changes = converge_paths
eager_unlock = expand_dependencies(@unlock[:gems])
Expand Down Expand Up @@ -414,6 +415,11 @@ def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
deleted = []
changed = []

new_platforms = @platforms - @locked_platforms
deleted_platforms = @locked_platforms - @platforms
added.concat new_platforms.map {|p| "* platform: #{p}" }
deleted.concat deleted_platforms.map {|p| "* platform: #{p}" }

gemfile_sources = sources.lock_sources

new_sources = gemfile_sources - @locked_sources
Expand Down Expand Up @@ -462,6 +468,11 @@ def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
raise ProductionError, msg if added.any? || deleted.any? || changed.any?
end

def validate_runtime!
validate_ruby!
validate_platforms!
end

def validate_ruby!
return unless ruby_version

Expand All @@ -487,6 +498,22 @@ def validate_ruby!
end
end

# TODO: refactor this so that `match_platform` can be called with two platforms
DummyPlatform = Struct.new(:platform)
class DummyPlatform; include MatchPlatform; end
def validate_platforms!
return if @platforms.any? do |bundle_platform|
bundle_platform = DummyPlatform.new(bundle_platform)
Bundler.rubygems.platforms.any? do |local_platform|
bundle_platform.match_platform(local_platform)
end
end

raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
"but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
"there's no compatible match between those two lists."
end

def add_platform(platform)
@new_platform ||= [email protected]?(platform)
@platforms |= [platform]
Expand All @@ -497,6 +524,12 @@ def remove_platform(platform)
raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
end

def add_current_platform
current_platform = Bundler.rubygems.platforms.last
add_platform(current_platform) if Bundler.settings[:specific_platform]
add_platform(generic(current_platform))
end

attr_reader :sources
private :sources

Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/dependency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class Dependency < Gem::Dependency
:x64_mingw_20 => Gem::Platform::X64_MINGW,
:x64_mingw_21 => Gem::Platform::X64_MINGW,
:x64_mingw_22 => Gem::Platform::X64_MINGW,
:x64_mingw_23 => Gem::Platform::X64_MINGW
:x64_mingw_23 => Gem::Platform::X64_MINGW,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really? we're doing trailing commas now? 😝

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have been since introducing rubocop

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😭

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this way we don't have to touch that line when adding a new element to the array

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what good is it to be in charge of a project if we adopt code style that clashes with my personal coding aesthetic 😝

}.freeze

REVERSE_PLATFORM_MAP = {}.tap do |reverse_platform_map|
Expand Down
68 changes: 68 additions & 0 deletions lib/bundler/gem_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,73 @@ def generic_local_platform
generic(Gem::Platform.local)
end
module_function :generic_local_platform

def platform_specificity_match(spec_platform, user_platform)
spec_platform = Gem::Platform.new(spec_platform)
return PlatformMatch::EXACT_MATCH if spec_platform == user_platform
return PlatformMatch::WORST_MATCH if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY

PlatformMatch.new(
PlatformMatch.os_match(spec_platform, user_platform),
PlatformMatch.cpu_match(spec_platform, user_platform),
PlatformMatch.platform_version_match(spec_platform, user_platform)
)
end
module_function :platform_specificity_match

def select_best_platform_match(specs, platform)
specs.select {|spec| spec.match_platform(platform) }.
min_by {|spec| platform_specificity_match(spec.platform, platform) }
end
module_function :select_best_platform_match

PlatformMatch = Struct.new(:os_match, :cpu_match, :platform_version_match)
class PlatformMatch
def <=>(other)
return nil unless other.is_a?(PlatformMatch)

m = os_match <=> other.os_match
return m unless m.zero?

m = cpu_match <=> other.cpu_match
return m unless m.zero?

m = platform_version_match <=> other.platform_version_match
m
end

EXACT_MATCH = new(-1, -1, -1).freeze
WORST_MATCH = new(1_000_000, 1_000_000, 1_000_000).freeze

def self.os_match(spec_platform, user_platform)
if spec_platform.os == user_platform.os
0
else
1
end
end

def self.cpu_match(spec_platform, user_platform)
if spec_platform.cpu == user_platform.cpu
0
elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm")
0
elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal"
1
else
2
end
end

def self.platform_version_match(spec_platform, user_platform)
if spec_platform.version == user_platform.version
0
elsif spec_platform.version.nil?
1
else
2
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/bundler/inline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def Bundler.root

definition = builder.to_definition(nil, true)
def definition.lock(*); end
definition.validate_ruby!
definition.validate_runtime!

missing_specs = proc do
begin
Expand Down
17 changes: 16 additions & 1 deletion lib/bundler/lazy_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@
module Bundler
class LazySpecification
Identifier = Struct.new(:name, :version, :source, :platform, :dependencies)
class Identifier
include Comparable
def <=>(other)
return unless other.is_a?(Identifier)
[name, version, platform_string] <=> [other.name, other.version, other.platform_string]
end

protected

def platform_string
platform_string = platform.to_s
platform_string == Index::RUBY ? Index::NULL : platform_string
end
end

include MatchPlatform

Expand Down Expand Up @@ -55,7 +69,8 @@ def to_lock
end

def __materialize__
@specification = source.specs.search(Gem::Dependency.new(name, version)).last
search_object = Bundler.settings[:specific_platform] ? self : Dependency.new(name, version)
@specification = source.specs.search(search_object).last
end

def respond_to?(*args)
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/lockfile_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def initialize(lockfile)
end
end
@sources << @rubygems_aggregate
@specs = @specs.values
@specs = @specs.values.sort_by(&:identifier)
warn_for_outdated_bundler_version
rescue ArgumentError => e
Bundler.ui.debug(e)
Expand Down
3 changes: 2 additions & 1 deletion lib/bundler/match_platform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ module MatchPlatform
def match_platform(p)
Gem::Platform::RUBY == platform ||
platform.nil? || p == platform ||
generic(Gem::Platform.new(platform)) === p
generic(Gem::Platform.new(platform)) === p ||
Gem::Platform.new(platform) === p
end
end
end
4 changes: 3 additions & 1 deletion lib/bundler/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def gemfile_install(gemfile = nil, &inline)

save_plugins plugins, installed_specs, builder.inferred_plugins
rescue => e
Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}"
unless e.is_a?(GemfileError)
Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}"
end
raise
end

Expand Down
37 changes: 12 additions & 25 deletions lib/bundler/resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,44 +66,33 @@ def message
end
end

ALL = Bundler::Dependency::PLATFORM_MAP.values.uniq.freeze

class SpecGroup < Array
include GemHelpers

attr_reader :activated, :required_by
attr_reader :activated

def initialize(a)
super
@required_by = []
@activated_platforms = []
@dependencies = nil
@specs = {}

ALL.each do |p|
@specs[p] = reverse.find {|s| s.match_platform(p) }
@specs = Hash.new do |specs, platform|
specs[platform] = select_best_platform_match(self, platform)
end
end

def initialize_copy(o)
super
@required_by = o.required_by.dup
@activated_platforms = o.activated.dup
end

def to_specs
specs = {}

@activated_platforms.each do |p|
@activated_platforms.map do |p|
next unless s = @specs[p]
platform = generic(Gem::Platform.new(s.platform))
next if specs[platform]

lazy_spec = LazySpecification.new(name, version, platform, source)
lazy_spec = LazySpecification.new(name, version, s.platform, source)
lazy_spec.dependencies.replace s.dependencies
specs[platform] = lazy_spec
end
specs.values
lazy_spec
end.compact
end

def activate_platform!(platform)
Expand Down Expand Up @@ -148,17 +137,15 @@ def platforms_for_dependency_named(dependency)
private

def __dependencies
@dependencies ||= begin
dependencies = {}
ALL.each do |p|
next unless spec = @specs[p]
dependencies[p] = []
@dependencies = Hash.new do |dependencies, platform|
dependencies[platform] = []
if spec = @specs[platform]
spec.dependencies.each do |dep|
next if dep.type == :development
dependencies[p] << DepProxy.new(dep, p)
dependencies[platform] << DepProxy.new(dep, platform)
end
end
dependencies
dependencies[platform]
end
end

Expand Down
Loading