From d5b002cb3748c9350c4a121eec1130aace289689 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 15:32:39 +0000 Subject: [PATCH 01/14] Bump the libcnb group across 1 directory with 3 updates Bumps the libcnb group with 3 updates in the / directory: [libcnb](https://github.com/heroku/libcnb.rs), [libherokubuildpack](https://github.com/heroku/libcnb.rs) and [libcnb-test](https://github.com/heroku/libcnb.rs). Updates `libcnb` from 0.17.0 to 0.19.0 - [Release notes](https://github.com/heroku/libcnb.rs/releases) - [Changelog](https://github.com/heroku/libcnb.rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/heroku/libcnb.rs/compare/v0.17.0...v0.19.0) Updates `libherokubuildpack` from 0.17.0 to 0.21.0 - [Release notes](https://github.com/heroku/libcnb.rs/releases) - [Changelog](https://github.com/heroku/libcnb.rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/heroku/libcnb.rs/compare/v0.17.0...v0.21.0) Updates `libcnb-test` from 0.17.0 to 0.19.0 - [Release notes](https://github.com/heroku/libcnb.rs/releases) - [Changelog](https://github.com/heroku/libcnb.rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/heroku/libcnb.rs/compare/v0.17.0...v0.19.0) --- updated-dependencies: - dependency-name: libcnb dependency-type: direct:production update-type: version-update:semver-minor dependency-group: libcnb - dependency-name: libherokubuildpack dependency-type: direct:production update-type: version-update:semver-minor dependency-group: libcnb - dependency-name: libcnb-test dependency-type: direct:production update-type: version-update:semver-minor dependency-group: libcnb ... Signed-off-by: dependabot[bot] --- Cargo.lock | 152 ++++++++++--------------------------- buildpacks/ruby/Cargo.toml | 6 +- commons/Cargo.toml | 6 +- 3 files changed, 47 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8daca83b..91386dfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,7 +190,7 @@ dependencies = [ "ascii_table", "byte-unit", "const_format", - "fancy-regex 0.13.0", + "fancy-regex", "filetime", "fs-err", "fs_extra", @@ -319,17 +319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "fancy-regex" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7493d4c459da9f84325ad297371a6b2b8a162800873a22e3b6b6512e61d18c05" -dependencies = [ - "bit-set", - "regex", + "windows-sys", ] [[package]] @@ -358,7 +348,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -502,7 +492,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -585,9 +575,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libcnb" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c385c618fa8afebe2d1b499b74bc0a3682507b0d91aa4aad09708b81681e2ca" +checksum = "7db217651ab45597152c94ad849defb079fc7ced7d72de2fcc2e9c3dec6e990e" dependencies = [ "libcnb-common", "libcnb-data", @@ -599,9 +589,9 @@ dependencies = [ [[package]] name = "libcnb-common" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28fede7cd4353004ff1976ce66c34bb266fa35095be12c6d3d4c2358ef790778" +checksum = "f3abf2056162dd76ade12884e002ba88f068a26594b2eb9579ef8af40cfbca1b" dependencies = [ "serde", "thiserror", @@ -610,11 +600,11 @@ dependencies = [ [[package]] name = "libcnb-data" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c0c825002ee57279d0c9e23309863804536f0c45687436d574dd3e8c7420fb" +checksum = "afc6b01af8b624193ca6b247667ef82f36dd85d62b90f5a7e8d047b46642ce7c" dependencies = [ - "fancy-regex 0.12.0", + "fancy-regex", "libcnb-proc-macros", "serde", "thiserror", @@ -624,12 +614,13 @@ dependencies = [ [[package]] name = "libcnb-package" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934ec4398991f7e926889a6e5046d83935e39de5c047feb591ed0333b83abf75" +checksum = "2678c2e0882c622a01d415e64625258849e533aeba8531110a5b3db9593d97d5" dependencies = [ "cargo_metadata", "ignore", + "indoc", "libcnb-common", "libcnb-data", "petgraph", @@ -640,21 +631,21 @@ dependencies = [ [[package]] name = "libcnb-proc-macros" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f0afde3a7327936afd743e2cb52f6de3a0d4a4894f6f13bdae1a41e6879c17" +checksum = "b0308e3b554dd8b0b969ab42d19b50b02bdb712dc72652849fa1e33bd1d16709" dependencies = [ "cargo_metadata", - "fancy-regex 0.12.0", + "fancy-regex", "quote", "syn", ] [[package]] name = "libcnb-test" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2471f098af746db385e0e254dd423de21db3347ea26cfd4c758a37cccaa1674a" +checksum = "f094d9c229c481fb868d36231dcc4b7596491503d0a297eb239b08e942eb483c" dependencies = [ "fastrand", "fs_extra", @@ -667,9 +658,9 @@ dependencies = [ [[package]] name = "libherokubuildpack" -version = "0.17.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e800ca80376b707d57d55ea95f48c88d2621864a0250cc41f54eab8e9481887" +checksum = "146f61983fd384cb5ab5373acdd8f53fcb4b27ecb200435a6bfb6a70b421bc9d" dependencies = [ "crossbeam-utils", "sha2", @@ -893,7 +884,7 @@ dependencies = [ "libc", "spin", "untrusted", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -906,7 +897,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1063,7 +1054,7 @@ dependencies = [ "cfg-if", "fastrand", "rustix", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1256,15 +1247,14 @@ dependencies = [ [[package]] name = "which" -version = "5.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "once_cell", "rustix", - "windows-sys 0.48.0", + "winsafe", ] [[package]] @@ -1311,37 +1301,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -1350,93 +1316,51 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.4" @@ -1452,6 +1376,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "yansi" version = "0.5.1" diff --git a/buildpacks/ruby/Cargo.toml b/buildpacks/ruby/Cargo.toml index d19c17f5..be30cb6e 100644 --- a/buildpacks/ruby/Cargo.toml +++ b/buildpacks/ruby/Cargo.toml @@ -16,8 +16,8 @@ glob = "0.3" indoc = "2" # libcnb has a much bigger impact on buildpack behaviour than any other dependencies, # so it's pinned to an exact version to isolate it from lockfile refreshes. -libcnb = "=0.17.0" -libherokubuildpack = { version = "=0.17.0", default-features = false, features = ["digest"] } +libcnb = "=0.19.0" +libherokubuildpack = { version = "=0.21.0", default-features = false, features = ["digest"] } rand = "0.8" # TODO: Consolidate on either the regex crate or the fancy-regex crate, since this repo currently uses both. regex = "1" @@ -29,5 +29,5 @@ ureq = { version = "2", default-features = false, features = ["tls"] } url = "2" [dev-dependencies] -libcnb-test = "=0.17.0" +libcnb-test = "=0.19.0" toml = "0.8" diff --git a/commons/Cargo.toml b/commons/Cargo.toml index 4a655fd3..ccc1a150 100644 --- a/commons/Cargo.toml +++ b/commons/Cargo.toml @@ -24,8 +24,8 @@ indoc = "2" lazy_static = "1" # libcnb has a much bigger impact on buildpack behaviour than any other dependencies, # so it's pinned to an exact version to isolate it from lockfile refreshes. -libcnb = "=0.17.0" -libherokubuildpack = { version = "=0.17.0", default-features = false, features = ["command"] } +libcnb = "=0.19.0" +libherokubuildpack = { version = "=0.21.0", default-features = false, features = ["command"] } regex = "1" serde = "1" sha2 = "0.10" @@ -36,6 +36,6 @@ walkdir = "2" [dev-dependencies] filetime = "0.2" indoc = "2" -libcnb-test = "=0.17.0" +libcnb-test = "=0.19.0" pretty_assertions = "1" toml = "0.8" From 85b661f6b0edadeda58a0ebbeaa5916673345944 Mon Sep 17 00:00:00 2001 From: Schneems Date: Thu, 9 May 2024 13:02:31 -0400 Subject: [PATCH 02/14] Change to mutable layers The layer trait interface was changed to mutable in https://github.com/heroku/libcnb.rs/pull/669 --- buildpacks/ruby/src/layers/bundle_download_layer.rs | 4 ++-- buildpacks/ruby/src/layers/bundle_install_layer.rs | 7 ++++--- buildpacks/ruby/src/layers/metrics_agent_install.rs | 8 ++++---- buildpacks/ruby/src/layers/ruby_install_layer.rs | 4 ++-- commons/src/cache/in_app_dir_cache_layer.rs | 4 ++-- commons/src/layer/configure_env_layer.rs | 2 +- commons/src/metadata_digest.rs | 4 ++-- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/buildpacks/ruby/src/layers/bundle_download_layer.rs b/buildpacks/ruby/src/layers/bundle_download_layer.rs index 7917c4f8..dcb55de0 100644 --- a/buildpacks/ruby/src/layers/bundle_download_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_download_layer.rs @@ -46,7 +46,7 @@ impl<'a> Layer for BundleDownloadLayer<'a> { } fn create( - &self, + &mut self, _context: &BuildContext, layer_path: &Path, ) -> Result, RubyBuildpackError> { @@ -106,7 +106,7 @@ impl<'a> Layer for BundleDownloadLayer<'a> { } fn existing_layer_strategy( - &self, + &mut self, _context: &BuildContext, layer_data: &LayerData, ) -> Result { diff --git a/buildpacks/ruby/src/layers/bundle_install_layer.rs b/buildpacks/ruby/src/layers/bundle_install_layer.rs index 05e7d741..6ab8bfd7 100644 --- a/buildpacks/ruby/src/layers/bundle_install_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_install_layer.rs @@ -114,9 +114,10 @@ impl Layer for BundleInstallLayer<'_> { cache: true, } } + /// Runs with gems cache from last execution fn update( - &self, + &mut self, context: &BuildContext, layer_data: &LayerData, ) -> Result, RubyBuildpackError> { @@ -150,7 +151,7 @@ impl Layer for BundleInstallLayer<'_> { /// Runs when with empty cache fn create( - &self, + &mut self, context: &BuildContext, layer_path: &Path, ) -> Result, RubyBuildpackError> { @@ -172,7 +173,7 @@ impl Layer for BundleInstallLayer<'_> { /// if a coder updates env vars they won't be set unless update or /// create is run. fn existing_layer_strategy( - &self, + &mut self, _context: &BuildContext, layer_data: &LayerData, ) -> Result { diff --git a/buildpacks/ruby/src/layers/metrics_agent_install.rs b/buildpacks/ruby/src/layers/metrics_agent_install.rs index 1c65e72a..6e1f74bb 100644 --- a/buildpacks/ruby/src/layers/metrics_agent_install.rs +++ b/buildpacks/ruby/src/layers/metrics_agent_install.rs @@ -77,7 +77,7 @@ impl<'a> Layer for MetricsAgentInstall<'a> { } fn create( - &self, + &mut self, _context: &libcnb::build::BuildContext, layer_path: &std::path::Path, ) -> Result< @@ -102,7 +102,7 @@ impl<'a> Layer for MetricsAgentInstall<'a> { } fn update( - &self, + &mut self, _context: &libcnb::build::BuildContext, layer_data: &libcnb::layer::LayerData, ) -> Result< @@ -123,7 +123,7 @@ impl<'a> Layer for MetricsAgentInstall<'a> { } fn existing_layer_strategy( - &self, + &mut self, _context: &libcnb::build::BuildContext, layer_data: &libcnb::layer::LayerData, ) -> Result::Error> @@ -144,7 +144,7 @@ impl<'a> Layer for MetricsAgentInstall<'a> { } fn migrate_incompatible_metadata( - &self, + &mut self, _context: &libcnb::build::BuildContext, _metadata: &GenericMetadata, ) -> Result< diff --git a/buildpacks/ruby/src/layers/ruby_install_layer.rs b/buildpacks/ruby/src/layers/ruby_install_layer.rs index 35f0238d..d39b8553 100644 --- a/buildpacks/ruby/src/layers/ruby_install_layer.rs +++ b/buildpacks/ruby/src/layers/ruby_install_layer.rs @@ -54,7 +54,7 @@ impl<'a> Layer for RubyInstallLayer<'a> { } fn create( - &self, + &mut self, _context: &BuildContext, layer_path: &Path, ) -> Result, RubyBuildpackError> { @@ -76,7 +76,7 @@ impl<'a> Layer for RubyInstallLayer<'a> { } fn existing_layer_strategy( - &self, + &mut self, _context: &BuildContext, layer_data: &LayerData, ) -> Result { diff --git a/commons/src/cache/in_app_dir_cache_layer.rs b/commons/src/cache/in_app_dir_cache_layer.rs index be581e0d..7a60b141 100644 --- a/commons/src/cache/in_app_dir_cache_layer.rs +++ b/commons/src/cache/in_app_dir_cache_layer.rs @@ -57,7 +57,7 @@ where } fn create( - &self, + &mut self, _context: &BuildContext, _layer_path: &Path, ) -> Result, B::Error> { @@ -68,7 +68,7 @@ where } fn existing_layer_strategy( - &self, + &mut self, _context: &BuildContext, layer_data: &LayerData, ) -> Result { diff --git a/commons/src/layer/configure_env_layer.rs b/commons/src/layer/configure_env_layer.rs index 3c063917..34bc868f 100644 --- a/commons/src/layer/configure_env_layer.rs +++ b/commons/src/layer/configure_env_layer.rs @@ -115,7 +115,7 @@ where } fn create( - &self, + &mut self, _context: &BuildContext, _layer_path: &Path, ) -> Result, B::Error> { diff --git a/commons/src/metadata_digest.rs b/commons/src/metadata_digest.rs index 3391e43f..79a9664f 100644 --- a/commons/src/metadata_digest.rs +++ b/commons/src/metadata_digest.rs @@ -101,7 +101,7 @@ const PLATFORM_ENV_VAR: &str = "user configured environment variables"; /// # } /// # /// fn update( -/// &self, +/// &mut self, /// context: &BuildContext, /// layer_data: &LayerData, /// ) -> Result, ::Error> { @@ -124,7 +124,7 @@ const PLATFORM_ENV_VAR: &str = "user configured environment variables"; /// } /// # /// # fn create( -/// # &self, +/// # &mut self, /// # context: &BuildContext, /// # layer_path: &Path, /// # ) -> Result, ::Error> { From 220b2eb5b3afcd2a61a74011b38c00b5883595a1 Mon Sep 17 00:00:00 2001 From: Schneems Date: Fri, 10 May 2024 10:27:43 -0400 Subject: [PATCH 03/14] Switch from stacks to targets AKA "Stay on target" Buildpack API 0.10 removed the concept of stacks in favor of targets https://github.com/heroku/libcnb.rs/pull/773. This commit works to upgrade applications in place by migrating metadata to support the new serialization format. This is supported by implementing TryMigrate from the `magic_migrate` crate https://docs.rs/magic_migrate/latest/magic_migrate/macro.try_migrate_link.html. https://www.youtube.com/watch?v=NnP5iDKwuwk --- Cargo.lock | 50 +++--- buildpacks/ruby/Cargo.toml | 7 +- buildpacks/ruby/buildpack.toml | 23 ++- .../ruby/src/layers/bundle_install_layer.rs | 165 ++++++++++++++++-- .../ruby/src/layers/ruby_install_layer.rs | 158 ++++++++++++++--- buildpacks/ruby/src/main.rs | 12 +- buildpacks/ruby/src/target_id.rs | 101 +++++++++++ commons/Cargo.toml | 4 +- commons/src/metadata_digest.rs | 2 - 9 files changed, 454 insertions(+), 68 deletions(-) create mode 100644 buildpacks/ruby/src/target_id.rs diff --git a/Cargo.lock b/Cargo.lock index 91386dfb..928cc0fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,9 +335,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "filetime" @@ -475,6 +475,7 @@ dependencies = [ "libcnb", "libcnb-test", "libherokubuildpack", + "magic_migrate", "rand", "regex", "serde", @@ -575,9 +576,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libcnb" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db217651ab45597152c94ad849defb079fc7ced7d72de2fcc2e9c3dec6e990e" +checksum = "aacc89bfeaef5f43cdee664798e3c0aa36e052a412ab1391f0750aee4df1f407" dependencies = [ "libcnb-common", "libcnb-data", @@ -589,9 +590,9 @@ dependencies = [ [[package]] name = "libcnb-common" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3abf2056162dd76ade12884e002ba88f068a26594b2eb9579ef8af40cfbca1b" +checksum = "a356bd77381b51f1ca42450694f4c7d1c7533a57c5f6a49553a96af96963b6e3" dependencies = [ "serde", "thiserror", @@ -600,9 +601,9 @@ dependencies = [ [[package]] name = "libcnb-data" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc6b01af8b624193ca6b247667ef82f36dd85d62b90f5a7e8d047b46642ce7c" +checksum = "dfcd102bfb1bf98ee4c18da0b29be6f23a19681937924bf758e9ea8499668b18" dependencies = [ "fancy-regex", "libcnb-proc-macros", @@ -614,9 +615,9 @@ dependencies = [ [[package]] name = "libcnb-package" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2678c2e0882c622a01d415e64625258849e533aeba8531110a5b3db9593d97d5" +checksum = "3b8d9b42112212a875c07fb3acf19504cf330edaa63cddd1823e9d03a5e2b934" dependencies = [ "cargo_metadata", "ignore", @@ -631,9 +632,9 @@ dependencies = [ [[package]] name = "libcnb-proc-macros" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0308e3b554dd8b0b969ab42d19b50b02bdb712dc72652849fa1e33bd1d16709" +checksum = "f83bba477c3a6cd69b29f77a6591411bac15ab7b341ad3d3cd38943bfbbd412f" dependencies = [ "cargo_metadata", "fancy-regex", @@ -643,9 +644,9 @@ dependencies = [ [[package]] name = "libcnb-test" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f094d9c229c481fb868d36231dcc4b7596491503d0a297eb239b08e942eb483c" +checksum = "9471152703833b74d565c7f7c910b4d5e084f955c327eba2bdb6658e86bd6dd6" dependencies = [ "fastrand", "fs_extra", @@ -689,6 +690,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "magic_migrate" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6398143367a78d596246f39b67e7ea09eea13318bdaa3ec9e0c4042517d2b44c" +dependencies = [ + "serde", +] + [[package]] name = "memchr" version = "2.7.1" @@ -768,18 +778,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1026,9 +1036,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.52" +version = "2.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" dependencies = [ "proc-macro2", "quote", diff --git a/buildpacks/ruby/Cargo.toml b/buildpacks/ruby/Cargo.toml index be30cb6e..9e65ebc8 100644 --- a/buildpacks/ruby/Cargo.toml +++ b/buildpacks/ruby/Cargo.toml @@ -16,7 +16,7 @@ glob = "0.3" indoc = "2" # libcnb has a much bigger impact on buildpack behaviour than any other dependencies, # so it's pinned to an exact version to isolate it from lockfile refreshes. -libcnb = "=0.19.0" +libcnb = "=0.21.0" libherokubuildpack = { version = "=0.21.0", default-features = false, features = ["digest"] } rand = "0.8" # TODO: Consolidate on either the regex crate or the fancy-regex crate, since this repo currently uses both. @@ -27,7 +27,8 @@ tempfile = "3" thiserror = "1" ureq = { version = "2", default-features = false, features = ["tls"] } url = "2" +magic_migrate = "0.1" +toml = "0.8" [dev-dependencies] -libcnb-test = "=0.19.0" -toml = "0.8" +libcnb-test = "=0.21.0" diff --git a/buildpacks/ruby/buildpack.toml b/buildpacks/ruby/buildpack.toml index 4871fc40..84128cd6 100644 --- a/buildpacks/ruby/buildpack.toml +++ b/buildpacks/ruby/buildpack.toml @@ -1,4 +1,4 @@ -api = "0.9" +api = "0.10" [buildpack] id = "heroku/ruby" @@ -12,10 +12,23 @@ keywords = ["ruby", "rails", "heroku"] type = "BSD-3-Clause" [[stacks]] -id = "heroku-20" - -[[stacks]] -id = "heroku-22" +id = "*" [metadata.release] image = { repository = "docker.io/heroku/buildpack-ruby" } + +[[targets]] +os = "linux" +arch = "arm64" + +[[targets]] +os = "linux" +arch = "amd64" + +[[targets.distros]] +name = "ubuntu" +version = "20.04" + +[[targets.distros]] +name = "ubuntu" +version = "22.04" diff --git a/buildpacks/ruby/src/layers/bundle_install_layer.rs b/buildpacks/ruby/src/layers/bundle_install_layer.rs index 6ab8bfd7..72f3c7ae 100644 --- a/buildpacks/ruby/src/layers/bundle_install_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_install_layer.rs @@ -11,14 +11,18 @@ use fun_run::CommandWithName; use fun_run::{self, CmdError}; use libcnb::{ build::BuildContext, - data::{buildpack::StackId, layer_content_metadata::LayerTypes}, + data::layer_content_metadata::LayerTypes, layer::{ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder}, layer_env::{LayerEnv, ModificationBehavior, Scope}, Env, }; -use serde::{Deserialize, Serialize}; +use magic_migrate::{try_migrate_link, TryMigrate}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::convert::Infallible; use std::{path::Path, process::Command}; +use crate::target_id::{TargetId, TargetIdError}; + const HEROKU_SKIP_BUNDLE_DIGEST: &str = "HEROKU_SKIP_BUNDLE_DIGEST"; pub(crate) const FORCE_BUNDLE_INSTALL_CACHE_KEY: &str = "v1"; @@ -38,8 +42,16 @@ pub(crate) struct BundleInstallLayer<'a> { } #[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] -pub(crate) struct BundleInstallLayerMetadata { - pub(crate) stack: StackId, +pub(crate) struct BundleInstallLayerMetadataV1 { + pub(crate) stack: String, + pub(crate) ruby_version: ResolvedRubyVersion, + pub(crate) force_bundle_install_key: String, + pub(crate) digest: MetadataDigest, // Must be last for serde to be happy https://github.com/toml-rs/toml-rs/issues/142 +} + +#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] +pub(crate) struct BundleInstallLayerMetadataV2 { + pub(crate) target_id: TargetId, pub(crate) ruby_version: ResolvedRubyVersion, pub(crate) force_bundle_install_key: String, @@ -56,6 +68,45 @@ pub(crate) struct BundleInstallLayerMetadata { /// pub(crate) digest: MetadataDigest, // Must be last for serde to be happy https://github.com/toml-rs/toml-rs/issues/142 } +try_migrate_link!(BundleInstallLayerMetadataV1, BundleInstallLayerMetadataV2); +pub(crate) type BundleInstallLayerMetadata = BundleInstallLayerMetadataV2; + +#[derive(thiserror::Error, Debug)] +pub(crate) enum MigrateMetadataError { + #[error("Could not migrate metadata {0}")] + UnsupportedStack(TargetIdError), +} + +// CNB spec moved from the concept of "stacks" (i.e. "heroku-22" which represented an OS and system dependencies) to finer +// grained "target" which includes the OS, OS version, and architecture. This function converts the old stack id to the new target id. +impl TryFrom for BundleInstallLayerMetadataV2 { + type Error = MigrateMetadataError; + + fn try_from(v1: BundleInstallLayerMetadataV1) -> Result { + Ok(Self { + target_id: TargetId::from_stack(&v1.stack) + .map_err(MigrateMetadataError::UnsupportedStack)?, + ruby_version: v1.ruby_version, + force_bundle_install_key: v1.force_bundle_install_key, + digest: v1.digest, + }) + } +} + +impl From for MigrateMetadataError { + fn from(_: Infallible) -> Self { + unreachable!() + } +} + +impl TryMigrate for BundleInstallLayerMetadataV1 { + type TryFrom = Self; + type Error = MigrateMetadataError; + + fn deserializer<'de>(input: &str) -> impl Deserializer<'de> { + toml::Deserializer::new(input) + } +} impl<'a> BundleInstallLayer<'a> { #[allow(clippy::unnecessary_wraps)] @@ -189,7 +240,7 @@ impl Layer for BundleInstallLayer<'_> { keep_and_run } - Changed::Stack(_old, _now) => { + Changed::Target(_old, _now) => { log_step(format!("Clearing cache {}", fmt::details("stack changed"))); clear_and_run @@ -204,6 +255,29 @@ impl Layer for BundleInstallLayer<'_> { } } } + + fn migrate_incompatible_metadata( + &mut self, + _context: &BuildContext, + metadata: &libcnb::generic::GenericMetadata, + ) -> Result< + libcnb::layer::MetadataMigration, + ::Error, + > { + match Self::Metadata::try_from_str_migrations( + &toml::to_string(&metadata).expect("TOML deserialization of GenericMetadata"), + ) { + Some(Ok(metadata)) => Ok(libcnb::layer::MetadataMigration::ReplaceMetadata(metadata)), + Some(Err(e)) => { + log_step(format!("Clearing cache (metadata migration error {e})")); + Ok(libcnb::layer::MetadataMigration::RecreateLayer) + } + None => { + log_step("Clearing cache (invalid metadata)"); + Ok(libcnb::layer::MetadataMigration::RecreateLayer) + } + } + } } /// The possible states of the cache values, used for determining `ExistingLayerStrategy` @@ -216,7 +290,7 @@ enum Changed { /// because they're compiled against system dependencies /// i.e. /// TODO: Only clear native dependencies instead of the whole cache - Stack(StackId, StackId), // (old, now) + Target(TargetId, TargetId), // (old, now) /// Ruby version changed i.e. 3.0.2 to 3.1.2 /// When that happens we must invalidate native dependency gems @@ -229,14 +303,14 @@ enum Changed { // cache. Based on that state, we can log and determine `ExistingLayerStrategy` fn cache_state(old: BundleInstallLayerMetadata, now: BundleInstallLayerMetadata) -> Changed { let BundleInstallLayerMetadata { - stack, + target_id, ruby_version, force_bundle_install_key: _, digest: _, // digest state handled elsewhere } = now; // ensure all values are handled or we get a clippy warning - if old.stack != stack { - Changed::Stack(old.stack, stack) + if old.target_id != target_id { + Changed::Target(old.target_id, target_id) } else if old.ruby_version != ruby_version { Changed::RubyVersion(old.ruby_version, ruby_version) } else { @@ -348,7 +422,6 @@ pub(crate) struct BundleDigest { #[cfg(test)] mod test { use super::*; - use libcnb::data::stack_id; use std::path::PathBuf; #[cfg(test)] @@ -401,8 +474,9 @@ GEM_PATH=layer_path assert_eq!(expected.trim(), actual.trim()); } - /// If this test fails due to a change you'll need to implement - /// `migrate_incompatible_metadata` for the Layer trait + /// Guards the current metadata deserialization + /// If this fails you need to implement a migration from the last format + /// to the current format. #[test] fn metadata_guard() { let tmpdir = tempfile::tempdir().unwrap(); @@ -419,7 +493,7 @@ GEM_PATH=layer_path std::fs::write(&gemfile, "iamagemfile").unwrap(); let metadata = BundleInstallLayerMetadata { - stack: stack_id!("heroku-22"), + target_id: TargetId::from_stack("heroku-24").unwrap(), ruby_version: ResolvedRubyVersion(String::from("3.1.3")), force_bundle_install_key: String::from("v1"), digest: MetadataDigest::new_env_files( @@ -433,10 +507,14 @@ GEM_PATH=layer_path let gemfile_path = gemfile.display(); let toml_string = format!( r#" -stack = "heroku-22" ruby_version = "3.1.3" force_bundle_install_key = "v1" +[target_id] +arch = "amd64" +distro_name = "ubuntu" +distro_version = "24.04" + [digest] platform_env = "c571543beaded525b7ee46ceb0b42c0fb7b9f6bfc3a211b3bbcfe6956b69ace3" @@ -452,4 +530,63 @@ platform_env = "c571543beaded525b7ee46ceb0b42c0fb7b9f6bfc3a211b3bbcfe6956b69ace3 assert_eq!(metadata, deserialized); } + + #[test] + fn metadata_migrate_v1_to_v2() { + let tmpdir = tempfile::tempdir().unwrap(); + let app_path = tmpdir.path().to_path_buf(); + let gemfile = app_path.join("Gemfile"); + + let mut env = Env::new(); + env.insert("SECRET_KEY_BASE", "abcdgoldfish"); + + let context = FakeContext { + platform: FakePlatform { env }, + app_path, + }; + std::fs::write(&gemfile, "iamagemfile").unwrap(); + + let metadata = BundleInstallLayerMetadataV1 { + stack: String::from("heroku-22"), + ruby_version: ResolvedRubyVersion(String::from("3.1.3")), + force_bundle_install_key: String::from("v1"), + digest: MetadataDigest::new_env_files( + &context.platform, + &[&context.app_path.join("Gemfile")], + ) + .unwrap(), + }; + + let actual = toml::to_string(&metadata).unwrap(); + let gemfile_path = gemfile.display(); + let toml_string = format!( + r#" +stack = "heroku-22" +ruby_version = "3.1.3" +force_bundle_install_key = "v1" + +[digest] +platform_env = "c571543beaded525b7ee46ceb0b42c0fb7b9f6bfc3a211b3bbcfe6956b69ace3" + +[digest.files] +"{gemfile_path}" = "32b27d2934db61b105fea7c2cb6159092fed6e121f8c72a948f341ab5afaa1ab" +"# + ) + .trim() + .to_string(); + assert_eq!(toml_string, actual.trim()); + + let deserialized: BundleInstallLayerMetadataV2 = + BundleInstallLayerMetadataV2::try_from_str_migrations(&toml_string) + .unwrap() + .unwrap(); + + let expected = BundleInstallLayerMetadataV2 { + target_id: TargetId::from_stack(&metadata.stack).unwrap(), + ruby_version: metadata.ruby_version, + force_bundle_install_key: metadata.force_bundle_install_key, + digest: metadata.digest, + }; + assert_eq!(expected, deserialized); + } } diff --git a/buildpacks/ruby/src/layers/ruby_install_layer.rs b/buildpacks/ruby/src/layers/ruby_install_layer.rs index d39b8553..037913dc 100644 --- a/buildpacks/ruby/src/layers/ruby_install_layer.rs +++ b/buildpacks/ruby/src/layers/ruby_install_layer.rs @@ -2,15 +2,19 @@ use commons::output::{ fmt::{self}, section_log::{log_step, log_step_timed, SectionLogger}, }; +use magic_migrate::{try_migrate_link, TryMigrate}; -use crate::{RubyBuildpack, RubyBuildpackError}; +use crate::{ + target_id::{TargetId, TargetIdError}, + RubyBuildpack, RubyBuildpackError, +}; use commons::gemfile_lock::ResolvedRubyVersion; use flate2::read::GzDecoder; use libcnb::build::BuildContext; -use libcnb::data::buildpack::StackId; use libcnb::data::layer_content_metadata::LayerTypes; use libcnb::layer::{ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::convert::Infallible; use std::io; use std::path::Path; use tar::Archive; @@ -36,10 +40,51 @@ pub(crate) struct RubyInstallLayer<'a> { } #[derive(Deserialize, Serialize, Debug, Clone)] -pub(crate) struct RubyInstallLayerMetadata { - pub(crate) stack: StackId, +pub(crate) struct RubyInstallLayerMetadataV1 { + pub(crate) stack: String, + pub(crate) version: ResolvedRubyVersion, +} + +#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] +pub(crate) struct RubyInstallLayerMetadataV2 { + pub(crate) target_id: TargetId, pub(crate) version: ResolvedRubyVersion, } +try_migrate_link!(RubyInstallLayerMetadataV1, RubyInstallLayerMetadataV2); +pub(crate) type RubyInstallLayerMetadata = RubyInstallLayerMetadataV2; + +#[derive(thiserror::Error, Debug)] +pub(crate) enum MetadataMigrateError { + #[error("Cannot migrate metadata due to target id error: {0}")] + TargetIdError(TargetIdError), +} + +impl TryFrom for RubyInstallLayerMetadataV2 { + type Error = MetadataMigrateError; + + fn try_from(v1: RubyInstallLayerMetadataV1) -> Result { + Ok(Self { + target_id: TargetId::from_stack(&v1.stack) + .map_err(MetadataMigrateError::TargetIdError)?, + version: v1.version, + }) + } +} + +impl From for MetadataMigrateError { + fn from(_: Infallible) -> Self { + unreachable!() + } +} + +impl TryMigrate for RubyInstallLayerMetadataV1 { + type TryFrom = Self; + type Error = MetadataMigrateError; + + fn deserializer<'de>(input: &str) -> impl Deserializer<'de> { + toml::Deserializer::new(input) + } +} impl<'a> Layer for RubyInstallLayer<'a> { type Buildpack = RubyBuildpack; @@ -63,7 +108,7 @@ impl<'a> Layer for RubyInstallLayer<'a> { .map_err(RubyInstallError::CouldNotCreateDestinationFile) .map_err(RubyBuildpackError::RubyInstallError)?; - let url = download_url(&self.metadata.stack, &self.metadata.version) + let url = download_url(&self.metadata.target_id, &self.metadata.version) .map_err(RubyBuildpackError::RubyInstallError)?; download(url.as_ref(), tmp_ruby_tgz.path()) @@ -75,6 +120,29 @@ impl<'a> Layer for RubyInstallLayer<'a> { }) } + fn migrate_incompatible_metadata( + &mut self, + _context: &BuildContext, + metadata: &libcnb::generic::GenericMetadata, + ) -> Result< + libcnb::layer::MetadataMigration, + ::Error, + > { + match Self::Metadata::try_from_str_migrations( + &toml::to_string(&metadata).expect("TOML deserialization of GenericMetadata"), + ) { + Some(Ok(metadata)) => Ok(libcnb::layer::MetadataMigration::ReplaceMetadata(metadata)), + Some(Err(e)) => { + log_step(format!("Clearing cache (metadata migration error {e})")); + Ok(libcnb::layer::MetadataMigration::RecreateLayer) + } + None => { + log_step("Clearing cache (invalid metadata)"); + Ok(libcnb::layer::MetadataMigration::RecreateLayer) + } + } + } + fn existing_layer_strategy( &mut self, _context: &BuildContext, @@ -89,8 +157,8 @@ impl<'a> Layer for RubyInstallLayer<'a> { Ok(ExistingLayerStrategy::Keep) } - Changed::Stack(_old, _now) => { - log_step(format!("Clearing cache {}", fmt::details("stack changed"))); + Changed::Target(_old, _now) => { + log_step(format!("Clearing cache {}", fmt::details("OS changed"))); Ok(ExistingLayerStrategy::Recreate) } @@ -107,10 +175,10 @@ impl<'a> Layer for RubyInstallLayer<'a> { } fn cache_state(old: RubyInstallLayerMetadata, now: RubyInstallLayerMetadata) -> Changed { - let RubyInstallLayerMetadata { stack, version } = now; + let RubyInstallLayerMetadata { target_id, version } = now; - if old.stack != stack { - Changed::Stack(old.stack, stack) + if old.target_id != target_id { + Changed::Target(old.target_id, target_id) } else if old.version != version { Changed::RubyVersion(old.version, version) } else { @@ -121,18 +189,21 @@ fn cache_state(old: RubyInstallLayerMetadata, now: RubyInstallLayerMetadata) -> #[derive(Debug)] enum Changed { Nothing(ResolvedRubyVersion), - Stack(StackId, StackId), + Target(TargetId, TargetId), RubyVersion(ResolvedRubyVersion, ResolvedRubyVersion), } -fn download_url(stack: &StackId, version: impl std::fmt::Display) -> Result { +fn download_url( + target: &TargetId, + version: impl std::fmt::Display, +) -> Result { 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)?; url.path_segments_mut() .map_err(|()| RubyInstallError::InvalidBaseUrl(String::from(base)))? - .push(stack) + .push(&target.stack_name().map_err(RubyInstallError::TargetError)?) .push(&filename); Ok(url) } @@ -168,6 +239,9 @@ pub(crate) fn untar( #[derive(thiserror::Error, Debug)] pub(crate) enum RubyInstallError { + #[error("Unknown install target: {0}")] + TargetError(TargetIdError), + #[error("Could not parse url {0}")] UrlParseError(url::ParseError), @@ -194,14 +268,39 @@ pub(crate) enum RubyInstallError { #[cfg(test)] mod tests { use super::*; - use libcnb::data::stack_id; - /// If this test fails due to a change you'll need to implement - /// `migrate_incompatible_metadata` for the Layer trait + /// If this test fails due to a change you'll need to + /// implement `TryMigrate` for the new layer data and add + /// another test ensuring the latest metadata struct can + /// be built from the previous version. #[test] fn metadata_guard() { let metadata = RubyInstallLayerMetadata { - stack: stack_id!("heroku-22"), + target_id: TargetId { + arch: String::from("amd64"), + distro_name: String::from("ubuntu"), + distro_version: String::from("22.04"), + }, + version: ResolvedRubyVersion(String::from("3.1.3")), + }; + + let actual = toml::to_string(&metadata).unwrap(); + let expected = r#" +version = "3.1.3" + +[target_id] +arch = "amd64" +distro_name = "ubuntu" +distro_version = "22.04" +"# + .trim(); + assert_eq!(expected, actual.trim()); + } + + #[test] + fn metadata_migrate_v1_to_v2() { + let metadata = RubyInstallLayerMetadataV1 { + stack: String::from("heroku-22"), version: ResolvedRubyVersion(String::from("3.1.3")), }; @@ -212,14 +311,33 @@ version = "3.1.3" "# .trim(); assert_eq!(expected, actual.trim()); + + let deserialized: RubyInstallLayerMetadataV2 = + RubyInstallLayerMetadataV2::try_from_str_migrations(&actual) + .unwrap() + .unwrap(); + + let expected = RubyInstallLayerMetadataV2 { + target_id: TargetId::from_stack(&metadata.stack).expect("Valid stack"), + version: metadata.version, + }; + assert_eq!(expected, deserialized); } #[test] fn test_ruby_url() { - let out = download_url(&stack_id!("heroku-20"), "2.7.4").unwrap(); + let out = download_url( + &TargetId { + arch: String::from("amd64"), + distro_name: String::from("ubuntu"), + distro_version: String::from("22.04"), + }, + "2.7.4", + ) + .unwrap(); assert_eq!( out.as_ref(), - "https://heroku-buildpack-ruby.s3.us-east-1.amazonaws.com/heroku-20/ruby-2.7.4.tgz", + "https://heroku-buildpack-ruby.s3.us-east-1.amazonaws.com/heroku-22/ruby-2.7.4.tgz", ); } } diff --git a/buildpacks/ruby/src/main.rs b/buildpacks/ruby/src/main.rs index 751aa65b..9c5de79d 100644 --- a/buildpacks/ruby/src/main.rs +++ b/buildpacks/ruby/src/main.rs @@ -23,12 +23,14 @@ use libcnb::layer_env::Scope; use libcnb::Platform; use libcnb::{buildpack_main, Buildpack}; use std::io::stdout; +use target_id::TargetId; mod gem_list; mod layers; mod rake_status; mod rake_task_detect; mod steps; +mod target_id; mod user_errors; #[cfg(test)] @@ -117,6 +119,12 @@ impl Buildpack for RubyBuildpack { let mut logger = BuildLog::new(stdout()).buildpack_name("Heroku Ruby Buildpack"); let warn_later = WarnGuard::new(stdout()); + let target_id = TargetId { + arch: context.target.arch.clone(), + distro_name: context.target.distro_name.clone(), + distro_version: context.target.distro_version.clone(), + }; + // ## Set default environment let (mut env, store) = crate::steps::default_env(&context, &context.platform.env().clone())?; @@ -170,7 +178,7 @@ impl Buildpack for RubyBuildpack { RubyInstallLayer { _in_section: section.as_ref(), metadata: RubyInstallLayerMetadata { - stack: context.stack_id.clone(), + target_id: target_id.clone(), version: ruby_version.clone(), }, }, @@ -211,7 +219,7 @@ impl Buildpack for RubyBuildpack { without: BundleWithout::new("development:test"), _section_log: section.as_ref(), metadata: BundleInstallLayerMetadata { - stack: context.stack_id.clone(), + target_id: target_id.clone(), ruby_version: ruby_version.clone(), force_bundle_install_key: String::from( crate::layers::bundle_install_layer::FORCE_BUNDLE_INSTALL_CACHE_KEY, diff --git a/buildpacks/ruby/src/target_id.rs b/buildpacks/ruby/src/target_id.rs new file mode 100644 index 00000000..8c16df18 --- /dev/null +++ b/buildpacks/ruby/src/target_id.rs @@ -0,0 +1,101 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub(crate) struct TargetId { + pub(crate) arch: String, + pub(crate) distro_name: String, + pub(crate) distro_version: String, +} + +const DISTRO_VERSION_STACK: &[(&str, &str, &str)] = &[ + ("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(", "))] + 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(", "))] + UnknownStack(String), +} + +impl TargetId { + pub(crate) fn stack_name(&self) -> Result { + DISTRO_VERSION_STACK + .iter() + .find(|&&(name, version, _)| name == self.distro_name && version == self.distro_version) + .map(|&(_, _, stack)| stack.to_owned()) + .ok_or_else(|| { + TargetIdError::UnknownDistroNameVersionCombo( + self.distro_name.clone(), + self.distro_version.clone(), + ) + }) + } + + pub(crate) fn from_stack(stack_id: &str) -> Result { + DISTRO_VERSION_STACK + .iter() + .find(|&&(_, _, stack)| stack == stack_id) + .map(|&(name, version, _)| TargetId { + arch: String::from("amd64"), + distro_name: name.to_owned(), + distro_version: version.to_owned(), + }) + .ok_or_else(|| TargetIdError::UnknownStack(stack_id.to_owned())) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_stack_name() { + assert_eq!( + String::from("heroku-22"), + TargetId { + arch: String::from("amd64"), + distro_name: String::from("ubuntu"), + distro_version: String::from("22.04"), + } + .stack_name() + .unwrap() + ); + + assert_eq!( + String::from("heroku-24"), + TargetId { + arch: String::from("amd64"), + distro_name: String::from("ubuntu"), + distro_version: String::from("24.04"), + } + .stack_name() + .unwrap() + ); + } + + #[test] + fn test_from_stack() { + assert_eq!( + TargetId::from_stack("heroku-22").unwrap(), + TargetId { + arch: String::from("amd64"), + distro_name: String::from("ubuntu"), + distro_version: String::from("22.04"), + } + ); + + assert_eq!( + TargetId::from_stack("heroku-24").unwrap(), + TargetId { + arch: String::from("amd64"), + distro_name: String::from("ubuntu"), + distro_version: String::from("24.04"), + } + ); + } +} diff --git a/commons/Cargo.toml b/commons/Cargo.toml index ccc1a150..3e28a5f6 100644 --- a/commons/Cargo.toml +++ b/commons/Cargo.toml @@ -24,7 +24,7 @@ indoc = "2" lazy_static = "1" # libcnb has a much bigger impact on buildpack behaviour than any other dependencies, # so it's pinned to an exact version to isolate it from lockfile refreshes. -libcnb = "=0.19.0" +libcnb = "=0.21.0" libherokubuildpack = { version = "=0.21.0", default-features = false, features = ["command"] } regex = "1" serde = "1" @@ -36,6 +36,6 @@ walkdir = "2" [dev-dependencies] filetime = "0.2" indoc = "2" -libcnb-test = "=0.19.0" +libcnb-test = "=0.21.0" pretty_assertions = "1" toml = "0.8" diff --git a/commons/src/metadata_digest.rs b/commons/src/metadata_digest.rs index 79a9664f..1ef09719 100644 --- a/commons/src/metadata_digest.rs +++ b/commons/src/metadata_digest.rs @@ -18,11 +18,9 @@ const PLATFORM_ENV_VAR: &str = "user configured environment variables"; /// ```rust /// use serde::{Deserialize, Serialize}; /// use commons::metadata_digest::MetadataDigest; -/// use libcnb::data::buildpack::StackId; /// /// #[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] /// pub(crate) struct BundleInstallLayerMetadata { -/// stack: StackId, /// ruby_version: String, /// force_bundle_install_key: String, /// From 557d8885897b3adaf2d2fa7aedf3a64980959e8b Mon Sep 17 00:00:00 2001 From: Schneems Date: Sat, 11 May 2024 12:45:52 -0400 Subject: [PATCH 04/14] Add explicit Distro/Architecture cache messages --- .../ruby/src/layers/bundle_install_layer.rs | 86 +++++++++----- .../ruby/src/layers/ruby_install_layer.rs | 112 ++++++++++++------ buildpacks/ruby/src/main.rs | 17 ++- buildpacks/ruby/src/target_id.rs | 12 +- 4 files changed, 148 insertions(+), 79 deletions(-) diff --git a/buildpacks/ruby/src/layers/bundle_install_layer.rs b/buildpacks/ruby/src/layers/bundle_install_layer.rs index 72f3c7ae..d1da1557 100644 --- a/buildpacks/ruby/src/layers/bundle_install_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_install_layer.rs @@ -51,7 +51,9 @@ pub(crate) struct BundleInstallLayerMetadataV1 { #[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] pub(crate) struct BundleInstallLayerMetadataV2 { - pub(crate) target_id: TargetId, + pub(crate) distro_name: String, + pub(crate) distro_version: String, + pub(crate) cpu_architecture: String, pub(crate) ruby_version: ResolvedRubyVersion, pub(crate) force_bundle_install_key: String, @@ -83,9 +85,13 @@ impl TryFrom for BundleInstallLayerMetadataV2 { type Error = MigrateMetadataError; fn try_from(v1: BundleInstallLayerMetadataV1) -> Result { + let target_id = + TargetId::from_stack(&v1.stack).map_err(MigrateMetadataError::UnsupportedStack)?; + Ok(Self { - target_id: TargetId::from_stack(&v1.stack) - .map_err(MigrateMetadataError::UnsupportedStack)?, + distro_name: target_id.distro_name.clone(), + distro_version: target_id.distro_version.clone(), + cpu_architecture: target_id.cpu_architecture.clone(), ruby_version: v1.ruby_version, force_bundle_install_key: v1.force_bundle_install_key, digest: v1.digest, @@ -240,15 +246,34 @@ impl Layer for BundleInstallLayer<'_> { keep_and_run } - Changed::Target(_old, _now) => { - log_step(format!("Clearing cache {}", fmt::details("stack changed"))); + Changed::DistroName(old, now) => { + log_step(format!( + "Clearing cache {}", + fmt::details(format!("distro name changed from {old} to {now}")) + )); + + clear_and_run + } + Changed::DistroVersion(old, now) => { + log_step(format!( + "Clearing cache {}", + fmt::details(format!("distro version changed from {old} to {now}")) + )); + + clear_and_run + } + Changed::CpuArchitecture(old, now) => { + log_step(format!( + "Clearing cache {}", + fmt::details(format!("cpu architecture changed from {old} to {now}")) + )); clear_and_run } - Changed::RubyVersion(_old, _now) => { + Changed::RubyVersion(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details("ruby version changed") + fmt::details(format!("ruby version changed: {old} to {now}")) )); clear_and_run @@ -284,33 +309,30 @@ impl Layer for BundleInstallLayer<'_> { #[derive(Debug)] enum Changed { Nothing, - - /// The stack changed i.e. from `heroku-20` to `heroku-22` - /// When that happens we must invalidate native dependency gems - /// because they're compiled against system dependencies - /// i.e. - /// TODO: Only clear native dependencies instead of the whole cache - Target(TargetId, TargetId), // (old, now) - - /// Ruby version changed i.e. 3.0.2 to 3.1.2 - /// When that happens we must invalidate native dependency gems - /// because they're linked to a specific compiled version of Ruby. - /// TODO: Only clear native dependencies instead of the whole cache - RubyVersion(ResolvedRubyVersion, ResolvedRubyVersion), // (old, now) + DistroName(String, String), + DistroVersion(String, String), + CpuArchitecture(String, String), + RubyVersion(ResolvedRubyVersion, ResolvedRubyVersion), } // Compare the old metadata to current metadata to determine the state of the // cache. Based on that state, we can log and determine `ExistingLayerStrategy` fn cache_state(old: BundleInstallLayerMetadata, now: BundleInstallLayerMetadata) -> Changed { let BundleInstallLayerMetadata { - target_id, + distro_name, + distro_version, + cpu_architecture, ruby_version, force_bundle_install_key: _, digest: _, // digest state handled elsewhere } = now; // ensure all values are handled or we get a clippy warning - if old.target_id != target_id { - Changed::Target(old.target_id, target_id) + if old.distro_name != distro_name { + Changed::DistroName(old.distro_name, distro_name) + } else if old.distro_version != distro_version { + Changed::DistroVersion(old.distro_version, distro_version) + } else if old.cpu_architecture != cpu_architecture { + Changed::CpuArchitecture(old.cpu_architecture, cpu_architecture) } else if old.ruby_version != ruby_version { Changed::RubyVersion(old.ruby_version, ruby_version) } else { @@ -492,8 +514,11 @@ GEM_PATH=layer_path }; std::fs::write(&gemfile, "iamagemfile").unwrap(); + let target_id = TargetId::from_stack("heroku-24").unwrap(); let metadata = BundleInstallLayerMetadata { - target_id: TargetId::from_stack("heroku-24").unwrap(), + distro_name: target_id.distro_name, + distro_version: target_id.distro_version, + cpu_architecture: target_id.cpu_architecture, ruby_version: ResolvedRubyVersion(String::from("3.1.3")), force_bundle_install_key: String::from("v1"), digest: MetadataDigest::new_env_files( @@ -507,13 +532,11 @@ GEM_PATH=layer_path let gemfile_path = gemfile.display(); let toml_string = format!( r#" -ruby_version = "3.1.3" -force_bundle_install_key = "v1" - -[target_id] -arch = "amd64" distro_name = "ubuntu" distro_version = "24.04" +cpu_architecture = "amd64" +ruby_version = "3.1.3" +force_bundle_install_key = "v1" [digest] platform_env = "c571543beaded525b7ee46ceb0b42c0fb7b9f6bfc3a211b3bbcfe6956b69ace3" @@ -581,8 +604,11 @@ platform_env = "c571543beaded525b7ee46ceb0b42c0fb7b9f6bfc3a211b3bbcfe6956b69ace3 .unwrap() .unwrap(); + let target_id = TargetId::from_stack(&metadata.stack).unwrap(); let expected = BundleInstallLayerMetadataV2 { - target_id: TargetId::from_stack(&metadata.stack).unwrap(), + distro_name: target_id.distro_name, + distro_version: target_id.distro_version, + cpu_architecture: target_id.cpu_architecture, ruby_version: metadata.ruby_version, force_bundle_install_key: metadata.force_bundle_install_key, digest: metadata.digest, diff --git a/buildpacks/ruby/src/layers/ruby_install_layer.rs b/buildpacks/ruby/src/layers/ruby_install_layer.rs index 037913dc..2ef5fffd 100644 --- a/buildpacks/ruby/src/layers/ruby_install_layer.rs +++ b/buildpacks/ruby/src/layers/ruby_install_layer.rs @@ -47,9 +47,22 @@ pub(crate) struct RubyInstallLayerMetadataV1 { #[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] pub(crate) struct RubyInstallLayerMetadataV2 { - pub(crate) target_id: TargetId, - pub(crate) version: ResolvedRubyVersion, + pub(crate) distro_name: String, + pub(crate) distro_version: String, + pub(crate) cpu_architecture: String, + pub(crate) ruby_version: ResolvedRubyVersion, +} + +impl RubyInstallLayerMetadataV2 { + pub(crate) fn target_id(&self) -> TargetId { + TargetId { + cpu_architecture: self.cpu_architecture.clone(), + distro_name: self.distro_name.clone(), + distro_version: self.distro_version.clone(), + } + } } + try_migrate_link!(RubyInstallLayerMetadataV1, RubyInstallLayerMetadataV2); pub(crate) type RubyInstallLayerMetadata = RubyInstallLayerMetadataV2; @@ -63,10 +76,14 @@ impl TryFrom for RubyInstallLayerMetadataV2 { type Error = MetadataMigrateError; fn try_from(v1: RubyInstallLayerMetadataV1) -> Result { + let target_id = + TargetId::from_stack(&v1.stack).map_err(MetadataMigrateError::TargetIdError)?; + Ok(Self { - target_id: TargetId::from_stack(&v1.stack) - .map_err(MetadataMigrateError::TargetIdError)?, - version: v1.version, + distro_name: target_id.distro_name, + distro_version: target_id.distro_version, + cpu_architecture: target_id.cpu_architecture, + ruby_version: v1.version, }) } } @@ -108,7 +125,7 @@ impl<'a> Layer for RubyInstallLayer<'a> { .map_err(RubyInstallError::CouldNotCreateDestinationFile) .map_err(RubyBuildpackError::RubyInstallError)?; - let url = download_url(&self.metadata.target_id, &self.metadata.version) + let url = download_url(&self.metadata.target_id(), &self.metadata.ruby_version) .map_err(RubyBuildpackError::RubyInstallError)?; download(url.as_ref(), tmp_ruby_tgz.path()) @@ -152,20 +169,39 @@ impl<'a> Layer for RubyInstallLayer<'a> { let now = self.metadata.clone(); match cache_state(old.clone(), now) { - Changed::Nothing(_version) => { + Changed::Nothing => { log_step("Using cached version"); Ok(ExistingLayerStrategy::Keep) } - Changed::Target(_old, _now) => { - log_step(format!("Clearing cache {}", fmt::details("OS changed"))); + Changed::CpuArchitecture(old, now) => { + log_step(format!( + "Clearing cache {}", + fmt::details(format!("CPU architecture changed: {old} to {now}")) + )); Ok(ExistingLayerStrategy::Recreate) } - Changed::RubyVersion(_old, _now) => { + Changed::DistroVersion(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details("ruby version changed") + fmt::details(format!("OS version changed: {old} to {now}")) + )); + + Ok(ExistingLayerStrategy::Recreate) + } + Changed::DistroName(old, now) => { + log_step(format!( + "Clearing cache {}", + fmt::details(format!("OS distribution changed: {old} to {now}")) + )); + + Ok(ExistingLayerStrategy::Recreate) + } + Changed::RubyVersion(old, now) => { + log_step(format!( + "Clearing cache {}", + fmt::details(format!("Ruby version changed: {old} to {now}")) )); Ok(ExistingLayerStrategy::Recreate) @@ -175,21 +211,32 @@ impl<'a> Layer for RubyInstallLayer<'a> { } fn cache_state(old: RubyInstallLayerMetadata, now: RubyInstallLayerMetadata) -> Changed { - let RubyInstallLayerMetadata { target_id, version } = now; - - if old.target_id != target_id { - Changed::Target(old.target_id, target_id) - } else if old.version != version { - Changed::RubyVersion(old.version, version) + let RubyInstallLayerMetadata { + distro_name, + distro_version, + cpu_architecture, + ruby_version, + } = now; + + if old.distro_name != distro_name { + Changed::DistroName(old.distro_name, distro_name) + } else if old.distro_version != distro_version { + Changed::DistroVersion(old.distro_version, distro_version) + } else if old.cpu_architecture != cpu_architecture { + Changed::CpuArchitecture(old.cpu_architecture, cpu_architecture) + } else if old.ruby_version != ruby_version { + Changed::RubyVersion(old.ruby_version, ruby_version) } else { - Changed::Nothing(version) + Changed::Nothing } } #[derive(Debug)] enum Changed { - Nothing(ResolvedRubyVersion), - Target(TargetId, TargetId), + Nothing, + DistroName(String, String), + DistroVersion(String, String), + CpuArchitecture(String, String), RubyVersion(ResolvedRubyVersion, ResolvedRubyVersion), } @@ -276,22 +323,18 @@ mod tests { #[test] fn metadata_guard() { let metadata = RubyInstallLayerMetadata { - target_id: TargetId { - arch: String::from("amd64"), - distro_name: String::from("ubuntu"), - distro_version: String::from("22.04"), - }, - version: ResolvedRubyVersion(String::from("3.1.3")), + distro_name: String::from("ubuntu"), + distro_version: String::from("22.04"), + cpu_architecture: String::from("amd64"), + ruby_version: ResolvedRubyVersion(String::from("3.1.3")), }; let actual = toml::to_string(&metadata).unwrap(); let expected = r#" -version = "3.1.3" - -[target_id] -arch = "amd64" distro_name = "ubuntu" distro_version = "22.04" +cpu_architecture = "amd64" +ruby_version = "3.1.3" "# .trim(); assert_eq!(expected, actual.trim()); @@ -317,9 +360,12 @@ version = "3.1.3" .unwrap() .unwrap(); + let target_id = TargetId::from_stack(&metadata.stack).unwrap(); let expected = RubyInstallLayerMetadataV2 { - target_id: TargetId::from_stack(&metadata.stack).expect("Valid stack"), - version: metadata.version, + distro_name: target_id.distro_name, + distro_version: target_id.distro_version, + cpu_architecture: target_id.cpu_architecture, + ruby_version: metadata.version, }; assert_eq!(expected, deserialized); } @@ -328,7 +374,7 @@ version = "3.1.3" fn test_ruby_url() { let out = download_url( &TargetId { - arch: String::from("amd64"), + cpu_architecture: String::from("amd64"), distro_name: String::from("ubuntu"), distro_version: String::from("22.04"), }, diff --git a/buildpacks/ruby/src/main.rs b/buildpacks/ruby/src/main.rs index 9c5de79d..fb6b6973 100644 --- a/buildpacks/ruby/src/main.rs +++ b/buildpacks/ruby/src/main.rs @@ -23,7 +23,6 @@ use libcnb::layer_env::Scope; use libcnb::Platform; use libcnb::{buildpack_main, Buildpack}; use std::io::stdout; -use target_id::TargetId; mod gem_list; mod layers; @@ -119,12 +118,6 @@ impl Buildpack for RubyBuildpack { let mut logger = BuildLog::new(stdout()).buildpack_name("Heroku Ruby Buildpack"); let warn_later = WarnGuard::new(stdout()); - let target_id = TargetId { - arch: context.target.arch.clone(), - distro_name: context.target.distro_name.clone(), - distro_version: context.target.distro_version.clone(), - }; - // ## Set default environment let (mut env, store) = crate::steps::default_env(&context, &context.platform.env().clone())?; @@ -178,8 +171,10 @@ impl Buildpack for RubyBuildpack { RubyInstallLayer { _in_section: section.as_ref(), metadata: RubyInstallLayerMetadata { - target_id: target_id.clone(), - version: ruby_version.clone(), + distro_name: context.target.distro_name.clone(), + distro_version: context.target.distro_version.clone(), + cpu_architecture: context.target.arch.clone(), + ruby_version: ruby_version.clone(), }, }, )?; @@ -219,7 +214,9 @@ impl Buildpack for RubyBuildpack { without: BundleWithout::new("development:test"), _section_log: section.as_ref(), metadata: BundleInstallLayerMetadata { - target_id: target_id.clone(), + distro_name: context.target.distro_name.clone(), + distro_version: context.target.distro_version.clone(), + cpu_architecture: context.target.arch.clone(), ruby_version: ruby_version.clone(), force_bundle_install_key: String::from( crate::layers::bundle_install_layer::FORCE_BUNDLE_INSTALL_CACHE_KEY, diff --git a/buildpacks/ruby/src/target_id.rs b/buildpacks/ruby/src/target_id.rs index 8c16df18..b58759ee 100644 --- a/buildpacks/ruby/src/target_id.rs +++ b/buildpacks/ruby/src/target_id.rs @@ -3,9 +3,9 @@ use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub(crate) struct TargetId { - pub(crate) arch: String, pub(crate) distro_name: String, pub(crate) distro_version: String, + pub(crate) cpu_architecture: String, } const DISTRO_VERSION_STACK: &[(&str, &str, &str)] = &[ @@ -41,7 +41,7 @@ impl TargetId { .iter() .find(|&&(_, _, stack)| stack == stack_id) .map(|&(name, version, _)| TargetId { - arch: String::from("amd64"), + cpu_architecture: String::from("amd64"), distro_name: name.to_owned(), distro_version: version.to_owned(), }) @@ -58,7 +58,7 @@ mod test { assert_eq!( String::from("heroku-22"), TargetId { - arch: String::from("amd64"), + cpu_architecture: String::from("amd64"), distro_name: String::from("ubuntu"), distro_version: String::from("22.04"), } @@ -69,7 +69,7 @@ mod test { assert_eq!( String::from("heroku-24"), TargetId { - arch: String::from("amd64"), + cpu_architecture: String::from("amd64"), distro_name: String::from("ubuntu"), distro_version: String::from("24.04"), } @@ -83,7 +83,7 @@ mod test { assert_eq!( TargetId::from_stack("heroku-22").unwrap(), TargetId { - arch: String::from("amd64"), + cpu_architecture: String::from("amd64"), distro_name: String::from("ubuntu"), distro_version: String::from("22.04"), } @@ -92,7 +92,7 @@ mod test { assert_eq!( TargetId::from_stack("heroku-24").unwrap(), TargetId { - arch: String::from("amd64"), + cpu_architecture: String::from("amd64"), distro_name: String::from("ubuntu"), distro_version: String::from("24.04"), } From 3d1488b0687a526e0bd665d80c3e9c5d40d52115 Mon Sep 17 00:00:00 2001 From: Schneems Date: Sun, 12 May 2024 16:24:37 -0400 Subject: [PATCH 05/14] Use updated magic_migrate macros --- Cargo.lock | 4 +-- buildpacks/ruby/Cargo.toml | 2 +- .../ruby/src/layers/bundle_download_layer.rs | 7 ++-- .../ruby/src/layers/bundle_install_layer.rs | 33 +++++++------------ .../ruby/src/layers/ruby_install_layer.rs | 29 +++++----------- 5 files changed, 26 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 928cc0fe..23c51f5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,9 +692,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "magic_migrate" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6398143367a78d596246f39b67e7ea09eea13318bdaa3ec9e0c4042517d2b44c" +checksum = "9c05b570dc24563cf1720263bbeeb78822c8d3ce75debe510ca6bae90dd0cccf" dependencies = [ "serde", ] diff --git a/buildpacks/ruby/Cargo.toml b/buildpacks/ruby/Cargo.toml index 9e65ebc8..f3aff1dd 100644 --- a/buildpacks/ruby/Cargo.toml +++ b/buildpacks/ruby/Cargo.toml @@ -27,7 +27,7 @@ tempfile = "3" thiserror = "1" ureq = { version = "2", default-features = false, features = ["tls"] } url = "2" -magic_migrate = "0.1" +magic_migrate = "0.2" toml = "0.8" [dev-dependencies] diff --git a/buildpacks/ruby/src/layers/bundle_download_layer.rs b/buildpacks/ruby/src/layers/bundle_download_layer.rs index dcb55de0..933ae722 100644 --- a/buildpacks/ruby/src/layers/bundle_download_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_download_layer.rs @@ -1,11 +1,10 @@ +use crate::RubyBuildpack; +use crate::RubyBuildpackError; +use commons::gemfile_lock::ResolvedBundlerVersion; use commons::output::{ fmt, section_log::{log_step, log_step_timed, SectionLogger}, }; - -use crate::RubyBuildpack; -use crate::RubyBuildpackError; -use commons::gemfile_lock::ResolvedBundlerVersion; use fun_run::{self, CommandWithName}; use libcnb::build::BuildContext; use libcnb::data::layer_content_metadata::LayerTypes; diff --git a/buildpacks/ruby/src/layers/bundle_install_layer.rs b/buildpacks/ruby/src/layers/bundle_install_layer.rs index d1da1557..8d1fdfc8 100644 --- a/buildpacks/ruby/src/layers/bundle_install_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_install_layer.rs @@ -1,9 +1,8 @@ +use crate::{BundleWithout, RubyBuildpack, RubyBuildpackError}; use commons::output::{ fmt::{self, HELP}, section_log::{log_step, log_step_stream, SectionLogger}, }; - -use crate::{BundleWithout, RubyBuildpack, RubyBuildpackError}; use commons::{ display::SentenceList, gemfile_lock::ResolvedRubyVersion, metadata_digest::MetadataDigest, }; @@ -16,7 +15,7 @@ use libcnb::{ layer_env::{LayerEnv, ModificationBehavior, Scope}, Env, }; -use magic_migrate::{try_migrate_link, TryMigrate}; +use magic_migrate::{try_migrate_deserializer_chain, TryMigrate}; use serde::{Deserialize, Deserializer, Serialize}; use std::convert::Infallible; use std::{path::Path, process::Command}; @@ -70,11 +69,16 @@ pub(crate) struct BundleInstallLayerMetadataV2 { /// pub(crate) digest: MetadataDigest, // Must be last for serde to be happy https://github.com/toml-rs/toml-rs/issues/142 } -try_migrate_link!(BundleInstallLayerMetadataV1, BundleInstallLayerMetadataV2); + +try_migrate_deserializer_chain!( + chain: [BundleInstallLayerMetadataV1, BundleInstallLayerMetadataV2], + error: MetadataMigrateError, + deserializer: toml::Deserializer::new, +); pub(crate) type BundleInstallLayerMetadata = BundleInstallLayerMetadataV2; #[derive(thiserror::Error, Debug)] -pub(crate) enum MigrateMetadataError { +pub(crate) enum MetadataMigrateError { #[error("Could not migrate metadata {0}")] UnsupportedStack(TargetIdError), } @@ -82,11 +86,11 @@ pub(crate) enum MigrateMetadataError { // CNB spec moved from the concept of "stacks" (i.e. "heroku-22" which represented an OS and system dependencies) to finer // grained "target" which includes the OS, OS version, and architecture. This function converts the old stack id to the new target id. impl TryFrom for BundleInstallLayerMetadataV2 { - type Error = MigrateMetadataError; + type Error = MetadataMigrateError; fn try_from(v1: BundleInstallLayerMetadataV1) -> Result { let target_id = - TargetId::from_stack(&v1.stack).map_err(MigrateMetadataError::UnsupportedStack)?; + TargetId::from_stack(&v1.stack).map_err(MetadataMigrateError::UnsupportedStack)?; Ok(Self { distro_name: target_id.distro_name.clone(), @@ -99,21 +103,6 @@ impl TryFrom for BundleInstallLayerMetadataV2 { } } -impl From for MigrateMetadataError { - fn from(_: Infallible) -> Self { - unreachable!() - } -} - -impl TryMigrate for BundleInstallLayerMetadataV1 { - type TryFrom = Self; - type Error = MigrateMetadataError; - - fn deserializer<'de>(input: &str) -> impl Deserializer<'de> { - toml::Deserializer::new(input) - } -} - impl<'a> BundleInstallLayer<'a> { #[allow(clippy::unnecessary_wraps)] fn build_layer_env( diff --git a/buildpacks/ruby/src/layers/ruby_install_layer.rs b/buildpacks/ruby/src/layers/ruby_install_layer.rs index 2ef5fffd..93afedd4 100644 --- a/buildpacks/ruby/src/layers/ruby_install_layer.rs +++ b/buildpacks/ruby/src/layers/ruby_install_layer.rs @@ -2,7 +2,7 @@ use commons::output::{ fmt::{self}, section_log::{log_step, log_step_timed, SectionLogger}, }; -use magic_migrate::{try_migrate_link, TryMigrate}; +use magic_migrate::{try_migrate_deserializer_chain, TryMigrate}; use crate::{ target_id::{TargetId, TargetIdError}, @@ -63,21 +63,25 @@ impl RubyInstallLayerMetadataV2 { } } -try_migrate_link!(RubyInstallLayerMetadataV1, RubyInstallLayerMetadataV2); +try_migrate_deserializer_chain!( + chain: [RubyInstallLayerMetadataV1, RubyInstallLayerMetadataV2], + error: MigrateMetadataError, + deserializer: toml::Deserializer::new, +); pub(crate) type RubyInstallLayerMetadata = RubyInstallLayerMetadataV2; #[derive(thiserror::Error, Debug)] -pub(crate) enum MetadataMigrateError { +pub(crate) enum MigrateMetadataError { #[error("Cannot migrate metadata due to target id error: {0}")] TargetIdError(TargetIdError), } impl TryFrom for RubyInstallLayerMetadataV2 { - type Error = MetadataMigrateError; + type Error = MigrateMetadataError; fn try_from(v1: RubyInstallLayerMetadataV1) -> Result { let target_id = - TargetId::from_stack(&v1.stack).map_err(MetadataMigrateError::TargetIdError)?; + TargetId::from_stack(&v1.stack).map_err(MigrateMetadataError::TargetIdError)?; Ok(Self { distro_name: target_id.distro_name, @@ -88,21 +92,6 @@ impl TryFrom for RubyInstallLayerMetadataV2 { } } -impl From for MetadataMigrateError { - fn from(_: Infallible) -> Self { - unreachable!() - } -} - -impl TryMigrate for RubyInstallLayerMetadataV1 { - type TryFrom = Self; - type Error = MetadataMigrateError; - - fn deserializer<'de>(input: &str) -> impl Deserializer<'de> { - toml::Deserializer::new(input) - } -} - impl<'a> Layer for RubyInstallLayer<'a> { type Buildpack = RubyBuildpack; type Metadata = RubyInstallLayerMetadata; From d14247627c6ee0591a2427d82b6f81fb427d93f5 Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Tue, 14 May 2024 09:26:13 -0500 Subject: [PATCH 06/14] Apply suggestions from code review Co-authored-by: Ed Morley <501702+edmorley@users.noreply.github.com> --- buildpacks/ruby/buildpack.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/buildpacks/ruby/buildpack.toml b/buildpacks/ruby/buildpack.toml index 84128cd6..c678fbef 100644 --- a/buildpacks/ruby/buildpack.toml +++ b/buildpacks/ruby/buildpack.toml @@ -17,10 +17,6 @@ id = "*" [metadata.release] image = { repository = "docker.io/heroku/buildpack-ruby" } -[[targets]] -os = "linux" -arch = "arm64" - [[targets]] os = "linux" arch = "amd64" From 80c38fb66ccbb4f90b5b8ea6ea7d60ad6f4dd312 Mon Sep 17 00:00:00 2001 From: Schneems Date: Tue, 14 May 2024 12:12:38 -0500 Subject: [PATCH 07/14] Document breaking changes --- buildpacks/ruby/CHANGELOG.md | 2 ++ docs/application_contract.md | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/buildpacks/ruby/CHANGELOG.md b/buildpacks/ruby/CHANGELOG.md index 17e51171..7c7dddf4 100644 --- a/buildpacks/ruby/CHANGELOG.md +++ b/buildpacks/ruby/CHANGELOG.md @@ -7,6 +7,8 @@ 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)) + ## [2.1.3] - 2024-03-18 ### Changed diff --git a/docs/application_contract.md b/docs/application_contract.md index 08b22fc5..df365d19 100644 --- a/docs/application_contract.md +++ b/docs/application_contract.md @@ -25,7 +25,8 @@ Once an application has passed the detect phase, the build phase will execute to - Given a `Gemfile.lock` with an explicit Ruby version, we will install that Ruby version. - Given a `Gemfile.lock` without an explicit Ruby version, we will install a default Ruby version. - When the default value changes, applications without an explicit Ruby version will receive the updated version on their next deployment. - - We will reinstall Ruby if your stack (operating system) changes. + - We will reinstall Ruby if your distribution name or version (operating system) changes. + - We will reinstall Ruby if your CPU architecture (i.e. amd64) changes. - Bundler version: - Given a `Gemfile.lock` with an explicit Bundler version we will install that bundler version. - Given a `Gemfile.lock` without an explicit Bundler version we will install a default Ruby version. @@ -39,7 +40,8 @@ Once an application has passed the detect phase, the build phase will execute to -To always run `bundle install` even if there are changes if the environment variable `HEROKU_SKIP_BUNDLE_DIGEST=1` is found. - We will always run `bundle clean` after a successful `bundle install` via setting `BUNDLE_CLEAN=1` environment variable. - We will always cache the contents of your gem dependencies. - - We will always invalidate the dependency cache if your stack (operating system) changes. + - We will always invalidate the dependency cache if your distribution name or version (operating system) changes. + - We will always invalidate the dependency cache if your CPU architecture (i.e. amd64) changes. - We will always invalidate the dependency cache if your Ruby version changes. - We may invalidate the dependency cache if there was a bug in a prior buildpack version that needs to be fixed. - Gem specific behavior - We will parse your `Gemfile.lock` to determine what dependencies your app need for use in specializing your install behavior (i.e. Rails 5 versus Rails 4). The inclusion of these gems may trigger different behavior: From ee6d24063850cf4335373eebeaa4e2300b775332 Mon Sep 17 00:00:00 2001 From: Schneems Date: Tue, 14 May 2024 12:14:46 -0500 Subject: [PATCH 08/14] Align warning messages and error names --- buildpacks/ruby/src/layers/bundle_install_layer.rs | 8 ++++---- buildpacks/ruby/src/layers/ruby_install_layer.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/buildpacks/ruby/src/layers/bundle_install_layer.rs b/buildpacks/ruby/src/layers/bundle_install_layer.rs index 8d1fdfc8..ad64798a 100644 --- a/buildpacks/ruby/src/layers/bundle_install_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_install_layer.rs @@ -238,7 +238,7 @@ impl Layer for BundleInstallLayer<'_> { Changed::DistroName(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details(format!("distro name changed from {old} to {now}")) + fmt::details(format!("distro name changed: {old} to {now}")) )); clear_and_run @@ -246,7 +246,7 @@ impl Layer for BundleInstallLayer<'_> { Changed::DistroVersion(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details(format!("distro version changed from {old} to {now}")) + fmt::details(format!("distro version: {old} to {now}")) )); clear_and_run @@ -254,7 +254,7 @@ impl Layer for BundleInstallLayer<'_> { Changed::CpuArchitecture(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details(format!("cpu architecture changed from {old} to {now}")) + fmt::details(format!("cpu architecture: {old} to {now}")) )); clear_and_run @@ -262,7 +262,7 @@ impl Layer for BundleInstallLayer<'_> { Changed::RubyVersion(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details(format!("ruby version changed: {old} to {now}")) + fmt::details(format!("Ruby version changed: {old} to {now}")) )); clear_and_run diff --git a/buildpacks/ruby/src/layers/ruby_install_layer.rs b/buildpacks/ruby/src/layers/ruby_install_layer.rs index 93afedd4..a1641d47 100644 --- a/buildpacks/ruby/src/layers/ruby_install_layer.rs +++ b/buildpacks/ruby/src/layers/ruby_install_layer.rs @@ -65,23 +65,23 @@ impl RubyInstallLayerMetadataV2 { try_migrate_deserializer_chain!( chain: [RubyInstallLayerMetadataV1, RubyInstallLayerMetadataV2], - error: MigrateMetadataError, + error: MetadataMigrateError, deserializer: toml::Deserializer::new, ); pub(crate) type RubyInstallLayerMetadata = RubyInstallLayerMetadataV2; #[derive(thiserror::Error, Debug)] -pub(crate) enum MigrateMetadataError { +pub(crate) enum MetadataMigrateError { #[error("Cannot migrate metadata due to target id error: {0}")] TargetIdError(TargetIdError), } impl TryFrom for RubyInstallLayerMetadataV2 { - type Error = MigrateMetadataError; + type Error = MetadataMigrateError; fn try_from(v1: RubyInstallLayerMetadataV1) -> Result { let target_id = - TargetId::from_stack(&v1.stack).map_err(MigrateMetadataError::TargetIdError)?; + TargetId::from_stack(&v1.stack).map_err(MetadataMigrateError::TargetIdError)?; Ok(Self { distro_name: target_id.distro_name, @@ -174,7 +174,7 @@ impl<'a> Layer for RubyInstallLayer<'a> { Changed::DistroVersion(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details(format!("OS version changed: {old} to {now}")) + fmt::details(format!("distro version changed: {old} to {now}")) )); Ok(ExistingLayerStrategy::Recreate) @@ -182,7 +182,7 @@ impl<'a> Layer for RubyInstallLayer<'a> { Changed::DistroName(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details(format!("OS distribution changed: {old} to {now}")) + fmt::details(format!("distro name changed: {old} to {now}")) )); Ok(ExistingLayerStrategy::Recreate) From ebf1c72b82e124347ff08248d938c436efdd07c0 Mon Sep 17 00:00:00 2001 From: Schneems Date: Tue, 14 May 2024 12:15:28 -0500 Subject: [PATCH 09/14] Update buildpack.toml - Add explanation for when stacks can be removed - Move "targets" closer to "stacks" as they're logically tied together. --- buildpacks/ruby/buildpack.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/buildpacks/ruby/buildpack.toml b/buildpacks/ruby/buildpack.toml index c678fbef..502b2fc3 100644 --- a/buildpacks/ruby/buildpack.toml +++ b/buildpacks/ruby/buildpack.toml @@ -11,12 +11,11 @@ keywords = ["ruby", "rails", "heroku"] [[buildpack.licenses]] type = "BSD-3-Clause" +# This workaround can be removed once a new Pack release ships that includes: +# https://github.com/buildpacks/pack/pull/2081 [[stacks]] id = "*" -[metadata.release] -image = { repository = "docker.io/heroku/buildpack-ruby" } - [[targets]] os = "linux" arch = "amd64" @@ -28,3 +27,6 @@ version = "20.04" [[targets.distros]] name = "ubuntu" version = "22.04" + +[metadata.release] +image = { repository = "docker.io/heroku/buildpack-ruby" } From 731d2f1e2847c8623c2ba801cdf622b296e6429f Mon Sep 17 00:00:00 2001 From: Schneems Date: Tue, 14 May 2024 13:44:37 -0500 Subject: [PATCH 10/14] Integration test for metadata migration --- .../ruby/src/layers/bundle_install_layer.rs | 2 +- .../ruby/src/layers/ruby_install_layer.rs | 2 +- buildpacks/ruby/tests/integration_test.rs | 27 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/buildpacks/ruby/src/layers/bundle_install_layer.rs b/buildpacks/ruby/src/layers/bundle_install_layer.rs index ad64798a..833c99bf 100644 --- a/buildpacks/ruby/src/layers/bundle_install_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_install_layer.rs @@ -231,7 +231,7 @@ impl Layer for BundleInstallLayer<'_> { match cache_state(old.clone(), now) { Changed::Nothing => { - log_step("Loading cache"); + log_step("Loading cached gems"); keep_and_run } diff --git a/buildpacks/ruby/src/layers/ruby_install_layer.rs b/buildpacks/ruby/src/layers/ruby_install_layer.rs index a1641d47..cd6f90cd 100644 --- a/buildpacks/ruby/src/layers/ruby_install_layer.rs +++ b/buildpacks/ruby/src/layers/ruby_install_layer.rs @@ -159,7 +159,7 @@ impl<'a> Layer for RubyInstallLayer<'a> { match cache_state(old.clone(), now) { Changed::Nothing => { - log_step("Using cached version"); + log_step("Using cached Ruby version"); Ok(ExistingLayerStrategy::Keep) } diff --git a/buildpacks/ruby/tests/integration_test.rs b/buildpacks/ruby/tests/integration_test.rs index 61decac6..0bfab912 100644 --- a/buildpacks/ruby/tests/integration_test.rs +++ b/buildpacks/ruby/tests/integration_test.rs @@ -11,6 +11,33 @@ use std::thread; use std::time::{Duration, Instant}; use ureq::Response; +// Test that: +// - Cached data "stack" is preserved and will be successfully migrated to "targets" +#[test] +#[ignore = "integration test"] +fn test_migrating_metadata() { + let builder = "heroku/builder:22"; + let app_dir = "tests/fixtures/default_ruby"; + + TestRunner::default().build( + BuildConfig::new(builder, app_dir).buildpacks([BuildpackReference::Other( + "docker://docker.io/heroku/buildpack-ruby:2.1.2".to_string(), + )]), + |context| { + println!("{}", context.pack_stdout); + context.rebuild( + BuildConfig::new(builder, app_dir).buildpacks([BuildpackReference::CurrentCrate]), + |rebuild_context| { + println!("{}", rebuild_context.pack_stdout); // Needed to get full failure as `rebuild` truncates stdout + + assert_contains!(rebuild_context.pack_stdout, "Using cached Ruby version"); + assert_contains!(rebuild_context.pack_stdout, "Loading cached gems"); + }, + ); + }, + ); +} + #[test] #[ignore = "integration test"] fn test_default_app() { From d0c1bbdb31102fafc6ce6e62f292aec9c71ad9b4 Mon Sep 17 00:00:00 2001 From: Schneems Date: Tue, 14 May 2024 14:00:00 -0500 Subject: [PATCH 11/14] Support heroku-20 and heroku-22 (only) This buildpack does not support heroku-24 (yet) remove this stack from the TargetId struct. It also supports heroku-20 which was previously not present. --- .../ruby/src/layers/bundle_install_layer.rs | 4 ++-- buildpacks/ruby/src/target_id.rs | 18 +++++++++--------- buildpacks/ruby/tests/integration_test.rs | 19 ++++++++++++++++++- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/buildpacks/ruby/src/layers/bundle_install_layer.rs b/buildpacks/ruby/src/layers/bundle_install_layer.rs index 833c99bf..26451d54 100644 --- a/buildpacks/ruby/src/layers/bundle_install_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_install_layer.rs @@ -503,7 +503,7 @@ GEM_PATH=layer_path }; std::fs::write(&gemfile, "iamagemfile").unwrap(); - let target_id = TargetId::from_stack("heroku-24").unwrap(); + let target_id = TargetId::from_stack("heroku-22").unwrap(); let metadata = BundleInstallLayerMetadata { distro_name: target_id.distro_name, distro_version: target_id.distro_version, @@ -522,7 +522,7 @@ GEM_PATH=layer_path let toml_string = format!( r#" distro_name = "ubuntu" -distro_version = "24.04" +distro_version = "22.04" cpu_architecture = "amd64" ruby_version = "3.1.3" force_bundle_install_key = "v1" diff --git a/buildpacks/ruby/src/target_id.rs b/buildpacks/ruby/src/target_id.rs index b58759ee..cad39764 100644 --- a/buildpacks/ruby/src/target_id.rs +++ b/buildpacks/ruby/src/target_id.rs @@ -9,8 +9,8 @@ pub(crate) struct TargetId { } 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)] @@ -56,22 +56,22 @@ mod test { #[test] fn test_stack_name() { assert_eq!( - String::from("heroku-22"), + String::from("heroku-20"), TargetId { cpu_architecture: String::from("amd64"), distro_name: String::from("ubuntu"), - distro_version: String::from("22.04"), + distro_version: String::from("20.04"), } .stack_name() .unwrap() ); assert_eq!( - String::from("heroku-24"), + String::from("heroku-22"), TargetId { cpu_architecture: String::from("amd64"), distro_name: String::from("ubuntu"), - distro_version: String::from("24.04"), + distro_version: String::from("22.04"), } .stack_name() .unwrap() @@ -81,20 +81,20 @@ mod test { #[test] fn test_from_stack() { assert_eq!( - TargetId::from_stack("heroku-22").unwrap(), + TargetId::from_stack("heroku-20").unwrap(), TargetId { cpu_architecture: String::from("amd64"), distro_name: String::from("ubuntu"), - distro_version: String::from("22.04"), + distro_version: String::from("20.04"), } ); assert_eq!( - TargetId::from_stack("heroku-24").unwrap(), + TargetId::from_stack("heroku-22").unwrap(), TargetId { cpu_architecture: String::from("amd64"), distro_name: String::from("ubuntu"), - distro_version: String::from("24.04"), + distro_version: String::from("22.04"), } ); } diff --git a/buildpacks/ruby/tests/integration_test.rs b/buildpacks/ruby/tests/integration_test.rs index 0bfab912..5681639a 100644 --- a/buildpacks/ruby/tests/integration_test.rs +++ b/buildpacks/ruby/tests/integration_test.rs @@ -40,7 +40,24 @@ fn test_migrating_metadata() { #[test] #[ignore = "integration test"] -fn test_default_app() { +fn test_default_app_ubuntu20() { + TestRunner::default().build( + BuildConfig::new("heroku/builder:20", "tests/fixtures/default_ruby"), + |context| { + 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`"#); + + println!("{}", context.pack_stdout); // Needed to get full failure as `rebuild` truncates stdout + assert_contains!(context.pack_stdout, "Installing webrick"); + }, + ); +} + +#[test] +#[ignore = "integration test"] +fn test_default_app_latest_distro() { TestRunner::default().build( BuildConfig::new("heroku/builder:22", "tests/fixtures/default_ruby"), |context| { From 7d4a60f353b4203cb167a511355f3afd1878e24f Mon Sep 17 00:00:00 2001 From: Schneems Date: Wed, 15 May 2024 13:08:28 -0500 Subject: [PATCH 12/14] Fix changelog --- buildpacks/ruby/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildpacks/ruby/CHANGELOG.md b/buildpacks/ruby/CHANGELOG.md index 7c7dddf4..f3fc2868 100644 --- a/buildpacks/ruby/CHANGELOG.md +++ b/buildpacks/ruby/CHANGELOG.md @@ -7,7 +7,7 @@ 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)) +- 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)) ## [2.1.3] - 2024-03-18 From 4025cb484a384d5711460ee09ade4b566f978ac1 Mon Sep 17 00:00:00 2001 From: Schneems Date: Wed, 15 May 2024 13:10:50 -0500 Subject: [PATCH 13/14] Standardize println in integration tests --- buildpacks/ruby/tests/integration_test.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/buildpacks/ruby/tests/integration_test.rs b/buildpacks/ruby/tests/integration_test.rs index 5681639a..b6e0c4f5 100644 --- a/buildpacks/ruby/tests/integration_test.rs +++ b/buildpacks/ruby/tests/integration_test.rs @@ -28,7 +28,7 @@ fn test_migrating_metadata() { context.rebuild( BuildConfig::new(builder, app_dir).buildpacks([BuildpackReference::CurrentCrate]), |rebuild_context| { - println!("{}", rebuild_context.pack_stdout); // Needed to get full failure as `rebuild` truncates stdout + println!("{}", rebuild_context.pack_stdout); assert_contains!(rebuild_context.pack_stdout, "Using cached Ruby version"); assert_contains!(rebuild_context.pack_stdout, "Loading cached gems"); @@ -44,12 +44,12 @@ fn test_default_app_ubuntu20() { TestRunner::default().build( BuildConfig::new("heroku/builder:20", "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`"#); - println!("{}", context.pack_stdout); // Needed to get full failure as `rebuild` truncates stdout assert_contains!(context.pack_stdout, "Installing webrick"); }, ); @@ -61,16 +61,17 @@ fn test_default_app_latest_distro() { 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`"#); - println!("{}", context.pack_stdout); // Needed to get full failure as `rebuild` truncates stdout assert_contains!(context.pack_stdout, "Installing webrick"); let config = context.config.clone(); context.rebuild(config, |rebuild_context| { + println!("{}", rebuild_context.pack_stdout); assert_contains!(rebuild_context.pack_stdout, "Skipping `bundle install` (no changes found in /workspace/Gemfile, /workspace/Gemfile.lock, or user configured environment variables)"); rebuild_context.start_container( @@ -129,6 +130,7 @@ DEPENDENCIES BuildpackReference::CurrentCrate, ]), |context| { + println!("{}", context.pack_stdout); assert_contains!(context.pack_stdout, "# Heroku Ruby Buildpack"); assert_contains!( context.pack_stdout, @@ -149,6 +151,7 @@ fn test_ruby_app_with_yarn_app() { BuildpackReference::CurrentCrate, ]), |context| { + println!("{}", context.pack_stdout); assert_contains!(context.pack_stdout, "# Heroku Ruby Buildpack"); assert_contains!( context.pack_stdout, @@ -163,8 +166,9 @@ fn test_barnes_app() { TestRunner::default().build( BuildConfig::new("heroku/builder:22", "tests/fixtures/barnes_app"), |context| { - assert_contains!(context.pack_stdout, "# Heroku Ruby Buildpack"); + println!("{}", context.pack_stdout); + assert_contains!(context.pack_stdout, "# Heroku Ruby Buildpack"); context.start_container( ContainerConfig::new() .entrypoint("launcher") From c1a5f7f3926552ecf4a50e988409b01f8c02d7e9 Mon Sep 17 00:00:00 2001 From: Schneems Date: Wed, 15 May 2024 15:29:09 -0500 Subject: [PATCH 14/14] Consistent cache clear messages --- buildpacks/ruby/src/layers/bundle_install_layer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildpacks/ruby/src/layers/bundle_install_layer.rs b/buildpacks/ruby/src/layers/bundle_install_layer.rs index 26451d54..368a0ca2 100644 --- a/buildpacks/ruby/src/layers/bundle_install_layer.rs +++ b/buildpacks/ruby/src/layers/bundle_install_layer.rs @@ -246,7 +246,7 @@ impl Layer for BundleInstallLayer<'_> { Changed::DistroVersion(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details(format!("distro version: {old} to {now}")) + fmt::details(format!("distro version changed: {old} to {now}")) )); clear_and_run @@ -254,7 +254,7 @@ impl Layer for BundleInstallLayer<'_> { Changed::CpuArchitecture(old, now) => { log_step(format!( "Clearing cache {}", - fmt::details(format!("cpu architecture: {old} to {now}")) + fmt::details(format!("cpu architecture changed: {old} to {now}")) )); clear_and_run