Skip to content

Commit 1528499

Browse files
authored
Add heroku-26 stack support (heroku#1727)
* Default ruby_url to arch-aware path for future stacks Invert the if/else in download_ruby so heroku-22 (amd64-only) is the special case and all other stacks default to the arch-aware binary path ($stack/$arch/ruby-$version.tgz). This means future stacks automatically get multi-arch bootstrap ruby downloads without needing a code change here. Previously every new multi-arch stack had to be added to the if-check. Now only legacy amd64-only stacks need to be listed. This is a pure refactor with no behavior change. * Refactor MULTI_ARCH_STACKS to AMD_ONLY_STACKS Invert the Ruby-side multi-arch stack logic so that heroku-22 (amd64-only) is the special case and all other stacks default to arch-aware binary paths. This means future stacks automatically get multi-arch support without code changes. Previously, every new multi-arch stack had to be added to MULTI_ARCH_STACKS. Now only legacy amd64-only stacks need to be listed in AMD_ONLY_STACKS. Renamed across source and specs: - MULTI_ARCH_STACKS -> AMD_ONLY_STACKS (base.rb) - multi_arch_stacks: param -> amd_only_stacks: (download_presence.rb, heroku_ruby_installer.rb, ruby.rb) This is a pure refactor with no behavior change. * Add heroku-26 to supported stacks Add heroku-26 to the checks::ensure_supported_stack case statement in bash_functions.sh and add a unit test verifying it passes validation. * Add heroku-26 to download presence stacks Add heroku-26 to DownloadPresence::STACKS so the buildpack can check Ruby binary availability on heroku-26 and warn users upgrading stacks if their Ruby version is not available on the next stack. - download_presence.rb: Add heroku-26 to STACKS - download_presence_spec.rb: Update "files that do not exist" test to use heroku-24/heroku-26 pair (as the old test's comment requested), add test verifying next_stack from heroku-24 is heroku-26 * Add pending heroku-26 integration test Deploy Ruby 3.3.10 on heroku-26 using the default_ruby fixture. Marked pending until heroku-26 is available on the platform ``` $ heroku stack:set heroku-26 Setting stack to heroku-26... ! › Error: You can't switch to heroku-26. Available options: container, heroku-22, heroku-24. › › Error ID: unsupported ``` The job fails with 422 ``` Pending: (Failures listed here are expected and do not affect your suite's status) 1) Ruby on heroku-26 deploys Ruby 3.3.10 # heroku-26 not yet available on the platform Failure/Error: app.deploy do |app| expect(app.run("command -v ruby").strip).to eq("/app/bin/ruby") end Excon::Error::UnprocessableEntity: Expected([200, 201, 202, 204, 206, 304, 429]) <=> Actual(422 Unprocessable Entity) # /Users/rschneeman/.gem/ruby/3.3.9/gems/excon-1.4.0/lib/excon/middlewares/expects.rb:13:in `response_call' # /Users/rschneeman/.gem/ruby/3.3.9/gems/excon-1.4.0/lib/excon/middlewares/decompress.rb:35:in `response_call' ```
1 parent b8cbf7a commit 1528499

File tree

12 files changed

+73
-40
lines changed

12 files changed

+73
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [Unreleased]
44

5+
- Add heroku-26 stack support
56

67
## [v352] - 2026-03-16
78

bin/support/bash_functions.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ function checks::ensure_supported_stack() {
169169

170170
case "${stack}" in
171171
# When removing support from a stack, move it to the bottom of the list
172-
heroku-22 | heroku-24)
172+
heroku-22 | heroku-24 | heroku-26)
173173
return 0
174174
;;
175175
heroku-18 | heroku-20)

bin/support/download_ruby

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ ruby_url() {
2929
local stack=$1
3030
local version=$2
3131

32-
if [ "$stack" == "heroku-24" ]; then
32+
if [ "$stack" == "heroku-22" ]; then
33+
echo "${BUILDPACK_VENDOR_URL:-https://heroku-buildpack-ruby.s3.dualstack.us-east-1.amazonaws.com}/$stack/ruby-$version.tgz"
34+
else
3335
local arch
3436
arch=$(dpkg --print-architecture)
3537
echo "${BUILDPACK_VENDOR_URL:-https://heroku-buildpack-ruby.s3.dualstack.us-east-1.amazonaws.com}/$stack/$arch/ruby-$version.tgz"
36-
else
37-
echo "${BUILDPACK_VENDOR_URL:-https://heroku-buildpack-ruby.s3.dualstack.us-east-1.amazonaws.com}/$stack/ruby-$version.tgz"
3838
fi
3939
}
4040

lib/language_pack/base.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class LanguagePack::Base
1717

1818
VENDOR_URL = ENV["BUILDPACK_VENDOR_URL"] || "https://heroku-buildpack-ruby.s3.dualstack.us-east-1.amazonaws.com"
1919
ROOT_DIR = File.expand_path("../../..", __FILE__)
20-
MULTI_ARCH_STACKS = ["heroku-24"]
20+
AMD_ONLY_STACKS = ["heroku-22"]
2121
KNOWN_ARCHITECTURES = ["amd64", "arm64"]
2222

2323
attr_reader :app_path, :bundler, :cache, :environment_name, :stack

lib/language_pack/helpers/download_presence.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,27 @@
88
#
99
# download = LanguagePack::Helpers::DownloadPresence.new(
1010
# 'ruby-3.1.7.tgz',
11-
# stacks: ['heroku-22', 'heroku-24']
11+
# stacks: ['heroku-22', 'heroku-24', 'heroku-26']
1212
# )
1313
#
1414
# download.call
1515
#
1616
# puts download.exists? #=> true
17-
# puts download.valid_stack_list #=> ['heroku-22', 'heroku-24']
17+
# puts download.valid_stack_list #=> ['heroku-22', 'heroku-24', 'heroku-26']
1818
class LanguagePack::Helpers::DownloadPresence
19-
# heroku-22 and heroku-24 have identical ruby versions supported
20-
STACKS = ["heroku-22", "heroku-24"]
19+
# heroku-22, heroku-24, and heroku-26 have identical ruby versions supported
20+
STACKS = ["heroku-22", "heroku-24", "heroku-26"]
2121

22-
def initialize(file_name:, arch:, multi_arch_stacks:, stacks: STACKS)
22+
def initialize(file_name:, arch:, amd_only_stacks:, stacks: STACKS)
2323
@file_name = file_name
2424
@stacks = stacks
2525
@fetchers = []
2626
@threads = []
2727
@stacks.each do |stack|
28-
@fetchers << if multi_arch_stacks.include?(stack)
29-
LanguagePack::Fetcher.new(LanguagePack::Base::VENDOR_URL, stack: stack, arch: arch)
30-
else
28+
@fetchers << if amd_only_stacks.include?(stack)
3129
LanguagePack::Fetcher.new(LanguagePack::Base::VENDOR_URL, stack: stack)
30+
else
31+
LanguagePack::Fetcher.new(LanguagePack::Base::VENDOR_URL, stack: stack, arch: arch)
3232
end
3333
end
3434
end

lib/language_pack/installers/heroku_ruby_installer.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ class LanguagePack::Installers::HerokuRubyInstaller
1010

1111
attr_reader :fetcher, :environment
1212

13-
def initialize(stack:, multi_arch_stacks:, arch:, app_path:, env:, report: HerokuBuildReport::GLOBAL)
13+
def initialize(stack:, amd_only_stacks:, arch:, app_path:, env:, report: HerokuBuildReport::GLOBAL)
1414
@report = report
1515
@environment = env
1616
@app_path = Pathname.new(app_path).expand_path
17-
@fetcher = self.class.fetcher(multi_arch_stacks: multi_arch_stacks, stack: stack, arch: arch)
17+
@fetcher = self.class.fetcher(amd_only_stacks: amd_only_stacks, stack: stack, arch: arch)
1818
end
1919

20-
def self.fetcher(multi_arch_stacks:, stack:, arch:)
21-
if multi_arch_stacks.include?(stack)
22-
LanguagePack::Fetcher.new(BASE_URL, stack: stack, arch: arch)
23-
else
20+
def self.fetcher(amd_only_stacks:, stack:, arch:)
21+
if amd_only_stacks.include?(stack)
2422
LanguagePack::Fetcher.new(BASE_URL, stack: stack)
23+
else
24+
LanguagePack::Fetcher.new(BASE_URL, stack: stack, arch: arch)
2525
end
2626
end
2727

lib/language_pack/ruby.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def best_practice_warnings
7979
def compile
8080
@outdated_version_check = LanguagePack::Helpers::OutdatedRubyVersion.new(
8181
current_ruby_version: ruby_version,
82-
fetcher: LanguagePack::Installers::HerokuRubyInstaller.fetcher(multi_arch_stacks: MULTI_ARCH_STACKS, stack: stack, arch: @arch)
82+
fetcher: LanguagePack::Installers::HerokuRubyInstaller.fetcher(amd_only_stacks: AMD_ONLY_STACKS, stack: stack, arch: @arch)
8383
).call
8484

8585
@warn_io.warnings.each { |warning| warnings << warning }
@@ -477,15 +477,15 @@ def self.install_ruby(app_path:, ruby_version:, stack:, arch:, metadata:, io:)
477477
return false unless ruby_version
478478

479479
installer = LanguagePack::Installers::HerokuRubyInstaller.new(
480-
multi_arch_stacks: MULTI_ARCH_STACKS,
480+
amd_only_stacks: AMD_ONLY_STACKS,
481481
stack: stack,
482482
arch: arch,
483483
app_path: app_path,
484484
env: ENV
485485
)
486486

487487
@ruby_download_check = LanguagePack::Helpers::DownloadPresence.new(
488-
multi_arch_stacks: MULTI_ARCH_STACKS,
488+
amd_only_stacks: AMD_ONLY_STACKS,
489489
file_name: ruby_version.file_name,
490490
arch: arch
491491
)

lib/language_pack/test/ruby.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class LanguagePack::Ruby
77
def compile
88
@outdated_version_check = LanguagePack::Helpers::OutdatedRubyVersion.new(
99
current_ruby_version: ruby_version,
10-
fetcher: LanguagePack::Installers::HerokuRubyInstaller.fetcher(multi_arch_stacks: MULTI_ARCH_STACKS, stack: stack, arch: @arch)
10+
fetcher: LanguagePack::Installers::HerokuRubyInstaller.fetcher(amd_only_stacks: AMD_ONLY_STACKS, stack: stack, arch: @arch)
1111
).call
1212
@warn_io.warnings.each { |warning| warnings << warning }
1313
post_bundler(ruby_version: @ruby_version, app_path: app_path)

spec/hatchet/rubies_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@
4848
end
4949
end
5050

51+
describe "Ruby on heroku-26" do
52+
it "deploys Ruby 3.3.10" do
53+
pending("heroku-26 not yet available on the platform")
54+
Hatchet::Runner.new("default_ruby", stack: "heroku-26").tap do |app|
55+
app.before_deploy do
56+
set_ruby_version(version: "3.3.10")
57+
end
58+
59+
app.deploy do |app|
60+
expect(app.run("command -v ruby").strip).to eq("/app/bin/ruby")
61+
end
62+
end
63+
end
64+
end
65+
5166
describe "Upgrading ruby apps" do
5267
it "works when changing versions" do
5368
version = "3.3.1"

spec/helpers/download_presence_spec.rb

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
describe LanguagePack::Helpers::DownloadPresence do
44
it "handles multi-arch transitions for files that exist" do
55
download = LanguagePack::Helpers::DownloadPresence.new(
6-
multi_arch_stacks: ["heroku-24"],
6+
amd_only_stacks: ["heroku-22"],
77
file_name: "ruby-3.1.4.tgz",
88
stacks: ["heroku-22", "heroku-24"],
99
arch: "amd64"
@@ -19,26 +19,23 @@
1919

2020
it "handles multi-arch transitions for files that do not exist" do
2121
download = LanguagePack::Helpers::DownloadPresence.new(
22-
multi_arch_stacks: ["heroku-24"],
22+
amd_only_stacks: [],
2323
file_name: "ruby-3.0.5.tgz",
24-
# Heroku 20 is not longer supported however heroku-22 and heroku-24
25-
# have identical Ruby versions supported, this test can be updated to
26-
# use heroku-24 and heroku-26 (when that stack is released)
27-
stacks: ["heroku-20", "heroku-24"],
24+
stacks: ["heroku-24", "heroku-26"],
2825
arch: "amd64"
2926
)
3027

3128
download.call
3229

33-
expect(download.next_stack(current_stack: "heroku-20")).to eq("heroku-24")
34-
expect(download.next_stack(current_stack: "heroku-24")).to be_falsey
30+
expect(download.next_stack(current_stack: "heroku-24")).to eq("heroku-26")
31+
expect(download.next_stack(current_stack: "heroku-26")).to be_falsey
3532

36-
expect(download.exists_on_next_stack?(current_stack: "heroku-20")).to be_falsey
33+
expect(download.exists_on_next_stack?(current_stack: "heroku-24")).to be_falsey
3734
end
3835

3936
it "knows if exists on the next stack" do
4037
download = LanguagePack::Helpers::DownloadPresence.new(
41-
multi_arch_stacks: ["heroku-24"],
38+
amd_only_stacks: ["heroku-22"],
4239
file_name: "#{LanguagePack::RubyVersion::DEFAULT_VERSION}.tgz",
4340
stacks: ["heroku-22", "heroku-24"],
4441
arch: "amd64"
@@ -54,7 +51,7 @@
5451

5552
it "detects when a package is present on two stacks but not a third" do
5653
download = LanguagePack::Helpers::DownloadPresence.new(
57-
multi_arch_stacks: [],
54+
amd_only_stacks: ["cedar-14", "heroku-16", "heroku-18"],
5855
file_name: "ruby-2.3.0.tgz",
5956
stacks: ["cedar-14", "heroku-16", "heroku-18"],
6057
arch: nil
@@ -68,7 +65,7 @@
6865

6966
it "detects when a package does not exist" do
7067
download = LanguagePack::Helpers::DownloadPresence.new(
71-
multi_arch_stacks: [],
68+
amd_only_stacks: ["heroku-22"],
7269
file_name: "does-not-exist.tgz",
7370
stacks: ["heroku-22", "heroku-24"],
7471
arch: nil
@@ -82,20 +79,32 @@
8279

8380
it "detects default ruby version" do
8481
download = LanguagePack::Helpers::DownloadPresence.new(
85-
multi_arch_stacks: ["heroku-24"],
82+
amd_only_stacks: LanguagePack::Base::AMD_ONLY_STACKS,
8683
file_name: "#{LanguagePack::RubyVersion::DEFAULT_VERSION}.tgz",
8784
arch: "amd64"
8885
)
8986

9087
download.call
9188

9289
expect(download.exists?).to eq(true)
93-
expect(download.valid_stack_list).to include(LanguagePack::Helpers::DownloadPresence::STACKS.last)
90+
expect(download.valid_stack_list).to include("heroku-24")
91+
end
92+
93+
it "next stack for ruby 3.0.10 from heroku-24 is heroku-26" do
94+
download = LanguagePack::Helpers::DownloadPresence.new(
95+
amd_only_stacks: LanguagePack::Base::AMD_ONLY_STACKS,
96+
file_name: "ruby-3.0.10.tgz",
97+
arch: "amd64"
98+
)
99+
100+
download.call
101+
102+
expect(download.next_stack(current_stack: "heroku-24")).to eq("heroku-26")
94103
end
95104

96105
it "handles the current stack not being in the known stacks list" do
97106
download = LanguagePack::Helpers::DownloadPresence.new(
98-
multi_arch_stacks: [],
107+
amd_only_stacks: LanguagePack::Base::AMD_ONLY_STACKS,
99108
file_name: "#{LanguagePack::RubyVersion::DEFAULT_VERSION}.tgz",
100109
arch: nil
101110
)

0 commit comments

Comments
 (0)