diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e026530..cb6b686c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ separate changelogs for each crate were used. If you need to refer to these old ### Added +- `libherokubuildpack`: Add `env::DefaultEnvLayer` and `env::ConfigureEnvLayer` structs for setting environment variables ([#598](https://github.com/heroku/libcnb.rs/pull/598)) - `libcnb-package`: Add cross-compilation assistance for Linux `aarch64-unknown-linux-musl`. ([#577](https://github.com/heroku/libcnb.rs/pull/577)) ### Changed diff --git a/libherokubuildpack/Cargo.toml b/libherokubuildpack/Cargo.toml index c4af51b3..7a6e110e 100644 --- a/libherokubuildpack/Cargo.toml +++ b/libherokubuildpack/Cargo.toml @@ -15,9 +15,10 @@ include = ["src/**/*", "LICENSE", "README.md"] all-features = true [features] -default = ["command", "download", "digest", "error", "log", "tar", "toml", "fs", "write"] +default = ["command", "download", "digest", "error", "env", "log", "tar", "toml", "fs", "write"] download = ["dep:ureq", "dep:thiserror"] digest = ["dep:sha2"] +env = ["dep:libcnb"] error = ["log", "dep:libcnb"] log = ["dep:termcolor"] tar = ["dep:tar", "dep:flate2"] diff --git a/libherokubuildpack/src/env.rs b/libherokubuildpack/src/env.rs new file mode 100644 index 00000000..9831516a --- /dev/null +++ b/libherokubuildpack/src/env.rs @@ -0,0 +1,209 @@ +use libcnb::build::BuildContext; +use libcnb::data::layer_content_metadata::LayerTypes; +use libcnb::generic::GenericMetadata; +use libcnb::layer::{Layer, LayerResult, LayerResultBuilder}; +use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; +use std::ffi::OsString; +use std::marker::PhantomData; +use std::path::Path; + +/// Set default environment variables +/// +/// If all you need to do is set default environment values, you can use +/// the `DefaultEnvLayer::new` function to set those values without having +/// to create a struct from scratch. +/// +/// ```no_run +///# use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; +///# use libcnb::data::launch::{LaunchBuilder, ProcessBuilder}; +///# use libcnb::data::process_type; +///# use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder}; +///# use libcnb::generic::{GenericError, GenericMetadata, GenericPlatform}; +///# use libcnb::{buildpack_main, Buildpack}; +///# use libcnb::data::layer::LayerName; +/// +///# pub(crate) struct HelloWorldBuildpack; +/// +/// use libcnb::Env; +/// use libcnb::data::layer_name; +/// use libcnb::layer_env::Scope; +/// use libherokubuildpack::env::DefaultEnvLayer; +/// +///# impl Buildpack for HelloWorldBuildpack { +///# type Platform = GenericPlatform; +///# type Metadata = GenericMetadata; +///# type Error = GenericError; +/// +///# fn detect(&self, _context: DetectContext) -> libcnb::Result { +///# todo!() +///# } +/// +///# fn build(&self, context: BuildContext) -> libcnb::Result { +/// let env = Env::from_current(); +/// // Don't forget to apply context.platform.env() too; +/// +/// let layer = context // +/// .handle_layer( +/// layer_name!("default_env"), +/// DefaultEnvLayer::new( +/// [ +/// ("JRUBY_OPTS", "-Xcompile.invokedynamic=false"), +/// ("RACK_ENV", "production"), +/// ("RAILS_ENV", "production"), +/// ("RAILS_SERVE_STATIC_FILES", "enabled"), +/// ("RAILS_LOG_TO_STDOUT", "enabled"), +/// ("MALLOC_ARENA_MAX", "2"), +/// ("DISABLE_SPRING", "1"), +/// ] +/// .into_iter(), +/// ), +/// )?; +/// let env = layer.env.apply(Scope::Build, &env); +/// +///# todo!() +///# } +///# } +/// +/// ``` +pub struct DefaultEnvLayer; + +impl DefaultEnvLayer { + #[allow(clippy::new_ret_no_self)] + pub fn new(env: E) -> ConfigureEnvLayer + where + E: IntoIterator + Clone, + K: Into, + V: Into, + B: libcnb::Buildpack, + { + let mut layer_env = LayerEnv::new(); + for (key, value) in env { + layer_env = + layer_env.chainable_insert(Scope::All, ModificationBehavior::Default, key, value); + } + + ConfigureEnvLayer { + data: layer_env, + _buildpack: PhantomData, + } + } +} + +/// Set environment variables +/// +/// If you want to set many default environment variables you can use +/// `DefaultEnvLayer`. If you need to set different types of environment +/// variables you can use this struct `ConfigureEnvLayer` +/// +/// Example: +/// +/// ```no_run +///# use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; +///# use libcnb::data::launch::{LaunchBuilder, ProcessBuilder}; +///# use libcnb::data::process_type; +///# use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder}; +///# use libcnb::generic::{GenericError, GenericMetadata, GenericPlatform}; +///# use libcnb::{buildpack_main, Buildpack}; +///# use libcnb::data::layer::LayerName; +/// +///# pub(crate) struct HelloWorldBuildpack; +/// +/// use libcnb::Env; +/// use libcnb::data::layer_name; +/// use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; +/// use libherokubuildpack::env::ConfigureEnvLayer; +/// +///# impl Buildpack for HelloWorldBuildpack { +///# type Platform = GenericPlatform; +///# type Metadata = GenericMetadata; +///# type Error = GenericError; +/// +///# fn detect(&self, _context: DetectContext) -> libcnb::Result { +///# todo!() +///# } +/// +///# fn build(&self, context: BuildContext) -> libcnb::Result { +/// let env = Env::from_current(); +/// // Don't forget to apply context.platform.env() too; +/// +/// let layer = context // +/// .handle_layer( +/// layer_name!("configure_env"), +/// ConfigureEnvLayer::new( +/// LayerEnv::new() +/// .chainable_insert( +/// Scope::All, +/// ModificationBehavior::Override, +/// "BUNDLE_GEMFILE", // Tells bundler where to find the `Gemfile` +/// context.app_dir.join("Gemfile"), +/// ) +/// .chainable_insert( +/// Scope::All, +/// ModificationBehavior::Override, +/// "BUNDLE_CLEAN", // After successful `bundle install` bundler will automatically run `bundle clean` +/// "1", +/// ) +/// .chainable_insert( +/// Scope::All, +/// ModificationBehavior::Override, +/// "BUNDLE_DEPLOYMENT", // Requires the `Gemfile.lock` to be in sync with the current `Gemfile`. +/// "1", +/// ) +/// .chainable_insert( +/// Scope::All, +/// ModificationBehavior::Default, +/// "MY_ENV_VAR", +/// "Whatever I want" +/// ) +/// ), +/// )?; +/// let env = layer.env.apply(Scope::Build, &env); +/// +///# todo!() +///# } +///# } +/// +/// ``` +pub struct ConfigureEnvLayer { + pub(crate) data: LayerEnv, + pub(crate) _buildpack: std::marker::PhantomData, +} + +impl ConfigureEnvLayer +where + B: libcnb::Buildpack, +{ + #[must_use] + pub fn new(env: LayerEnv) -> Self { + ConfigureEnvLayer { + data: env, + _buildpack: PhantomData, + } + } +} + +impl Layer for ConfigureEnvLayer +where + B: libcnb::Buildpack, +{ + type Buildpack = B; + type Metadata = GenericMetadata; + + fn types(&self) -> LayerTypes { + LayerTypes { + build: true, + launch: true, + cache: false, + } + } + + fn create( + &self, + _context: &BuildContext, + _layer_path: &Path, + ) -> Result, B::Error> { + LayerResultBuilder::new(GenericMetadata::default()) + .env(self.data.clone()) + .build() + } +} diff --git a/libherokubuildpack/src/lib.rs b/libherokubuildpack/src/lib.rs index 35693e9b..784810f5 100644 --- a/libherokubuildpack/src/lib.rs +++ b/libherokubuildpack/src/lib.rs @@ -13,6 +13,8 @@ pub mod command; pub mod digest; #[cfg(feature = "download")] pub mod download; +#[cfg(feature = "env")] +pub mod env; #[cfg(feature = "error")] pub mod error; #[cfg(feature = "fs")]