diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dda2faea..d245604e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: # which include the latest stable release of Rust, Rustup, Clippy and rustfmt. run: rustup update - name: Rust Cache - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: Clippy # Using --all-targets so tests are checked and --deny to fail on warnings. # Not using --locked here and below since Cargo.lock is in .gitignore. @@ -44,7 +44,7 @@ jobs: - name: Update Rust toolchain run: rustup update - name: Rust Cache - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: Run unit tests run: cargo test --all-features @@ -60,9 +60,9 @@ jobs: - name: Install Rust linux-musl target run: rustup target add x86_64-unknown-linux-musl - name: Rust Cache - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: Install Pack CLI - uses: buildpacks/github-actions/setup-pack@c502bcff683efa6f6d56a325df3fbe1722e21881 # v5.8.11 + uses: buildpacks/github-actions/setup-pack@8203df0b7ac31e358daa391b1949da5650e7f4f0 # v5.9.3 - name: Run integration tests # Runs only tests annotated with the `ignore` attribute (which in this repo, are the integration tests). run: cargo test -- --ignored diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index b3a2aec6..25c3d1d6 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -44,7 +44,7 @@ jobs: run: rustup update - name: Rust Cache - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: Install cargo-edit run: cargo install --locked cargo-edit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec7da526..be4e9037 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: run: rustup update - name: Rust Cache - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: Install cargo-release run: cargo install --locked cargo-release @@ -70,7 +70,7 @@ jobs: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - name: Create GitHub Release - uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: token: ${{ steps.generate-token.outputs.token }} tag_name: v${{ steps.new-version.outputs.version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2eca73..c132f984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.29.1] - 2025-08-01 + +### Fixed + +- libcnb: + - Order of automatically applied environment variables by libcnb, such as `PATH=/bin`, now matches the upstream CNB lifecycle. ([#938](https://github.com/heroku/libcnb.rs/pull/938)) + + ## [0.29.0] - 2025-05-02 ### Changed @@ -385,7 +393,8 @@ version number. See the changelog below for other changes. - Remove support for legacy BOM. Remove `Launch::bom`, `Build::bom`, `bom::Bom`, `bom::Entry`. ([#489](https://github.com/heroku/libcnb.rs/pull/489)) -[unreleased]: https://github.com/heroku/libcnb.rs/compare/v0.29.0...HEAD +[unreleased]: https://github.com/heroku/libcnb.rs/compare/v0.29.1...HEAD +[0.29.1]: https://github.com/heroku/libcnb.rs/compare/v0.29.0...v0.29.1 [0.29.0]: https://github.com/heroku/libcnb.rs/compare/v0.28.1...v0.29.0 [0.28.1]: https://github.com/heroku/libcnb.rs/compare/v0.28.0...v0.28.1 [0.28.0]: https://github.com/heroku/libcnb.rs/compare/v0.27.0...v0.28.0 diff --git a/Cargo.toml b/Cargo.toml index e35eb0cd..5f814316 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.29.0" +version = "0.29.1" rust-version = "1.85" edition = "2024" license = "BSD-3-Clause" @@ -37,10 +37,10 @@ unwrap_used = "warn" missing_errors_doc = "allow" [workspace.dependencies] -libcnb = { version = "=0.29.0", path = "libcnb" } -libcnb-common = { version = "=0.29.0", path = "libcnb-common" } -libcnb-data = { version = "=0.29.0", path = "libcnb-data" } -libcnb-package = { version = "=0.29.0", path = "libcnb-package" } -libcnb-proc-macros = { version = "=0.29.0", path = "libcnb-proc-macros" } -libcnb-test = { version = "=0.29.0", path = "libcnb-test" } -toml = { version = "0.8.22" } +libcnb = { version = "=0.29.1", path = "libcnb" } +libcnb-common = { version = "=0.29.1", path = "libcnb-common" } +libcnb-data = { version = "=0.29.1", path = "libcnb-data" } +libcnb-package = { version = "=0.29.1", path = "libcnb-package" } +libcnb-proc-macros = { version = "=0.29.1", path = "libcnb-proc-macros" } +libcnb-test = { version = "=0.29.1", path = "libcnb-test" } +toml = { version = "0.9.4" } diff --git a/libcnb-cargo/Cargo.toml b/libcnb-cargo/Cargo.toml index 894c026f..1c28469c 100644 --- a/libcnb-cargo/Cargo.toml +++ b/libcnb-cargo/Cargo.toml @@ -19,7 +19,7 @@ path = "src/main.rs" workspace = true [dependencies] -clap = { version = "4.5.37", default-features = false, features = [ +clap = { version = "4.5.42", default-features = false, features = [ "derive", "error-context", "help", @@ -33,4 +33,4 @@ thiserror = "2.0.12" [dev-dependencies] libcnb-common.workspace = true -tempfile = "3.19.1" +tempfile = "3.20.0" diff --git a/libcnb-data/Cargo.toml b/libcnb-data/Cargo.toml index 2d7f5008..65832df0 100644 --- a/libcnb-data/Cargo.toml +++ b/libcnb-data/Cargo.toml @@ -15,7 +15,7 @@ include = ["src/**/*", "LICENSE", "README.md"] workspace = true [dependencies] -fancy-regex = { version = "0.14.0", default-features = false, features = ["std"] } +fancy-regex = { version = "0.16.0", default-features = false, features = ["std"] } libcnb-proc-macros.workspace = true serde = { version = "1.0.219", features = ["derive"] } thiserror = "2.0.12" diff --git a/libcnb-package/Cargo.toml b/libcnb-package/Cargo.toml index 4bb15808..8f10cc36 100644 --- a/libcnb-package/Cargo.toml +++ b/libcnb-package/Cargo.toml @@ -15,12 +15,12 @@ include = ["src/**/*", "LICENSE", "README.md"] workspace = true [dependencies] -cargo_metadata = "0.19.2" +cargo_metadata = "0.21.0" ignore = "0.4.23" indoc = "2.0.6" libcnb-common.workspace = true libcnb-data.workspace = true -petgraph = { version = "0.8.1", default-features = false, features = ["std"] } +petgraph = { version = "0.8.2", default-features = false, features = ["std"] } thiserror = "2.0.12" uriparse = "0.6.4" -which = "7.0.3" +which = "8.0.0" diff --git a/libcnb-package/src/cargo.rs b/libcnb-package/src/cargo.rs index 65a83398..693da128 100644 --- a/libcnb-package/src/cargo.rs +++ b/libcnb-package/src/cargo.rs @@ -14,7 +14,7 @@ pub(crate) fn determine_buildpack_cargo_target_name( .ok_or(DetermineBuildpackCargoTargetNameError::NoBinTargets), _ => binary_target_names .contains(&root_package.name) - .then_some(root_package.name.clone()) + .then_some(root_package.name.to_string()) .ok_or(DetermineBuildpackCargoTargetNameError::AmbiguousBinTargets), } } diff --git a/libcnb-proc-macros/Cargo.toml b/libcnb-proc-macros/Cargo.toml index 9bf860c8..ac11fbba 100644 --- a/libcnb-proc-macros/Cargo.toml +++ b/libcnb-proc-macros/Cargo.toml @@ -17,7 +17,7 @@ proc-macro = true workspace = true [dependencies] -cargo_metadata = "0.19.2" -fancy-regex = { version = "0.14.0", default-features = false, features = ["std"] } +cargo_metadata = "0.21.0" +fancy-regex = { version = "0.16.0", default-features = false, features = ["std"] } quote = "1.0.40" -syn = { version = "2.0.101", features = ["full"] } +syn = { version = "2.0.104", features = ["full"] } diff --git a/libcnb-test/Cargo.toml b/libcnb-test/Cargo.toml index 65ffe668..051d92d4 100644 --- a/libcnb-test/Cargo.toml +++ b/libcnb-test/Cargo.toml @@ -21,10 +21,10 @@ libcnb-common.workspace = true libcnb-data.workspace = true libcnb-package.workspace = true regex = "1.11.1" -tempfile = "3.19.1" +tempfile = "3.20.0" thiserror = "2.0.12" [dev-dependencies] indoc = "2.0.6" libcnb.workspace = true -ureq = { version = "3.0.11", default-features = false } +ureq = { version = "3.0.12", default-features = false } diff --git a/libcnb/Cargo.toml b/libcnb/Cargo.toml index 872aa9fa..52efb2c7 100644 --- a/libcnb/Cargo.toml +++ b/libcnb/Cargo.toml @@ -42,7 +42,7 @@ opentelemetry = { version = "0.28.0", optional = true, default-features = false opentelemetry_sdk = { version = "0.28.0", optional = true, default-features = false } opentelemetry-proto = { version = "0.28.0", optional = true, default-features = false } serde = { version = "1.0.219", features = ["derive"] } -serde_json = { version = "1.0.140", optional = true } +serde_json = { version = "1.0.142", optional = true } thiserror = "2.0.12" tracing = { version = "0.1", optional = true } tracing-opentelemetry = { version = "0.29", optional = true } @@ -50,5 +50,5 @@ tracing-subscriber = { version = "0.3", optional = true } toml.workspace = true [dev-dependencies] -tempfile = "3.19.1" -serde_json = "1.0.140" +tempfile = "3.20.0" +serde_json = "1.0.142" diff --git a/libcnb/src/layer/shared.rs b/libcnb/src/layer/shared.rs index 0c50d7cf..c220cf15 100644 --- a/libcnb/src/layer/shared.rs +++ b/libcnb/src/layer/shared.rs @@ -363,7 +363,7 @@ mod test { match super::read_layer::(layers_dir, &layer_name) { Err(ReadLayerError::LayerContentMetadataParseError(toml_error)) => { - assert_eq!(toml_error.span(), Some(19..20)); + assert_eq!(toml_error.span(), Some(19..19)); } _ => panic!("Expected ReadLayerError::LayerContentMetadataParseError!"), } @@ -399,7 +399,7 @@ mod test { match super::read_layer::(layers_dir, &layer_name) { Err(ReadLayerError::LayerContentMetadataParseError(toml_error)) => { - assert_eq!(toml_error.span(), Some(110..148)); + assert_eq!(toml_error.span(), Some(110..120)); } _ => panic!("Expected ReadLayerError::LayerContentMetadataParseError!"), } diff --git a/libcnb/src/layer_env.rs b/libcnb/src/layer_env.rs index f04e26f2..1db4672b 100644 --- a/libcnb/src/layer_env.rs +++ b/libcnb/src/layer_env.rs @@ -28,12 +28,14 @@ use std::path::Path; /// logic that uses the build tool to download dependencies. The download process does not need to /// know the layer name or any of the logic for constructing `PATH`. /// -/// # Applying the delta +/// ## Applying the delta +/// /// `LayerEnv` is not a static set of environment variables, but a delta. Layers can modify existing /// variables by appending, prepending or setting variables only if they were not already defined. If you only need a /// static set of environment variables, see [`Env`]. /// /// To apply a `LayerEnv` delta to a given `Env`, use [`LayerEnv::apply`] like so: +/// /// ``` /// use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; /// use libcnb::Env; @@ -51,16 +53,18 @@ use std::path::Path; /// assert_eq!(modified_env.get("VAR2").unwrap(), "previous-value"); /// ``` /// -/// # Implicit Entries +/// ## Implicit Entries +/// /// Some directories in a layer directory are implicitly added to the layer environment if they /// exist. The prime example for this behaviour is the `bin` directory. If it exists, its path will -/// be automatically appended to `PATH` using the operating systems path delimiter as the delimiter. +/// be automatically added to `PATH` using the operating systems path delimiter as the delimiter. /// /// A full list of these special directories can be found in the /// [Cloud Native Buildpack specification](https://github.com/buildpacks/spec/blob/main/buildpack.md#layer-paths). /// /// libcnb supports these, including all precedence and lifecycle rules, when a `LayerEnv` is read /// from disk: +/// /// ``` /// use libcnb::layer_env::{LayerEnv, Scope}; /// use std::fs; @@ -139,8 +143,8 @@ impl LayerEnv { pub fn apply(&self, scope: Scope, env: &Env) -> Env { let deltas = match scope { Scope::All => vec![&self.all], - Scope::Build => vec![&self.all, &self.build, &self.layer_paths_build], - Scope::Launch => vec![&self.all, &self.launch, &self.layer_paths_launch], + Scope::Build => vec![&self.layer_paths_build, &self.all, &self.build], + Scope::Launch => vec![&self.layer_paths_launch, &self.all, &self.launch], Scope::Process(process) => { let mut process_deltas = vec![&self.all]; if let Some(process_specific_delta) = self.process.get(&process) { @@ -606,8 +610,8 @@ const PATH_LIST_SEPARATOR: &str = ";"; mod tests { use std::cmp::Ordering; use std::collections::HashMap; + use std::ffi::OsString; use std::fs; - use tempfile::tempdir; use crate::layer_env::{Env, LayerEnv, ModificationBehavior, Scope}; @@ -899,6 +903,52 @@ mod tests { assert_eq!(env.get("PKG_CONFIG_PATH"), None); } + // https://github.com/heroku/libcnb.rs/issues/900 + #[test] + fn layer_paths_come_before_manually_added_paths() { + const TEST_ENV_VALUE: &str = "test-value"; + + let test_cases = [ + ("bin", "PATH", Scope::Build), + ("bin", "PATH", Scope::Launch), + ("lib", "LIBRARY_PATH", Scope::Build), + ]; + + for (path, name, scope) in test_cases { + // Construct test layer environment on disk + let temp_dir = tempdir().unwrap(); + let layer_dir = temp_dir.path(); + + let absolute_path = layer_dir.join(path); + fs::create_dir_all(&absolute_path).unwrap(); + + let mut layer_env = LayerEnv::new(); + layer_env.insert( + scope.clone(), + ModificationBehavior::Prepend, + name, + TEST_ENV_VALUE, + ); + + layer_env.write_to_layer_dir(layer_dir).unwrap(); + + // Validate LayerEnv after reading it from disk + let env = LayerEnv::read_from_layer_dir(layer_dir) + .unwrap() + .apply_to_empty(scope.clone()); + + let mut expected_env_value = OsString::new(); + expected_env_value.push(TEST_ENV_VALUE); + expected_env_value.push(absolute_path.into_os_string()); + + assert_eq!( + env.get(name), + Some(&expected_env_value), + "For ENV var `{name}` scope `{scope:?}`" + ); + } + } + #[test] fn read_from_layer_dir_layer_paths_build() { let temp_dir = tempdir().unwrap(); diff --git a/libherokubuildpack/Cargo.toml b/libherokubuildpack/Cargo.toml index a63903ea..34b451f2 100644 --- a/libherokubuildpack/Cargo.toml +++ b/libherokubuildpack/Cargo.toml @@ -39,7 +39,7 @@ crossbeam-utils = { version = "0.8.21", optional = true } # Ideally we'd use the fastest `zlib-ng` backend, however it fails to cross-compile: # https://github.com/rust-lang/libz-sys/issues/93 # As such we have to use the next best alternate backend, which is `zlib`. -flate2 = { version = "1.1.1", default-features = false, features = ["zlib"], optional = true } +flate2 = { version = "1.1.2", default-features = false, features = ["zlib"], optional = true } hex = { version = "0.4.3", optional = true } libcnb = { workspace = true, optional = true } pathdiff = { version = "0.2.3", optional = true } @@ -50,8 +50,8 @@ tar = { version = "0.4.44", default-features = false, optional = true } termcolor = { version = "1.4.1", optional = true } thiserror = { version = "2.0.12", optional = true } toml = { workspace = true, optional = true } -ureq = { version = "3.0.11", default-features = false, features = ["rustls"], optional = true } +ureq = { version = "3.0.12", default-features = false, features = ["rustls"], optional = true } [dev-dependencies] serde_test = "1.0.177" -tempfile = "3.19.1" +tempfile = "3.20.0"