Hey @tad! Thank you for your work. Projects like this make it much more realistic for me to do Nix-based Gradle builds of our products for my job.
For some background, I work on an application team at a medium to large size company, and have always been hampered by the sandbox on our Android builds because using Gradle in the sandbox has been annoying. I’m currently working on fully sandboxing a few other very complicated builds, one of which merits a blogpost in the future for the utter craziness you can get up to with Nix. In the meantime, though, here’s a sanitized review of my progress with gradle2nix so far.
The first thing I ran into yesterday was determining whether or not to use Gradle’s dependency locking. I realized after most of a day that this was basically futile. The Gradle lockfile doesn’t include URLs or hashes, but does include versions, making it basically useless to Nix.
So, I started today by just using gradle2nix. Since I had an older pre-flake shell.nix that had not been converted to a flake, I started there. That part was all fairly straightforward, especially because I had just updated all our project’s dependencies to meet the latest and greatest Gradle 8.8.
The first thing I had to do was come up with all the tasks to run in order to spit out the correct set of dependencies, since I could not figure out how to make Gradle spit them out itself, even though that is part of its job, and the default would not work for our large multi-module project. Considering that Gradle is a non-functional (in every single sense of the word), Turing-complete language where these can be literally anything depending on the current Zodiac sign, cosmological alignment, or phase of the moon, I lamented for humanity’s future for reproducible builds for a very brief moment and then got to work.
For our (rather large) corporate-scale Android Gradle project, this looks something like:
- Build
- Lint
- Test
- Android instrumented test
So I got to work adding a gradle-lock
script to our Nix devShell that would run all these via the gradle2nix wrapper using the right JAVA_HOME.
The build was easy enough; I just had to add assembleDebug and assembleRelease to the list of jobs. So was lint, and our unit tests. Android instrumented tests wanted a physical device or emulator to be working, so I settled for something similar to packageDebugAndroidTest
. Presumably Gradle will have figured out everything it depends on by then.
This was easy enough and I got a nearly 4000 line JSON file containing all of my project’s possible dependencies, in every month, Zodiac sign, or phase of the moon. I instantiated this using buildGradleApplication
(knowing damn well this wouldn’t be enough), and…
Oof. Two private repositories that require auth token HTTP headers set, so it can’t download those jars. Two days in, and that’s where I am, so I’ll be debugging this next.
This seems pretty promising and was easier to set up than I thought, especially considering the detour with Gradle’s lockfiles. I’ve got a couple considerations:
- Gradle’s native lockfile mechanism is next to useless for this and this isn’t documented anywhere.
- Fetching things that require API keys is hard, but it always has been.
- Gradle compatibility. Presumably Gradle will change something else and this approach will break again in the future.
- Probably something involving the gradle wrapper that will force me to parse a gradle-wrapper.properties file for an Android project in bash, but I’m committing to the bit at this point and will give it a shot.
Overall, thanks for a great project. My mental model of how this works at this point is that it hooks Gradle somewhere so it can collect up all the dependencies and write them to a lockfile at the end.