Skip to content

Commit

Permalink
Migrate go_build layer to struct API (#340)
Browse files Browse the repository at this point in the history
* Drop layer_version functionality from go_build

* Update layers/build.rs to struct API

* Consume new layers/build.rs struct API

* Re-add missing 'existing' from log message

* handle_build_layer now returns LayerEnv
  • Loading branch information
joshwlewis authored Feb 24, 2025
1 parent 0691480 commit becc7e8
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 97 deletions.
196 changes: 110 additions & 86 deletions buildpacks/go/src/layers/build.rs
Original file line number Diff line number Diff line change
@@ -1,122 +1,146 @@
use crate::{GoBuildpack, GoBuildpackError};
use heroku_go_utils::vrs::GoVersion;
use libcnb::build::BuildContext;
use libcnb::data::layer_content_metadata::LayerTypes;
use libcnb::layer::{ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder};
use libcnb::data::layer_name;
use libcnb::layer::{
CachedLayerDefinition, EmptyLayerCause, InvalidMetadataAction, LayerState, RestoredLayerAction,
};
use libcnb::layer_env::{LayerEnv, Scope};
use libcnb::{Buildpack, Target};
use libcnb::Target;
use libherokubuildpack::log::log_info;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

/// A layer for go incremental build cache artifacts
pub(crate) struct BuildLayer {
pub(crate) go_version: GoVersion,
}
const CACHE_ENV: &str = "GOCACHE";
const CACHE_DIR: &str = "cache";
const MAX_CACHE_USAGE_COUNT: f32 = 200.0;

#[derive(Deserialize, Serialize, Clone, PartialEq)]
pub(crate) struct BuildLayerMetadata {
layer_version: String,
go_major_version: GoVersion,
target_arch: String,
target_distro_name: String,
target_distro_version: String,
cache_usage_count: f32,
}

impl BuildLayerMetadata {
fn new(version: &GoVersion, target: &Target) -> Self {
Self {
go_major_version: version.major_release_version(),
target_arch: target.arch.to_string(),
target_distro_name: target.distro_name.to_string(),
target_distro_version: target.distro_version.to_string(),
cache_usage_count: 1.0,
}
}
}

#[derive(thiserror::Error, Debug)]
#[error("Couldn't write to build layer: {0}")]
pub(crate) struct BuildLayerError(std::io::Error);

const CACHE_ENV: &str = "GOCACHE";
const CACHE_DIR: &str = "cache";
const LAYER_VERSION: &str = "1";
const MAX_CACHE_USAGE_COUNT: f32 = 200.0;
impl From<BuildLayerError> for libcnb::Error<GoBuildpackError> {
fn from(value: BuildLayerError) -> Self {
libcnb::Error::BuildpackError(GoBuildpackError::BuildLayer(value))
}
}

impl Layer for BuildLayer {
type Buildpack = GoBuildpack;
type Metadata = BuildLayerMetadata;
enum BuildLayerCacheState {
Expired,
Invalid,
Valid,
}

fn types(&self) -> LayerTypes {
LayerTypes {
/// Create or restore the layer for cached incremental build artifacts
pub(crate) fn handle_build_layer(
context: &BuildContext<GoBuildpack>,
go_version: &GoVersion,
) -> libcnb::Result<LayerEnv, GoBuildpackError> {
let mut metadata = BuildLayerMetadata::new(go_version, &context.target);
let layer_ref = context.cached_layer(
layer_name!("go_build"),
CachedLayerDefinition {
build: true,
launch: false,
cache: true,
}
}
invalid_metadata_action: &|_| {
(
InvalidMetadataAction::DeleteLayer,
BuildLayerCacheState::Invalid,
)
},
restored_layer_action: &|restored_metadata: &BuildLayerMetadata, _| {
if restored_metadata.cache_usage_count >= MAX_CACHE_USAGE_COUNT {
return (
RestoredLayerAction::DeleteLayer,
(
BuildLayerCacheState::Expired,
restored_metadata.cache_usage_count,
),
);
}
if restored_metadata.go_major_version != metadata.go_major_version
|| restored_metadata.target_arch != metadata.target_arch
|| restored_metadata.target_distro_name != metadata.target_distro_name
|| restored_metadata.target_distro_version != metadata.target_distro_version
{
return (
RestoredLayerAction::DeleteLayer,
(
BuildLayerCacheState::Invalid,
restored_metadata.cache_usage_count,
),
);
}
(
RestoredLayerAction::KeepLayer,
(
BuildLayerCacheState::Valid,
restored_metadata.cache_usage_count,
),
)
},
},
)?;

fn create(
&mut self,
ctx: &BuildContext<Self::Buildpack>,
layer_path: &Path,
) -> Result<LayerResult<Self::Metadata>, GoBuildpackError> {
log_info("Creating Go build cache");
let cache_dir = layer_path.join(CACHE_DIR);
fs::create_dir(&cache_dir).map_err(BuildLayerError)?;
LayerResultBuilder::new(self.generate_layer_metadata(&ctx.target, 1.0))
.env(LayerEnv::new().chainable_insert(
Scope::Build,
libcnb::layer_env::ModificationBehavior::Override,
CACHE_ENV,
cache_dir,
))
.build()
}

fn update(
&mut self,
ctx: &BuildContext<Self::Buildpack>,
layer: &LayerData<Self::Metadata>,
) -> Result<LayerResult<Self::Metadata>, GoBuildpackError> {
LayerResultBuilder::new(self.generate_layer_metadata(
&ctx.target,
layer.content_metadata.metadata.cache_usage_count + 1.0,
))
.env(LayerEnv::new().chainable_insert(
Scope::Build,
libcnb::layer_env::ModificationBehavior::Override,
CACHE_ENV,
layer.path.join(CACHE_DIR),
))
.build()
}

fn existing_layer_strategy(
&mut self,
ctx: &BuildContext<Self::Buildpack>,
layer: &LayerData<Self::Metadata>,
) -> Result<ExistingLayerStrategy, <Self::Buildpack as Buildpack>::Error> {
let cached_metadata = &layer.content_metadata.metadata;
if cached_metadata.cache_usage_count >= MAX_CACHE_USAGE_COUNT {
match layer_ref.state {
LayerState::Empty {
cause: EmptyLayerCause::NewlyCreated,
} => (),
LayerState::Empty {
cause:
EmptyLayerCause::RestoredLayerAction {
cause: (BuildLayerCacheState::Expired, _),
},
} => {
log_info("Discarding expired Go build cache");
return Ok(ExistingLayerStrategy::Recreate);
}
let new_metadata =
&self.generate_layer_metadata(&ctx.target, cached_metadata.cache_usage_count);

if cached_metadata != new_metadata {
LayerState::Empty { .. } => {
log_info("Discarding invalid Go build cache");
return Ok(ExistingLayerStrategy::Recreate);
}
log_info("Reusing existing Go build cache");
Ok(ExistingLayerStrategy::Update)
LayerState::Restored { .. } => {
log_info("Reusing existing Go build cache");
}
}
}

impl BuildLayer {
fn generate_layer_metadata(
&self,
target: &Target,
cache_usage_count: f32,
) -> BuildLayerMetadata {
BuildLayerMetadata {
layer_version: LAYER_VERSION.to_string(),
go_major_version: self.go_version.major_release_version(),
target_arch: target.arch.to_string(),
target_distro_name: target.distro_name.to_string(),
target_distro_version: target.distro_version.to_string(),
cache_usage_count,
match layer_ref.state {
LayerState::Restored {
cause: (_, cache_usage_count),
} => {
metadata.cache_usage_count += cache_usage_count;
}
LayerState::Empty { .. } => {
log_info("Creating Go build cache");
let cache_dir = layer_ref.path().join(CACHE_DIR);
fs::create_dir(&cache_dir).map_err(BuildLayerError)?;
layer_ref.write_env(LayerEnv::new().chainable_insert(
Scope::Build,
libcnb::layer_env::ModificationBehavior::Override,
CACHE_ENV,
cache_dir,
))?;
}
}
layer_ref.write_metadata(metadata)?;
layer_ref.read_env()
}
13 changes: 2 additions & 11 deletions buildpacks/go/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ mod proc;
mod tgz;

use heroku_go_utils::vrs::GoVersion;
use layers::build::{BuildLayer, BuildLayerError};
use layers::build::{handle_build_layer, BuildLayerError};
use layers::deps::{handle_deps_layer, DepsLayerError};
use layers::dist::{handle_dist_layer, DistLayerError};
use layers::target::{handle_target_layer, TargetLayerError};
use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder};
use libcnb::data::build_plan::BuildPlanBuilder;
use libcnb::data::launch::{LaunchBuilder, Process};
use libcnb::data::layer_name;
use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder};
use libcnb::generic::GenericMetadata;
use libcnb::generic::GenericPlatform;
Expand Down Expand Up @@ -98,15 +97,7 @@ impl Buildpack for GoBuildpack {

go_env = handle_target_layer(&context)?.apply(Scope::Build, &go_env);

go_env = context
.handle_layer(
layer_name!("go_build"),
BuildLayer {
go_version: artifact.version.clone(),
},
)?
.env
.apply(Scope::Build, &go_env);
go_env = handle_build_layer(&context, &artifact.version)?.apply(Scope::Build, &go_env);

log_info("Resolving Go modules");
let packages = config.packages.unwrap_or(
Expand Down

0 comments on commit becc7e8

Please sign in to comment.