From aa74c1479c7ba82370eb1b8d859a3cb233b841f0 Mon Sep 17 00:00:00 2001 From: Schneems Date: Thu, 16 May 2024 12:02:56 -0500 Subject: [PATCH 1/4] Support Heroku-24 and ARM architecture Heroku 24 stack image supports both arm64 and amd64. The Ruby binaries are available on heroku-24 compiled to these two stacks https://github.com/heroku/docker-heroku-ruby-builder/pull/38. The buildpack adds support by: - Updating the `buildpackl.toml` to include ARM architecture - Using architecture information to build the URL for Ubuntu 24.04 (heroku-24) - Integration testing on all supported Ubuntu versions --- Cargo.lock | 4 ++-- buildpacks/ruby/CHANGELOG.md | 10 +++++++- buildpacks/ruby/buildpack.toml | 12 ++++++++++ .../ruby/src/layers/ruby_install_layer.rs | 15 ++++++++---- buildpacks/ruby/src/target_id.rs | 23 ++++++++++++------ buildpacks/ruby/tests/integration_test.rs | 24 ++++++++++++++++++- 6 files changed, 73 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23c51f5e..01af3817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1036,9 +1036,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.61" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", diff --git a/buildpacks/ruby/CHANGELOG.md b/buildpacks/ruby/CHANGELOG.md index f3fc2868..bc90ffc8 100644 --- a/buildpacks/ruby/CHANGELOG.md +++ b/buildpacks/ruby/CHANGELOG.md @@ -7,7 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- The buildpack now implements Buildpack API 0.10 instead of 0.9, and so requires `lifecycle` 0.17.x or newer. ([#283](https://github.com/heroku/buildpacks-ruby/pull/283/files#commit-suggestions)) +### Changed + +- The buildpack now implements Buildpack API 0.10 instead of 0.9, and so requires `lifecycle` 0.17.x or newer. ([#283](https://github.com/heroku/buildpacks-ruby/pull/283)) + +### Added + +- Added support for Ubuntu 24.04 (and thus Heroku-24 / `heroku/builder:24`). ([#284](https://github.com/heroku/buildpacks-ruby/pull/284)) + +### Fixed ## [2.1.3] - 2024-03-18 diff --git a/buildpacks/ruby/buildpack.toml b/buildpacks/ruby/buildpack.toml index 502b2fc3..254da129 100644 --- a/buildpacks/ruby/buildpack.toml +++ b/buildpacks/ruby/buildpack.toml @@ -28,5 +28,17 @@ version = "20.04" name = "ubuntu" version = "22.04" +[[targets.distros]] +name = "ubuntu" +version = "24.04" + +[[targets]] +os = "linux" +arch = "arm64" + +[[targets.distros]] +name = "ubuntu" +version = "24.04" + [metadata.release] image = { repository = "docker.io/heroku/buildpack-ruby" } diff --git a/buildpacks/ruby/src/layers/ruby_install_layer.rs b/buildpacks/ruby/src/layers/ruby_install_layer.rs index cd6f90cd..614f9a86 100644 --- a/buildpacks/ruby/src/layers/ruby_install_layer.rs +++ b/buildpacks/ruby/src/layers/ruby_install_layer.rs @@ -236,11 +236,18 @@ fn download_url( let filename = format!("ruby-{version}.tgz"); let base = "https://heroku-buildpack-ruby.s3.us-east-1.amazonaws.com"; let mut url = Url::parse(base).map_err(RubyInstallError::UrlParseError)?; + { + let mut segments = url + .path_segments_mut() + .map_err(|()| RubyInstallError::InvalidBaseUrl(String::from(base)))?; + + segments.push(&target.stack_name().map_err(RubyInstallError::TargetError)?); + if target.is_arch_aware() { + segments.push(&target.cpu_architecture); + } + segments.push(&filename); + } - url.path_segments_mut() - .map_err(|()| RubyInstallError::InvalidBaseUrl(String::from(base)))? - .push(&target.stack_name().map_err(RubyInstallError::TargetError)?) - .push(&filename); Ok(url) } diff --git a/buildpacks/ruby/src/target_id.rs b/buildpacks/ruby/src/target_id.rs index cad39764..42c07387 100644 --- a/buildpacks/ruby/src/target_id.rs +++ b/buildpacks/ruby/src/target_id.rs @@ -1,28 +1,30 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] -#[serde(deny_unknown_fields)] +#[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct TargetId { pub(crate) distro_name: String, pub(crate) distro_version: String, pub(crate) cpu_architecture: String, } - +const ARCH_AWARE_VERSIONS: &[&str] = &["24.04"]; const DISTRO_VERSION_STACK: &[(&str, &str, &str)] = &[ ("ubuntu", "20.04", "heroku-20"), ("ubuntu", "22.04", "heroku-22"), + ("ubuntu", "24.04", "heroku-24"), ]; #[derive(Debug, thiserror::Error)] pub(crate) enum TargetIdError { - #[error("Distro name and version {0}-{1} is not supported. Must be one of: {}", DISTRO_VERSION_STACK.iter().map(|&(name, version, _)| format!("{name}-{version}")).collect::>().join(", "))] + #[error("Distro name and version '{0}-{1}' is not supported. Must be one of: {}", DISTRO_VERSION_STACK.iter().map(|&(name, version, _)| format!("'{name}-{version}'")).collect::>().join(", "))] UnknownDistroNameVersionCombo(String, String), - #[error("Cannot convert stack name {0} into a target OS. Must be one of: {}", DISTRO_VERSION_STACK.iter().map(|&(_, _, stack)| String::from(stack)).collect::>().join(", "))] + #[error("Cannot convert stack name '{0}' into a target OS. Must be one of: {}", DISTRO_VERSION_STACK.iter().map(|&(_, _, stack)| format!("'{stack}'")).collect::>().join(", "))] UnknownStack(String), } impl TargetId { + pub(crate) fn is_arch_aware(&self) -> bool { + ARCH_AWARE_VERSIONS.contains(&self.distro_version.as_str()) + } + pub(crate) fn stack_name(&self) -> Result { DISTRO_VERSION_STACK .iter() @@ -53,6 +55,13 @@ impl TargetId { mod test { use super::*; + #[test] + fn test_arch_aware_versions_are_also_known_as_a_stack() { + for version in ARCH_AWARE_VERSIONS { + assert!(DISTRO_VERSION_STACK.iter().any(|&(_, v, _)| &v == version)); + } + } + #[test] fn test_stack_name() { assert_eq!( diff --git a/buildpacks/ruby/tests/integration_test.rs b/buildpacks/ruby/tests/integration_test.rs index b6e0c4f5..abb3a814 100644 --- a/buildpacks/ruby/tests/integration_test.rs +++ b/buildpacks/ruby/tests/integration_test.rs @@ -16,6 +16,11 @@ use ureq::Response; #[test] #[ignore = "integration test"] fn test_migrating_metadata() { + // This test is a placeholder for when a change modifies metadata structures. + // Remove the return and update the `buildpack-ruby` reference to the latest version. + #![allow(unreachable_code)] + return; + let builder = "heroku/builder:22"; let app_dir = "tests/fixtures/default_ruby"; @@ -57,9 +62,26 @@ fn test_default_app_ubuntu20() { #[test] #[ignore = "integration test"] -fn test_default_app_latest_distro() { +fn test_default_app_ubuntu22() { TestRunner::default().build( BuildConfig::new("heroku/builder:22", "tests/fixtures/default_ruby"), + |context| { + println!("{}", context.pack_stdout); + assert_contains!(context.pack_stdout, "# Heroku Ruby Buildpack"); + assert_contains!( + context.pack_stdout, + r#"`BUNDLE_BIN="/layers/heroku_ruby/gems/bin" BUNDLE_CLEAN="1" BUNDLE_DEPLOYMENT="1" BUNDLE_GEMFILE="/workspace/Gemfile" BUNDLE_PATH="/layers/heroku_ruby/gems" BUNDLE_WITHOUT="development:test" bundle install`"#); + + assert_contains!(context.pack_stdout, "Installing webrick"); + }, + ); +} + +#[test] +#[ignore = "integration test"] +fn test_default_app_latest_distro() { + TestRunner::default().build( + BuildConfig::new("heroku/builder:24", "tests/fixtures/default_ruby"), |context| { println!("{}", context.pack_stdout); assert_contains!(context.pack_stdout, "# Heroku Ruby Buildpack"); From a392d2080926bdbaee10be2687fd404d64009a84 Mon Sep 17 00:00:00 2001 From: Schneems Date: Fri, 17 May 2024 12:16:11 -0500 Subject: [PATCH 2/4] Fix multi arch test compilation From: https://github.com/heroku/buildpacks-ruby/pull/284/files#r1603946649. The tests currently fail locally on a Mac (arm64/aarch64) because tests are always being compiled for amd64/x86. This change is a workaround to force compilation to the current target architecture. --- buildpacks/ruby/tests/integration_test.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/buildpacks/ruby/tests/integration_test.rs b/buildpacks/ruby/tests/integration_test.rs index abb3a814..8a225626 100644 --- a/buildpacks/ruby/tests/integration_test.rs +++ b/buildpacks/ruby/tests/integration_test.rs @@ -80,8 +80,10 @@ fn test_default_app_ubuntu22() { #[test] #[ignore = "integration test"] fn test_default_app_latest_distro() { + let config = amd_arm_builder_config("heroku/builder:24", "tests/fixtures/default_ruby"); + TestRunner::default().build( - BuildConfig::new("heroku/builder:24", "tests/fixtures/default_ruby"), + config, |context| { println!("{}", context.pack_stdout); assert_contains!(context.pack_stdout, "# Heroku Ruby Buildpack"); @@ -279,3 +281,17 @@ fn frac_seconds(seconds: f64) -> Duration { } const TEST_PORT: u16 = 1234; + +// TODO: Once Pack build supports `--platform` and libcnb-test adjusted accordingly, change this +// to allow configuring the target arch independently of the builder name (eg via env var). +fn amd_arm_builder_config(builder_name: &str, app_dir: &str) -> BuildConfig { + let mut builder = BuildConfig::new(builder_name, app_dir); + + match builder_name { + "heroku/builder:24" if cfg!(target_arch = "aarch64") => { + builder.target_triple("aarch64-unknown-linux-musl") + } + _ => builder.target_triple("x86_64-unknown-linux-musl"), + }; + builder +} From 2d874fbb0d9aa9dbf063add0b1dc4da9c4cad26f Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Fri, 17 May 2024 14:30:02 -0500 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Ed Morley <501702+edmorley@users.noreply.github.com> --- buildpacks/ruby/CHANGELOG.md | 2 -- buildpacks/ruby/tests/integration_test.rs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/buildpacks/ruby/CHANGELOG.md b/buildpacks/ruby/CHANGELOG.md index bc90ffc8..b26e80f3 100644 --- a/buildpacks/ruby/CHANGELOG.md +++ b/buildpacks/ruby/CHANGELOG.md @@ -15,8 +15,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for Ubuntu 24.04 (and thus Heroku-24 / `heroku/builder:24`). ([#284](https://github.com/heroku/buildpacks-ruby/pull/284)) -### Fixed - ## [2.1.3] - 2024-03-18 ### Changed diff --git a/buildpacks/ruby/tests/integration_test.rs b/buildpacks/ruby/tests/integration_test.rs index 8a225626..de00158e 100644 --- a/buildpacks/ruby/tests/integration_test.rs +++ b/buildpacks/ruby/tests/integration_test.rs @@ -285,7 +285,7 @@ const TEST_PORT: u16 = 1234; // TODO: Once Pack build supports `--platform` and libcnb-test adjusted accordingly, change this // to allow configuring the target arch independently of the builder name (eg via env var). fn amd_arm_builder_config(builder_name: &str, app_dir: &str) -> BuildConfig { - let mut builder = BuildConfig::new(builder_name, app_dir); + let mut config = BuildConfig::new(builder_name, app_dir); match builder_name { "heroku/builder:24" if cfg!(target_arch = "aarch64") => { @@ -293,5 +293,5 @@ fn amd_arm_builder_config(builder_name: &str, app_dir: &str) -> BuildConfig { } _ => builder.target_triple("x86_64-unknown-linux-musl"), }; - builder + config } From d748cb479c3ef2f9290815bbc88d3c60b28489a5 Mon Sep 17 00:00:00 2001 From: Schneems Date: Fri, 17 May 2024 14:33:59 -0500 Subject: [PATCH 4/4] Fix variable name --- buildpacks/ruby/tests/integration_test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildpacks/ruby/tests/integration_test.rs b/buildpacks/ruby/tests/integration_test.rs index de00158e..ff17b57f 100644 --- a/buildpacks/ruby/tests/integration_test.rs +++ b/buildpacks/ruby/tests/integration_test.rs @@ -289,9 +289,9 @@ fn amd_arm_builder_config(builder_name: &str, app_dir: &str) -> BuildConfig { match builder_name { "heroku/builder:24" if cfg!(target_arch = "aarch64") => { - builder.target_triple("aarch64-unknown-linux-musl") + config.target_triple("aarch64-unknown-linux-musl") } - _ => builder.target_triple("x86_64-unknown-linux-musl"), + _ => config.target_triple("x86_64-unknown-linux-musl"), }; config }