Offline mode build dependencies fetching (AGP) from local repo

Hello, it’s my first post here so I tried to look for all the precedant posts before writing my own and I couldn’t find anything to answer my question. I also have to point out that I’m a newbie regarding gradle.

I’m a Nixpkgs maintainer and a Nix user and my end-goal is to get reproducible android gradle builds in CI.
And the nix build environment is very restricted, no internet access otherwise it wouldn’t be reproducible.

So to solve this issue, many suggested to copy the gradle cache to the offline build env to avoid any downloads.

But this is not great because the gradle cache is quite an opaque way of keeping locally a locked closure of dependencies.

Others suggested to use artifact proxies like artifactory or nexus to achieve the same goal. Unfortunately, those solutions aren’t open source, I couldn’t find any FOSS alternatives and it seems a bit overkill to spinn a proxy just for local dependency storage.

Finally, some suggested to use a plugin to copy the resolved dependencies to a local repository that could then be used offline in the build env. This was the best I could find so far.

I found those 2 plugins:

The first one wasn’t maintained anymore and didn’t work with my gradle 7.3.3 . So I forked it and rewrote it in Kotlin DSL (it was in groovy) and made it work for newer gradle versions.
That’s until I discovered the latter ivypot plugin which seems more maintained even though I didn’t give it a try yet.

The problem is not anymore on the dependency resolving side because my plugin works and some dependencies are fetched from the generated local repo during offline build. (a maven repo).

My issue is with the AGP build dependency (the Android Gradle Plugin, com.android.application:com.android.application.gradle.plugin).

Here is a scan of the build that fails me : Build Scan™ | Gradle Cloud Services

(there are actually two local repos but it’s not relevant)

As you can see the error is org.gradle.internal.resolve.ModuleVersionResolveException: No cached version of com.android.tools.build:gradle:7.2.0-beta02 available for offline mode.

No cached version sounds like gradle only tried to find it in the gradle cache and not in my local repo which it couldn’t because it’s not there.

My take on this is that gradle doesn’t even try to look in the local repo probably because of the offline mode.

And this doesn’t happen with other dependencies. For example, the plugin dependencies are correctly fetched from the local repo in offline mode.

I base this conclusion also on the observations of the logs.

When debugging previous dependency issues, I was always seeing a line saying (trying to rephrase because I didn’t save the logs):
« dependency com.foo.bar:blablabla:version couldn’t be fetched,
We tried in those local repos:

  • repo A
  • repo B
  • My local repo

»
And each time I actually added the necessary files to the local repo it fixed the problem but this time it doesn’t and I don’t see the list of repos gradle tried to fetch AGP from, in the logs.

As you can see the local repo is in ./app/offline-repository and here is what it contains among every other dependencies:

$ ls -lh app/offline-repository/com/android/tools/build/gradle/7.2.0-beta02
total 12M
-rw-r--r-- 1 scott users 8.5M Mar  3 16:48 gradle-7.2.0-beta02.jar
-rw-r--r-- 1 scott users 923K Mar  3 16:48 gradle-7.2.0-beta02-javadoc.jar
-rw-r--r-- 1 scott users  13K Feb  8 21:42 gradle-7.2.0-beta02.module
-rw-r--r-- 1 scott users  12K Mar  3 16:48 gradle-7.2.0-beta02.pom
-rw-r--r-- 1 scott users 2.5M Mar  3 16:48 gradle-7.2.0-beta02-sources.jar

Which is the exact copy of what’s in the google repo: Google's Maven Repository

Maybe this has to do with the build dependencies being specific or the AGP being specific.

If you feel missing concrete context, here is the code in its current WIP state: GitHub - SCOTT-HAMILTON/Trollslate: An android app to troll your friends by showing them hidden messages in codebar style font

(It’s the full app, not just the plugin because it’s easier to develop when the both are bundled in the same git repo, using composite builds).

The offline plugin is in ./offlineRepoPlugin, it is used both in the top level build.gradle.kts and in the :app project. If you want to generate the offline repo, use the updateOfflineRepositoryTask task like so:

$ ./gradlew updateOfflineRepositoryTask

Then to simulate an approximate nix build sandbox env, create a temp folder for the gradle cache, i.e. /tmp/mycache and run:

$ GRADLE_USER_HOME=/tmp/mycache ./gradlew --offline app:assembleRelease

Delete the cache content between two builds because it will mess up the reproducibility of the failure, especially if you run non-offline builds before offline builds.

And if you wonder how I managed to upload the build scan in offline mode, I used the buildScanPublishPrevious task from the com.gradle.enterprise plugin.

Thank you for taking the time to read this long text and I hope some of you may have some hints on what’s going wrong.