Skip to content

Commit

Permalink
Improve build cache expiration logic (#267)
Browse files Browse the repository at this point in the history
Signed-off-by: Josh W Lewis <[email protected]>
  • Loading branch information
joshwlewis authored May 21, 2024
1 parent e518444 commit db17d88
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 33 deletions.
5 changes: 5 additions & 0 deletions buildpacks/go/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- The build cache is now invalidated when the target distribution changes. ([#267](https://github.com/heroku/buildpacks-go/pull/267))
- The build cache is no longer invalidated on minor go version changes. ([#267](https://github.com/heroku/buildpacks-go/pull/267))

## [0.3.1] - 2024-05-07

- Added go1.21.10 (linux-amd64), go1.21.10 (linux-arm64), go1.22.3 (linux-amd64), go1.22.3 (linux-arm64).
Expand Down
81 changes: 49 additions & 32 deletions buildpacks/go/src/layers/build.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
use crate::{GoBuildpack, GoBuildpackError};
use heroku_go_utils::vrs::GoVersion;
use heroku_inventory_utils::inv::Artifact;
use libcnb::build::BuildContext;
use libcnb::data::layer_content_metadata::LayerTypes;
use libcnb::layer::{ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder};
use libcnb::layer_env::{LayerEnv, Scope};
use libcnb::Buildpack;
use libcnb::{Buildpack, Target};
use libherokubuildpack::log::log_info;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::fs;
use std::path::Path;

/// A layer for go incremental build cache artifacts
pub(crate) struct BuildLayer {
pub(crate) artifact: Artifact<GoVersion, Sha256>,
pub(crate) go_version: GoVersion,
}

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

Expand All @@ -47,36 +48,31 @@ impl Layer for BuildLayer {

fn create(
&mut self,
_ctx: &BuildContext<Self::Buildpack>,
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(BuildLayerMetadata {
artifact: self.artifact.clone(),
layer_version: LAYER_VERSION.to_string(),
cache_usage_count: 1.0,
})
.env(LayerEnv::new().chainable_insert(
Scope::Build,
libcnb::layer_env::ModificationBehavior::Override,
CACHE_ENV,
cache_dir,
))
.build()
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>,
ctx: &BuildContext<Self::Buildpack>,
layer: &LayerData<Self::Metadata>,
) -> Result<LayerResult<Self::Metadata>, GoBuildpackError> {
LayerResultBuilder::new(BuildLayerMetadata {
artifact: self.artifact.clone(),
layer_version: LAYER_VERSION.to_string(),
cache_usage_count: layer.content_metadata.metadata.cache_usage_count + 1.0,
})
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,
Expand All @@ -88,18 +84,39 @@ impl Layer for BuildLayer {

fn existing_layer_strategy(
&mut self,
_ctx: &BuildContext<Self::Buildpack>,
ctx: &BuildContext<Self::Buildpack>,
layer: &LayerData<Self::Metadata>,
) -> Result<ExistingLayerStrategy, <Self::Buildpack as Buildpack>::Error> {
let mdata = &layer.content_metadata.metadata;
if mdata.cache_usage_count >= MAX_CACHE_USAGE_COUNT
|| mdata.layer_version != LAYER_VERSION
|| mdata.artifact != self.artifact
{
log_info("Expired Go build cache");
let cached_metadata = &layer.content_metadata.metadata;
if cached_metadata.cache_usage_count >= MAX_CACHE_USAGE_COUNT {
log_info("Discarding expired Go build cache");
return Ok(ExistingLayerStrategy::Recreate);
}
log_info("Reusing Go build cache");
let new_metadata =
&self.generate_layer_metadata(&ctx.target, cached_metadata.cache_usage_count);

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

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,
}
}
}
2 changes: 1 addition & 1 deletion buildpacks/go/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl Buildpack for GoBuildpack {
.handle_layer(
layer_name!("go_build"),
BuildLayer {
artifact: artifact.clone(),
go_version: artifact.version.clone(),
},
)?
.env
Expand Down
16 changes: 16 additions & 0 deletions common/go-utils/src/vrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ pub struct GoVersion {
semantic_version: semver::Version,
}

impl GoVersion {
/// Get the corresponding Go major release. Go identifies major releases
/// as increments to the second identifier (e.g.: 1.16.0, 1.22.0). In
/// semver this corresponds to a change to major and/or minor identifiers.
#[must_use]
pub fn major_release_version(&self) -> GoVersion {
let go_major_release =
semver::Version::new(self.semantic_version.major, self.semantic_version.minor, 0);

GoVersion {
value: go_major_release.to_string(),
semantic_version: go_major_release,
}
}
}

impl Display for GoVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value)
Expand Down

0 comments on commit db17d88

Please sign in to comment.