hi! we would like to set up a two-tiered build ca...
# community-support
g
hi! we would like to set up a two-tiered build caching setup for our builds: we don't want any of the prod builds to use any caching from dev, but we want our dev builds to be able to benefit from the prod cache. it seemed to me that a requirement like this is not supported out of the box, so i need to roll my own cache service and cache service factory. which was not that hard, and got it working in a couple of dozens of lines. however that implementation can only support http build caches, and also has to rely on gradle internal classes, like
DefaultHttpBuildCacheServiceFactory
and
HttpBuildCacheRequestCustomizer
. my question is: is there a more straightforward way to do this? see my current implementation for the factory in the thread.
this is how i had to implement the cache service factory:
Copy code
class FallbackBuildCacheServiceFactory(
    private val httpBuildCacheServiceFactory: DefaultHttpBuildCacheServiceFactory,
): BuildCacheServiceFactory<FallbackBuildCache> {
    @Inject
    constructor(
        sslContextFactory: SslContextFactory,
        requestCustomizer: HttpBuildCacheRequestCustomizer,
        httpClientHelperFactory: HttpClientHelper.Factory,
    ): this(DefaultHttpBuildCacheServiceFactory(sslContextFactory, requestCustomizer, httpClientHelperFactory))

    override fun createBuildCacheService(
        configuration: FallbackBuildCache,
        describer: BuildCacheServiceFactory.Describer,
    ): BuildCacheService {

        return FallbackBuildCacheService(
            devCache = httpBuildCacheServiceFactory.createBuildCacheService(
                configuration.devCache!!,
                describer,
            ),
            prodCache = httpBuildCacheServiceFactory.createBuildCacheService(
                configuration.prodCache!!,
                describer,
            ),
        )
    }
}
it needs to create an instance of
DefaultHttpBuildCacheServiceFactory
to be able to initialize the http caches that will be used with the fallback.
v
What does "dev cache" and "prod cache" mean? Do you mean that builds on developer machines push to dev cache and other devs pull from it? Or do you mean that non-prod CI builds push to dev cache, prod CI builds push to prod cache, dev and prod builds should both pull from prod cache, prod builds should only pull from prod cache? Something different? ...?
g
• builds on dev machines and CI-s that run on a branch will: check for the prod cache first (because that's the more authoritative), and would fall back to checking the dev cache - but would only push to the dev-cache. • CI builds that build artifacts based on what's on the main branch are going to only look for things in the prod cache and push to the prod cache. the idea is that only things that are built from code that has been reviewed and approved should go into the prod cache, but the prod cache should also benefit every build.
v
How about putting a proxy in front of the dev-cache that first asks the prod cache, then the actual dev cache. (Or using a Nexus raw repository or similar as build cache which you can then configure accordingly) Then "dev" builds ask that proxy which first asks prod cache, then dev cache and push to dev cache. And "prod" builds ask the prod cache directly and push to the prod cache. (Also, typically builds on developer machines are not going into the build cache. For me too high risk that the cache gets poisoned. And once it is poisened you have a really hard time to heal it, as simply deleting from remote cache is not enough as each client copies it to its local cache after retrieval from remote cache for not needing to download again)
g
hm... we have artifactory, i will look into proxying solutions for that, thanks for the idea! (also, yeah, we planned for the dev machine not to push to cache, i just omitted it from the explanation so i can write less 🙂 ) but with all that - does that mean that we would not be able to have a solution within gradle that relies less on internal classes?
v
Dunno, never tried. > it needs to create an instance of
DefaultHttpBuildCacheServiceFactory
to be able to initialize the http caches that will be used with the fallback. Why? Can't you do it something like
Copy code
open class FallbackBuildCache : BuildCache {
    var devCache: HttpBuildCache? = null
    var prodCache: HttpBuildCache? = null
    override fun isEnabled(): Boolean = true
    override fun setEnabled(enabled: Boolean) = Unit
    override fun isPush(): Boolean = false
    override fun setPush(enabled: Boolean) = Unit
}
class FallbackBuildCacheService(devCache: BuildCacheService, prodCache: BuildCacheService) : BuildCacheService {
    override fun load(key: BuildCacheKey, reader: BuildCacheEntryReader): Boolean = TODO("Not yet implemented")
    override fun store(key: BuildCacheKey, writer: BuildCacheEntryWriter) = TODO("Not yet implemented")
    override fun close() = TODO("Not yet implemented")
}
class FallbackBuildCacheServiceFactory(
    private val httpBuildCacheServiceFactory: BuildCacheServiceFactory<HttpBuildCache>,
) : BuildCacheServiceFactory<FallbackBuildCache> {
    override fun createBuildCacheService(
        configuration: FallbackBuildCache,
        describer: BuildCacheServiceFactory.Describer,
    ): BuildCacheService {
        return FallbackBuildCacheService(
            devCache = httpBuildCacheServiceFactory.createBuildCacheService(
                configuration.devCache!!,
                describer,
            ),
            prodCache = httpBuildCacheServiceFactory.createBuildCacheService(
                configuration.prodCache!!,
                describer,
            ),
        )
    }
}
buildCache {
    registerBuildCacheService<FallbackBuildCache>(FallbackBuildCacheServiceFactory::class)
    remote<FallbackBuildCache> {
        devCache = remote<HttpBuildCache> {
            url = uri("...")
        }
        prodCache = remote<HttpBuildCache>() {
            url = uri("...")
        }
    }
}
?
g
Cant inject
BuildCacheServiceFactory<HttpBuildCache>
:
Copy code
Unable to determine constructor argument #1: missing parameter of type BuildCacheServiceFactory, or no service of type BuildCacheServiceFactory<HttpBuildCache>.
v
Ah, it was not used due to the
remote
call order, I see. 😞
You can probably just make a complete own implementation without relying on the built-in one. Iirc is just does a simple
PUT
and
GET
request.