Skip to content

Commit

Permalink
Add the ruby platform to the gemset
Browse files Browse the repository at this point in the history
Knowing the exact platform of a compiled gem is important
because different binaries have different hashes.

Note that this change makes teh gemset platform dependant.
  • Loading branch information
lavoiesl committed Oct 15, 2019
1 parent 26ff582 commit 89e56d5
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 11 deletions.
52 changes: 41 additions & 11 deletions lib/bundix/source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,35 +92,59 @@ def format_hash(hash)
end

def fetch_local_hash(spec)
spec.source.caches.each do |cache|
path = File.join(cache, "#{spec.full_name}.gem")
next unless File.file?(path)
has_platform = spec.platform && spec.platform != Gem::Platform::RUBY
name_version = "#{spec.name}-#{spec.version}"
filename = has_platform ? "#{name_version}-*" : name_version

paths = spec.source.caches.map(&:to_s)
Dir.glob("{#{paths.join(',')}}/#{filename}.gem").each do |path|
if has_platform
# Find first gem that matches the platform
platform = File.basename(path, '.gem')[(name_version.size + 1)..-1]
next unless Gem::Platform.match(platform)
end

hash = nix_prefetch_url(path)[SHA256_32]
return format_hash(hash) if hash
return format_hash(hash), platform if hash
end

nil
end

def fetch_remotes_hash(spec, remotes)
remotes.each do |remote|
hash = fetch_remote_hash(spec, remote)
return remote, format_hash(hash) if hash
hash, platform = fetch_remote_hash(spec, remote)
return remote, format_hash(hash), platform if hash
end

nil
end

def fetch_remote_hash(spec, remote)
has_platform = spec.platform && spec.platform != Gem::Platform::RUBY
if has_platform
# Fetch remote spec to determine the exact platform
# Note that we can't simply use the local platform; the platform of the gem might differ.
# e.g. universal-darwin-14 covers x86_64-darwin-14
spec = spec_for_dependency(remote, spec.name, spec.version)
return unless spec
end

uri = "#{remote}/gems/#{spec.full_name}.gem"
result = nix_prefetch_url(uri)
return unless result
result[SHA256_32]

return result[SHA256_32], spec.platform&.to_s
rescue => e
puts "ignoring error during fetching: #{e}"
puts e.backtrace
nil
end

def spec_for_dependency(remote, name, version)
sources = Gem::SourceList.from([remote])
Gem::SpecFetcher.new(sources).spec_for_dependency(Gem::Dependency.new(name, version)).first&.first&.first
end
end

class Source < Struct.new(:spec, :fetcher)
Expand Down Expand Up @@ -149,18 +173,24 @@ def convert_path

def convert_rubygems
remotes = spec.source.remotes.map{|remote| remote.to_s.sub(/\/+$/, '') }
hash = fetcher.fetch_local_hash(spec)
remote, hash = fetcher.fetch_remotes_hash(spec, remotes) unless hash
hash, platform = fetcher.fetch_local_hash(spec)
remote, hash, platform = fetcher.fetch_remotes_hash(spec, remotes) unless hash
fail "couldn't fetch hash for #{spec.full_name}" unless hash
puts "#{hash} => #{spec.full_name}.gem" if $VERBOSE

if $VERBOSE
# 'spec' may not have the correct platform, so rebuild the name
full_name = "#{spec.name}-#{spec.version}"
full_name = "#{full_name}-#{platform}" if platform
puts "#{hash} => #{full_name}.gem"
end

{
source: {
type: 'gem',
remotes: (remote ? [remote] : remotes),
sha256: hash,
},
}
}.tap { |s| s[:platform] = platform if platform && platform != Gem::Platform::RUBY }
end

def convert_git
Expand Down
19 changes: 19 additions & 0 deletions test/convert.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@

class TestConvert < Minitest::Test
class PrefetchStub < Bundix::Fetcher
SPECS = {
"sorbet-static" => {
platform: 'java-123',
version: "0.4.4821",
},
}

def nix_prefetch_url(*args)
format_hash(Digest::SHA256.hexdigest(args.to_s))
end
Expand All @@ -18,6 +25,16 @@ def fetch_local_hash(spec)
return nil
end

def spec_for_dependency(remote, name, version)
opts = SPECS[name]
raise "Unexpected spec query: #{name}" unless opts

Gem::Specification.new do |s|
s.name = name
s.version = version
s.platform = Gem::Platform.new(opts[:platform]) if opts[:platform]
end
end
end

def with_gemset(options)
Expand All @@ -40,8 +57,10 @@ def test_bundler_dep
) do |gemset|
assert_equal("0.5.0", gemset.dig("bundler-audit", :version))
assert_equal("0.19.4", gemset.dig("thor", :version))
assert_nil(gemset.dig("thor", :platform))
gemset["sorbet-static"].tap do |gem|
assert_equal("0.4.4821", gem[:version])
assert_equal('java-unknown', gem[:platform])
end
end
end
Expand Down

0 comments on commit 89e56d5

Please sign in to comment.