From b17681cc0af6753af2aa8fd2e45fa7bd3a0aea8f Mon Sep 17 00:00:00 2001 From: Schneems Date: Fri, 10 Jan 2025 14:25:19 -0600 Subject: [PATCH] Move user binstubs to its own path At runtime, the alphabetical order of the layer name determines the order it's loaded. At build time, the order that the `env` variable is modified determines the order. At both build and runtime we want the bin stubs to come first on the PATH when executing user defined code. This was already working for runtime, but wasn't for build time as the "gems" layer was being prepended to the path after the "venv" layer (because the `venv` layer was being defined first, last definition wins). I originally tried to fix this by defining the PATH inside of the "gems" layer along with the gems path but ran into https://github.com/heroku/libcnb.rs/issues/899. The libcnb.rs project loads the user defined PATH modification last, but I'm unclear if that's spec defined behavior or not https://github.com/buildpacks/spec/blob/main/buildpack.md#layer-paths. --- buildpacks/ruby/src/main.rs | 26 +++++++++++++++++++++++- buildpacks/ruby/src/steps/default_env.rs | 12 ++--------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/buildpacks/ruby/src/main.rs b/buildpacks/ruby/src/main.rs index 0717d357..9ff4f2f5 100644 --- a/buildpacks/ruby/src/main.rs +++ b/buildpacks/ruby/src/main.rs @@ -11,9 +11,11 @@ use layers::{ use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; use libcnb::data::build_plan::BuildPlanBuilder; use libcnb::data::launch::LaunchBuilder; +use libcnb::data::layer_name; use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder}; use libcnb::generic::{GenericMetadata, GenericPlatform}; -use libcnb::layer_env::Scope; +use libcnb::layer::UncachedLayerDefinition; +use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; use libcnb::Platform; use libcnb::{buildpack_main, Buildpack}; use std::io::stdout; @@ -220,6 +222,28 @@ impl Buildpack for RubyBuildpack { (bullet.done(), layer_env.apply(Scope::Build, &env)) }; + env = { + let user_binstubs = context.uncached_layer( + layer_name!("user_binstubs"), + UncachedLayerDefinition { + build: true, + launch: true, + }, + )?; + user_binstubs.write_env( + LayerEnv::new() + .chainable_insert(Scope::All, ModificationBehavior::Delimiter, "PATH", ":") + .chainable_insert( + Scope::All, + ModificationBehavior::Prepend, + "PATH", + context.app_dir.join("bin"), + ), + )?; + + user_binstubs.read_env()?.apply(Scope::Build, &env) + }; + // ## Detect gems let (mut build_output, gem_list, default_process) = { let bullet = build_output.bullet("Default process detection"); diff --git a/buildpacks/ruby/src/steps/default_env.rs b/buildpacks/ruby/src/steps/default_env.rs index 1b66a358..d8f3849e 100644 --- a/buildpacks/ruby/src/steps/default_env.rs +++ b/buildpacks/ruby/src/steps/default_env.rs @@ -38,20 +38,12 @@ pub(crate) fn default_env( .to_string(); let layer_ref = context.uncached_layer( - layer_name!("venv"), + layer_name!("env_defaults"), UncachedLayerDefinition { build: true, launch: true, }, )?; - let update_env = LayerEnv::new() - .chainable_insert(Scope::All, ModificationBehavior::Delimiter, "PATH", ":") - .chainable_insert( - Scope::All, - ModificationBehavior::Prepend, - "PATH", - context.app_dir.join("bin"), - ); let env = layer_ref .write_env({ [ @@ -65,7 +57,7 @@ pub(crate) fn default_env( ("DISABLE_SPRING", "1"), ] .iter() - .fold(update_env, |layer_env, (name, value)| { + .fold(LayerEnv::new(), |layer_env, (name, value)| { layer_env.chainable_insert(Scope::All, ModificationBehavior::Default, name, value) }) })