-
Notifications
You must be signed in to change notification settings - Fork 7
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
Move shared caching logic to public location, rename to DiffMigrateLayer #376
base: main
Are you sure you want to change the base?
Conversation
The `magic_migrate` library now requires errors to be Display + Debug, so this extra constraint is no longer needed on these interfaces.
5115c5c
to
2a863ff
Compare
It's basically the same logic as `cached_layer_write_metadata` but with configurable build/launch logic
The dir is `layer` singular, not `layers` plural.
2a863ff
to
580ca5b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems the CacheBuddy
struct and function implementations are mostly here to provide an interface. Perhaps it'd make sense to consider an approach that resembles the libcnb
implementation closer in terms of both usage and naming, for instance by renaming CacheBuddy
to CacheBuddyLayerDefinition
(similar to the UncachedLayerDefinition
and CachedLayerDefinition
variants in libcnb) and use the same semantics for the build
and launch
types:
pub struct CacheBuddyLayerDefinition {
/// Whether the layer is intended for build.
pub build: bool,
/// Whether the layer is intended for launch.
pub launch: bool,
}
You could of course just use that in a function similar to the current CacheBuddy#layer
(though the function name should probably be updated for readability, e.g. CacheBuddyLayerDefinition#create_layer
or similar).
Alternatively, it might make sense to consider an interface that allows use of CacheBuddyLayerDefinition
similar to how other definitions are used when creating layers. I'm not sure if there are more elegant solutions than defining and implementing a trait to achieve that, but here's one way to do that:
pub trait CacheBuddyLayer<B> {
fn cache_buddy_layer<M>(
&self,
layer_name: LayerName,
layer_definition: CacheBuddyLayerDefinition,
metadata: &M,
) -> libcnb::Result<LayerRef<B, Meta<M>, Meta<M>>, B::Error>
where
B: libcnb::Buildpack,
M: CacheDiff + TryMigrate + Serialize + Debug + Clone;
}
impl<B> CacheBuddyLayer<B> for BuildContext<B>
where
B: libcnb::Buildpack,
{
fn cache_buddy_layer<M>(
&self,
layer_name: LayerName,
definition: CacheBuddyLayerDefinition,
metadata: &M,
) -> libcnb::Result<LayerRef<B, Meta<M>, Meta<M>>, B::Error>
where
M: CacheDiff + TryMigrate + Serialize + Debug + Clone,
{
let layer_ref = self.cached_layer(
layer_name,
CachedLayerDefinition {
build: definition.build,
launch: definition.launch,
invalid_metadata_action: &invalid_metadata_action,
restored_layer_action: &|old: &M, _| restored_layer_action(old, metadata),
},
)?;
layer_ref.write_metadata(metadata)?;
Ok(layer_ref)
}
}
This could then be used in for instance the Ruby layer like so:
let layer_ref = context.cache_buddy_layer(
layer_name!("ruby"),
CacheBuddyLayerDefinition {
build: true,
launch: true,
},
metadata,
)?;
The benefits compared to the current use (CacheBuddy::new().layer(layer_name!("bundler"), context, metadata)?;
):
- Improved readability: For
libcnb
users, this interface closely resembles how they create layers. This also allows writing less, and more focused documentation. It's clear that theCacheBuddyLayerDefinition
is equivalent to other layer definition structs. Similarly, thecache_buddy_layer
implementation onBuildContext
indicates that this function will do something similar touncached_layer
andcached_layer
(i.e. create a layer with some added CacheBuddy magic). - Clearer semantics: Assuming you don't plan to handle scenarios where
None
is a meaningful value, usingbool
for thebuild
andlaunch
fields avoids confusion compared toOption<bool>
. None of theCacheBuddy
callers in this repo set these values, and instantiate the struct with theCacheBuddy::new
function. Thelayer
function in turn defaults totrue
when these options areNone
, which might be a bit surprising.
These map 1:1 with `CachedLayerDefinition`. The extra indirection of supporting default values isn't buying us much.
ed8bde3
to
193ff02
Compare
Exactly. That's the sticking point. Naming + an ergonomic interface. When creating/reading a cached layer I want the interface to:
Based on your feedback. I experimented with trying to have a struct implement Another alternative is shipping functions only. The current implementation makes Using a trait extension idea is an interesting option. I'm worried that there would be some confusion around associated function naming, in general they're a little awkward to work with. I don't want to end up in a situation where the compiler says something like "No such function As an alternative. I've experimented with a builder approach which allows us to be be maximally configurable while giving me all the defaults I want. Here's a sketch of an alternative interface using the builder pattern on a function with the |
15375da
to
a3c259c
Compare
Updated naming which is hopefully more clear. Added a tutorial. After exploring something more like an extremely flexible builder, I want to start with something highly opionated and only move to something more flexible if there's a specific reason for it. |
a3c259c
to
ae49cf0
Compare
The goal of the shared layer logic in
layers/shared
was to iterate on an interface that usesmagic_migrate
andcache_diff
to produce a declarative interface for layer caching. I'm happy with that interface and wanting to make that interface public so other buildpacks may use it if they choose.Commits are small(ish) and in order. Details about the goals and functionality are in the module docs https://github.com/heroku/buildpacks-ruby/blob/schneems/cache_buddy/commons/src/layer/diff_migrate.rs.