As a followup from this comment heroku/buildpacks-go#439 (comment). We should investigate adding support for CNB_EXEC_ENV. I had Claude Opus 4.6 do a rough pass on a summary and first suggested rough plan for what an API could look like. I take ownership of the contents (listing the generation mechanism for clarity and transparency...I am on the team that maintains this tool...in general, we don't want unfiltered LLM output in our issues).
Summary
Add framework-level support for the Execution Environments RFC (0134), which was finalized in Buildpack API 0.12. libcnb.rs currently supports Buildpack API 0.10.
Execution Environments allow buildpacks to adapt behavior based on a target environment (production, test, development, or custom values) set via the CNB_EXEC_ENV environment variable. The lifecycle (v0.21.0+) and Pack CLI (v0.40.0+) already have support.
Upstream tracking issue: buildpacks/rfcs#327
Motivation: heroku/buildpacks-go#439 needs this feature and currently implements it manually by reading CNB_EXEC_ENV from Env::from_current(). Adding framework-level support avoids each buildpack re-implementing the same pattern and keeps parity with the spec.
What the Spec Adds
1. CNB_EXEC_ENV environment variable
Provided by the lifecycle to both bin/detect and bin/build as a read-only, platform-provided env var. When not set, buildpacks MUST assume "production". The value MUST NOT contain / (reserved for future namespacing). Standard values are production, test, and development, but any valid string is allowed. See Execution Environments in the spec.
An optional array field on [[processes]] to restrict a process to specific execution environments. If omitted, the process applies to all environments.
[[processes]]
type = "web"
command = ["./server"]
exec-env = ["production"]
[[processes]]
type = "test"
command = ["go", "test", "./..."]
default = true
exec-env = ["test"]
Optional metadata declaring which execution environments a buildpack supports:
[[buildpack.exec-env]]
name = "production"
[[buildpack.exec-env]]
name = "test"
Restrict order group members to specific execution environments:
[[order.group]]
id = "heroku/go"
version = "2.1.8"
exec-env = ["test"]
Optional array in the [metadata] section of layer TOML to indicate environment-specific layers.
Proposed Changes
A. Core: Expose CNB_EXEC_ENV on BuildContext and DetectContext
Read CNB_EXEC_ENV in runtime.rs (similar to how context_target() reads CNB_TARGET_* env vars) and add an exec_env field to both context structs:
// libcnb/src/build.rs
pub struct BuildContext<B: Buildpack + ?Sized> {
pub layers_dir: PathBuf,
pub app_dir: PathBuf,
pub buildpack_dir: PathBuf,
pub target: Target,
pub exec_env: ExecEnv, // NEW
pub platform: B::Platform,
pub buildpack_plan: BuildpackPlan,
pub buildpack_descriptor: ComponentBuildpackDescriptor<B::Metadata>,
pub store: Option<Store>,
}
// libcnb/src/detect.rs
pub struct DetectContext<B: Buildpack + ?Sized> {
pub app_dir: PathBuf,
pub buildpack_dir: PathBuf,
pub target: Target,
pub exec_env: ExecEnv, // NEW
pub platform: B::Platform,
pub buildpack_descriptor: ComponentBuildpackDescriptor<B::Metadata>,
}
Since the spec says to default to "production" when CNB_EXEC_ENV is unset, reading it should not produce an error (unlike CNB_TARGET_OS which is mandatory). Something like:
// libcnb/src/runtime.rs
fn context_exec_env<E: Debug>() -> crate::Result<ExecEnv, E> {
let value = env::var("CNB_EXEC_ENV").unwrap_or_else(|_| String::from("production"));
value.parse().map_err(Error::InvalidExecEnv)
}
B. The ExecEnv Type
The spec says CNB_EXEC_ENV is a string that MUST NOT contain / and reserves production, test, development as standard values while allowing any other conforming string. There are a few options for the Rust type:
Option 1: Validated newtype (recommended)
Follows the existing pattern used by ProcessType and LayerName:
// In libcnb-data
libcnb_newtype!(
exec_env,
/// Construct an [`ExecEnv`] value at compile time.
exec_env,
/// The execution environment for a buildpack build.
///
/// Standard values are `production`, `test`, and `development`.
/// MUST only contain numbers, letters, and the characters `.`, `_`, and `-`.
ExecEnv,
ExecEnvError,
r"^[[:alnum:]._-]+$"
);
Usage by buildpack authors:
use libcnb_data::exec_env;
fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
if context.exec_env == exec_env!("test") {
// include test dependencies, leave toolchain on image, etc.
}
// ...
}
Option 2: Enum with known + custom variants
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExecEnv {
Production,
Test,
Development,
Other(String), // validated: no `/`
}
This is more ergonomic for match but diverges from the newtype pattern used elsewhere in the codebase and requires a custom Serialize/Deserialize implementation.
Option 3: Plain String
The simplest approach, matching how Target fields are plain Strings:
However, this loses the validation of the /-exclusion rule and provides no discoverability of standard values.
C. Data Types in libcnb-data
- Add the
ExecEnv type (whichever option is chosen above).
- Add optional
exec_env field to Process in libcnb-data/src/launch.rs:
pub struct Process {
pub r#type: ProcessType,
pub command: Vec<String>,
pub args: Vec<String>,
pub default: bool,
pub working_directory: WorkingDirectory,
pub exec_env: Vec<ExecEnv>, // NEW - empty means "all environments"
}
- Add
exec_env to ProcessBuilder.
- Add
[[buildpack.exec-env]] support to ComponentBuildpackDescriptor.
- Add
exec-env to order group in CompositeBuildpackDescriptor.
D. Buildpack API Version Bump
Update LIBCNB_SUPPORTED_BUILDPACK_API from { major: 0, minor: 10 } to { major: 0, minor: 12 } in libcnb/src/lib.rs.
Note: Buildpack API 0.11 exists between 0.10 and 0.12 and should be reviewed before bumping the version.
E. Test Framework (libcnb-test)
Verify that BuildConfig::env() in libcnb-test can set CNB_EXEC_ENV for integration tests. This likely already works via the generic env var support, but should be verified and documented.
Implementation Priority
The changes can be shipped incrementally:
- Phase 1 (minimum viable): Add
ExecEnv type, read CNB_EXEC_ENV, expose on both contexts. This unblocks buildpacks like the Go buildpack.
- Phase 2: Add
exec-env to Process in launch.toml, update ProcessBuilder.
- Phase 3: Add
exec-env to buildpack.toml descriptors (component and composite).
- Phase 4: Bump
LIBCNB_SUPPORTED_BUILDPACK_API to 0.12 once all pieces are in place.
References
As a followup from this comment heroku/buildpacks-go#439 (comment). We should investigate adding support for
CNB_EXEC_ENV. I had Claude Opus 4.6 do a rough pass on a summary and first suggested rough plan for what an API could look like. I take ownership of the contents (listing the generation mechanism for clarity and transparency...I am on the team that maintains this tool...in general, we don't want unfiltered LLM output in our issues).Summary
Add framework-level support for the Execution Environments RFC (0134), which was finalized in Buildpack API 0.12. libcnb.rs currently supports Buildpack API 0.10.
Execution Environments allow buildpacks to adapt behavior based on a target environment (
production,test,development, or custom values) set via theCNB_EXEC_ENVenvironment variable. The lifecycle (v0.21.0+) and Pack CLI (v0.40.0+) already have support.Upstream tracking issue: buildpacks/rfcs#327
Motivation: heroku/buildpacks-go#439 needs this feature and currently implements it manually by reading
CNB_EXEC_ENVfromEnv::from_current(). Adding framework-level support avoids each buildpack re-implementing the same pattern and keeps parity with the spec.What the Spec Adds
1.
CNB_EXEC_ENVenvironment variableProvided by the lifecycle to both
bin/detectandbin/buildas a read-only, platform-provided env var. When not set, buildpacks MUST assume"production". The value MUST NOT contain/(reserved for future namespacing). Standard values areproduction,test, anddevelopment, but any valid string is allowed. See Execution Environments in the spec.2.
exec-envon processes inlaunch.tomlAn optional array field on
[[processes]]to restrict a process to specific execution environments. If omitted, the process applies to all environments.3.
[[buildpack.exec-env]]inbuildpack.tomlOptional metadata declaring which execution environments a buildpack supports:
4.
exec-envon[[order.group]](composite buildpacks)Restrict order group members to specific execution environments:
5.
exec-envin layer content metadataOptional array in the
[metadata]section of layer TOML to indicate environment-specific layers.Proposed Changes
A. Core: Expose
CNB_EXEC_ENVonBuildContextandDetectContextRead
CNB_EXEC_ENVinruntime.rs(similar to howcontext_target()readsCNB_TARGET_*env vars) and add anexec_envfield to both context structs:Since the spec says to default to
"production"whenCNB_EXEC_ENVis unset, reading it should not produce an error (unlikeCNB_TARGET_OSwhich is mandatory). Something like:B. The
ExecEnvTypeThe spec says
CNB_EXEC_ENVis a string that MUST NOT contain/and reservesproduction,test,developmentas standard values while allowing any other conforming string. There are a few options for the Rust type:Option 1: Validated newtype (recommended)
Follows the existing pattern used by
ProcessTypeandLayerName:Usage by buildpack authors:
Option 2: Enum with known + custom variants
This is more ergonomic for
matchbut diverges from the newtype pattern used elsewhere in the codebase and requires a customSerialize/Deserializeimplementation.Option 3: Plain
StringThe simplest approach, matching how
Targetfields are plainStrings:However, this loses the validation of the
/-exclusion rule and provides no discoverability of standard values.C. Data Types in
libcnb-dataExecEnvtype (whichever option is chosen above).exec_envfield toProcessinlibcnb-data/src/launch.rs:exec_envtoProcessBuilder.[[buildpack.exec-env]]support toComponentBuildpackDescriptor.exec-envto order group inCompositeBuildpackDescriptor.D. Buildpack API Version Bump
Update
LIBCNB_SUPPORTED_BUILDPACK_APIfrom{ major: 0, minor: 10 }to{ major: 0, minor: 12 }inlibcnb/src/lib.rs.Note: Buildpack API 0.11 exists between 0.10 and 0.12 and should be reviewed before bumping the version.
E. Test Framework (
libcnb-test)Verify that
BuildConfig::env()inlibcnb-testcan setCNB_EXEC_ENVfor integration tests. This likely already works via the generic env var support, but should be verified and documented.Implementation Priority
The changes can be shipped incrementally:
ExecEnvtype, readCNB_EXEC_ENV, expose on both contexts. This unblocks buildpacks like the Go buildpack.exec-envtoProcessinlaunch.toml, updateProcessBuilder.exec-envtobuildpack.tomldescriptors (component and composite).LIBCNB_SUPPORTED_BUILDPACK_APIto 0.12 once all pieces are in place.References