Skip to content

Commit 8c0d3cc

Browse files
OughtPutsjenshenny
authored andcommitted
Support content addressable gem builds / gem naming in PackageTask
Adds Gem::PackageTask#content_addressable. When true, the gem task builds a content addressable gem for the single Ruby ABI identified by gem_spec.required_ruby_version, passing content_addressable through to Gem::Package.build. A content addressable gem's file name contains a hash of its contents, so it isn't known until the gem is built and can't be a Rake target. In this mode the task uses a stamp file (pkg/<full_name>-<ruby abi>.gem-built) as its Rake target, writes the built gem's path into it, and rebuilds when the referenced gem no longer exists. The staging directory is suffixed with the Ruby ABI (pkg/<full_name>-<ruby abi>) so per-ABI builds of the same full_name don't share a directory. When content_addressable is false, behaviour is unchanged.
1 parent 1a2d80a commit 8c0d3cc

4 files changed

Lines changed: 472 additions & 38 deletions

File tree

‎lib/rubygems/content_address.rb‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,25 @@ def self.eligible?(spec, validate_ruby_abi: true)
9090
!ruby_abi_for(spec.required_ruby_version).nil?
9191
end
9292

93+
##
94+
# Raises ArgumentError unless +spec+ is eligible for content addressing
95+
# (see eligible?). The message explains which requirement is unmet so
96+
# gem authors know what to change in their gemspec.
97+
98+
def self.eligible!(spec)
99+
unless platform_eligible?(spec.platform)
100+
raise ArgumentError,
101+
"Cannot build a content-addressable gem as no platform or a Ruby platform has been set"
102+
end
103+
104+
return unless ruby_abi_for(spec.required_ruby_version).nil?
105+
106+
raise ArgumentError,
107+
"Cannot build a content-addressable gem because required_ruby_version is set to " \
108+
"#{spec.required_ruby_version}. Please set required_ruby_version to \"~> X.Y.0\" " \
109+
"so that it identifies a single Ruby ABI."
110+
end
111+
93112
##
94113
# Whether +spec+ is content-addressed: it is eligible for content
95114
# addressing and has a valid content address set. See eligible? for

‎lib/rubygems/package.rb‎

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ def build(skip_validation = false, strict_validation = false)
385385
# +required_rubygems_version+ is constrained before building.
386386

387387
def build_content_addressable_file(skip_validation = false, strict_validation = false)
388-
validate_content_addressable_spec
388+
Gem::ContentAddress.eligible!(@spec)
389389
ruby_abi = Gem::ContentAddress.ruby_abi_for(@spec.required_ruby_version)
390390
@spec.required_rubygems_version = normalized_required_rubygems_version(ruby_abi)
391391

@@ -788,23 +788,6 @@ def satisfies_rubygems_floor?(requirement, floor)
788788
end
789789
end
790790

791-
##
792-
# Validates that the spec can be built as a content-addressable gem: it
793-
# must declare a non-Ruby platform and its +required_ruby_version+ must
794-
# identify a single Ruby ABI.
795-
796-
def validate_content_addressable_spec
797-
return if Gem::ContentAddress.eligible?(@spec)
798-
799-
unless Gem::ContentAddress.platform_eligible?(@spec.platform)
800-
raise ArgumentError, "Cannot build a gem scoped to a single Ruby ABI as no platform or a Ruby platform has been set"
801-
end
802-
803-
raise ArgumentError,
804-
"Cannot build a content-addressable gem because required_ruby_version is set to #{@spec.required_ruby_version}. " \
805-
"Please set required_ruby_version to \"~> X.Y.0\" so that it identifies a single Ruby ABI."
806-
end
807-
808791
##
809792
# Returns the full path for installing +filename+ into +destination_dir+,
810793
# which must already be resolved with File.realpath by the caller.

‎lib/rubygems/package_task.rb‎

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ class Gem::PackageTask < Rake::PackageTask
6565

6666
attr_accessor :gem_spec
6767

68+
##
69+
# Indicates whether this package should be built as a content-addressable gem.
70+
attr_accessor :content_addressable
71+
6872
##
6973
# Create a Gem Package task library. Automatically define the gem if a
7074
# block is given. If no block is supplied, then #define needs to be called
@@ -82,6 +86,7 @@ def initialize(gem_spec)
8286
def init(gem)
8387
super gem.full_name, :noversion
8488
@gem_spec = gem
89+
@content_addressable = false
8590
@package_files += gem_spec.files if gem_spec.files
8691
@fileutils_output = $stdout
8792
end
@@ -91,33 +96,97 @@ def init(gem)
9196
# (+define+ is automatically called if a block is given to +new+).
9297

9398
def define
99+
Gem::ContentAddress.eligible!(gem_spec) if content_addressable
100+
94101
super
95102

96103
gem_file = File.basename gem_spec.cache_file
97104
gem_path = File.join package_dir, gem_file
98-
gem_dir = File.join package_dir, gem_spec.full_name
105+
gem_dir = package_dir_path
106+
107+
if content_addressable
108+
build_target = stamp_file
109+
gem_description = "content-addressable gem #{gem_spec.name}-#{gem_spec.version} " \
110+
"(Platform: #{gem_spec.platform}, Ruby ABI: #{content_addressable_ruby_abi})"
111+
112+
discard_stale_stamp_file
113+
else
114+
build_target = gem_path
115+
gem_description = "gem file #{gem_file}"
116+
end
99117

100118
task package: [:gem]
101119

102120
directory package_dir
103121
directory gem_dir
104122

105-
desc "Build the gem file #{gem_file}"
106-
task gem: [gem_path]
123+
desc "Build the #{gem_description}"
124+
task gem: [build_target]
107125

108126
trace = Rake.application.options.trace
109127
Gem.configuration.verbose = trace
110128

111-
file gem_path => [package_dir, gem_dir] + @gem_spec.files do
129+
file build_target => [package_dir, gem_dir] + @gem_spec.files do
130+
previous_gem = recorded_gem_path if content_addressable
131+
built_gem_path = nil
132+
112133
chdir(gem_dir) do
113-
when_writing "Creating #{gem_spec.file_name}" do
114-
built_gem_file = Gem::Package.build gem_spec
134+
when_writing "Creating #{gem_description}" do
135+
built_gem_file = Gem::Package.build gem_spec, false, false, nil, content_addressable
115136

116137
verbose trace do
117138
mv built_gem_file, ".."
118139
end
140+
141+
if content_addressable
142+
built_gem_path = File.join(package_dir, built_gem_file)
143+
File.write File.join("..", File.basename(stamp_file)), built_gem_path
144+
end
145+
end
146+
end
147+
148+
if previous_gem && built_gem_path && previous_gem != built_gem_path
149+
verbose trace do
150+
rm_f previous_gem
119151
end
120152
end
121153
end
122154
end
155+
156+
##
157+
# The name of this package. Content-addressable builds append the Ruby ABI
158+
# so per-ABI builds of the same +full_name+ do not share a staging
159+
# directory.
160+
161+
def package_name
162+
name = super
163+
ruby_abi = content_addressable_ruby_abi
164+
ruby_abi ? "#{name}-#{ruby_abi}" : name
165+
end
166+
167+
private
168+
169+
def content_addressable_ruby_abi
170+
return unless content_addressable
171+
172+
Gem::ContentAddress.ruby_abi_for(gem_spec.required_ruby_version)
173+
end
174+
175+
def stamp_file
176+
File.join package_dir, "#{package_name}.gem-built"
177+
end
178+
179+
def recorded_gem_path
180+
return unless File.file?(stamp_file)
181+
182+
path = File.read(stamp_file).strip
183+
path unless path.empty?
184+
end
185+
186+
def discard_stale_stamp_file
187+
return unless File.file?(stamp_file)
188+
189+
path = recorded_gem_path
190+
File.delete(stamp_file) unless path && File.file?(path)
191+
end
123192
end

0 commit comments

Comments
 (0)