Android-specific build cache is no longer supported by AGP
Version 7.0 of the Android Gradle Plugin (AGP) removed all support for the AGP build cache (first introduced in AGP 2.3, then superseded by Gradle's build cache in AGP 4.1). This orb's save_build_cache and restore_build_cache commands still expect this build cache to exist, at the default locations ~/.android/build-cache and ~/.android/cache.
The Gradle build cache location is configurable. If someone has set up the Gradle build cache to use one of the former AGP build cache directories for caching, then these commands will continue to work. But if a user picks a different directory for caching, these commands will do nothing.
The docs for save_build_cache also reference https://developer.android.com/studio/build/build-cache, which no longer exists (archived version).
I'd be happy to put together a PR revising the command docs, but it seems like there's a bigger question here about what, if anything, these commands should do in a world without the AGP build cache. Should users just manage their own (CircleCI) caching depending on their Gradle setup? Should these commands be indicated for (very) old versions of AGP only? Is there some sort of general solution the orb could still provide? I'm not sure how Circle would like to approach this but I'll help however I can.
If AGP is fully removed, I don't see why keep supporting it in the future versions of this orb. If people want to continue using that because they have it configured, they can continue using the old versions.
Feel free to work on this changes as you consider it better, and don't worry about retro compatibility with AGP. Any help is well received.
Still planning to put together a removal PR. In the meantime, wanted to share some additional learning around the (non-AGP) Gradle build cache.
There's a post from 2021 on the CircleCI blog about the build cache. I think it's generally accurate, though some syntax has changed. It talks about using a build cache node to store a build cache for access across machines. This method — "remote" caching in Gradle's terminology — is the right way to use the Gradle build cache in CircleCI.
The key lesson from my recent experiments, which I think might be worth documenting somewhere, is that putting a local Gradle build cache into a CircleCI cache will not work properly. The local build cache relies on file access timestamps (atime) to know which files are safe to remove when cleaning the cache. So putting all of those cache files into a CircleCI cache, or unzipping them from the CircleCI cache, updates the atime value and nothing ever gets removed from the cache.
Removing the two commands we're already talking about will help people avoid this pitfall, but it would be pretty easy to set up a manual dependency cache for the local Gradle cache directory without realizing the problem. And it will appear to be fine, to start, because it will contain artifacts that will speed up builds. It just will never get cleaned up, eventually resulting in very long save and restore operations.
Is there a place where it would be sensible to document this guidance (to use remote cache nodes, and not to put local caches into the Circle cache)?
I will check what is the best way for that. But I'm not understanding something, you are talking about cleaning the cache on CircleCI and CircleCI cache cannot be deleted.
Ah sorry, I forgot to explain something. The "local" directory-based caching in Gradle will clean up the cache directory based on when each item in the cache was last accessed. This behavior is configurable but you can assume it checks for old cache entries on every build.
So if I configure Gradle to put the local build cache in ~/my-build-cache, and I have a workflow like...
steps:
...
- restore_cache:
keys:
- v1-gradle-build-cache-{{ checksum "build.gradle.kts" }}
- v1-gradle-build-cache-
- < do a gradle build >
- save_cache:
key: my-cache
paths:
- ~/my-build-cache
...
...someone might think they could rely on Gradle's cleaning behavior to prune the contents of that directory (during each build, before storing it in the Circle cache). However, even though the Gradle cleaning behavior will run, it will never remove anything from the directory (on the build executor) because all of the cache's atime values will be recent, from when the cache was restored.
So it's not that anything should be deleted directly from the CircleCI cache, it's that things should be deleted (by Gradle, on the executor) from the cache folder, and then that directory should get stored in the CircleCI cache. But Gradle won't delete anything. Does that make more sense? It's tough to explain with two things named "cache" involved. 😂
Got it, I understand the problem and why it happens on CircleCI. What is the solution?
As far as I know, the solution is: don't use the CircleCI cache feature to store the Gradle build cache, and use Gradle's remote caching feature instead. I'm happy to be wrong, but I can't imagine there's a way to preserve atime values when copying/caching files, so the CircleCI cache / workspace / etc. don't seem likely to work. And I don't imagine Gradle is likely to change the way their cache works. 😅