Skip to content

Commit

Permalink
Migrate to struct layer API (#721)
Browse files Browse the repository at this point in the history
  • Loading branch information
Malax authored Aug 30, 2024
1 parent 9c5b3e0 commit dd64fc1
Show file tree
Hide file tree
Showing 22 changed files with 713 additions and 910 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ rust-version = "1.80"
edition = "2021"

[workspace.lints.rust]
# Temporarily allow use of deprecated items until all buildpacks migrated away
# from the trait based layer API.
deprecated = "allow"
unreachable_pub = "warn"
unsafe_code = "warn"
unused_crate_dependencies = "warn"
Expand Down
121 changes: 52 additions & 69 deletions buildpacks/gradle/src/layers/gradle_home.rs
Original file line number Diff line number Diff line change
@@ -1,92 +1,75 @@
use crate::{GradleBuildpack, GradleBuildpackError, GRADLE_TASK_NAME_HEROKU_START_DAEMON};
use indoc::{formatdoc, indoc};
use libcnb::build::BuildContext;
use libcnb::data::layer_content_metadata::LayerTypes;
use libcnb::data::layer_name;
use libcnb::generic::GenericMetadata;
use libcnb::layer::{ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder};
use libcnb::layer::{
CachedLayerDefinition, InvalidMetadataAction, LayerState, RestoredLayerAction,
};
use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope};
use libcnb::Buildpack;
use libcnb::Env;
use std::fs;
use std::path::Path;

pub(crate) struct GradleHomeLayer;

impl Layer for GradleHomeLayer {
type Buildpack = GradleBuildpack;
type Metadata = GenericMetadata;

fn types(&self) -> LayerTypes {
LayerTypes {
launch: true,
pub(crate) fn handle_gradle_home_layer(
context: &BuildContext<GradleBuildpack>,
env: &mut Env,
) -> libcnb::Result<(), GradleBuildpackError> {
let layer_ref = context.cached_layer(
layer_name!("home"),
CachedLayerDefinition {
build: true,
cache: true,
}
}
launch: true,
invalid_metadata_action: &|_| InvalidMetadataAction::DeleteLayer,
restored_layer_action: &|_: &GenericMetadata, _| RestoredLayerAction::KeepLayer,
},
)?;

fn create(
&mut self,
_context: &BuildContext<Self::Buildpack>,
layer_path: &Path,
) -> Result<LayerResult<Self::Metadata>, <Self::Buildpack as Buildpack>::Error> {
// https://docs.gradle.org/8.3/userguide/build_environment.html#sec:gradle_configuration_properties
fs::write(
layer_path.join("gradle.properties"),
indoc! {"
match layer_ref.state {
LayerState::Restored { .. } => {
// Remove daemon metadata from the cached directory. Among other things, it contains a list
// of PIDs from previous runs that will clutter up the output and aren't meaningful with
// containerized builds anyway.
let daemon_dir_path = layer_ref.path().join("daemon");
if daemon_dir_path.is_dir() {
// We explicitly ignore potential errors since not being able to remove this directory
// should not fail the build as it's mostly for output cosmetics only.
let _ignored_result = fs::remove_dir_all(daemon_dir_path);
}
}
LayerState::Empty { .. } => {
// https://docs.gradle.org/8.3/userguide/build_environment.html#sec:gradle_configuration_properties
fs::write(
layer_ref.path().join("gradle.properties"),
indoc! {"
org.gradle.welcome=never
org.gradle.caching=true
"},
)
.map_err(GradleBuildpackError::WriteGradlePropertiesError)?;
)
.map_err(GradleBuildpackError::WriteGradlePropertiesError)?;

// We're adding this empty task to all projects to ensure we have a task we can run when
// we start the Gradle daemon that doesn't side-effect or output anything to the console.
// https://docs.gradle.org/8.3/userguide/init_scripts.html
fs::write(
layer_path.join("init.gradle.kts"),
formatdoc! {"
// We're adding this empty task to all projects to ensure we have a task we can run when
// we start the Gradle daemon that doesn't side-effect or output anything to the console.
// https://docs.gradle.org/8.3/userguide/init_scripts.html
fs::write(
layer_ref.path().join("init.gradle.kts"),
formatdoc! {"
allprojects {{
tasks.register(\"{task_name}\")
}}",
task_name = GRADLE_TASK_NAME_HEROKU_START_DAEMON
},
)
.map_err(GradleBuildpackError::WriteGradleInitScriptError)?;
task_name = GRADLE_TASK_NAME_HEROKU_START_DAEMON
},
)
.map_err(GradleBuildpackError::WriteGradleInitScriptError)?;

LayerResultBuilder::new(None)
.env(LayerEnv::new().chainable_insert(
layer_ref.write_env(LayerEnv::new().chainable_insert(
Scope::All,
ModificationBehavior::Override,
"GRADLE_USER_HOME",
layer_path,
))
.build()
}

fn existing_layer_strategy(
&mut self,
_context: &BuildContext<Self::Buildpack>,
_layer_data: &LayerData<Self::Metadata>,
) -> Result<ExistingLayerStrategy, <Self::Buildpack as Buildpack>::Error> {
Ok(ExistingLayerStrategy::Update)
}

fn update(
&mut self,
_context: &BuildContext<Self::Buildpack>,
layer_data: &LayerData<Self::Metadata>,
) -> Result<LayerResult<Self::Metadata>, <Self::Buildpack as Buildpack>::Error> {
// Remove daemon metadata from the cached directory. Among other things, it contains a list
// of PIDs from previous runs that will clutter up the output and aren't meaningful with
// containerized builds anyway.
let daemon_dir_path = layer_data.path.join("daemon");
if daemon_dir_path.is_dir() {
// We explicitly ignore potential errors since not being able to remove this directory
// should not fail the build as it's mostly for output cosmetics only.
let _ignored_result = fs::remove_dir_all(daemon_dir_path);
layer_ref.path(),
))?;
}

LayerResultBuilder::new(layer_data.content_metadata.metadata.clone())
.env(layer_data.env.clone())
.build()
}

*env = layer_ref.read_env()?.apply(Scope::Build, env);
Ok(())
}
8 changes: 2 additions & 6 deletions buildpacks/gradle/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ use crate::detect::is_gradle_project_directory;
use crate::errors::on_error_gradle_buildpack;
use crate::framework::{detect_framework, Framework};
use crate::gradle_command::GradleCommandError;
use crate::layers::gradle_home::GradleHomeLayer;
use crate::layers::gradle_home::handle_gradle_home_layer;
use crate::GradleBuildpackError::{GradleBuildIoError, GradleBuildUnexpectedStatusError};
use buildpacks_jvm_shared as shared;
#[cfg(test)]
use buildpacks_jvm_shared_test as _;
use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder};
use libcnb::data::build_plan::BuildPlanBuilder;
use libcnb::data::layer_name;
use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder};
use libcnb::generic::GenericPlatform;
use libcnb::{buildpack_main, Buildpack, Env};
Expand Down Expand Up @@ -85,10 +84,7 @@ impl Buildpack for GradleBuildpack {
.map_err(GradleBuildpackError::CannotSetGradleWrapperExecutableBit)?;

let mut gradle_env = Env::from_current();
shared::env::extend_build_env(
context.handle_layer(layer_name!("home"), GradleHomeLayer)?,
&mut gradle_env,
);
handle_gradle_home_layer(&context, &mut gradle_env)?;

log_header("Starting Gradle Daemon");
gradle_command::start_daemon(&gradle_wrapper_executable_path, &gradle_env)
Expand Down
113 changes: 54 additions & 59 deletions buildpacks/jvm-function-invoker/src/layers/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::error::JvmFunctionInvokerBuildpackError;
use crate::JvmFunctionInvokerBuildpack;
use libcnb::build::BuildContext;
use libcnb::data::layer_content_metadata::LayerTypes;
use libcnb::generic::GenericMetadata;
use libcnb::layer::{Layer, LayerResult, LayerResultBuilder};
use libcnb::data::layer_name;
use libcnb::layer::UncachedLayerDefinition;
use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope};
use libcnb::{read_toml_file, Env, TomlFileError};
use libherokubuildpack::log::{log_header, log_info};
Expand All @@ -12,65 +11,55 @@ use std::path::Path;
use std::process::Command;
use thiserror::Error;

pub(crate) struct BundleLayer {
pub(crate) env: Env,
}

impl Layer for BundleLayer {
type Buildpack = JvmFunctionInvokerBuildpack;
type Metadata = GenericMetadata;

fn types(&self) -> LayerTypes {
LayerTypes {
launch: true,
pub(crate) fn handle_bundle(
context: &BuildContext<JvmFunctionInvokerBuildpack>,
env: &Env,
) -> libcnb::Result<(), JvmFunctionInvokerBuildpackError> {
let layer_ref = context.uncached_layer(
layer_name!("bundle"),
UncachedLayerDefinition {
build: false,
cache: false,
launch: true,
},
)?;

log_header("Detecting function");

let invoker_jar_path = env
.get(crate::layers::runtime::RUNTIME_JAR_PATH_ENV_VAR_NAME)
.ok_or(BundleLayerError::FunctionRuntimeNotFound)?;

let exit_status = Command::new("java")
.args(vec![
"-jar",
&invoker_jar_path.to_string_lossy(),
"bundle",
&context.app_dir.to_string_lossy(),
&layer_ref.path().to_string_lossy(),
])
.spawn()
.map_err(BundleLayerError::BundleCommandIoError)?
.wait()
.map_err(BundleLayerError::BundleCommandIoError)?;

match exit_status.code() {
Some(0) => {
log_function_metadata(layer_ref.path())?;

layer_ref.write_env(LayerEnv::new().chainable_insert(
Scope::All,
ModificationBehavior::Override,
FUNCTION_BUNDLE_DIR_ENV_VAR_NAME,
layer_ref.path(),
))?;
}
}
Some(1) => Err(BundleLayerError::NoFunctionsFound)?,
Some(2) => Err(BundleLayerError::MultipleFunctionsFound)?,
Some(code) => Err(BundleLayerError::DetectionFailed(code))?,
None => Err(BundleLayerError::UnexpectedDetectionTermination)?,
};

fn create(
&mut self,
context: &BuildContext<Self::Buildpack>,
layer_path: &Path,
) -> Result<LayerResult<Self::Metadata>, JvmFunctionInvokerBuildpackError> {
log_header("Detecting function");

let invoker_jar_path = self
.env
.get(crate::layers::runtime::RUNTIME_JAR_PATH_ENV_VAR_NAME)
.ok_or(BundleLayerError::FunctionRuntimeNotFound)?;

let exit_status = Command::new("java")
.args(vec![
"-jar",
&invoker_jar_path.to_string_lossy(),
"bundle",
&context.app_dir.to_string_lossy(),
&layer_path.to_string_lossy(),
])
.spawn()
.map_err(BundleLayerError::BundleCommandIoError)?
.wait()
.map_err(BundleLayerError::BundleCommandIoError)?;

match exit_status.code() {
Some(0) => {
log_function_metadata(layer_path)?;
LayerResultBuilder::new(GenericMetadata::default())
.env(LayerEnv::new().chainable_insert(
Scope::All,
ModificationBehavior::Override,
FUNCTION_BUNDLE_DIR_ENV_VAR_NAME,
layer_path,
))
.build()
}
Some(1) => Err(BundleLayerError::NoFunctionsFound.into()),
Some(2) => Err(BundleLayerError::MultipleFunctionsFound.into()),
Some(code) => Err(BundleLayerError::DetectionFailed(code).into()),
None => Err(BundleLayerError::UnexpectedDetectionTermination.into()),
}
}
Ok(())
}

fn log_function_metadata(bundle_dir: impl AsRef<Path>) -> Result<(), BundleLayerError> {
Expand Down Expand Up @@ -126,4 +115,10 @@ pub(crate) enum BundleLayerError {
CouldNotReadFunctionBundleToml(TomlFileError),
}

impl From<BundleLayerError> for libcnb::Error<JvmFunctionInvokerBuildpackError> {
fn from(value: BundleLayerError) -> Self {
libcnb::Error::BuildpackError(JvmFunctionInvokerBuildpackError::BundleLayerError(value))
}
}

const FUNCTION_BUNDLE_DIR_ENV_VAR_NAME: &str = "JVM_FUNCTION_BUNDLE_DIR";
Loading

0 comments on commit dd64fc1

Please sign in to comment.