Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve build cache expiration logic #267

Merged
merged 1 commit into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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