Skip to content

Commit 9cf1b8c

Browse files
authored
Refactor RubyVersion class (heroku#1601)
* Refactor RubyVersion args Prefer kwargs, remove open-ended options hash in favor of explicit kwarg. * Inline initialize logic * Remove non-public instance variables These values do not need to be persisted as they're not used after initialization * Remove unused variable * Rename ivar and remove pub, method already exposed * Refactor version increment logic * Rename ivar to match method * Remove unused ivar * Derive version_for_download * RubyVersion is now a simple store The bundle parsing logic feeds into the RubyVersion class instead of being tightly coupled * Use new interface * Update method The other method was removed, this is the new name * Add test, passes on main, fails here * Fix test, capture "pre" release version information * Support pre-release versions without numbers Convention says they have numbers, but it's not strictly required. * Remove shell helper include * Extract RubyVersion.default into a method * Capture when Ruby version is the default
1 parent 45dd287 commit 9cf1b8c

7 files changed

Lines changed: 162 additions & 87 deletions

File tree

lib/language_pack/helpers/outdated_ruby_version.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# Example:
66
#
7-
# ruby_version = LanguagePack::RubyVersion.new("ruby-2.2.5")
7+
# ruby_version = LanguagePack::RubyVersion.bundle_platform_ruby(bundler_output: "ruby-2.2.5")
88
# outdated = LanguagePack::Helpers::OutdatedRubyVersion.new(
99
# current_ruby_version: ruby_version,
1010
# fetcher: LanguagePack::Fetcher.new(LanguagePack::Base::VENDOR_URL, stack: "heroku-22")
@@ -175,7 +175,7 @@ def suggest_ruby_eol_version
175175
next if !@fetcher.exists?("#{version}.tgz")
176176

177177
check_eol_versions_minor(
178-
base_version: LanguagePack::RubyVersion.new(version)
178+
base_version: LanguagePack::RubyVersion.bundle_platform_ruby(bundler_output: version)
179179
)
180180

181181
version

lib/language_pack/installers/heroku_ruby_installer.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def install(ruby_version, install_dir)
2727
"ruby.major" => ruby_version.major,
2828
"ruby.minor" => ruby_version.minor,
2929
"ruby.patch" => ruby_version.patch,
30+
"ruby.default" => ruby_version.default?,
3031
)
3132
fetch_unpack(ruby_version, install_dir)
3233
setup_binstubs(install_dir)

lib/language_pack/ruby.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def slug_vendor_base
171171
# the relative path to the vendored ruby directory
172172
# @return [String] resulting path
173173
def slug_vendor_ruby
174-
"vendor/#{ruby_version.version_without_patchlevel}"
174+
"vendor/#{ruby_version.version_for_download}"
175175
end
176176

177177
# fetch the ruby version from bundler
@@ -182,9 +182,10 @@ def ruby_version
182182
last_version = nil
183183
last_version = @metadata.read(last_version_file).strip if @metadata.exists?(last_version_file)
184184

185-
@ruby_version = LanguagePack::RubyVersion.new(bundler.ruby_version,
186-
is_new: new_app?,
187-
last_version: last_version)
185+
@ruby_version = LanguagePack::RubyVersion.bundle_platform_ruby(
186+
bundler_output: bundler.ruby_version,
187+
last_version: last_version
188+
)
188189
return @ruby_version
189190
end
190191

@@ -470,7 +471,7 @@ def install_ruby(install_path)
470471
@metadata.write("buildpack_ruby_version", ruby_version.version_for_download)
471472

472473
topic "Using Ruby version: #{ruby_version.version_for_download}"
473-
if !ruby_version.set
474+
if ruby_version.default?
474475
warn(<<~WARNING)
475476
You have not declared a Ruby version in your Gemfile.
476477

lib/language_pack/ruby_version.rb

Lines changed: 54 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require "language_pack/shell_helpers"
1+
require "language_pack/shell_helpers" # Holds BuildpackError
22

33
module LanguagePack
44
class RubyVersion
@@ -21,51 +21,76 @@ def initialize(output = "")
2121
(?<engine>\w+){0}
2222
(?<engine_version>.+){0}
2323
24-
ruby-\g<ruby_version>(-\g<patchlevel>)?(-\g<engine>-\g<engine_version>)?
24+
ruby-\g<ruby_version>(-\g<patchlevel>)?(\.(?<pre>\S*))?(-\g<engine>-\g<engine_version>)?
2525
}x
2626

27-
28-
# `version` is the bundler output like `ruby-3.4.2`
29-
attr_reader :version,
30-
# `set` is either `:gemfile` when the app specified a version or `nil` when using
31-
# the default version
32-
:set,
33-
# `version_without_patchlevel` removes any `-p<number>` as they're not significant
34-
# effectively this is `version_for_download`
35-
:version_without_patchlevel,
36-
# `patchlevel` is the `-p<number>` or is empty
37-
:patchlevel,
27+
# String formatted `<major>.<minor>.<patch>` for Ruby and JRuby
28+
attr_reader :ruby_version,
3829
# `engine` is `:ruby` or `:jruby`
3930
:engine,
40-
# `ruby_version` is `<major>.<minor>.<patch>` extracted from `version`
41-
:ruby_version,
4231
# `engine_version` is the Jruby version or for MRI it is the same as `ruby_version`
4332
# i.e. `<major>.<minor>.<patch>`
4433
:engine_version
4534

46-
include LanguagePack::ShellHelpers
35+
def self.bundle_platform_ruby(bundler_output:, last_version: nil)
36+
default = bundler_output.empty?
37+
if default
38+
default(last_version: last_version)
39+
elsif md = RUBY_VERSION_REGEX.match(bundler_output)
40+
new(
41+
pre: md[:pre],
42+
engine: md[:engine]&.to_sym || :ruby,
43+
default: default,
44+
ruby_version: md[:ruby_version],
45+
engine_version: md[:engine_version] || md[:ruby_version],
46+
)
47+
else
48+
raise BadVersionError.new("'#{bundler_output}' is not valid") unless md
49+
end
50+
end
4751

48-
def initialize(bundler_output, app = {})
49-
@set = nil
50-
@bundler_output = bundler_output
51-
@app = app
52-
set_version
53-
parse_version
52+
def self.default(last_version: )
53+
ruby_version = last_version&.split("-")&.last || DEFAULT_VERSION_NUMBER
54+
new(
55+
pre: nil,
56+
engine: :ruby,
57+
default: true,
58+
ruby_version: ruby_version,
59+
engine_version: ruby_version,
60+
)
61+
end
5462

55-
@version_without_patchlevel = @version.sub(/-p-?\d+/, '')
63+
def initialize(
64+
pre:,
65+
engine:,
66+
default:,
67+
ruby_version:,
68+
engine_version:
69+
)
70+
@pre = pre
71+
@engine = engine
72+
@default = default
73+
@ruby_version = ruby_version
74+
@engine_version = engine_version
5675
end
5776

58-
# https://github.com/bundler/bundler/issues/4621
77+
# i.e. `ruby-3.4.2`
5978
def version_for_download
60-
version_without_patchlevel
79+
if @engine == :jruby
80+
"ruby-#{ruby_version}-jruby-#{engine_version}"
81+
elsif @pre
82+
"ruby-#{ruby_version}.#{@pre}"
83+
else
84+
"ruby-#{ruby_version}"
85+
end
6186
end
6287

6388
def file_name
6489
"#{version_for_download}.tgz"
6590
end
6691

6792
def default?
68-
!set
93+
@default
6994
end
7095

7196
# determine if we're using jruby
@@ -101,45 +126,15 @@ def patch
101126
# `ruby-2.3.1` then then `next_logical_version(1)`
102127
# will produce `ruby-2.3.2`.
103128
def next_logical_version(increment = 1)
104-
split_version = @version_without_patchlevel.split(".")
105-
teeny = split_version.pop
106-
split_version << teeny.to_i + increment
107-
split_version.join(".")
129+
"ruby-#{major}.#{minor}.#{patch + increment}"
108130
end
109131

110132
def next_minor_version(increment = 1)
111-
split_version = @version_without_patchlevel.split(".")
112-
split_version[1] = split_version[1].to_i + increment
113-
split_version[2] = 0
114-
split_version.join(".")
133+
"ruby-#{major}.#{minor + increment}.0"
115134
end
116135

117136
def next_major_version(increment = 1)
118-
split_version = @version_without_patchlevel.split("-").last.split(".")
119-
split_version[0] = Integer(split_version[0]) + increment
120-
split_version[1] = 0
121-
split_version[2] = 0
122-
return "ruby-#{split_version.join(".")}"
123-
end
124-
125-
private
126-
def set_version
127-
if @bundler_output.empty?
128-
@set = false
129-
@version = @app[:last_version] || DEFAULT_VERSION
130-
else
131-
@set = :gemfile
132-
@version = @bundler_output
133-
end
134-
end
135-
136-
def parse_version
137-
md = RUBY_VERSION_REGEX.match(version)
138-
raise BadVersionError.new("'#{version}' is not valid") unless md
139-
@ruby_version = md[:ruby_version]
140-
@patchlevel = md[:patchlevel]
141-
@engine_version = md[:engine_version] || @ruby_version
142-
@engine = (md[:engine] || :ruby).to_sym
137+
"ruby-#{major + increment}.0.0"
143138
end
144139
end
145140
end

spec/helpers/heroku_ruby_installer_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def installer(report: HerokuBuildReport::GLOBAL)
1111
end
1212

1313
def ruby_version
14-
LanguagePack::RubyVersion.new("ruby-3.1.7")
14+
LanguagePack::RubyVersion.bundle_platform_ruby(bundler_output: "ruby-3.1.7")
1515
end
1616

1717
describe "#fetch_unpack" do
@@ -58,7 +58,7 @@ def ruby_version
5858
arch: "arm64",
5959
report: report
6060
).install(
61-
LanguagePack::RubyVersion.new("ruby-3.1.4-p0-jruby-9.4.9.0"),
61+
LanguagePack::RubyVersion.bundle_platform_ruby(bundler_output: "ruby-3.1.4-p0-jruby-9.4.9.0"),
6262
"#{dir}/vendor/ruby"
6363
)
6464

spec/helpers/outdated_ruby_version_spec.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
}
88

99
it "handles amd ↗️ architecture on heroku-24" do
10-
ruby_version = LanguagePack::RubyVersion.new("ruby-3.1.0")
10+
ruby_version = LanguagePack::RubyVersion.bundle_platform_ruby(bundler_output: "ruby-3.1.0")
1111
fetcher = LanguagePack::Fetcher.new(
1212
LanguagePack::Base::VENDOR_URL,
1313
stack: "heroku-24",
@@ -23,7 +23,7 @@
2323
end
2424

2525
it "handles arm 💪 architecture on heroku-24" do
26-
ruby_version = LanguagePack::RubyVersion.new("ruby-3.1.0")
26+
ruby_version = LanguagePack::RubyVersion::bundle_platform_ruby(bundler_output: "ruby-3.1.0")
2727
fetcher = LanguagePack::Fetcher.new(
2828
LanguagePack::Base::VENDOR_URL,
2929
stack: "heroku-24",
@@ -39,7 +39,7 @@
3939
end
4040

4141
it "finds the latest version on a stack" do
42-
ruby_version = LanguagePack::RubyVersion.new("ruby-2.2.5")
42+
ruby_version = LanguagePack::RubyVersion::bundle_platform_ruby(bundler_output: "ruby-2.2.5")
4343
outdated = LanguagePack::Helpers::OutdatedRubyVersion.new(
4444
current_ruby_version: ruby_version,
4545
fetcher: fetcher
@@ -52,7 +52,7 @@
5252
end
5353

5454
it "detects returns original ruby version when using the latest" do
55-
ruby_version = LanguagePack::RubyVersion.new("ruby-2.2.10")
55+
ruby_version = LanguagePack::RubyVersion::bundle_platform_ruby(bundler_output: "ruby-2.2.10")
5656
outdated = LanguagePack::Helpers::OutdatedRubyVersion.new(
5757
current_ruby_version: ruby_version,
5858
fetcher: fetcher
@@ -64,8 +64,8 @@
6464
end
6565

6666
it "recommends a non EOL version of Ruby" do
67-
ruby_version_one = LanguagePack::RubyVersion.new("ruby-2.1.10")
68-
ruby_version_two = LanguagePack::RubyVersion.new("ruby-2.2.10")
67+
ruby_version_one = LanguagePack::RubyVersion::bundle_platform_ruby(bundler_output: "ruby-2.1.10")
68+
ruby_version_two = LanguagePack::RubyVersion::bundle_platform_ruby(bundler_output: "ruby-2.2.10")
6969

7070
outdated_one = LanguagePack::Helpers::OutdatedRubyVersion.new(
7171
current_ruby_version: ruby_version_one,
@@ -94,7 +94,7 @@
9494
end
9595

9696
it "does not recommend EOL for recent ruby version" do
97-
ruby_version = LanguagePack::RubyVersion.new("ruby-2.2.10")
97+
ruby_version = LanguagePack::RubyVersion::bundle_platform_ruby(bundler_output: "ruby-2.2.10")
9898

9999
outdated = LanguagePack::Helpers::OutdatedRubyVersion.new(
100100
current_ruby_version: ruby_version,
@@ -104,7 +104,7 @@
104104
outdated.call
105105

106106
good_version = outdated.suggest_ruby_eol_version.sub("x", "0")
107-
ruby_version = LanguagePack::RubyVersion.new("ruby-#{good_version}")
107+
ruby_version = LanguagePack::RubyVersion::bundle_platform_ruby(bundler_output: "ruby-#{good_version}")
108108

109109
outdated = LanguagePack::Helpers::OutdatedRubyVersion.new(
110110
current_ruby_version: ruby_version,
@@ -117,7 +117,7 @@
117117
end
118118

119119
it "can call eol? on the latest Ruby version" do
120-
ruby_version = LanguagePack::RubyVersion.new("ruby-2.6.0")
120+
ruby_version = LanguagePack::RubyVersion::bundle_platform_ruby(bundler_output: "bundler_output: ruby-2.6.0")
121121

122122
new_fetcher = fetcher.dup
123123
def new_fetcher.exists?(value); false; end

0 commit comments

Comments
 (0)