Skip to content
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
17 changes: 17 additions & 0 deletions lib/rubygems/content_address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,23 @@ def self.eligible?(spec, validate_ruby_abi: true)
!ruby_abi_for(spec.required_ruby_version).nil?
end

##
# Raises ArgumentError unless +spec+ is eligible for content addressing

def self.eligible!(spec)
unless platform_eligible?(spec.platform)
raise ArgumentError,
"Cannot build a content-addressable gem as no platform or a Ruby platform has been set"
end

return unless ruby_abi_for(spec.required_ruby_version).nil?

raise ArgumentError,
"Cannot build a content-addressable gem because required_ruby_version is set to " \
"#{spec.required_ruby_version}. Please set required_ruby_version to \"~> X.Y.0\" " \
"so that it identifies a single Ruby ABI."
end

##
# Whether +spec+ is content-addressed: it is eligible for content
# addressing and has a valid content address set. See eligible? for
Expand Down
19 changes: 1 addition & 18 deletions lib/rubygems/package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def build(skip_validation = false, strict_validation = false)
# +required_rubygems_version+ is constrained before building.

def build_content_addressable_file(skip_validation = false, strict_validation = false)
validate_content_addressable_spec
Gem::ContentAddress.eligible!(@spec)
ruby_abi = Gem::ContentAddress.ruby_abi_for(@spec.required_ruby_version)
@spec.required_rubygems_version = normalized_required_rubygems_version(ruby_abi)

Expand Down Expand Up @@ -819,23 +819,6 @@ def satisfies_rubygems_floor?(requirement, floor)
end
end

##
# Validates that the spec can be built as a content-addressable gem: it
# must declare a non-Ruby platform and its +required_ruby_version+ must
# identify a single Ruby ABI.

def validate_content_addressable_spec
return if Gem::ContentAddress.eligible?(@spec)

unless Gem::ContentAddress.platform_eligible?(@spec.platform)
raise ArgumentError, "Cannot build a content-addressable gem as no platform or a Ruby platform has been set"
end

raise ArgumentError,
"Cannot build a content-addressable gem because required_ruby_version is set to #{@spec.required_ruby_version}. " \
"Please set required_ruby_version to \"~> X.Y.0\" so that it identifies a single Ruby ABI."
end

##
# Returns the full path for installing +filename+ into +destination_dir+,
# which must already be resolved with File.realpath by the caller.
Expand Down
81 changes: 75 additions & 6 deletions lib/rubygems/package_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ class Gem::PackageTask < Rake::PackageTask

attr_accessor :gem_spec

##
# Indicates whether this package should be built as a content-addressable gem.
attr_accessor :content_addressable

##
# Create a Gem Package task library. Automatically define the gem if a
# block is given. If no block is supplied, then #define needs to be called
Expand All @@ -82,6 +86,7 @@ def initialize(gem_spec)
def init(gem)
super gem.full_name, :noversion
@gem_spec = gem
@content_addressable = false
@package_files += gem_spec.files if gem_spec.files
@fileutils_output = $stdout
end
Expand All @@ -91,33 +96,97 @@ def init(gem)
# (+define+ is automatically called if a block is given to +new+).

def define
Gem::ContentAddress.eligible!(gem_spec) if content_addressable

super

gem_file = File.basename gem_spec.cache_file
gem_path = File.join package_dir, gem_file
gem_dir = File.join package_dir, gem_spec.full_name
Comment thread
OughtPuts marked this conversation as resolved.
gem_dir = package_dir_path

if content_addressable
build_target = stamp_file
gem_description = "content-addressable gem #{gem_spec.name}-#{gem_spec.version} " \
"(Platform: #{gem_spec.platform}, Ruby ABI: #{content_addressable_ruby_abi})"

discard_stale_stamp_file
else
build_target = gem_path
gem_description = "gem file #{gem_file}"
end

task package: [:gem]

directory package_dir
directory gem_dir

desc "Build the gem file #{gem_file}"
task gem: [gem_path]
desc "Build the #{gem_description}"
task gem: [build_target]

trace = Rake.application.options.trace
Gem.configuration.verbose = trace

file gem_path => [package_dir, gem_dir] + @gem_spec.files do
file build_target => [package_dir, gem_dir] + @gem_spec.files do
previous_gem = recorded_gem_path if content_addressable
built_gem_path = nil

chdir(gem_dir) do
when_writing "Creating #{gem_spec.file_name}" do
built_gem_file = Gem::Package.build gem_spec
when_writing "Creating #{gem_description}" do
built_gem_file = Gem::Package.build gem_spec, content_addressable: content_addressable

verbose trace do
mv built_gem_file, ".."
end

if content_addressable
built_gem_path = File.join(package_dir, built_gem_file)
File.write File.join("..", File.basename(stamp_file)), built_gem_path
end
end
end

if previous_gem && built_gem_path && previous_gem != built_gem_path
verbose trace do
rm_f previous_gem
end
end
end
end

##
# The name of this package. Content-addressable builds append the Ruby ABI
# so per-ABI builds of the same +full_name+ do not share a staging
# directory.

def package_name
name = super
ruby_abi = content_addressable_ruby_abi
ruby_abi ? "#{name}-#{ruby_abi}" : name
end

private

def content_addressable_ruby_abi
return unless content_addressable

Gem::ContentAddress.ruby_abi_for(gem_spec.required_ruby_version)
end

def stamp_file
File.join package_dir, "#{package_name}.gem-built"
end

def recorded_gem_path
return unless File.file?(stamp_file)

path = File.read(stamp_file).strip
path unless path.empty?
end

def discard_stale_stamp_file
return unless File.file?(stamp_file)

path = recorded_gem_path
File.delete(stamp_file) unless path && File.file?(path)
end
end
1 change: 1 addition & 0 deletions spec/commands/clean_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ def should_not_have_gems(*gems)
end

it "does not remove gemspecs for content-addressed gems in the bundle", :compact_index, rubygems: ">= 4.1.0.a" do
skip "Gem::ContentAddress not available" if ruby_core?
skip "A prerelease Ruby does not satisfy the ~> X.Y.0 ABI pin of content-addressed gems" if Gem.ruby_version.prerelease?

simulate_platform "x86_64-linux" do
Expand Down
1 change: 1 addition & 0 deletions spec/install/gemfile/content_addressable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

RSpec.describe "bundle install with content-addressable gems", :compact_index, rubygems: ">= 4.1.0.a" do
before do
skip "Gem::ContentAddress not available" if ruby_core?
skip "A prerelease Ruby does not satisfy the ~> X.Y.0 ABI pin of content-addressed gems" if Gem.ruby_version.prerelease?
end

Expand Down
Loading
Loading