Skip to content

Commit

Permalink
Draft: lock_platform directive in Gemfile
Browse files Browse the repository at this point in the history
This allows the Gemfile to include a deploy platform by default on first
deploy, and since Gemfiles are often cargo culted or generated, we can
make the deploy platform intention more more clear from the beginning.
  • Loading branch information
martinemde committed Nov 16, 2023
1 parent f8e87eb commit cf3f10d
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 5 deletions.
8 changes: 5 additions & 3 deletions bundler/lib/bundler/definition.rb
Expand Up @@ -55,7 +55,7 @@ def self.build(gemfile, lockfile, unlock)
# to be updated or true if all gems should be updated
# @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version
# @param optional_groups [Array(String)] A list of optional groups
def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [])
def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [], platforms = [])
if [true, false].include?(unlock)
@unlocking_bundler = false
@unlocking = unlock
Expand Down Expand Up @@ -89,6 +89,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
@locked_gems = LockfileParser.new(@lockfile_contents)
@locked_platforms = @locked_gems.platforms
@platforms = @locked_platforms.dup
platforms.each {|p| add_platform(p) }
@locked_bundler_version = @locked_gems.bundler_version
@locked_ruby_version = @locked_gems.ruby_version
@originally_locked_specs = SpecSet.new(@locked_gems.specs)
Expand All @@ -105,13 +106,14 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
end
else
@unlock = {}
@platforms = []
@locked_gems = nil
@locked_deps = {}
@locked_specs = SpecSet.new([])
@originally_locked_specs = @locked_specs
@locked_sources = []
@locked_platforms = []
@platforms = platforms
@new_platform = true unless @platforms.empty?
end

locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
Expand Down Expand Up @@ -143,7 +145,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
@unlock[:gems] ||= @dependencies.map(&:name)
else
eager_unlock = (@unlock[:gems] || []).map {|name| Dependency.new(name, ">= 0") }
@unlock[:gems] = @locked_specs.for(eager_unlock, false, platforms).map(&:name).uniq
@unlock[:gems] = @locked_specs.for(eager_unlock, false, @platforms).map(&:name).uniq
end

@dependency_changes = converge_dependencies
Expand Down
16 changes: 15 additions & 1 deletion bundler/lib/bundler/dsl.rb
Expand Up @@ -32,6 +32,7 @@ def initialize
@install_conditionals = []
@optional_groups = []
@platforms = []
@lock_platforms = []
@env = nil
@ruby_version = nil
@gemspecs = []
Expand Down Expand Up @@ -215,7 +216,7 @@ def github(repo, options = {})

def to_definition(lockfile, unlock)
check_primary_source_safety
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles)
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles, @lock_platforms)
end

def group(*args, &blk)
Expand Down Expand Up @@ -249,6 +250,19 @@ def platforms(*platforms)
end
alias_method :platform, :platforms

# Name collision, working around it for a proof
def lock_platform(*platforms)
platforms.each do |p|
gem_platform = Gem::Platform.new(p)
# TODO: this is copied from lock
if gem_platform.to_s == "unknown"
Bundler.ui.warn "The platform `#{platform_string}` is unknown to RubyGems " \
"and adding it will likely lead to resolution errors"
end
@lock_platforms << gem_platform
end
end

def env(name)
old = @env
@env = name
Expand Down
2 changes: 1 addition & 1 deletion bundler/lib/bundler/spec_set.rb
Expand Up @@ -15,7 +15,7 @@ def initialize(specs, incomplete_specs = [])
end

def for(dependencies, check = false, platforms = [nil])
handled = ["bundler"].product(platforms).map {|k| [k, true] }.to_h
handled = ["bundler"].product(platforms).to_h {|k| [k, true] }
deps = dependencies.product(platforms)
specs = []

Expand Down
52 changes: 52 additions & 0 deletions bundler/spec/runtime/platform_spec.rb
Expand Up @@ -467,4 +467,56 @@
end
end
end

it "will add platform directly specified by a gemfile" do
simulate_platform "x86-darwin-100"

build_repo4 do
build_gem "nokogiri", "1.13.8"
build_gem "nokogiri", "1.13.8" do |s|
s.platform = "aarch64-linux"
end
end

install_gemfile <<-G
source "#{file_uri_for(gem_repo4)}"
lock_platform "aarch64-linux"
gem "nokogiri"
G

checksums = checksum_section do |c|
c.repo_gem gem_repo4, "nokogiri", "1.13.8"
# TODO: This is wrong, the point is to get the checksum
# Figure out why it's not fetching them for other platforms
c.no_checksum "nokogiri", "1.13.8", "aarch64-linux"
# c.repo_gem gem_repo4, "nokogiri", "1.13.8", "aarch64-linux"
end

expected_lockfile = <<~L
GEM
remote: #{file_uri_for(gem_repo4)}/
specs:
nokogiri (1.13.8)
nokogiri (1.13.8-aarch64-linux)
PLATFORMS
aarch64-linux
x86-darwin-100
DEPENDENCIES
nokogiri
CHECKSUMS
#{checksums}
BUNDLED WITH
#{Bundler::VERSION}
L

# TODO: "nokogiri was expected to be of platform ruby but was ruby"
# expect(the_bundle).to include_gems "nokogiri 1.13.8 ruby"
expect(lockfile).to eq(expected_lockfile)
end
end
5 changes: 5 additions & 0 deletions bundler/spec/support/builders.rb
Expand Up @@ -126,6 +126,11 @@ def build_repo1
s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 x86-darwin-100'"
end

build_gem "platform_specific" do |s|
s.platform = "x86_64-linux"
s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 x86_64-linux'"
end

build_gem "only_java", "1.0" do |s|
s.platform = "java"
s.write "lib/only_java.rb", "ONLY_JAVA = '1.0.0 JAVA'"
Expand Down

0 comments on commit cf3f10d

Please sign in to comment.